// 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 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" : , // "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" : , "min" : , "max" : } 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 GenHaystackIndexOptions(seed) { var attributes = GenIndexOptions(seed); // When using a haystack index, the following additional index properties are required: // { "bucketSize" : } attributes["bucketSize"] = (seed * 10000) % 100 + 10; return attributes; } function GenTextIndexOptions(seed) { return GenIndexOptions(seed); } function Gen2dSphereIndexOptions(seed) { return GenIndexOptions(seed); } 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 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: , autoIndexId: , size: , max // } ) 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) }; }