You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							998 lines
						
					
					
						
							27 KiB
						
					
					
				
			
		
		
	
	
							998 lines
						
					
					
						
							27 KiB
						
					
					
				| 'use strict';
 | |
| const MongoError = require('./core/error').MongoError;
 | |
| const WriteConcern = require('./write_concern');
 | |
| 
 | |
| var shallowClone = function(obj) {
 | |
|   var copy = {};
 | |
|   for (var name in obj) copy[name] = obj[name];
 | |
|   return copy;
 | |
| };
 | |
| 
 | |
| // Set simple property
 | |
| var getSingleProperty = function(obj, name, value) {
 | |
|   Object.defineProperty(obj, name, {
 | |
|     enumerable: true,
 | |
|     get: function() {
 | |
|       return value;
 | |
|     }
 | |
|   });
 | |
| };
 | |
| 
 | |
| var formatSortValue = (exports.formatSortValue = function(sortDirection) {
 | |
|   var value = ('' + sortDirection).toLowerCase();
 | |
| 
 | |
|   switch (value) {
 | |
|     case 'ascending':
 | |
|     case 'asc':
 | |
|     case '1':
 | |
|       return 1;
 | |
|     case 'descending':
 | |
|     case 'desc':
 | |
|     case '-1':
 | |
|       return -1;
 | |
|     default:
 | |
|       throw new Error(
 | |
|         'Illegal sort clause, must be of the form ' +
 | |
|           "[['field1', '(ascending|descending)'], " +
 | |
|           "['field2', '(ascending|descending)']]"
 | |
|       );
 | |
|   }
 | |
| });
 | |
| 
 | |
