summaryrefslogtreecommitdiff
path: root/jstests/libs/clustered_collection_util.js
blob: 1e6d9f18bcd6d2e5f3d9ac57b06022fdde48c857 (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
/**
 * Utilities for testing clustered collections.
 */
var ClusteredCollectionUtil = class {
    static areClusteredIndexesEnabled(conn) {
        const clusteredIndexesEnabled =
            assert
                .commandWorked(conn.adminCommand({getParameter: 1, featureFlagClusteredIndexes: 1}))
                .featureFlagClusteredIndexes.value;

        if (!clusteredIndexesEnabled) {
            return false;
        }
        return true;
    }

    // Returns a copy of the 'createOptions' used to create the clustered collection with default
    // values for fields absent in the user provided 'createOptions'.
    static constructFullCreateOptions(createOptions) {
        const fullCreateOptions = createOptions;

        // If the createOptions don't specify the name, expect the default.
        if (!createOptions.clusteredIndex.name) {
            const clusterKey = Object.keys(createOptions.clusteredIndex.key)[0];
            if (clusterKey == "_id") {
                fullCreateOptions.clusteredIndex.name = "_id_";
            } else {
                fullCreateOptions.clusteredIndex.name = clusterKey + "_1";
            }
        }

        // If the createOptions don't specify 'v', expect the default.
        if (!createOptions.clusteredIndex.v) {
            fullCreateOptions.clusteredIndex.v = 2;
        }

        return fullCreateOptions;
    }

    // Provided the createOptions used to create the collection, validates the output from
    // listCollections contains the correct information about the clusteredIndex.
    static validateListCollections(db, collName, createOptions) {
        const fullCreateOptions = ClusteredCollectionUtil.constructFullCreateOptions(createOptions);
        const listColls =
            assert.commandWorked(db.runCommand({listCollections: 1, filter: {name: collName}}));
        const listCollsOptions = listColls.cursor.firstBatch[0].options;
        assert(listCollsOptions.clusteredIndex);
        assert.docEq(listCollsOptions.clusteredIndex, fullCreateOptions.clusteredIndex);
    }

    // The clusteredIndex should appear in listIndexes with additional "clustered" field.
    static validateListIndexes(db, collName, fullCreationOptions) {
        const listIndexes = assert.commandWorked(db[collName].runCommand("listIndexes"));
        const expectedListIndexesOutput =
            Object.extend({clustered: true}, fullCreationOptions.clusteredIndex);
        assert.docEq(listIndexes.cursor.firstBatch[0], expectedListIndexesOutput);
    }

    static testBasicClusteredCollection(db, collName, clusterKey) {
        const lengths = [100, 1024, 1024 * 1024, 3 * 1024 * 1024];
        const coll = db[collName];
        const clusterKeyString = new String(clusterKey);

        assert.commandWorked(db.createCollection(
            collName, {clusteredIndex: {key: {[clusterKey]: 1}, unique: true}}));

        // Expect that duplicates are rejected.
        for (let len of lengths) {
            let id = 'x'.repeat(len);
            assert.commandWorked(coll.insert({[clusterKey]: id}));
            assert.commandFailedWithCode(coll.insert({[clusterKey]: id}), ErrorCodes.DuplicateKey);
            assert.eq(1, coll.find({[clusterKey]: id}).itcount());
        }

        // Updates should work.
        for (let len of lengths) {
            let id = 'x'.repeat(len);

            // Validate the below for _id-clustered collection only until the following tickets are
            // addressed:
            // * TODO SERVER-60734 replacement updates should preserve the cluster key
            // * TODO SERVER-60702 enable bounded collscans for arbitary cluster keys
            if (clusterKey == "_id") {
                assert.commandWorked(coll.update({[clusterKey]: id}, {a: len}));

                assert.eq(1, coll.find({[clusterKey]: id}).itcount());
                assert.eq(len, coll.findOne({[clusterKey]: id})['a']);
            }
        }

        // This section is based on jstests/core/timeseries/clustered_index_crud.js with
        // specific additions for general-purpose (non-timeseries) clustered collections
        assert.commandWorked(coll.insert({[clusterKey]: 0, a: 1}));
        assert.commandWorked(coll.insert({[clusterKey]: 1, a: 1}));
        assert.eq(1, coll.find({[clusterKey]: 0}).itcount());
        assert.commandWorked(coll.insert({[clusterKey]: "", a: 2}));
        assert.eq(1, coll.find({[clusterKey]: ""}).itcount());
        assert.commandWorked(coll.insert({[clusterKey]: NumberLong("9223372036854775807"), a: 3}));
        assert.eq(1, coll.find({[clusterKey]: NumberLong("9223372036854775807")}).itcount());
        assert.commandWorked(coll.insert({[clusterKey]: {a: 1, b: 1}, a: 4}));
        assert.eq(1, coll.find({[clusterKey]: {a: 1, b: 1}}).itcount());
        assert.commandWorked(coll.insert({[clusterKey]: {a: {b: 1}, c: 1}, a: 5}));
        assert.commandWorked(coll.insert({[clusterKey]: -1, a: 6}));
        assert.eq(1, coll.find({[clusterKey]: -1}).itcount());
        assert.commandWorked(coll.insert({[clusterKey]: "123456789012", a: 7}));
        assert.eq(1, coll.find({[clusterKey]: "123456789012"}).itcount());
        if (clusterKey == "_id") {
            assert.commandWorked(coll.insert({a: 8}));
        } else {
            // Missing required cluster key field.
            assert.commandFailedWithCode(coll.insert({a: 8}), 2);
            assert.commandWorked(coll.insert({[clusterKey]: "withFieldA", a: 8}));
        }
        assert.eq(1, coll.find({a: 8}).itcount());
        assert.commandWorked(coll.insert({[clusterKey]: null, a: 9}));
        assert.eq(1, coll.find({[clusterKey]: null}).itcount());
        assert.commandWorked(coll.insert({[clusterKey]: 'x'.repeat(99), a: 10}));

        if (clusterKey == "_id") {
            assert.commandWorked(coll.insert({}));
        } else {
            // Missing required ts field.
            assert.commandFailedWithCode(coll.insert({}), 2);
            assert.commandWorked(coll.insert({[clusterKey]: 'missingFieldA'}));
        }
        // Can build a secondary index with a 3MB RecordId doc.
        assert.commandWorked(coll.createIndex({a: 1}));
        // Can drop the secondary index
        assert.commandWorked(coll.dropIndex({a: 1}));

        // This key is too large.
        assert.commandFailedWithCode(
            coll.insert({[clusterKey]: 'x'.repeat(8 * 1024 * 1024), a: 11}), 5894900);

        // Look up using the secondary index on {a: 1}
        assert.commandWorked(coll.createIndex({a: 1}));

        // TODO remove the branch once SERVER-60734 "replacement updates should preserve the cluster
        // key" is resolved.
        if (clusterKey == "_id") {
            assert.eq(1, coll.find({a: null}).itcount());
        } else {
            assert.eq(5, coll.find({a: null}).itcount());
        }
        assert.eq(0, coll.find({a: 0}).itcount());
        assert.eq(2, coll.find({a: 1}).itcount());
        assert.eq(1, coll.find({a: 2}).itcount());
        assert.eq(1, coll.find({a: 8}).itcount());
        assert.eq(1, coll.find({a: 9}).itcount());
        assert.eq(null, coll.findOne({a: 9})[clusterKeyString]);
        assert.eq(1, coll.find({a: 10}).itcount());
        assert.eq(99, coll.findOne({a: 10})[clusterKeyString].length);

        // TODO make it unconditional once SERVER-60734 "replacement updates should preserve the
        // cluster key" is resolved.
        if (clusterKey == "_id") {
            for (let len of lengths) {
                // Secondary index lookups for documents with large RecordId's.
                assert.eq(1, coll.find({a: len}).itcount());
                assert.eq(len, coll.findOne({a: len})[clusterKeyString].length);
            }
        }

        // No support for numeric type differentiation.
        assert.commandWorked(coll.insert({[clusterKey]: 42.0}));
        assert.commandFailedWithCode(coll.insert({[clusterKey]: 42}), ErrorCodes.DuplicateKey);
        assert.commandFailedWithCode(coll.insert({[clusterKey]: NumberLong("42")}),
                                     ErrorCodes.DuplicateKey);
        assert.eq(1, coll.find({[clusterKey]: 42.0}).itcount());
        assert.eq(1, coll.find({[clusterKey]: 42}).itcount());
        assert.eq(1, coll.find({[clusterKey]: NumberLong("42")}).itcount());
    }
};