mirror of https://github.com/mongodb/mongo
188 lines
9.0 KiB
JavaScript
188 lines
9.0 KiB
JavaScript
/**
|
|
* Utilities for testing clustered collections.
|
|
*/
|
|
|
|
export var ClusteredCollectionUtil = class {
|
|
static areAllCollectionsClustered(conn) {
|
|
const res = conn.adminCommand({getParameter: 1, "failpoint.clusterAllCollectionsByDefault": 1});
|
|
if (res.ok) return res["failpoint.clusterAllCollectionsByDefault"].mode;
|
|
else return false;
|
|
}
|
|
|
|
static isArbitraryKeySupportEnabled(conn) {
|
|
const arbitraryKeySupportEnabled = assert.commandWorked(
|
|
conn.adminCommand({getParameter: 1, supportArbitraryClusterKeyIndex: 1}),
|
|
).supportArbitraryClusterKeyIndex.value;
|
|
return arbitraryKeySupportEnabled;
|
|
}
|
|
|
|
// 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 = Object.extend({}, createOptions, /* deep copy */ true);
|
|
|
|
// 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;
|
|
}
|
|
|
|
static validateListCollectionsNotClustered(db, collName) {
|
|
const listColls = assert.commandWorked(db.runCommand({listCollections: 1, filter: {name: collName}}));
|
|
const listCollsOptions = listColls.cursor.firstBatch[0].options;
|
|
assert.eq(listCollsOptions.clusteredIndex, undefined, "Expected clusteredIndex to be undefined");
|
|
}
|
|
|
|
static validateListIndexesNonClustered(db, collName) {
|
|
const listIndexes = assert.commandWorked(db[collName].runCommand("listIndexes"));
|
|
assert.eq(
|
|
listIndexes.cursor.firstBatch[0].clustered || false,
|
|
false,
|
|
"Index had clustering in it when it shouldn't",
|
|
);
|
|
}
|
|
|
|
// 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(fullCreateOptions.clusteredIndex, listCollsOptions.clusteredIndex);
|
|
}
|
|
|
|
// The clusteredIndex should appear in listIndexes with additional "clustered" field.
|
|
static validateListIndexes(db, collName, createOptions) {
|
|
const fullCreateOptions = ClusteredCollectionUtil.constructFullCreateOptions(createOptions);
|
|
const listIndexes = assert.commandWorked(db[collName].runCommand("listIndexes"));
|
|
let extraData = {clustered: true};
|
|
// ttl is not stored on the clusteredIndex but on the collection info. Therefore, we have to
|
|
// add it back in this check to match the getIndexes output.
|
|
if (typeof createOptions.expireAfterSeconds !== "undefined" && createOptions.expireAfterSeconds !== null) {
|
|
extraData.expireAfterSeconds = createOptions.expireAfterSeconds;
|
|
}
|
|
const expectedListIndexesOutput = Object.extend(extraData, fullCreateOptions.clusteredIndex);
|
|
assert.docEq(expectedListIndexesOutput, listIndexes.cursor.firstBatch[0]);
|
|
}
|
|
|
|
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 given replacement updates only
|
|
// preserve cluster key '_id'.
|
|
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"]);
|
|
}
|
|
}
|
|
|
|
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(9 * 1024 * 1024), a: 11}), 5894900);
|
|
|
|
// Look up using the secondary index on {a: 1}
|
|
assert.commandWorked(coll.createIndex({a: 1}));
|
|
|
|
if (clusterKey == "_id") {
|
|
// Replacement updates only preserve the '_id' cluster key.
|
|
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);
|
|
|
|
if (clusterKey == "_id") {
|
|
// Replacement updates only preserve the '_id' cluster key.
|
|
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());
|
|
coll.drop();
|
|
}
|
|
};
|