summaryrefslogtreecommitdiff
path: root/jstests/multiVersion/libs/data_generators.js
diff options
context:
space:
mode:
Diffstat (limited to 'jstests/multiVersion/libs/data_generators.js')
-rw-r--r--jstests/multiVersion/libs/data_generators.js667
1 files changed, 667 insertions, 0 deletions
diff --git a/jstests/multiVersion/libs/data_generators.js b/jstests/multiVersion/libs/data_generators.js
new file mode 100644
index 00000000000..610dbd14b8a
--- /dev/null
+++ b/jstests/multiVersion/libs/data_generators.js
@@ -0,0 +1,667 @@
+// 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);
+ }
+
+ 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;
+
+ // Make sure our timestamp is not zero, because that doesn't round trip from 2.4 to latest.
+ // See SERVER-12302.
+ if (seed == 0) {
+ seed = 1;
+ }
+
+ 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
+ "RegEx" : RegExp("a"),
+ "RegEx" : /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
+ 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.ensureIndex(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
+ 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 GenHaystackIndex(seed) {
+ var index = {};
+ index[getNextUniqueKey()] = "geoHaystack";
+ // Haystack indexes need a non geo field, and the geo field must be first
+ index[getNextUniqueKey()] = (seed % 2) == 1 ? 1 : -1;
+ 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 {
+ }
+ }
+ return attributes;
+ }
+
+ function GenHaystackIndexOptions(seed) {
+ var attributes = GenIndexOptions(seed);
+ // When using a haystack index, the following additional index properties are required:
+ // { "bucketSize" : <bucket value> }
+ attributes["bucketSize"] = (seed * 10000) % 100 + 10;
+ return attributes;
+ }
+
+ function GenTextIndexOptions(seed) {
+ var attributes = GenIndexOptions(seed);
+ // When using a text index, the following additional index properties are required when
+ // downgrading from 2.6:
+ // { "textIndexVersion" : 1 }
+ attributes["textIndexVersion"] = 1
+ return attributes;
+ }
+
+ function Gen2dSphereIndexOptions(seed) {
+ var attributes = GenIndexOptions(seed);
+ // When using a 2dsphere index, the following additional index properties are required when
+ // downgrading from 2.6:
+ // { "2dsphereIndexVersion" : 1 }
+ attributes["2dsphereIndexVersion"] = 1
+ return attributes;
+ }
+
+ 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) },
+ // Haystack
+ { "spec" : GenHaystackIndex(9), "options" : GenHaystackIndexOptions(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 "\"capped\" options must be boolean in CollectionMetadataGenerator";
+ }
+ capped = options['capped'];
+ }
+ else {
+ throw "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,
+ "usePowerOf2Sizes" : true,
+ //"autoIndexId" : false // XXX: this doesn't exist in 2.4
+ }
+ // We need to explicitly enable usePowerOf2Sizes, since it's the default in 2.6 but not in 2.4
+ var normalCollectionMetadata = {
+ "usePowerOf2Sizes" : true
+ };
+
+ return {
+ "get" : function () {
+ return capped ? cappedCollectionMetadata : normalCollectionMetadata;
+ }
+ }
+}
+
+//
+// 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)
+ }
+}