635 lines
19 KiB
JavaScript
635 lines
19 KiB
JavaScript
// This file contains test helpers to generate streams of various types of data.
|
|
|
|
//
|
|
// Generates a stream of normal documents, with all the different BSON types that are representable
|
|
// from the shell.
|
|
//
|
|
// Interface:
|
|
//
|
|
// next() // Get the next document in the stream
|
|
// hasNext() // Check if the stream has any more documents
|
|
//
|
|
// Usage:
|
|
//
|
|
// var generator = new DataGenerator();
|
|
// while (generator.hasNext()) {
|
|
// var nextDoc = generator.next();
|
|
// // Do something with nextDoc
|
|
// }
|
|
//
|
|
function DataGenerator() {
|
|
var hexChars = "0123456789abcdefABCDEF";
|
|
var regexOptions = "igm";
|
|
var stringChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
var base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
|
|
// Generator functions
|
|
// BSON Type: -1
|
|
function GenMinKey(seed) {
|
|
return MinKey();
|
|
}
|
|
// BSON Type: 0 (EOO)
|
|
// No Shell Equivalent
|
|
// BSON Type: 1
|
|
function GenNumberDouble(seed) {
|
|
var seed = seed || 0;
|
|
return Number(seed);
|
|
}
|
|
// BSON Type: 2
|
|
function GenString(seed) {
|
|
var seed = seed || 0;
|
|
var text = "";
|
|
|
|
for (var i = 0; i < (seed % 1000) + 1; i++) {
|
|
text += stringChars.charAt((seed + (i % 10)) % stringChars.length);
|
|
}
|
|
|
|
return text;
|
|
}
|
|
// Javascript Dates get stored as strings
|
|
function GenDate(seed) {
|
|
// The "Date" constructor without "new" ignores its arguments anyway, so don't bother
|
|
// using the seed.
|
|
return Date();
|
|
}
|
|
// BSON Type: 3
|
|
function GenObject(seed) {
|
|
var seed = seed || 0;
|
|
|
|
return {"object": true};
|
|
}
|
|
// BSON Type: 4
|
|
function GenArray(seed) {
|
|
var seed = seed || 0;
|
|
|
|
return ["array", true];
|
|
}
|
|
// BSON Type: 5
|
|
function GenBinData(seed) {
|
|
var seed = seed || 0;
|
|
|
|
var text = "";
|
|
|
|
for (var i = 0; i < (seed % 1000) + 1; i++) {
|
|
text += base64Chars.charAt((seed + (i % 10)) % base64Chars.length);
|
|
}
|
|
|
|
if ((text.length % 4) == 1) {
|
|
// Special case to avoid winding up with three terminating '=' chars
|
|
// which would be invalid base64 data
|
|
text += "A==";
|
|
}
|
|
|
|
while ((text.length % 4) != 0) {
|
|
text += "=";
|
|
}
|
|
|
|
return BinData(seed % 6, text);
|
|
}
|
|
// BSON Type: 6
|
|
function GenUndefined(seed) {
|
|
return undefined;
|
|
}
|
|
// BSON Type: 7
|
|
function GenObjectId(seed) {
|
|
var seed = seed || 0;
|
|
var hexString = "";
|
|
|
|
for (var i = 0; i < 24; i++) {
|
|
hexString += hexChars.charAt((seed + (i % 10)) % hexChars.length);
|
|
}
|
|
|
|
return ObjectId(hexString);
|
|
}
|
|
// BSON Type: 8
|
|
function GenBool(seed) {
|
|
var seed = seed || 0;
|
|
|
|
return (seed % 2) === 0;
|
|
}
|
|
// BSON Type: 9
|
|
// Our ISODate constructor equals the Date BSON type
|
|
function GenISODate(seed) {
|
|
var seed = seed || 0;
|
|
|
|
var year = (seed % (2037 - 1970)) + 1970;
|
|
var month = (seed % 12) + 1;
|
|
var day = (seed % 27) + 1;
|
|
var hour = seed % 24;
|
|
var minute = seed % 60;
|
|
var second = seed % 60;
|
|
var millis = seed % 1000;
|
|
|
|
function pad(number, length) {
|
|
var str = '' + number;
|
|
|
|
while (str.length < length) {
|
|
str = '0' + str;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
return ISODate(pad(year, 4) + "-" + pad(month, 2) + "-" + pad(day, 2) + "T" + pad(hour, 2) +
|
|
":" + pad(minute, 2) + ":" + pad(second, 2) + "." + pad(millis, 3));
|
|
}
|
|
// BSON Type: 10
|
|
function GenNull(seed) {
|
|
return null;
|
|
}
|
|
// BSON Type: 11
|
|
function GenRegExp(seed) {
|
|
var seed = seed || 0;
|
|
var options = "";
|
|
|
|
for (var i = 0; i < (seed % 3) + 1; i++) {
|
|
options += regexOptions.charAt((seed + (i % 10)) % regexOptions.length);
|
|
}
|
|
|
|
return RegExp(GenString(seed), options);
|
|
}
|
|
function GenRegExpLiteral(seed) {
|
|
// We can't pass variables to a regex literal, so we can't programmatically generate the
|
|
// data. Instead we rely on the "RegExp" constructor.
|
|
return /regexliteral/;
|
|
}
|
|
// BSON Type: 12
|
|
// The DBPointer type in the shell equals the DBRef BSON type
|
|
function GenDBPointer(seed) {
|
|
var seed = seed || 0;
|
|
|
|
return DBPointer(GenString(seed), GenObjectId(seed));
|
|
}
|
|
// BSON Type: 13 (Code)
|
|
// No Shell Equivalent
|
|
// BSON Type: 14 (Symbol)
|
|
// No Shell Equivalent
|
|
// BSON Type: 15 (CodeWScope)
|
|
// No Shell Equivalent
|
|
// BSON Type: 16
|
|
function GenNumberInt(seed) {
|
|
var seed = seed || 0;
|
|
|
|
return NumberInt(seed);
|
|
}
|
|
// BSON Type: 17
|
|
function GenTimestamp(seed) {
|
|
var seed = seed || 0;
|
|
|
|
return Timestamp(seed, (seed * 100000) / 99999);
|
|
}
|
|
// BSON Type: 18
|
|
function GenNumberLong(seed) {
|
|
var seed = seed || 0;
|
|
|
|
return NumberLong(seed);
|
|
}
|
|
// BSON Type: 127
|
|
function GenMaxKey(seed) {
|
|
return MaxKey();
|
|
}
|
|
// The DBRef type is not a BSON type but is treated specially in the shell:
|
|
function GenDBRef(seed) {
|
|
var seed = seed || 0;
|
|
|
|
return DBRef(GenString(seed), GenObjectId(seed));
|
|
}
|
|
|
|
function GenFlatObjectAllTypes(seed) {
|
|
return {
|
|
"MinKey": GenMinKey(seed),
|
|
"NumberDouble": GenNumberDouble(seed),
|
|
"String": GenString(seed),
|
|
// Javascript Dates get stored as strings
|
|
"Date": GenDate(seed),
|
|
// BSON Type: 3
|
|
"Object": GenObject(seed),
|
|
// BSON Type: 4
|
|
"Array": GenArray(seed),
|
|
// BSON Type: 5
|
|
"BinData": GenBinData(seed),
|
|
// BSON Type: 6
|
|
"Undefined": undefined,
|
|
// BSON Type: 7
|
|
"jstOID": GenObjectId(seed),
|
|
// BSON Type: 8
|
|
"Bool": GenBool(seed),
|
|
// BSON Type: 9
|
|
// Our ISODate constructor equals the Date BSON type
|
|
"ISODate": GenISODate(seed),
|
|
// BSON Type: 10
|
|
"jstNULL": GenNull(seed),
|
|
// BSON Type: 11
|
|
"RegExp": GenRegExp(seed),
|
|
"RegExpLiteral": GenRegExpLiteral(seed),
|
|
// BSON Type: 12
|
|
// The DBPointer type in the shell equals the DBRef BSON type
|
|
"DBPointer": GenDBPointer(seed),
|
|
// BSON Type: 13 (Code)
|
|
// No Shell Equivalent
|
|
// BSON Type: 14 (Symbol)
|
|
// No Shell Equivalent
|
|
// BSON Type: 15 (CodeWScope)
|
|
// No Shell Equivalent
|
|
// BSON Type: 16
|
|
"NumberInt": GenNumberInt(seed),
|
|
// BSON Type: 17
|
|
"Timestamp": GenTimestamp(seed),
|
|
// BSON Type: 18
|
|
"NumberLong": GenNumberLong(seed),
|
|
// BSON Type: 127
|
|
"MaxKey": GenMaxKey(seed),
|
|
// The DBRef type is not a BSON type but is treated specially in the shell:
|
|
"DBRef": GenDBRef(seed),
|
|
};
|
|
}
|
|
|
|
function GenFlatObjectAllTypesHardCoded() {
|
|
return {
|
|
// BSON Type: -1
|
|
"MinKey": MinKey(),
|
|
// BSON Type: 0 (EOO)
|
|
// No Shell Equivalent
|
|
// BSON Type: 1
|
|
"NumberDouble": Number(4.0),
|
|
// BSON Type: 2
|
|
"String": "string",
|
|
// Javascript Dates get stored as strings
|
|
"Date": Date("2013-12-11T19:38:24.055Z"),
|
|
"Date2": GenDate(10000),
|
|
// BSON Type: 3
|
|
"Object": {"object": true},
|
|
// BSON Type: 4
|
|
"Array": ["array", true],
|
|
// BSON Type: 5
|
|
"BinData": BinData(0, "aaaa"),
|
|
// BSON Type: 6
|
|
"Undefined": undefined,
|
|
// BSON Type: 7
|
|
"jstOID": ObjectId("aaaaaaaaaaaaaaaaaaaaaaaa"),
|
|
// BSON Type: 8
|
|
"Bool": true,
|
|
// BSON Type: 9
|
|
// Our ISODate constructor equals the Date BSON type
|
|
"ISODate": ISODate("2013-12-11T19:38:24.055Z"),
|
|
// BSON Type: 10
|
|
"jstNULL": null,
|
|
// BSON Type: 11
|
|
"RegExp": RegExp("a"),
|
|
"RegExpLiteral": /a/,
|
|
// BSON Type: 12
|
|
// The DBPointer type in the shell equals the DBRef BSON type
|
|
"DBPointer": DBPointer("foo", ObjectId("bbbbbbbbbbbbbbbbbbbbbbbb")),
|
|
// BSON Type: 13 (Code)
|
|
// No Shell Equivalent
|
|
// BSON Type: 14 (Symbol)
|
|
// No Shell Equivalent
|
|
// BSON Type: 15 (CodeWScope)
|
|
// No Shell Equivalent
|
|
// BSON Type: 16
|
|
"NumberInt": NumberInt(5),
|
|
// BSON Type: 17
|
|
"Timestamp": Timestamp(1, 2),
|
|
// BSON Type: 18
|
|
"NumberLong": NumberLong(6),
|
|
// BSON Type: 127
|
|
"MaxKey": MaxKey(),
|
|
// The DBRef type is not a BSON type but is treated specially in the shell:
|
|
"DBRef": DBRef("bar", 2)
|
|
};
|
|
}
|
|
|
|
// Data we are using as a source for our testing
|
|
let testData = [
|
|
GenFlatObjectAllTypesHardCoded(),
|
|
GenFlatObjectAllTypes(0),
|
|
GenFlatObjectAllTypes(2),
|
|
GenFlatObjectAllTypes(3),
|
|
GenFlatObjectAllTypes(5),
|
|
GenFlatObjectAllTypes(7),
|
|
GenFlatObjectAllTypes(23),
|
|
GenFlatObjectAllTypes(111),
|
|
GenFlatObjectAllTypes(11111111),
|
|
];
|
|
|
|
// Cursor interface
|
|
var i = 0;
|
|
return {
|
|
"hasNext": function() {
|
|
return i < testData.length;
|
|
},
|
|
"next": function() {
|
|
if (i >= testData.length) {
|
|
return undefined;
|
|
}
|
|
return testData[i++];
|
|
}
|
|
};
|
|
}
|
|
|
|
//
|
|
// Generates a stream of index data documents, with a few different attributes that are valid for
|
|
// the different index types.
|
|
//
|
|
// Interface:
|
|
//
|
|
// next() // Get the next document in the stream
|
|
// hasNext() // Check if the stream has any more documents
|
|
//
|
|
// Returns documents of the form:
|
|
//
|
|
// {
|
|
// "spec" : <index spec>,
|
|
// "options" : <index options>
|
|
// }
|
|
//
|
|
// Usage:
|
|
//
|
|
// var generator = new IndexDataGenerator();
|
|
// while (generator.hasNext()) {
|
|
// var nextIndexDocument = generator.next();
|
|
// var nextIndexSpec = nextIndexDocument["spec"];
|
|
// var nextIndexOptions = nextIndexDocument["options"];
|
|
// db.createIndex(nextIndexSpec, nextIndexOptions);
|
|
// }
|
|
//
|
|
function IndexDataGenerator(options) {
|
|
// getNextUniqueKey()
|
|
//
|
|
// This function returns a new key each time it is called and is guaranteed to not return
|
|
// duplicates.
|
|
//
|
|
// The sequence of values returned is a-z then A-Z. When "Z" is reached, a new character is
|
|
// added
|
|
// and the first one wraps around, resulting in "aa". The process is repeated, so we get a
|
|
// sequence
|
|
// like this:
|
|
//
|
|
// "a"
|
|
// "b"
|
|
// ...
|
|
// "z"
|
|
// "A"
|
|
// ...
|
|
// "Z"
|
|
// "aa"
|
|
// "ba"
|
|
// ...
|
|
var currentKey = "";
|
|
function getNextUniqueKey() {
|
|
function setCharAt(str, index, chr) {
|
|
if (index > str.length - 1) {
|
|
return str;
|
|
}
|
|
return str.substr(0, index) + chr + str.substr(index + 1);
|
|
}
|
|
|
|
// Index of the letter we are advancing in our current key
|
|
var currentKeyIndex = 0;
|
|
var keyChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
|
|
do {
|
|
// If we are advancing a letter that does not exist yet, append a new character and
|
|
// return the result. For example, this is the case where "aa" follows "Z".
|
|
if (currentKeyIndex + 1 > currentKey.length) {
|
|
currentKey += keyChars[0];
|
|
return currentKey;
|
|
}
|
|
|
|
// Find the character (index into keyChars) that we currently have at this position, set
|
|
// this position to the next character in the keyChars sequence
|
|
var keyCharsIndex = keyChars.search(currentKey[currentKeyIndex]);
|
|
currentKey = setCharAt(
|
|
currentKey, currentKeyIndex, keyChars[(keyCharsIndex + 1) % keyChars.length]);
|
|
currentKeyIndex = currentKeyIndex + 1;
|
|
|
|
// Loop again if we advanced the character past the end of keyChars and wrapped around,
|
|
// so that we can advance the next character over too. For example, this is the case
|
|
// where "ab" follows "Za".
|
|
} while (keyCharsIndex + 1 >= keyChars.length);
|
|
|
|
return currentKey;
|
|
}
|
|
|
|
//
|
|
// Index Generation
|
|
//
|
|
|
|
function GenSingleFieldIndex(seed) {
|
|
var index = {};
|
|
index[getNextUniqueKey()] = (seed % 2) == 1 ? 1 : -1;
|
|
return index;
|
|
}
|
|
|
|
function GenCompoundIndex(seed) {
|
|
var index = {};
|
|
var i;
|
|
for (i = 0; i < (seed % 2) + 2; i++) {
|
|
index[getNextUniqueKey()] = ((seed + i) % 2) == 1 ? 1 : -1;
|
|
}
|
|
return index;
|
|
}
|
|
|
|
function GenNestedIndex(seed) {
|
|
var index = {};
|
|
var i;
|
|
var key = getNextUniqueKey();
|
|
for (i = 0; i < (seed % 2) + 1; i++) {
|
|
key += "." + getNextUniqueKey();
|
|
}
|
|
index[key] = (seed % 2) == 1 ? 1 : -1;
|
|
return index;
|
|
}
|
|
|
|
function Gen2dsphereIndex(seed) {
|
|
var index = {};
|
|
index[getNextUniqueKey()] = "2dsphere";
|
|
return index;
|
|
}
|
|
|
|
function Gen2dIndex(seed) {
|
|
var index = {};
|
|
index[getNextUniqueKey()] = "2d";
|
|
return index;
|
|
}
|
|
|
|
function GenTextIndex(seed) {
|
|
var index = {};
|
|
index[getNextUniqueKey()] = "text";
|
|
return index;
|
|
}
|
|
|
|
function GenHashedIndex(seed) {
|
|
var index = {};
|
|
index[getNextUniqueKey()] = "hashed";
|
|
return index;
|
|
}
|
|
|
|
function GenIndexOptions(seed) {
|
|
var attributes = {};
|
|
var i;
|
|
for (i = 0; i < (seed % 2) + 1; i++) {
|
|
// Mod 3 first to make sure the property type doesn't correlate with (seed % 2)
|
|
var propertyType = (seed % 3 + i) % 2;
|
|
if (propertyType == 0) {
|
|
attributes["expireAfterSeconds"] = ((seed + i) * 10000) % 9999 + 1000;
|
|
}
|
|
if (propertyType == 1) {
|
|
attributes["sparse"] = true;
|
|
} else {
|
|
// TODO: We have to test this as a separate stage because we want to round trip
|
|
// multiple documents
|
|
// attributes["unique"] = true;
|
|
}
|
|
}
|
|
return attributes;
|
|
}
|
|
|
|
function Gen2dIndexOptions(seed) {
|
|
var attributes = GenIndexOptions(seed);
|
|
var i;
|
|
for (i = 0; i < (seed % 2) + 1; i++) {
|
|
// Mod 3 first to make sure the property type doesn't correlate with (seed % 2)
|
|
var propertyType = (seed + i) % 3;
|
|
// When using a 2d index, the following additional index properties are supported:
|
|
// { "bits" : <bit precision>, "min" : <lower bound>, "max" : <upper bound> }
|
|
if (propertyType == 0) {
|
|
attributes["bits"] = ((seed + i) * 10000) % 100 + 10;
|
|
}
|
|
if (propertyType == 1) {
|
|
attributes["min"] = ((seed + i) * 10000) % 100 + 10;
|
|
}
|
|
if (propertyType == 2) {
|
|
attributes["max"] = ((seed + i) * 10000) % 100 + 10;
|
|
} else {
|
|
}
|
|
}
|
|
// The region specified in a 2d index must be positive
|
|
if (attributes["min"] >= attributes["max"]) {
|
|
attributes["max"] = attributes["min"] + attributes["max"];
|
|
}
|
|
return attributes;
|
|
}
|
|
|
|
function GenTextIndexOptions(seed) {
|
|
return GenIndexOptions(seed);
|
|
}
|
|
|
|
function Gen2dSphereIndexOptions(seed) {
|
|
return GenIndexOptions(seed);
|
|
}
|
|
|
|
let testIndexes = [
|
|
// Single Field Indexes
|
|
{"spec": GenSingleFieldIndex(1), "options": GenIndexOptions(0)},
|
|
{"spec": GenSingleFieldIndex(0), "options": GenIndexOptions(1)},
|
|
|
|
// Compound Indexes
|
|
{"spec": GenCompoundIndex(0), "options": GenIndexOptions(2)},
|
|
{"spec": GenCompoundIndex(1), "options": GenIndexOptions(3)},
|
|
{"spec": GenCompoundIndex(2), "options": GenIndexOptions(4)},
|
|
{"spec": GenCompoundIndex(3), "options": GenIndexOptions(5)},
|
|
{"spec": GenCompoundIndex(4), "options": GenIndexOptions(6)},
|
|
{"spec": GenCompoundIndex(5), "options": GenIndexOptions(7)},
|
|
{"spec": GenCompoundIndex(6), "options": GenIndexOptions(8)},
|
|
|
|
// Multikey Indexes
|
|
// (Same index spec as single field)
|
|
|
|
// Nested Indexes
|
|
{"spec": GenNestedIndex(0), "options": GenIndexOptions(9)},
|
|
{"spec": GenNestedIndex(1), "options": GenIndexOptions(10)},
|
|
{"spec": GenNestedIndex(2), "options": GenIndexOptions(11)},
|
|
|
|
// Geospatial Indexes
|
|
// 2dsphere
|
|
{"spec": Gen2dsphereIndex(7), "options": Gen2dSphereIndexOptions(12)},
|
|
// 2d
|
|
{"spec": Gen2dIndex(8), "options": Gen2dIndexOptions(13)},
|
|
|
|
// Text Indexes
|
|
{"spec": GenTextIndex(10), "options": GenTextIndexOptions(14)},
|
|
|
|
// Hashed Index
|
|
{"spec": GenHashedIndex(10), "options": GenIndexOptions(14)},
|
|
];
|
|
|
|
// Cursor interface
|
|
var i = 0;
|
|
return {
|
|
"hasNext": function() {
|
|
return i < testIndexes.length;
|
|
},
|
|
"next": function() {
|
|
if (i >= testIndexes.length) {
|
|
return undefined;
|
|
}
|
|
return testIndexes[i++];
|
|
}
|
|
};
|
|
}
|
|
|
|
//
|
|
// Generates a collection metadata object
|
|
//
|
|
// Interface:
|
|
//
|
|
// get() // Get a collection metadata object, based on the given options
|
|
//
|
|
// Options:
|
|
//
|
|
// {
|
|
// "capped" : (true/false) // Return all capped collection metadata
|
|
// }
|
|
//
|
|
// Usage:
|
|
//
|
|
// var generator = new CollectionMetadataGenerator({ "capped" : true });
|
|
// var metadata = generator.get();
|
|
//
|
|
function CollectionMetadataGenerator(options) {
|
|
var capped = true;
|
|
var options = options || {};
|
|
|
|
for (var option in options) {
|
|
if (options.hasOwnProperty(option)) {
|
|
if (option === 'capped') {
|
|
if (typeof (options['capped']) !== 'boolean') {
|
|
throw Error(
|
|
"\"capped\" options must be boolean in CollectionMetadataGenerator");
|
|
}
|
|
capped = options['capped'];
|
|
} else {
|
|
throw Error("Unsupported key in options passed to CollectionMetadataGenerator: " +
|
|
option);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Collection metadata we are using as a source for testing
|
|
// db.createCollection(name, {capped: <Boolean>, autoIndexId: <Boolean>, size: <number>, max
|
|
// <number>} )
|
|
var cappedCollectionMetadata = {
|
|
"capped": true,
|
|
"size": 100000,
|
|
"max": 2000,
|
|
};
|
|
|
|
return {
|
|
"get": function() {
|
|
return capped ? cappedCollectionMetadata : {};
|
|
}
|
|
};
|
|
}
|
|
|
|
//
|
|
// Wrapper for the above classes useful for passing into functions that expect a data generator
|
|
//
|
|
function CollectionDataGenerator(options) {
|
|
return {
|
|
"data": new DataGenerator(),
|
|
"indexes": new IndexDataGenerator(),
|
|
"collectionMetadata": new CollectionMetadataGenerator(options)
|
|
};
|
|
}
|