mongo/jstests/multiVersion/libs/data_generators.js
Steve McClure 1ffbc6c2e9 SERVER-109432: Autofix JS var usage to favor let (#40637)
GitOrigin-RevId: 9674b7db36a0f3f650d39c1e3fb2ad6ff2141cfb
2025-08-28 19:21:01 +00:00

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),
};
}