| var formattedOrderClause = (exports.formattedOrderClause = function(sortValue) {
 | |
|   var orderBy = new Map();
 | |
|   if (sortValue == null) return null;
 | |
|   if (Array.isArray(sortValue)) {
 | |
|     if (sortValue.length === 0) {
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     for (var i = 0; i < sortValue.length; i++) {
 | |
|       if (sortValue[i].constructor === String) {
 | |
|         orderBy.set(`${sortValue[i]}`, 1);
 | |
|       } else {
 | |
|         orderBy.set(`${sortValue[i][0]}`, formatSortValue(sortValue[i][1]));
 | |
|       }
 | |
|     }
 | |
|   } else if (sortValue != null && typeof sortValue === 'object') {
 | |
|     if (sortValue instanceof Map) {
 | |
|       orderBy = sortValue;
 | |
|     } else {
 | |
|       var sortKeys = Object.keys(sortValue);
 | |
|       for (var k of sortKeys) {
 | |
|         orderBy.set(k, sortValue[k]);
 | |
|       }
 | |
|     }
 | |
|   } else if (typeof sortValue === 'string') {
 | |
|     orderBy.set(`${sortValue}`, 1);
 | |
|   } else {
 | |
|     throw new Error(
 | |
|       'Illegal sort clause, must be of the form ' +
 | |
|         "[['field1', '(ascending|descending)'], ['field2', '(ascending|descending)']]"
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   return orderBy;
 | |
| });
 | |
| 
 | |
| var checkCollectionName = function checkCollectionName(collectionName) {
 | |
|   if ('string' !== typeof collectionName) {
 | |
|     throw new MongoError('collection name must be a String');
 | |
|   }
 | |
| 
 | |
|   if (!collectionName || collectionName.indexOf('..') !== -1) {
 | |
|     throw new MongoError('collection names cannot be empty');
 | |
|   }
 | |
| 
 | |
|   if (
 | |
|     collectionName.indexOf('$') !== -1 &&
 | |
|     collectionName.match(/((^\$cmd)|(oplog\.\$main))/) == null
 | |
|   ) {
 | |
|     throw new MongoError("collection names must not contain '$'");
 | |
|   }
 | |
| 
 | |
|   if (collectionName.match(/^\.|\.$/) != null) {
 | |
|     throw new MongoError("collection names must not start or end with '.'");
 | |
|   }
 | |
| 
 | |
|   // Validate that we are not passing 0x00 in the collection name
 | |
|   if (collectionName.indexOf('\x00') !== -1) {
 | |
|     throw new MongoError('collection names cannot contain a null character');
 | |
|   }
 | |
| };
 | |
| 
 | |
| var handleCallback = function(callback, err, value1, value2) {
 | |
|   try {
 | |
|     if (callback == null) return;
 | |
| 
 | |
|     if (callback) {
 | |
|       return value2 ? callback(err, value1, value2) : callback(err, value1);
 | |
|     }
 | |
|   } catch (err) {
 | |
|     process.nextTick(function() {
 | |
|       throw err;
 | |
|     });
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Wrap a Mongo error document in an Error instance
 | |
|  * @ignore
 | |
|  * @api private
 | |
|  */
 | |
| var toError = function(error) {
 | |
|   if (error instanceof Error) return error;
 | |
| 
 | |
|   var msg = error.err || error.errmsg || error.errMessage || error;
 | |
|   var e = MongoError.create({ message: msg, driver: true });
 | |
| 
 | |
|   // Get all object keys
 | |
|   var keys = typeof error === 'object' ? Object.keys(error) : [];
 | |
| 
 | |
|   for (var i = 0; i < keys.length; i++) {
 | |
|     try {
 | |
|       e[keys[i]] = error[keys[i]];
 | |
|     } catch (err) {
 | |
|       // continue
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return e;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * @ignore
 | |
|  */
 | |
| var normalizeHintField = function normalizeHintField(hint) {
 | |
|   var finalHint = null;
 | |
| 
 | |
|   if (typeof hint === 'string') {
 | |
|     finalHint = hint;
 | |
|   } else if (Array.isArray(hint)) {
 | |
|     finalHint = {};
 | |
| 
 | |
|     hint.forEach(function(param) {
 | |
|       finalHint[param] = 1;
 | |
|     });
 | |
|   } else if (hint != null && typeof hint === 'object') {
 | |
|     finalHint = {};
 | |
|     for (var name in hint) {
 | |
|       finalHint[name] = hint[name];
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return finalHint;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Create index name based on field spec
 | |
|  *
 | |
|  * @ignore
 | |
|  * @api private
 | |
|  */
 | |
| var parseIndexOptions = function(fieldOrSpec) {
 | |
|   var fieldHash = {};
 | |
|   var indexes = [];
 | |
|   var keys;
 | |
| 
 | |
|   // Get all the fields accordingly
 | |
|   if ('string' === typeof fieldOrSpec) {
 | |
|     // 'type'
 | |
|     indexes.push(fieldOrSpec + '_' + 1);
 | |
|     fieldHash[fieldOrSpec] = 1;
 | |
|   } else if (Array.isArray(fieldOrSpec)) {
 | |
|     fieldOrSpec.forEach(function(f) {
 | |
|       if ('string' === typeof f) {
 | |
|         // [{location:'2d'}, 'type']
 | |
|         indexes.push(f + '_' + 1);
 | |
|         fieldHash[f] = 1;
 | |
|       } else if (Array.isArray(f)) {
 | |
|         // [['location', '2d'],['type', 1]]
 | |
|         indexes.push(f[0] + '_' + (f[1] || 1));
 | |
|         fieldHash[f[0]] = f[1] || 1;
 | |
|       } else if (isObject(f)) {
 | |
|         // [{location:'2d'}, {type:1}]
 | |
|         keys = Object.keys(f);
 | |
|         keys.forEach(function(k) {
 | |
|           indexes.push(k + '_' + f[k]);
 | |
|           fieldHash[k] = f[k];
 | |
|         });
 | |
|       } else {
 | |
|         // undefined (ignore)
 | |
|       }
 | |
|     });
 | |
|   } else if (isObject(fieldOrSpec)) {
 | |
|     // {location:'2d', type:1}
 | |
|     keys = Object.keys(fieldOrSpec);
 | |
|     keys.forEach(function(key) {
 | |
|       indexes.push(key + '_' + fieldOrSpec[key]);
 | |
|       fieldHash[key] = fieldOrSpec[key];
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   return {
 | |
|     name: indexes.join('_'),
 | |
|     keys: keys,
 | |
|     fieldHash: fieldHash
 | |
|   };
 | |
| };
 | |
| 
 | |
| var isObject = (exports.isObject = function(arg) {
 | |
|   return '[object Object]' === Object.prototype.toString.call(arg);
 | |
| });
 | |
| 
 | |
| var debugOptions = function(debugFields, options) {
 | |
|   var finaloptions = {};
 | |
|   debugFields.forEach(function(n) {
 | |
|     finaloptions[n] = options[n];
 | |
|   });
 | |
| 
 | |
|   return finaloptions;
 | |
| };
 | |
| 
 | |
| var decorateCommand = function(command, options, exclude) {
 | |
|   for (var name in options) {
 | |
|     if (exclude.indexOf(name) === -1) command[name] = options[name];
 | |
|   }
 | |
| 
 | |
|   return command;
 | |
| };
 | |
| 
 | |
| var mergeOptions = function(target, source) {
 | |
|   for (var name in source) {
 | |
|     target[name] = source[name];
 | |
|   }
 | |
| 
 | |
|   return target;
 | |
| };
 | |
| 
 | |
| // Merge options with translation
 | |
| var translateOptions = function(target, source) {
 | |
|   var translations = {
 | |
|     // SSL translation options
 | |
|     sslCA: 'ca',
 | |
|     sslCRL: 'crl',
 | |
|     sslValidate: 'rejectUnauthorized',
 | |
|     sslKey: 'key',
 | |
|     sslCert: 'cert',
 | |
|     sslPass: 'passphrase',
 | |
|     // SocketTimeout translation options
 | |
|     socketTimeoutMS: 'socketTimeout',
 | |
|     connectTimeoutMS: 'connectionTimeout',
 | |
|     // Replicaset options
 | |
|     replicaSet: 'setName',
 | |
|     rs_name: 'setName',
 | |
|     secondaryAcceptableLatencyMS: 'acceptableLatency',
 | |
|     connectWithNoPrimary: 'secondaryOnlyConnectionAllowed',
 | |
|     // Mongos options
 | |
|     acceptableLatencyMS: 'localThresholdMS'
 | |
|   };
 | |
| 
 | |
|   for (var name in source) {
 | |
|     if (translations[name]) {
 | |
|       target[translations[name]] = source[name];
 | |
|     } else {
 | |
|       target[name] = source[name];
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return target;
 | |
| };
 | |
| 
 | |
| var filterOptions = function(options, names) {
 | |
|   var filterOptions = {};
 | |
| 
 | |
|   for (var name in options) {
 | |
|     if (names.indexOf(name) !== -1) filterOptions[name] = options[name];
 | |
|   }
 | |
| 
 | |
|   // Filtered options
 | |
|   return filterOptions;
 | |
| };
 | |
| 
 | |
| // Write concern keys
 | |
| const WRITE_CONCERN_KEYS = ['w', 'j', 'wtimeout', 'fsync', 'writeConcern'];
 | |
| 
 | |
| /**
 | |
|  * If there is no WriteConcern related options defined on target then inherit from source.
 | |
|  * Otherwise, do not inherit **any** options from source.
 | |
|  * @internal
 | |
|  * @param {object} target - options object conditionally receiving the writeConcern options
 | |
|  * @param {object} source - options object containing the potentially inherited writeConcern options
 | |
|  */
 | |
| function conditionallyMergeWriteConcern(target, source) {
 | |
|   let found = false;
 | |
|   for (const wcKey of WRITE_CONCERN_KEYS) {
 | |
|     if (wcKey in target) {
 | |
|       // Found a writeConcern option
 | |
|       found = true;
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!found) {
 | |
|     for (const wcKey of WRITE_CONCERN_KEYS) {
 | |
|       if (source[wcKey]) {
 | |
|         if (!('writeConcern' in target)) {
 | |
|           target.writeConcern = {};
 | |
|         }
 | |
|         target.writeConcern[wcKey] = source[wcKey];
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return target;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Executes the given operation with provided arguments.
 | |
|  *
 | |
|  * This method reduces large amounts of duplication in the entire codebase by providing
 | |
|  * a single point for determining whether callbacks or promises should be used. Additionally
 | |
|  * it allows for a single point of entry to provide features such as implicit sessions, which
 | |
|  * are required by the Driver Sessions specification in the event that a ClientSession is
 | |
|  * not provided
 | |
|  *
 | |
|  * @param {object} topology The topology to execute this operation on
 | |
|  * @param {function} operation The operation to execute
 | |
|  * @param {array} args Arguments to apply the provided operation
 | |
|  * @param {object} [options] Options that modify the behavior of the method
 | |
|  */
 | |
| const executeLegacyOperation = (topology, operation, args, options) => {
 | |
|   if (topology == null) {
 | |
|     throw new TypeError('This method requires a valid topology instance');
 | |
|   }
 | |
| 
 | |
|   if (!Array.isArray(args)) {
 | |
|     throw new TypeError('This method requires an array of arguments to apply');
 | |
|   }
 | |
| 
 | |
|   options = options || {};
 | |
|   const Promise = topology.s.promiseLibrary;
 | |
|   let callback = args[args.length - 1];
 | |
| 
 | |
|   // The driver sessions spec mandates that we implicitly create sessions for operations
 | |
|   // that are not explicitly provided with a session.
 | |
|   let session, opOptions, owner;
 | |
|   if (!options.skipSessions && topology.hasSessionSupport()) {
 | |
|     opOptions = args[args.length - 2];
 | |
|     if (opOptions == null || opOptions.session == null) {
 | |
|       owner = Symbol();
 | |
|       session = topology.startSession({ owner });
 | |
|       const optionsIndex = args.length - 2;
 | |
|       args[optionsIndex] = Object.assign({}, args[optionsIndex], { session: session });
 | |
|     } else if (opOptions.session && opOptions.session.hasEnded) {
 | |
|       throw new MongoError('Use of expired sessions is not permitted');
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   const makeExecuteCallback = (resolve, reject) =>
 | |
|     function executeCallback(err, result) {
 | |
|       if (session && session.owner === owner && !options.returnsCursor) {
 | |
|         session.endSession(() => {
 | |
|           delete opOptions.session;
 | |
|           if (err) return reject(err);
 | |
|           resolve(result);
 | |
|         });
 | |
|       } else {
 | |
|         if (err) return reject(err);
 | |
|         resolve(result);
 | |
|       }
 | |
|     };
 | |
| 
 | |
|   // Execute using callback
 | |
|   if (typeof callback === 'function') {
 | |
|     callback = args.pop();
 | |
|     const handler = makeExecuteCallback(
 | |
|       result => callback(null, result),
 | |
|       err => callback(err, null)
 | |
|     );
 | |
|     args.push(handler);
 | |
| 
 | |
|     try {
 | |
|       return operation.apply(null, args);
 | |
|     } catch (e) {
 | |
|       handler(e);
 | |
|       throw e;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Return a Promise
 | |
|   if (args[args.length - 1] != null) {
 | |
|     throw new TypeError('final argument to `executeLegacyOperation` must be a callback');
 | |
|   }
 | |
| 
 | |
|   return new Promise(function(resolve, reject) {
 | |
|     const handler = makeExecuteCallback(resolve, reject);
 | |
|     args[args.length - 1] = handler;
 | |
| 
 | |
|     try {
 | |
|       return operation.apply(null, args);
 | |
|     } catch (e) {
 | |
|       handler(e);
 | |
|     }
 | |
|   });
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Applies retryWrites: true to a command if retryWrites is set on the command's database.
 | |
|  *
 | |
|  * @param {object} target The target command to which we will apply retryWrites.
 | |
|  * @param {object} db The database from which we can inherit a retryWrites value.
 | |
|  */
 | |
| function applyRetryableWrites(target, db) {
 | |
|   if (db && db.s.options.retryWrites) {
 | |
|     target.retryWrites = true;
 | |
|   }
 | |
| 
 | |
|   return target;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Applies a write concern to a command based on well defined inheritance rules, optionally
 | |
|  * detecting support for the write concern in the first place.
 | |
|  *
 | |
|  * @param {Object} target the target command we will be applying the write concern to
 | |
|  * @param {Object} sources sources where we can inherit default write concerns from
 | |
|  * @param {Object} [options] optional settings passed into a command for write concern overrides
 | |
|  * @returns {Object} the (now) decorated target
 | |
|  */
 | |
| function applyWriteConcern(target, sources, options) {
 | |
|   options = options || {};
 | |
|   const db = sources.db;
 | |
|   const coll = sources.collection;
 | |
| 
 | |
|   if (options.session && options.session.inTransaction()) {
 | |
|     // writeConcern is not allowed within a multi-statement transaction
 | |
|     if (target.writeConcern) {
 | |
|       delete target.writeConcern;
 | |
|     }
 | |
| 
 | |
|     return target;
 | |
|   }
 | |
| 
 | |
|   const writeConcern = WriteConcern.fromOptions(options);
 | |
|   if (writeConcern) {
 | |
|     return Object.assign(target, { writeConcern });
 | |
|   }
 | |
| 
 | |
|   if (coll && coll.writeConcern) {
 | |
|     return Object.assign(target, { writeConcern: Object.assign({}, coll.writeConcern) });
 | |
|   }
 | |
| 
 | |
|   if (db && db.writeConcern) {
 | |
|     return Object.assign(target, { writeConcern: Object.assign({}, db.writeConcern) });
 | |
|   }
 | |
| 
 | |
|   return target;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Checks if a given value is a Promise
 | |
|  *
 | |
|  * @param {*} maybePromise
 | |
|  * @return true if the provided value is a Promise
 | |
|  */
 | |
| function isPromiseLike(maybePromise) {
 | |
|   return maybePromise && typeof maybePromise.then === 'function';
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Applies collation to a given command.
 | |
|  *
 | |
|  * @param {object} [command] the command on which to apply collation
 | |
|  * @param {(Cursor|Collection)} [target] target of command
 | |
|  * @param {object} [options] options containing collation settings
 | |
|  */
 | |
| function decorateWithCollation(command, target, options) {
 | |
|   const topology = (target.s && target.s.topology) || target.topology;
 | |
| 
 | |
|   if (!topology) {
 | |
|     throw new TypeError('parameter "target" is missing a topology');
 | |
|   }
 | |
| 
 | |
|   const capabilities = topology.capabilities();
 | |
|   if (options.collation && typeof options.collation === 'object') {
 | |
|     if (capabilities && capabilities.commandsTakeCollation) {
 | |
|       command.collation = options.collation;
 | |
|     } else {
 | |
|       throw new MongoError(`Current topology does not support collation`);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Applies a read concern to a given command.
 | |
|  *
 | |
|  * @param {object} command the command on which to apply the read concern
 | |
|  * @param {Collection} coll the parent collection of the operation calling this method
 | |
|  */
 | |
| function decorateWithReadConcern(command, coll, options) {
 | |
|   if (options && options.session && options.session.inTransaction()) {
 | |
|     return;
 | |
|   }
 | |
|   let readConcern = Object.assign({}, command.readConcern || {});
 | |
|   if (coll.s.readConcern) {
 | |
|     Object.assign(readConcern, coll.s.readConcern);
 | |
|   }
 | |
| 
 | |
|   if (Object.keys(readConcern).length > 0) {
 | |
|     Object.assign(command, { readConcern: readConcern });
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Applies an explain to a given command.
 | |
|  * @internal
 | |
|  *
 | |
|  * @param {object} command - the command on which to apply the explain
 | |
|  * @param {Explain} explain - the options containing the explain verbosity
 | |
|  * @return the new command
 | |
|  */
 | |
| function decorateWithExplain(command, explain) {
 | |
|   if (command.explain) {
 | |
|     return command;
 | |
|   }
 | |
| 
 | |
|   return { explain: command, verbosity: explain.verbosity };
 | |
| }
 | |
| 
 | |
| const nodejsMajorVersion = +process.version.split('.')[0].substring(1);
 | |
| const emitProcessWarning = msg =>
 | |
|   nodejsMajorVersion <= 6
 | |
|     ? process.emitWarning(msg, 'DeprecationWarning', MONGODB_WARNING_CODE)
 | |
|     : process.emitWarning(msg, { type: 'DeprecationWarning', code: MONGODB_WARNING_CODE });
 | |
| // eslint-disable-next-line no-console
 | |
| const emitConsoleWarning = msg => console.error(msg);
 | |
| const emitDeprecationWarning = process.emitWarning ? emitProcessWarning : emitConsoleWarning;
 | |
| 
 | |
| /**
 | |
|  * Default message handler for generating deprecation warnings.
 | |
|  *
 | |
|  * @param {string} name function name
 | |
|  * @param {string} option option name
 | |
|  * @return {string} warning message
 | |
|  * @ignore
 | |
|  * @api private
 | |
|  */
 | |
| function defaultMsgHandler(name, option) {
 | |
|   return `${name} option [${option}] is deprecated and will be removed in a later version.`;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Deprecates a given function's options.
 | |
|  *
 | |
|  * @param {object} config configuration for deprecation
 | |
|  * @param {string} config.name function name
 | |
|  * @param {Array} config.deprecatedOptions options to deprecate
 | |
|  * @param {number} config.optionsIndex index of options object in function arguments array
 | |
|  * @param {function} [config.msgHandler] optional custom message handler to generate warnings
 | |
|  * @param {function} fn the target function of deprecation
 | |
|  * @return {function} modified function that warns once per deprecated option, and executes original function
 | |
|  * @ignore
 | |
|  * @api private
 | |
|  */
 | |
| function deprecateOptions(config, fn) {
 | |
|   if (process.noDeprecation === true) {
 | |
|     return fn;
 | |
|   }
 | |
| 
 | |
|   const msgHandler = config.msgHandler ? config.msgHandler : defaultMsgHandler;
 | |
| 
 | |
|   const optionsWarned = new Set();
 | |
|   function deprecated() {
 | |
|     const options = arguments[config.optionsIndex];
 | |
| 
 | |
|     // ensure options is a valid, non-empty object, otherwise short-circuit
 | |
|     if (!isObject(options) || Object.keys(options).length === 0) {
 | |
|       return fn.apply(this, arguments);
 | |
|     }
 | |
| 
 | |
|     config.deprecatedOptions.forEach(deprecatedOption => {
 | |
|       if (
 | |
|         Object.prototype.hasOwnProperty.call(options, deprecatedOption) &&
 | |
|         !optionsWarned.has(deprecatedOption)
 | |
|       ) {
 | |
|         optionsWarned.add(deprecatedOption);
 | |
|         const msg = msgHandler(config.name, deprecatedOption);
 | |
|         emitDeprecationWarning(msg);
 | |
|         if (this && this.getLogger) {
 | |
|           const logger = this.getLogger();
 | |
|           if (logger) {
 | |
|             logger.warn(msg);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     });
 | |
| 
 | |
|     return fn.apply(this, arguments);
 | |
|   }
 | |
| 
 | |
|   // These lines copied from https://github.com/nodejs/node/blob/25e5ae41688676a5fd29b2e2e7602168eee4ceb5/lib/internal/util.js#L73-L80
 | |
|   // The wrapper will keep the same prototype as fn to maintain prototype chain
 | |
|   Object.setPrototypeOf(deprecated, fn);
 | |
|   if (fn.prototype) {
 | |
|     // Setting this (rather than using Object.setPrototype, as above) ensures
 | |
|     // that calling the unwrapped constructor gives an instanceof the wrapped
 | |
|     // constructor.
 | |
|     deprecated.prototype = fn.prototype;
 | |
|   }
 | |
| 
 | |
|   return deprecated;
 | |
| }
 | |
| 
 | |
| const SUPPORTS = {};
 | |
| // Test asyncIterator support
 | |
| try {
 | |
|   require('./async/async_iterator');
 | |
|   SUPPORTS.ASYNC_ITERATOR = true;
 | |
| } catch (e) {
 | |
|   SUPPORTS.ASYNC_ITERATOR = false;
 | |
| }
 | |
| 
 | |
| class MongoDBNamespace {
 | |
|   constructor(db, collection) {
 | |
|     this.db = db;
 | |
|     this.collection = collection;
 | |
|   }
 | |
| 
 | |
|   toString() {
 | |
|     return this.collection ? `${this.db}.${this.collection}` : this.db;
 | |
|   }
 | |
| 
 | |
|   withCollection(collection) {
 | |
|     return new MongoDBNamespace(this.db, collection);
 | |
|   }
 | |
| 
 | |
|   static fromString(namespace) {
 | |
|     if (!namespace) {
 | |
|       throw new Error(`Cannot parse namespace from "${namespace}"`);
 | |
|     }
 | |
| 
 | |
|     const index = namespace.indexOf('.');
 | |
|     return new MongoDBNamespace(namespace.substring(0, index), namespace.substring(index + 1));
 | |
|   }
 | |
| }
 | |
| 
 | |
| function* makeCounter(seed) {
 | |
|   let count = seed || 0;
 | |
|   while (true) {
 | |
|     const newCount = count;
 | |
|     count += 1;
 | |
|     yield newCount;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Helper function for either accepting a callback, or returning a promise
 | |
|  *
 | |
|  * @param {Object} parent an instance of parent with promiseLibrary.
 | |
|  * @param {object} parent.s an object containing promiseLibrary.
 | |
|  * @param {function} parent.s.promiseLibrary an object containing promiseLibrary.
 | |
|  * @param {[Function]} callback an optional callback.
 | |
|  * @param {Function} fn A function that takes a callback
 | |
|  * @returns {Promise|void} Returns nothing if a callback is supplied, else returns a Promise.
 | |
|  */
 | |
| function maybePromise(parent, callback, fn) {
 | |
|   const PromiseLibrary = (parent && parent.s && parent.s.promiseLibrary) || Promise;
 | |
| 
 | |
|   let result;
 | |
|   if (typeof callback !== 'function') {
 | |
|     result = new PromiseLibrary((resolve, reject) => {
 | |
|       callback = (err, res) => {
 | |
|         if (err) return reject(err);
 | |
|         resolve(res);
 | |
|       };
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   fn(function(err, res) {
 | |
|     if (err != null) {
 | |
|       try {
 | |
|         callback(err);
 | |
|       } catch (error) {
 | |
|         return process.nextTick(() => {
 | |
|           throw error;
 | |
|         });
 | |
|       }
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     callback(err, res);
 | |
|   });
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| function now() {
 | |
|   const hrtime = process.hrtime();
 | |
|   return Math.floor(hrtime[0] * 1000 + hrtime[1] / 1000000);
 | |
| }
 | |
| 
 | |
| function calculateDurationInMs(started) {
 | |
|   if (typeof started !== 'number') {
 | |
|     throw TypeError('numeric value required to calculate duration');
 | |
|   }
 | |
| 
 | |
|   const elapsed = now() - started;
 | |
|   return elapsed < 0 ? 0 : elapsed;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Creates an interval timer which is able to be woken up sooner than
 | |
|  * the interval. The timer will also debounce multiple calls to wake
 | |
|  * ensuring that the function is only ever called once within a minimum
 | |
|  * interval window.
 | |
|  *
 | |
|  * @param {function} fn An async function to run on an interval, must accept a `callback` as its only parameter
 | |
|  * @param {object} [options] Optional settings
 | |
|  * @param {number} [options.interval] The interval at which to run the provided function
 | |
|  * @param {number} [options.minInterval] The minimum time which must pass between invocations of the provided function
 | |
|  * @param {boolean} [options.immediate] Execute the function immediately when the interval is started
 | |
|  */
 | |
| function makeInterruptableAsyncInterval(fn, options) {
 | |
|   let timerId;
 | |
|   let lastCallTime;
 | |
|   let lastWakeTime;
 | |
|   let stopped = false;
 | |
| 
 | |
|   options = options || {};
 | |
|   const interval = options.interval || 1000;
 | |
|   const minInterval = options.minInterval || 500;
 | |
|   const immediate = typeof options.immediate === 'boolean' ? options.immediate : false;
 | |
|   const clock = typeof options.clock === 'function' ? options.clock : now;
 | |
| 
 | |
|   function wake() {
 | |
|     const currentTime = clock();
 | |
|     const timeSinceLastWake = currentTime - lastWakeTime;
 | |
|     const timeSinceLastCall = currentTime - lastCallTime;
 | |
|     const timeUntilNextCall = interval - timeSinceLastCall;
 | |
|     lastWakeTime = currentTime;
 | |
| 
 | |
|     // For the streaming protocol: there is nothing obviously stopping this
 | |
|     // interval from being woken up again while we are waiting "infinitely"
 | |
|     // for `fn` to be called again`. Since the function effectively
 | |
|     // never completes, the `timeUntilNextCall` will continue to grow
 | |
|     // negatively unbounded, so it will never trigger a reschedule here.
 | |
| 
 | |
|     // debounce multiple calls to wake within the `minInterval`
 | |
|     if (timeSinceLastWake < minInterval) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // reschedule a call as soon as possible, ensuring the call never happens
 | |
|     // faster than the `minInterval`
 | |
|     if (timeUntilNextCall > minInterval) {
 | |
|       reschedule(minInterval);
 | |
|     }
 | |
| 
 | |
|     // This is possible in virtualized environments like AWS Lambda where our
 | |
|     // clock is unreliable. In these cases the timer is "running" but never
 | |
|     // actually completes, so we want to execute immediately and then attempt
 | |
|     // to reschedule.
 | |
|     if (timeUntilNextCall < 0) {
 | |
|       executeAndReschedule();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   function stop() {
 | |
|     stopped = true;
 | |
|     if (timerId) {
 | |
|       clearTimeout(timerId);
 | |
|       timerId = null;
 | |
|     }
 | |
| 
 | |
|     lastCallTime = 0;
 | |
|     lastWakeTime = 0;
 | |
|   }
 | |
| 
 | |
|   function reschedule(ms) {
 | |
|     if (stopped) return;
 | |
|     clearTimeout(timerId);
 | |
|     timerId = setTimeout(executeAndReschedule, ms || interval);
 | |
|   }
 | |
| 
 | |
|   function executeAndReschedule() {
 | |
|     lastWakeTime = 0;
 | |
|     lastCallTime = clock();
 | |
| 
 | |
|     fn(err => {
 | |
|       if (err) throw err;
 | |
|       reschedule(interval);
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   if (immediate) {
 | |
|     executeAndReschedule();
 | |
|   } else {
 | |
|     lastCallTime = clock();
 | |
|     reschedule();
 | |
|   }
 | |
| 
 | |
|   return { wake, stop };
 | |
| }
 | |
| 
 | |
| function hasAtomicOperators(doc) {
 | |
|   if (Array.isArray(doc)) {
 | |
|     return doc.reduce((err, u) => err || hasAtomicOperators(u), null);
 | |
|   }
 | |
| 
 | |
|   return (
 | |
|     Object.keys(typeof doc.toBSON !== 'function' ? doc : doc.toBSON())
 | |
|       .map(k => k[0])
 | |
|       .indexOf('$') >= 0
 | |
|   );
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * When the driver used emitWarning the code will be equal to this.
 | |
|  * @public
 | |
|  *
 | |
|  * @example
 | |
|  * ```js
 | |
|  * process.on('warning', (warning) => {
 | |
|  *  if (warning.code === MONGODB_WARNING_CODE) console.error('Ah an important warning! :)')
 | |
|  * })
 | |
|  * ```
 | |
|  */
 | |
| const MONGODB_WARNING_CODE = 'MONGODB DRIVER';
 | |
| 
 | |
| /**
 | |
|  * @internal
 | |
|  * @param {string} message - message to warn about
 | |
|  */
 | |
| function emitWarning(message) {
 | |
|   if (process.emitWarning) {
 | |
|     return nodejsMajorVersion <= 6
 | |
|       ? process.emitWarning(message, undefined, MONGODB_WARNING_CODE)
 | |
|       : process.emitWarning(message, { code: MONGODB_WARNING_CODE });
 | |
|   } else {
 | |
|     // Approximate the style of print out on node versions pre 8.x
 | |
|     // eslint-disable-next-line no-console
 | |
|     return console.error(`[${MONGODB_WARNING_CODE}] Warning:`, message);
 | |
|   }
 | |
| }
 | |
| 
 | |
| const emittedWarnings = new Set();
 | |
| /**
 | |
|  * Will emit a warning once for the duration of the application.
 | |
|  * Uses the message to identify if it has already been emitted
 | |
|  * so using string interpolation can cause multiple emits
 | |
|  * @internal
 | |
|  * @param {string} message - message to warn about
 | |
|  */
 | |
| function emitWarningOnce(message) {
 | |
|   if (!emittedWarnings.has(message)) {
 | |
|     emittedWarnings.add(message);
 | |
|     return emitWarning(message);
 | |
|   }
 | |
| }
 | |
| 
 | |
| function isSuperset(set, subset) {
 | |
|   set = Array.isArray(set) ? new Set(set) : set;
 | |
|   subset = Array.isArray(subset) ? new Set(subset) : subset;
 | |
|   for (const elem of subset) {
 | |
|     if (!set.has(elem)) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| function isRecord(value, requiredKeys) {
 | |
|   const toString = Object.prototype.toString;
 | |
|   const hasOwnProperty = Object.prototype.hasOwnProperty;
 | |
|   const isObject = v => toString.call(v) === '[object Object]';
 | |
|   if (!isObject(value)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   const ctor = value.constructor;
 | |
|   if (ctor && ctor.prototype) {
 | |
|     if (!isObject(ctor.prototype)) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     // Check to see if some method exists from the Object exists
 | |
|     if (!hasOwnProperty.call(ctor.prototype, 'isPrototypeOf')) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (requiredKeys) {
 | |
|     const keys = Object.keys(value);
 | |
|     return isSuperset(keys, requiredKeys);
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Make a deep copy of an object
 | |
|  *
 | |
|  * NOTE: This is not meant to be the perfect implementation of a deep copy,
 | |
|  * but instead something that is good enough for the purposes of
 | |
|  * command monitoring.
 | |
|  */
 | |
| function deepCopy(value) {
 | |
|   if (value == null) {
 | |
|     return value;
 | |
|   } else if (Array.isArray(value)) {
 | |
|     return value.map(item => deepCopy(item));
 | |
|   } else if (isRecord(value)) {
 | |
|     const res = {};
 | |
|     for (const key in value) {
 | |
|       res[key] = deepCopy(value[key]);
 | |
|     }
 | |
|     return res;
 | |
|   }
 | |
| 
 | |
|   const ctor = value.constructor;
 | |
|   if (ctor) {
 | |
|     switch (ctor.name.toLowerCase()) {
 | |
|       case 'date':
 | |
|         return new ctor(Number(value));
 | |
|       case 'map':
 | |
|         return new Map(value);
 | |
|       case 'set':
 | |
|         return new Set(value);
 | |
|       case 'buffer':
 | |
|         return Buffer.from(value);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return value;
 | |
| }
 | |
| /**
 | |
|  * @param {{version: string}} pkg
 | |
|  * @returns {{ major: number; minor: number; patch: number }}
 | |
|  */
 | |
| function parsePackageVersion(pkg) {
 | |
|   const versionParts = pkg.version.split('.').map(n => Number.parseInt(n, 10));
 | |
|   return { major: versionParts[0], minor: versionParts[1], patch: versionParts[2] };
 | |
| }
 | |
| 
 | |
| module.exports = {
 | |
|   filterOptions,
 | |
|   mergeOptions,
 | |
|   translateOptions,
 | |
|   shallowClone,
 | |
|   getSingleProperty,
 | |
|   checkCollectionName,
 | |
|   toError,
 | |
|   formattedOrderClause,
 | |
|   parseIndexOptions,
 | |
|   normalizeHintField,
 | |
|   handleCallback,
 | |
|   decorateCommand,
 | |
|   isObject,
 | |
|   debugOptions,
 | |
|   MAX_JS_INT: Number.MAX_SAFE_INTEGER + 1,
 | |
|   conditionallyMergeWriteConcern,
 | |
|   executeLegacyOperation,
 | |
|   applyRetryableWrites,
 | |
|   applyWriteConcern,
 | |
|   isPromiseLike,
 | |
|   decorateWithCollation,
 | |
|   decorateWithReadConcern,
 | |
|   decorateWithExplain,
 | |
|   deprecateOptions,
 | |
|   SUPPORTS,
 | |
|   MongoDBNamespace,
 | |
|   emitDeprecationWarning,
 | |
|   makeCounter,
 | |
|   maybePromise,
 | |
|   now,
 | |
|   calculateDurationInMs,
 | |
|   makeInterruptableAsyncInterval,
 | |
|   hasAtomicOperators,
 | |
|   MONGODB_WARNING_CODE,
 | |
|   emitWarning,
 | |
|   emitWarningOnce,
 | |
|   deepCopy,
 | |
|   parsePackageVersion
 | |
| };
 |