644 lines
19 KiB
JavaScript
644 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() {
|
|
let hexChars = "0123456789abcdefABCDEF";
|
|
let regexOptions = "igm";
|
|
let stringChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
let 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;
|
|
let text = "";
|
|
|
|
for (let 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;
|
|
|
|
let text = "";
|
|
|
|
for (let 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;
|
|
let hexString = "";
|
|
|
|
for (let 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;
|
|
|
|
let year = (seed % (2037 - 1970)) + 1970;
|
|
let month = (seed % 12) + 1;
|
|
let day = (seed % 27) + 1;
|
|
let hour = seed % 24;
|
|
let minute = seed % 60;
|
|
let second = seed % 60;
|
|
let millis = seed % 1000;
|
|
|
|
function pad(number, length) {
|
|
let 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;
|
|
let options = "";
|
|
|
|
for (let 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
|
|
let 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"
|
|
// ...
|
|
let 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
|
|
let currentKeyIndex = 0;
|
|
let 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) {
|
|
let index = {};
|
|
index[getNextUniqueKey()] = seed % 2 == 1 ? 1 : -1;
|
|
return index;
|
|
}
|
|
|
|
function GenCompoundIndex(seed) {
|
|
let index = {};
|
|
let i;
|
|
for (i = 0; i < (seed % 2) + 2; i++) {
|
|
index[getNextUniqueKey()] = (seed + i) % 2 == 1 ? 1 : -1;
|
|
}
|
|
return index;
|
|
}
|
|
|
|
function GenNestedIndex(seed) {
|
|
let index = {};
|
|
let i;
|
|
let key = getNextUniqueKey();
|
|
for (i = 0; i < (seed % 2) + 1; i++) {
|
|
key += "." + getNextUniqueKey();
|
|
}
|
|
index[key] = seed % 2 == 1 ? 1 : -1;
|
|
return index;
|
|
}
|
|
|
|
function Gen2dsphereIndex(seed) {
|
|
let index = {};
|
|
index[getNextUniqueKey()] = "2dsphere";
|
|
return index;
|
|
}
|
|
|
|
function Gen2dIndex(seed) {
|
|
let index = {};
|
|
index[getNextUniqueKey()] = "2d";
|
|
return index;
|
|
}
|
|
|
|
function GenTextIndex(seed) {
|
|
let index = {};
|
|
index[getNextUniqueKey()] = "text";
|
|
return index;
|
|
}
|
|
|
|
function GenHashedIndex(seed) {
|
|
let index = {};
|
|
index[getNextUniqueKey()] = "hashed";
|
|
return index;
|
|
}
|
|
|
|
function GenIndexOptions(seed) {
|
|
let attributes = {};
|
|
let i;
|
|
for (i = 0; i < (seed % 2) + 1; i++) {
|
|
// Mod 3 first to make sure the property type doesn't correlate with (seed % 2)
|
|
let 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) {
|
|
let attributes = GenIndexOptions(seed);
|
|
let i;
|
|
for (i = 0; i < (seed % 2) + 1; i++) {
|
|
// Mod 3 first to make sure the property type doesn't correlate with (seed % 2)
|
|
let 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
|
|
let 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) {
|
|
let capped = true;
|
|
var options = options || {};
|
|
|
|
for (let 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>, size: <number>, max <number>} )
|
|
let 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),
|
|
};
|
|
}
|