Skip to content

Commit

Permalink
SERVER-56933 Return options to create an identical time-series collec…
Browse files Browse the repository at this point in the history
…tion from listCollections
  • Loading branch information
gregorynoma authored and Evergreen Agent committed May 24, 2021
1 parent a5caa67 commit 8d5547c
Show file tree
Hide file tree
Showing 20 changed files with 472 additions and 94 deletions.
2 changes: 1 addition & 1 deletion jstests/core/list_collections_no_views.js
Expand Up @@ -52,7 +52,7 @@ assert.eq(allExpected,
return 0;
}));

// {type: {$exists: false}} is needed for versions <= 3.2
// TODO (SERVER-25493): {type: {$exists: false}} is needed for versions <= 3.2
let collOnlyCommand = {
listCollections: 1,
filter: {$or: [{type: 'collection'}, {type: {$exists: false}}]}
Expand Down
120 changes: 120 additions & 0 deletions jstests/core/timeseries/timeseries_list_collections.js
@@ -0,0 +1,120 @@
/**
* Tests the result of running listCollections when there are time-series collections present.
*
* @tags: [
* assumes_no_implicit_collection_creation_after_drop,
* does_not_support_transactions,
* requires_fcv_49,
* requires_find_command,
* requires_getmore,
* ]
*/
(function() {
'use strict';

const testDB = db.getSiblingDB(jsTestName());
assert.commandWorked(testDB.dropDatabase());

const timeFieldName = 'time';
const metaFieldName = 'meta';
const coll = testDB.getCollection('t');

const getBucketMaxSpanSeconds = function(granularity) {
switch (granularity) {
case 'seconds':
return 60 * 60;
case 'minutes':
return 60 * 60 * 24;
case 'hours':
return 60 * 60 * 24 * 30;
default:
assert(false, 'Invalid granularity: ' + granularity);
}
};

const testOptions = function(options) {
coll.drop();
jsTestLog('Creating time-series collection with options: ' + tojson(options));
assert.commandWorked(testDB.createCollection(coll.getName(), options));

if (!options.timeseries.hasOwnProperty('granularity')) {
Object.assign(options.timeseries, {granularity: 'seconds'});
}
if (!options.timeseries.hasOwnProperty('bucketMaxSpanSeconds')) {
Object.assign(
options.timeseries,
{bucketMaxSpanSeconds: getBucketMaxSpanSeconds(options.timeseries.granularity)});
}

if (options.hasOwnProperty('collation')) {
Object.assign(options.collation, {
caseLevel: false,
caseFirst: 'off',
strength: 3,
numericOrdering: false,
alternate: 'non-ignorable',
maxVariable: 'punct',
normalization: false,
backwards: false,
version: '57.1',
});
}

const collections =
assert.commandWorked(testDB.runCommand({listCollections: 1})).cursor.firstBatch;
jsTestLog('Checking listCollections result: ' + tojson(collections));
assert.eq(collections.length, 3);
assert(collections.find(entry => entry.name === 'system.views'));
assert(collections.find(entry => entry.name === 'system.buckets.' + coll.getName()));
assert.docEq(
collections.find(entry => entry.name === coll.getName()),
{name: coll.getName(), type: 'timeseries', options: options, info: {readOnly: false}});
};

testOptions({timeseries: {timeField: timeFieldName}});
testOptions({timeseries: {timeField: timeFieldName, metaField: metaFieldName}});
testOptions({
timeseries: {
timeField: timeFieldName,
granularity: 'minutes',
}
});
testOptions({
timeseries: {
timeField: timeFieldName,
granularity: 'minutes',
bucketMaxSpanSeconds: 60 * 60 * 24,
}
});
testOptions({
timeseries: {
timeField: timeFieldName,
},
storageEngine: {wiredTiger: {}},
});
testOptions({
timeseries: {
timeField: timeFieldName,
},
indexOptionDefaults: {storageEngine: {wiredTiger: {}}},
});
testOptions({
timeseries: {
timeField: timeFieldName,
},
collation: {locale: 'ja'},
});
testOptions({timeseries: {timeField: timeFieldName}, expireAfterSeconds: NumberLong(100)});
testOptions({
timeseries: {
timeField: timeFieldName,
metaField: metaFieldName,
granularity: 'minutes',
bucketMaxSpanSeconds: 60 * 60 * 24,
},
storageEngine: {wiredTiger: {}},
indexOptionDefaults: {storageEngine: {wiredTiger: {}}},
collation: {locale: 'ja'},
expireAfterSeconds: NumberLong(100),
});
})();
37 changes: 37 additions & 0 deletions jstests/core/timeseries/timeseries_list_collections_filter_name.js
@@ -0,0 +1,37 @@
/**
* Tests that listCollections includes time-series collections and their options when filtering on
* name.
*
* @tags: [
* assumes_no_implicit_collection_creation_after_drop,
* does_not_support_transactions,
* requires_fcv_49,
* requires_find_command,
* requires_getmore,
* ]
*/
(function() {
'use strict';

const testDB = db.getSiblingDB(jsTestName());
assert.commandWorked(testDB.dropDatabase());

const timeFieldName = 'time';
const coll = testDB.getCollection('t');

assert.commandWorked(
testDB.createCollection(coll.getName(), {timeseries: {timeField: timeFieldName}}));

const collections =
assert.commandWorked(testDB.runCommand({listCollections: 1, filter: {name: coll.getName()}}))
.cursor.firstBatch;
assert.eq(collections, [{
name: coll.getName(),
type: 'timeseries',
options: {
timeseries:
{timeField: timeFieldName, granularity: 'seconds', bucketMaxSpanSeconds: 3600}
},
info: {readOnly: false},
}]);
})();
@@ -0,0 +1,51 @@
/**
* Tests the behavior of listCollections in the presence of both a time-series collection and an
* invalid view definition.
*
* @tags: [
* assumes_against_mongod_not_mongos,
* assumes_no_implicit_collection_creation_after_drop,
* does_not_support_transactions,
* requires_fcv_49,
* requires_find_command,
* requires_getmore,
* ]
*/
(function() {
'use strict';

const testDB = db.getSiblingDB(jsTestName());
assert.commandWorked(testDB.dropDatabase());

const timeFieldName = 'time';
const coll = testDB.getCollection('t');

assert.commandWorked(
testDB.createCollection(coll.getName(), {timeseries: {timeField: timeFieldName}}));
assert.commandWorked(testDB.adminCommand({
applyOps: [
{op: 'i', ns: testDB.getName() + '.system.views', o: {_id: 'invalid', pipeline: 'invalid'}}
]
}));

assert.commandFailedWithCode(testDB.runCommand({listCollections: 1}),
ErrorCodes.InvalidViewDefinition);
assert.commandFailedWithCode(testDB.runCommand({listCollections: 1, filter: {type: 'timeseries'}}),
ErrorCodes.InvalidViewDefinition);
assert.commandFailedWithCode(
testDB.runCommand({listCollections: 1, filter: {name: coll.getName()}}),
ErrorCodes.InvalidViewDefinition);

// TODO (SERVER-25493): Change filter to {type: 'collection'}.
const collections =
assert
.commandWorked(testDB.runCommand(
{listCollections: 1, filter: {$or: [{type: 'collection'}, {type: {$exists: false}}]}}))
.cursor.firstBatch;
jsTestLog('Checking listCollections result: ' + tojson(collections));
assert.eq(collections.length, 2);
assert(collections.find(entry => entry.name === 'system.views'));
assert(collections.find(entry => entry.name === 'system.buckets.' + coll.getName()));

assert(testDB.system.views.drop());
})();
@@ -0,0 +1,39 @@
/**
* Tests that listCollections shows the time-series view, but not the buckets collection, if the
* backing time-series buckets collection is missing.
*
* @tags: [
* assumes_no_implicit_collection_creation_after_drop,
* does_not_support_transactions,
* requires_fcv_49,
* requires_find_command,
* requires_getmore,
* ]
*/
(function() {
'use strict';

const testDB = db.getSiblingDB(jsTestName());
assert.commandWorked(testDB.dropDatabase());

const timeFieldName = 'time';
const coll = testDB.getCollection('t');
const bucketsColl = testDB.getCollection('system.buckets.' + coll.getName());

assert.commandWorked(
testDB.createCollection(coll.getName(), {timeseries: {timeField: timeFieldName}}));
assert(bucketsColl.drop());

let collections = assert.commandWorked(testDB.runCommand({listCollections: 1})).cursor.firstBatch;
jsTestLog('Checking listCollections result: ' + tojson(collections));
assert.eq(collections.length, 2);
assert(collections.find(entry => entry.name === 'system.views'));
assert.docEq(collections.find(entry => entry.name === coll.getName()),
{name: coll.getName(), type: 'timeseries', options: {}, info: {readOnly: false}});

collections =
assert.commandWorked(testDB.runCommand({listCollections: 1, filter: {name: coll.getName()}}))
.cursor.firstBatch;
assert.eq(collections,
[{name: coll.getName(), type: 'timeseries', options: {}, info: {readOnly: false}}]);
})();
@@ -0,0 +1,30 @@
/**
* Tests that listCollections shows the time-series buckets collection, but not the view, if the
* time-series view is missing.
*
* @tags: [
* assumes_no_implicit_collection_creation_after_drop,
* does_not_support_transactions,
* requires_fcv_49,
* requires_find_command,
* requires_getmore,
* ]
*/
(function() {
'use strict';

const testDB = db.getSiblingDB(jsTestName());
assert.commandWorked(testDB.dropDatabase());

const timeFieldName = 'time';
const coll = testDB.getCollection('t');

assert.commandWorked(
testDB.createCollection(coll.getName(), {timeseries: {timeField: timeFieldName}}));
assert(testDB.system.views.drop());

const collections = assert.commandWorked(testDB.runCommand({listCollections: 1})).cursor.firstBatch;
jsTestLog('Checking listCollections result: ' + tojson(collections));
assert.eq(collections.length, 1);
assert(collections.find(entry => entry.name === 'system.buckets.' + coll.getName()));
})();
1 change: 1 addition & 0 deletions jstests/hooks/run_validate_collections_background.js
Expand Up @@ -94,6 +94,7 @@ const validateCollectionsBackgroundThread = function validateCollectionsBackgrou
for (let dbName of dbNames) {
let db = conn.getDB(dbName);

// TODO (SERVER-25493): Change filter to {type: 'collection'}.
const listCollRes = assert.commandWorked(db.runCommand({
"listCollections": 1,
"nameOnly": true,
Expand Down

0 comments on commit 8d5547c

Please sign in to comment.