'use strict'; var EventEmitter = require('events').EventEmitter, inherits = require('util').inherits, getSingleProperty = require('./utils').getSingleProperty, shallowClone = require('./utils').shallowClone, parseIndexOptions = require('./utils').parseIndexOptions, debugOptions = require('./utils').debugOptions, CommandCursor = require('./command_cursor'), handleCallback = require('./utils').handleCallback, filterOptions = require('./utils').filterOptions, toError = require('./utils').toError, ReadPreference = require('mongodb-core').ReadPreference, f = require('util').format, Admin = require('./admin'), Code = require('mongodb-core').BSON.Code, MongoError = require('mongodb-core').MongoError, ObjectID = require('mongodb-core').ObjectID, Define = require('./metadata'), Logger = require('mongodb-core').Logger, Collection = require('./collection'), crypto = require('crypto'), mergeOptionsAndWriteConcern = require('./utils').mergeOptionsAndWriteConcern, executeOperation = require('./utils').executeOperation; var debugFields = [ 'authSource', 'w', 'wtimeout', 'j', 'native_parser', 'forceServerObjectId', 'serializeFunctions', 'raw', 'promoteLongs', 'promoteValues', 'promoteBuffers', 'bufferMaxEntries', 'numberOfRetries', 'retryMiliSeconds', 'readPreference', 'pkFactory', 'parentDb', 'promiseLibrary', 'noListener' ]; // Filter out any write concern options var illegalCommandFields = [ 'w', 'wtimeout', 'j', 'fsync', 'autoIndexId', 'strict', 'serializeFunctions', 'pkFactory', 'raw', 'readPreference', 'session' ]; /** * @fileOverview The **Db** class is a class that represents a MongoDB Database. * * @example * const MongoClient = require('mongodb').MongoClient; * const test = require('assert'); * // Connection url * const url = 'mongodb://localhost:27017'; * // Database Name * const dbName = 'test'; * // Connect using MongoClient * MongoClient.connect(url, function(err, client) { * // Get an additional db * const testDb = client.db('test'); * client.close(); * }); */ // Allowed parameters var legalOptionNames = [ 'w', 'wtimeout', 'fsync', 'j', 'readPreference', 'readPreferenceTags', 'native_parser', 'forceServerObjectId', 'pkFactory', 'serializeFunctions', 'raw', 'bufferMaxEntries', 'authSource', 'ignoreUndefined', 'promoteLongs', 'promiseLibrary', 'readConcern', 'retryMiliSeconds', 'numberOfRetries', 'parentDb', 'noListener', 'loggerLevel', 'logger', 'promoteBuffers', 'promoteLongs', 'promoteValues', 'compression', 'retryWrites' ]; /** * Creates a new Db instance * @class * @param {string} databaseName The name of the database this instance represents. * @param {(Server|ReplSet|Mongos)} topology The server topology for the database. * @param {object} [options=null] Optional settings. * @param {string} [options.authSource=null] If the database authentication is dependent on another databaseName. * @param {(number|string)} [options.w=null] The write concern. * @param {number} [options.wtimeout=null] The write concern timeout. * @param {boolean} [options.j=false] Specify a journal write concern. * @param {boolean} [options.forceServerObjectId=false] Force server to assign _id values instead of driver. * @param {boolean} [options.serializeFunctions=false] Serialize functions on any object. * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields. * @param {boolean} [options.raw=false] Return document results as raw BSON buffers. * @param {boolean} [options.promoteLongs=true] Promotes Long values to number if they fit inside the 53 bits resolution. * @param {boolean} [options.promoteBuffers=false] Promotes Binary BSON values to native Node Buffers. * @param {boolean} [options.promoteValues=true] Promotes BSON values to native types where possible, set to false to only receive wrapper types. * @param {number} [options.bufferMaxEntries=-1] Sets a cap on how many operations the driver will buffer up before giving up on getting a working connection, default is -1 which is unlimited. * @param {(ReadPreference|string)} [options.readPreference=null] The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). * @param {object} [options.pkFactory=null] A primary key factory object for generation of custom _id keys. * @param {object} [options.promiseLibrary=null] A Promise library class the application wishes to use such as Bluebird, must be ES6 compatible * @param {object} [options.readConcern=null] Specify a read concern for the collection. (only MongoDB 3.2 or higher supported) * @param {object} [options.readConcern.level='local'] Specify a read concern level for the collection operations, one of [local|majority]. (only MongoDB 3.2 or higher supported) * @property {(Server|ReplSet|Mongos)} serverConfig Get the current db topology. * @property {number} bufferMaxEntries Current bufferMaxEntries value for the database * @property {string} databaseName The name of the database this instance represents. * @property {object} options The options associated with the db instance. * @property {boolean} native_parser The current value of the parameter native_parser. * @property {boolean} slaveOk The current slaveOk value for the db instance. * @property {object} writeConcern The current write concern values. * @property {object} topology Access the topology object (single server, replicaset or mongos). * @fires Db#close * @fires Db#reconnect * @fires Db#error * @fires Db#timeout * @fires Db#parseError * @fires Db#fullsetup * @return {Db} a Db instance. */ var Db = function(databaseName, topology, options) { options = options || {}; if (!(this instanceof Db)) return new Db(databaseName, topology, options); EventEmitter.call(this); var self = this; // Get the promiseLibrary var promiseLibrary = options.promiseLibrary || Promise; // Filter the options options = filterOptions(options, legalOptionNames); // Ensure we put the promiseLib in the options options.promiseLibrary = promiseLibrary; // var self = this; // Internal state of the db object this.s = { // Database name databaseName: databaseName, // DbCache dbCache: {}, // Children db's children: [], // Topology topology: topology, // Options options: options, // Logger instance logger: Logger('Db', options), // Get the bson parser bson: topology ? topology.bson : null, // Unpack read preference readPreference: options.readPreference, // Set buffermaxEntries bufferMaxEntries: typeof options.bufferMaxEntries === 'number' ? options.bufferMaxEntries : -1, // Parent db (if chained) parentDb: options.parentDb || null, // Set up the primary key factory or fallback to ObjectID pkFactory: options.pkFactory || ObjectID, // Get native parser nativeParser: options.nativeParser || options.native_parser, // Promise library promiseLibrary: promiseLibrary, // No listener noListener: typeof options.noListener === 'boolean' ? options.noListener : false, // ReadConcern readConcern: options.readConcern }; // Ensure we have a valid db name validateDatabaseName(self.s.databaseName); // Add a read Only property getSingleProperty(this, 'serverConfig', self.s.topology); getSingleProperty(this, 'bufferMaxEntries', self.s.bufferMaxEntries); getSingleProperty(this, 'databaseName', self.s.databaseName); // This is a child db, do not register any listeners if (options.parentDb) return; if (this.s.noListener) return; // Add listeners topology.on('error', createListener(self, 'error', self)); topology.on('timeout', createListener(self, 'timeout', self)); topology.on('close', createListener(self, 'close', self)); topology.on('parseError', createListener(self, 'parseError', self)); topology.once('open', createListener(self, 'open', self)); topology.once('fullsetup', createListener(self, 'fullsetup', self)); topology.once('all', createListener(self, 'all', self)); topology.on('reconnect', createListener(self, 'reconnect', self)); }; inherits(Db, EventEmitter); var define = (Db.define = new Define('Db', Db, false)); // Topology Object.defineProperty(Db.prototype, 'topology', { enumerable: true, get: function() { return this.s.topology; } }); // Options Object.defineProperty(Db.prototype, 'options', { enumerable: true, get: function() { return this.s.options; } }); // slaveOk specified Object.defineProperty(Db.prototype, 'slaveOk', { enumerable: true, get: function() { if ( this.s.options.readPreference != null && (this.s.options.readPreference !== 'primary' || this.s.options.readPreference.mode !== 'primary') ) { return true; } return false; } }); // get the write Concern Object.defineProperty(Db.prototype, 'writeConcern', { enumerable: true, get: function() { var ops = {}; if (this.s.options.w != null) ops.w = this.s.options.w; if (this.s.options.j != null) ops.j = this.s.options.j; if (this.s.options.fsync != null) ops.fsync = this.s.options.fsync; if (this.s.options.wtimeout != null) ops.wtimeout = this.s.options.wtimeout; return ops; } }); /** * Ensures provided read preference is properly converted into an object * @param {(ReadPreference|string|object)} readPreference the user provided read preference * @return {ReadPreference} */ const convertReadPreference = function(readPreference) { if (readPreference) { if (typeof readPreference === 'string') { return new ReadPreference(readPreference); } else if ( readPreference && !(readPreference instanceof ReadPreference) && typeof readPreference === 'object' ) { const mode = readPreference.mode || readPreference.preference; if (mode && typeof mode === 'string') { return new ReadPreference(mode, readPreference.tags, { maxStalenessSeconds: readPreference.maxStalenessSeconds }); } } else if (!(readPreference instanceof ReadPreference)) { throw new TypeError('Invalid read preference: ' + readPreference); } } return readPreference; }; /** * The callback format for results * @callback Db~resultCallback * @param {MongoError} error An error instance representing the error during the execution. * @param {object} result The result object if the command was executed successfully. */ var executeCommand = function(self, command, options, callback) { // Did the user destroy the topology if (self.serverConfig && self.serverConfig.isDestroyed()) return callback(new MongoError('topology was destroyed')); // Get the db name we are executing against var dbName = options.dbName || options.authdb || self.s.databaseName; // If we have a readPreference set if (options.readPreference == null && self.s.readPreference) { options.readPreference = self.s.readPreference; } // Convert the readPreference if its not a write if (options.readPreference) { options.readPreference = convertReadPreference(options.readPreference); } else { options.readPreference = ReadPreference.primary; } // Debug information if (self.s.logger.isDebug()) self.s.logger.debug( f( 'executing command %s against %s with options [%s]', JSON.stringify(command), f('%s.$cmd', dbName), JSON.stringify(debugOptions(debugFields, options)) ) ); // Execute command self.s.topology.command(f('%s.$cmd', dbName), command, options, function(err, result) { if (err) return handleCallback(callback, err); if (options.full) return handleCallback(callback, null, result); handleCallback(callback, null, result.result); }); }; /** * Execute a command * @method * @param {object} command The command hash * @param {object} [options=null] Optional settings. * @param {(ReadPreference|string)} [options.readPreference=null] The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). * @param {ClientSession} [options.session] optional session to use for this operation * @param {Db~resultCallback} [callback] The command result callback * @return {Promise} returns Promise if no callback passed */ Db.prototype.command = function(command, options, callback) { // Change the callback if (typeof options === 'function') (callback = options), (options = {}); // Clone the options options = shallowClone(options); return executeOperation(this.s.topology, executeCommand, [this, command, options, callback]); }; define.classMethod('command', { callback: true, promise: true }); /** * Return the Admin db instance * @method * @return {Admin} return the new Admin db instance */ Db.prototype.admin = function() { return new Admin(this, this.s.topology, this.s.promiseLibrary); }; define.classMethod('admin', { callback: false, promise: false, returns: [Admin] }); /** * The callback format for the collection method, must be used if strict is specified * @callback Db~collectionResultCallback * @param {MongoError} error An error instance representing the error during the execution. * @param {Collection} collection The collection instance. */ var collectionKeys = [ 'pkFactory', 'readPreference', 'serializeFunctions', 'strict', 'readConcern', 'ignoreUndefined', 'promoteValues', 'promoteBuffers', 'promoteLongs' ]; /** * Fetch a specific collection (containing the actual collection information). If the application does not use strict mode you can * can use it without a callback in the following way: `var collection = db.collection('mycollection');` * * @method * @param {string} name the collection name we wish to access. * @param {object} [options=null] Optional settings. * @param {(number|string)} [options.w=null] The write concern. * @param {number} [options.wtimeout=null] The write concern timeout. * @param {boolean} [options.j=false] Specify a journal write concern. * @param {boolean} [options.raw=false] Return document results as raw BSON buffers. * @param {object} [options.pkFactory=null] A primary key factory object for generation of custom _id keys. * @param {(ReadPreference|string)} [options.readPreference=null] The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). * @param {boolean} [options.serializeFunctions=false] Serialize functions on any object. * @param {boolean} [options.strict=false] Returns an error if the collection does not exist * @param {object} [options.readConcern=null] Specify a read concern for the collection. (only MongoDB 3.2 or higher supported) * @param {object} [options.readConcern.level='local'] Specify a read concern level for the collection operations, one of [local|majority]. (only MongoDB 3.2 or higher supported) * @param {Db~collectionResultCallback} callback The collection result callback * @return {Collection} return the new Collection instance if not in strict mode */ Db.prototype.collection = function(name, options, callback) { var self = this; if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; options = shallowClone(options); // Set the promise library options.promiseLibrary = this.s.promiseLibrary; // If we have not set a collection level readConcern set the db level one options.readConcern = options.readConcern || this.s.readConcern; // Do we have ignoreUndefined set if (this.s.options.ignoreUndefined) { options.ignoreUndefined = this.s.options.ignoreUndefined; } // Merge in all needed options and ensure correct writeConcern merging from db level options = mergeOptionsAndWriteConcern(options, this.s.options, collectionKeys, true); // Execute if (options == null || !options.strict) { try { var collection = new Collection( this, this.s.topology, this.s.databaseName, name, this.s.pkFactory, options ); if (callback) callback(null, collection); return collection; } catch (err) { if (err instanceof MongoError && callback) return callback(err); throw err; } } // Strict mode if (typeof callback !== 'function') { throw toError(f('A callback is required in strict mode. While getting collection %s.', name)); } // Did the user destroy the topology if (self.serverConfig && self.serverConfig.isDestroyed()) { return callback(new MongoError('topology was destroyed')); } // Strict mode this.listCollections({ name: name }, options).toArray(function(err, collections) { if (err != null) return handleCallback(callback, err, null); if (collections.length === 0) return handleCallback( callback, toError(f('Collection %s does not exist. Currently in strict mode.', name)), null ); try { return handleCallback( callback, null, new Collection(self, self.s.topology, self.s.databaseName, name, self.s.pkFactory, options) ); } catch (err) { return handleCallback(callback, err, null); } }); }; define.classMethod('collection', { callback: true, promise: false, returns: [Collection] }); function decorateWithWriteConcern(command, self, options) { // Do we support write concerns 3.4 and higher if (self.s.topology.capabilities().commandsTakeWriteConcern) { // Get the write concern settings var finalOptions = writeConcern(shallowClone(options), self, options); // Add the write concern to the command if (finalOptions.writeConcern) { command.writeConcern = finalOptions.writeConcern; } } } var createCollection = function(self, name, options, callback) { // Get the write concern options var finalOptions = writeConcern(shallowClone(options), self, options); // Did the user destroy the topology if (self.serverConfig && self.serverConfig.isDestroyed()) { return callback(new MongoError('topology was destroyed')); } // Check if we have the name self .listCollections({ name: name }, finalOptions) .setReadPreference(ReadPreference.PRIMARY) .toArray(function(err, collections) { if (err != null) return handleCallback(callback, err, null); if (collections.length > 0 && finalOptions.strict) { return handleCallback( callback, MongoError.create({ message: f('Collection %s already exists. Currently in strict mode.', name), driver: true }), null ); } else if (collections.length > 0) { try { return handleCallback( callback, null, new Collection( self, self.s.topology, self.s.databaseName, name, self.s.pkFactory, options ) ); } catch (err) { return handleCallback(callback, err); } } // Create collection command var cmd = { create: name }; // Decorate command with writeConcern if supported decorateWithWriteConcern(cmd, self, options); // Add all optional parameters for (var n in options) { if ( options[n] != null && typeof options[n] !== 'function' && illegalCommandFields.indexOf(n) === -1 ) { cmd[n] = options[n]; } } // Force a primary read Preference finalOptions.readPreference = ReadPreference.PRIMARY; // Execute command self.command(cmd, finalOptions, function(err) { if (err) return handleCallback(callback, err); handleCallback( callback, null, new Collection( self, self.s.topology, self.s.databaseName, name, self.s.pkFactory, options ) ); }); }); }; /** * Create a new collection on a server with the specified options. Use this to create capped collections. * More information about command options available at https://docs.mongodb.com/manual/reference/command/create/ * * @method * @param {string} name the collection name we wish to access. * @param {object} [options=null] Optional settings. * @param {(number|string)} [options.w=null] The write concern. * @param {number} [options.wtimeout=null] The write concern timeout. * @param {boolean} [options.j=false] Specify a journal write concern. * @param {boolean} [options.raw=false] Return document results as raw BSON buffers. * @param {object} [options.pkFactory=null] A primary key factory object for generation of custom _id keys. * @param {(ReadPreference|string)} [options.readPreference=null] The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). * @param {boolean} [options.serializeFunctions=false] Serialize functions on any object. * @param {boolean} [options.strict=false] Returns an error if the collection does not exist * @param {boolean} [options.capped=false] Create a capped collection. * @param {boolean} [options.autoIndexId=true] Create an index on the _id field of the document, True by default on MongoDB 2.2 or higher off for version < 2.2. * @param {number} [options.size=null] The size of the capped collection in bytes. * @param {number} [options.max=null] The maximum number of documents in the capped collection. * @param {number} [options.flags=null] Optional. Available for the MMAPv1 storage engine only to set the usePowerOf2Sizes and the noPadding flag. * @param {object} [options.storageEngine=null] Allows users to specify configuration to the storage engine on a per-collection basis when creating a collection on MongoDB 3.0 or higher. * @param {object} [options.validator=null] Allows users to specify validation rules or expressions for the collection. For more information, see Document Validation on MongoDB 3.2 or higher. * @param {string} [options.validationLevel=null] Determines how strictly MongoDB applies the validation rules to existing documents during an update on MongoDB 3.2 or higher. * @param {string} [options.validationAction=null] Determines whether to error on invalid documents or just warn about the violations but allow invalid documents to be inserted on MongoDB 3.2 or higher. * @param {object} [options.indexOptionDefaults=null] Allows users to specify a default configuration for indexes when creating a collection on MongoDB 3.2 or higher. * @param {string} [options.viewOn=null] The name of the source collection or view from which to create the view. The name is not the full namespace of the collection or view; i.e. does not include the database name and implies the same database as the view to create on MongoDB 3.4 or higher. * @param {array} [options.pipeline=null] An array that consists of the aggregation pipeline stage. create creates the view by applying the specified pipeline to the viewOn collection or view on MongoDB 3.4 or higher. * @param {object} [options.collation=null] Specify collation (MongoDB 3.4 or higher) settings for update operation (see 3.4 documentation for available fields). * @param {ClientSession} [options.session] optional session to use for this operation * @param {Db~collectionResultCallback} [callback] The results callback * @return {Promise} returns Promise if no callback passed */ Db.prototype.createCollection = function(name, options, callback) { if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; options.promiseLibrary = options.promiseLibrary || this.s.promiseLibrary; return executeOperation(this.s.topology, createCollection, [this, name, options, callback]); }; define.classMethod('createCollection', { callback: true, promise: true }); /** * Get all the db statistics. * * @method * @param {object} [options=null] Optional settings. * @param {number} [options.scale=null] Divide the returned sizes by scale value. * @param {ClientSession} [options.session] optional session to use for this operation * @param {Db~resultCallback} [callback] The collection result callback * @return {Promise} returns Promise if no callback passed */ Db.prototype.stats = function(options, callback) { if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; // Build command object var commandObject = { dbStats: true }; // Check if we have the scale value if (options['scale'] != null) commandObject['scale'] = options['scale']; // If we have a readPreference set if (options.readPreference == null && this.s.readPreference) { options.readPreference = this.s.readPreference; } // Execute the command return this.command(commandObject, options, callback); }; define.classMethod('stats', { callback: true, promise: true }); // Transformation methods for cursor results var listCollectionsTranforms = function(databaseName) { var matching = f('%s.', databaseName); return { doc: function(doc) { var index = doc.name.indexOf(matching); // Remove database name if available if (doc.name && index === 0) { doc.name = doc.name.substr(index + matching.length); } return doc; } }; }; /** * Get the list of all collection information for the specified db. * * @method * @param {object} [filter={}] Query to filter collections by * @param {object} [options=null] Optional settings. * @param {number} [options.batchSize=null] The batchSize for the returned command cursor or if pre 2.8 the systems batch collection * @param {(ReadPreference|string)} [options.readPreference=null] The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). * @param {ClientSession} [options.session] optional session to use for this operation * @return {CommandCursor} */ Db.prototype.listCollections = function(filter, options) { filter = filter || {}; options = options || {}; // Shallow clone the object options = shallowClone(options); // Set the promise library options.promiseLibrary = this.s.promiseLibrary; // Ensure valid readPreference if (options.readPreference) { options.readPreference = convertReadPreference(options.readPreference); } else { options.readPreference = this.s.readPreference || ReadPreference.primary; } // We have a list collections command if (this.serverConfig.capabilities().hasListCollectionsCommand) { // Cursor options var cursor = options.batchSize ? { batchSize: options.batchSize } : {}; // Build the command var command = { listCollections: true, filter: filter, cursor: cursor }; // Set the AggregationCursor constructor options.cursorFactory = CommandCursor; // Create the cursor cursor = this.s.topology.cursor(f('%s.$cmd', this.s.databaseName), command, options); // Do we have a readPreference, apply it if (options.readPreference) { cursor.setReadPreference(options.readPreference); } // Return the cursor return cursor; } // We cannot use the listCollectionsCommand if (!this.serverConfig.capabilities().hasListCollectionsCommand) { // If we have legacy mode and have not provided a full db name filter it if ( typeof filter.name === 'string' && !new RegExp('^' + this.databaseName + '\\.').test(filter.name) ) { filter = shallowClone(filter); filter.name = f('%s.%s', this.s.databaseName, filter.name); } } // No filter, filter by current database if (filter == null) { filter.name = f('/%s/', this.s.databaseName); } // Rewrite the filter to use $and to filter out indexes if (filter.name) { filter = { $and: [{ name: filter.name }, { name: /^((?!\$).)*$/ }] }; } else { filter = { name: /^((?!\$).)*$/ }; } // Return options var _options = { transforms: listCollectionsTranforms(this.s.databaseName) }; // Get the cursor cursor = this.collection(Db.SYSTEM_NAMESPACE_COLLECTION).find(filter, _options); // Do we have a readPreference, apply it if (options.readPreference) cursor.setReadPreference(options.readPreference); // Set the passed in batch size if one was provided if (options.batchSize) cursor = cursor.batchSize(options.batchSize); // We have a fallback mode using legacy systems collections return cursor; }; define.classMethod('listCollections', { callback: false, promise: false, returns: [CommandCursor] }); var evaluate = function(self, code, parameters, options, callback) { var finalCode = code; var finalParameters = []; // Did the user destroy the topology if (self.serverConfig && self.serverConfig.isDestroyed()) return callback(new MongoError('topology was destroyed')); // If not a code object translate to one if (!(finalCode && finalCode._bsontype === 'Code')) finalCode = new Code(finalCode); // Ensure the parameters are correct if (parameters != null && !Array.isArray(parameters) && typeof parameters !== 'function') { finalParameters = [parameters]; } else if (parameters != null && Array.isArray(parameters) && typeof parameters !== 'function') { finalParameters = parameters; } // Create execution selector var cmd = { $eval: finalCode, args: finalParameters }; // Check if the nolock parameter is passed in if (options['nolock']) { cmd['nolock'] = options['nolock']; } // Set primary read preference options.readPreference = new ReadPreference(ReadPreference.PRIMARY); // Execute the command self.command(cmd, options, function(err, result) { if (err) return handleCallback(callback, err, null); if (result && result.ok === 1) return handleCallback(callback, null, result.retval); if (result) return handleCallback( callback, MongoError.create({ message: f('eval failed: %s', result.errmsg), driver: true }), null ); handleCallback(callback, err, result); }); }; /** * Evaluate JavaScript on the server * * @method * @param {Code} code JavaScript to execute on server. * @param {(object|array)} parameters The parameters for the call. * @param {object} [options=null] Optional settings. * @param {boolean} [options.nolock=false] Tell MongoDB not to block on the evaulation of the javascript. * @param {ClientSession} [options.session] optional session to use for this operation * @param {Db~resultCallback} [callback] The results callback * @deprecated Eval is deprecated on MongoDB 3.2 and forward * @return {Promise} returns Promise if no callback passed */ Db.prototype.eval = function(code, parameters, options, callback) { var args = Array.prototype.slice.call(arguments, 1); callback = typeof args[args.length - 1] === 'function' ? args.pop() : undefined; parameters = args.length ? args.shift() : parameters; options = args.length ? args.shift() || {} : {}; return executeOperation(this.s.topology, evaluate, [this, code, parameters, options, callback]); }; define.classMethod('eval', { callback: true, promise: true }); /** * Rename a collection. * * @method * @param {string} fromCollection Name of current collection to rename. * @param {string} toCollection New name of of the collection. * @param {object} [options=null] Optional settings. * @param {boolean} [options.dropTarget=false] Drop the target name collection if it previously exists. * @param {ClientSession} [options.session] optional session to use for this operation * @param {Db~collectionResultCallback} [callback] The results callback * @return {Promise} returns Promise if no callback passed */ Db.prototype.renameCollection = function(fromCollection, toCollection, options, callback) { if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; // Add return new collection options.new_collection = true; const collection = this.collection(fromCollection); return executeOperation(this.s.topology, collection.rename.bind(collection), [ toCollection, options, callback ]); }; define.classMethod('renameCollection', { callback: true, promise: true }); /** * Drop a collection from the database, removing it permanently. New accesses will create a new collection. * * @method * @param {string} name Name of collection to drop * @param {Object} [options] Optional settings * @param {ClientSession} [options.session] optional session to use for this operation * @param {Db~resultCallback} [callback] The results callback * @return {Promise} returns Promise if no callback passed */ Db.prototype.dropCollection = function(name, options, callback) { if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; // Command to execute var cmd = { drop: name }; // Decorate with write concern decorateWithWriteConcern(cmd, this, options); // options const opts = Object.assign({}, this.s.options, { readPreference: ReadPreference.PRIMARY }); if (options.session) opts.session = options.session; return executeOperation(this.s.topology, dropCollection, [this, cmd, opts, callback]); }; const dropCollection = (self, cmd, options, callback) => { return self.command(cmd, options, function(err, result) { // Did the user destroy the topology if (self.serverConfig && self.serverConfig.isDestroyed()) { return callback(new MongoError('topology was destroyed')); } if (err) return handleCallback(callback, err); if (result.ok) return handleCallback(callback, null, true); handleCallback(callback, null, false); }); }; define.classMethod('dropCollection', { callback: true, promise: true }); /** * Drop a database, removing it permanently from the server. * * @method * @param {Object} [options] Optional settings * @param {ClientSession} [options.session] optional session to use for this operation * @param {Db~resultCallback} [callback] The results callback * @return {Promise} returns Promise if no callback passed */ Db.prototype.dropDatabase = function(options, callback) { if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; // Drop database command var cmd = { dropDatabase: 1 }; // Decorate with write concern decorateWithWriteConcern(cmd, this, options); // Ensure primary only const finalOptions = Object.assign( {}, { readPreference: ReadPreference.PRIMARY }, this.s.options ); if (options.session) { finalOptions.session = options.session; } return executeOperation(this.s.topology, dropDatabase, [this, cmd, finalOptions, callback]); }; const dropDatabase = (self, cmd, options, callback) => { self.command(cmd, options, function(err, result) { // Did the user destroy the topology if (self.serverConfig && self.serverConfig.isDestroyed()) { return callback(new MongoError('topology was destroyed')); } if (callback == null) return; if (err) return handleCallback(callback, err, null); handleCallback(callback, null, result.ok ? true : false); }); }; define.classMethod('dropDatabase', { callback: true, promise: true }); /** * Fetch all collections for the current db. * * @method * @param {Object} [options] Optional settings * @param {ClientSession} [options.session] optional session to use for this operation * @param {Db~collectionsResultCallback} [callback] The results callback * @return {Promise} returns Promise if no callback passed */ Db.prototype.collections = function(options, callback) { if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; return executeOperation(this.s.topology, collections, [this, options, callback]); }; var collections = function(self, options, callback) { // Let's get the collection names self.listCollections({}, options).toArray(function(err, documents) { if (err != null) return handleCallback(callback, err, null); // Filter collections removing any illegal ones documents = documents.filter(function(doc) { return doc.name.indexOf('$') === -1; }); // Return the collection objects handleCallback( callback, null, documents.map(function(d) { return new Collection( self, self.s.topology, self.s.databaseName, d.name, self.s.pkFactory, self.s.options ); }) ); }); }; define.classMethod('collections', { callback: true, promise: true }); /** * Runs a command on the database as admin. * @method * @param {object} command The command hash * @param {object} [options=null] Optional settings. * @param {(ReadPreference|string)} [options.readPreference=null] The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). * @param {ClientSession} [options.session] optional session to use for this operation * @param {Db~resultCallback} [callback] The command result callback * @return {Promise} returns Promise if no callback passed */ Db.prototype.executeDbAdminCommand = function(selector, options, callback) { if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; // Convert read preference if (options.readPreference) { options.readPreference = convertReadPreference(options.readPreference); } return executeOperation(this.s.topology, executeDbAdminCommand, [ this, selector, options, callback ]); }; const executeDbAdminCommand = (self, selector, options, callback) => { self.s.topology.command('admin.$cmd', selector, options, function(err, result) { // Did the user destroy the topology if (self.serverConfig && self.serverConfig.isDestroyed()) { return callback(new MongoError('topology was destroyed')); } if (err) return handleCallback(callback, err); handleCallback(callback, null, result.result); }); }; define.classMethod('executeDbAdminCommand', { callback: true, promise: true }); /** * Creates an index on the db and collection collection. * @method * @param {string} name Name of the collection to create the index on. * @param {(string|object)} fieldOrSpec Defines the index. * @param {object} [options=null] Optional settings. * @param {(number|string)} [options.w=null] The write concern. * @param {number} [options.wtimeout=null] The write concern timeout. * @param {boolean} [options.j=false] Specify a journal write concern. * @param {boolean} [options.unique=false] Creates an unique index. * @param {boolean} [options.sparse=false] Creates a sparse index. * @param {boolean} [options.background=false] Creates the index in the background, yielding whenever possible. * @param {boolean} [options.dropDups=false] A unique index cannot be created on a key that has pre-existing duplicate values. If you would like to create the index anyway, keeping the first document the database indexes and deleting all subsequent documents that have duplicate value * @param {number} [options.min=null] For geospatial indexes set the lower bound for the co-ordinates. * @param {number} [options.max=null] For geospatial indexes set the high bound for the co-ordinates. * @param {number} [options.v=null] Specify the format version of the indexes. * @param {number} [options.expireAfterSeconds=null] Allows you to expire data on indexes applied to a data (MongoDB 2.2 or higher) * @param {number} [options.name=null] Override the autogenerated index name (useful if the resulting name is larger than 128 bytes) * @param {object} [options.partialFilterExpression=null] Creates a partial index based on the given filter object (MongoDB 3.2 or higher) * @param {ClientSession} [options.session] optional session to use for this operation * @param {Db~resultCallback} [callback] The command result callback * @return {Promise} returns Promise if no callback passed */ Db.prototype.createIndex = function(name, fieldOrSpec, options, callback) { if (typeof options === 'function') (callback = options), (options = {}); options = options ? shallowClone(options) : {}; return executeOperation(this.s.topology, createIndex, [ this, name, fieldOrSpec, options, callback ]); }; var createIndex = function(self, name, fieldOrSpec, options, callback) { // Get the write concern options var finalOptions = Object.assign({}, { readPreference: ReadPreference.PRIMARY }, options); finalOptions = writeConcern(finalOptions, self, options); // Ensure we have a callback if (finalOptions.writeConcern && typeof callback !== 'function') { throw MongoError.create({ message: 'Cannot use a writeConcern without a provided callback', driver: true }); } // Did the user destroy the topology if (self.serverConfig && self.serverConfig.isDestroyed()) return callback(new MongoError('topology was destroyed')); // Attempt to run using createIndexes command createIndexUsingCreateIndexes(self, name, fieldOrSpec, options, function(err, result) { if (err == null) return handleCallback(callback, err, result); // 67 = 'CannotCreateIndex' (malformed index options) // 85 = 'IndexOptionsConflict' (index already exists with different options) // 11000 = 'DuplicateKey' (couldn't build unique index because of dupes) // 11600 = 'InterruptedAtShutdown' (interrupted at shutdown) // These errors mean that the server recognized `createIndex` as a command // and so we don't need to fallback to an insert. if (err.code === 67 || err.code === 11000 || err.code === 85 || err.code === 11600) { return handleCallback(callback, err, result); } // Create command var doc = createCreateIndexCommand(self, name, fieldOrSpec, options); // Set no key checking finalOptions.checkKeys = false; // Insert document self.s.topology.insert( f('%s.%s', self.s.databaseName, Db.SYSTEM_INDEX_COLLECTION), doc, finalOptions, function(err, result) { if (callback == null) return; if (err) return handleCallback(callback, err); if (result == null) return handleCallback(callback, null, null); if (result.result.writeErrors) return handleCallback(callback, MongoError.create(result.result.writeErrors[0]), null); handleCallback(callback, null, doc.name); } ); }); }; define.classMethod('createIndex', { callback: true, promise: true }); /** * Ensures that an index exists, if it does not it creates it * @method * @deprecated since version 2.0 * @param {string} name The index name * @param {(string|object)} fieldOrSpec Defines the index. * @param {object} [options=null] Optional settings. * @param {(number|string)} [options.w=null] The write concern. * @param {number} [options.wtimeout=null] The write concern timeout. * @param {boolean} [options.j=false] Specify a journal write concern. * @param {boolean} [options.unique=false] Creates an unique index. * @param {boolean} [options.sparse=false] Creates a sparse index. * @param {boolean} [options.background=false] Creates the index in the background, yielding whenever possible. * @param {boolean} [options.dropDups=false] A unique index cannot be created on a key that has pre-existing duplicate values. If you would like to create the index anyway, keeping the first document the database indexes and deleting all subsequent documents that have duplicate value * @param {number} [options.min=null] For geospatial indexes set the lower bound for the co-ordinates. * @param {number} [options.max=null] For geospatial indexes set the high bound for the co-ordinates. * @param {number} [options.v=null] Specify the format version of the indexes. * @param {number} [options.expireAfterSeconds=null] Allows you to expire data on indexes applied to a data (MongoDB 2.2 or higher) * @param {number} [options.name=null] Override the autogenerated index name (useful if the resulting name is larger than 128 bytes) * @param {ClientSession} [options.session] optional session to use for this operation * @param {Db~resultCallback} [callback] The command result callback * @return {Promise} returns Promise if no callback passed */ Db.prototype.ensureIndex = function(name, fieldOrSpec, options, callback) { if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; return executeOperation(this.s.topology, ensureIndex, [ this, name, fieldOrSpec, options, callback ]); }; var ensureIndex = function(self, name, fieldOrSpec, options, callback) { // Get the write concern options var finalOptions = writeConcern({}, self, options); // Create command var selector = createCreateIndexCommand(self, name, fieldOrSpec, options); var index_name = selector.name; // Did the user destroy the topology if (self.serverConfig && self.serverConfig.isDestroyed()) return callback(new MongoError('topology was destroyed')); // Merge primary readPreference finalOptions.readPreference = ReadPreference.PRIMARY; // Check if the index allready exists self.indexInformation(name, finalOptions, function(err, indexInformation) { if (err != null && err.code !== 26) return handleCallback(callback, err, null); // If the index does not exist, create it if (indexInformation == null || !indexInformation[index_name]) { self.createIndex(name, fieldOrSpec, options, callback); } else { if (typeof callback === 'function') return handleCallback(callback, null, index_name); } }); }; define.classMethod('ensureIndex', { callback: true, promise: true }); Db.prototype.addChild = function(db) { if (this.s.parentDb) return this.s.parentDb.addChild(db); this.s.children.push(db); }; var _executeAuthCreateUserCommand = function(self, username, password, options, callback) { // Special case where there is no password ($external users) if (typeof username === 'string' && password != null && typeof password === 'object') { options = password; password = null; } // Unpack all options if (typeof options === 'function') { callback = options; options = {}; } // Error out if we digestPassword set if (options.digestPassword != null) { throw toError( "The digestPassword option is not supported via add_user. Please use db.command('createUser', ...) instead for this option." ); } // Get additional values var customData = options.customData != null ? options.customData : {}; var roles = Array.isArray(options.roles) ? options.roles : []; var maxTimeMS = typeof options.maxTimeMS === 'number' ? options.maxTimeMS : null; // If not roles defined print deprecated message if (roles.length === 0) { console.log('Creating a user without roles is deprecated in MongoDB >= 2.6'); } // Get the error options var commandOptions = { writeCommand: true }; if (options['dbName']) commandOptions.dbName = options['dbName']; // Add maxTimeMS to options if set if (maxTimeMS != null) commandOptions.maxTimeMS = maxTimeMS; // Check the db name and add roles if needed if ( (self.databaseName.toLowerCase() === 'admin' || options.dbName === 'admin') && !Array.isArray(options.roles) ) { roles = ['root']; } else if (!Array.isArray(options.roles)) { roles = ['dbOwner']; } // Build the command to execute var command = { createUser: username, customData: customData, roles: roles, digestPassword: false }; // Apply write concern to command command = writeConcern(command, self, options); // Use node md5 generator var md5 = crypto.createHash('md5'); // Generate keys used for authentication md5.update(username + ':mongo:' + password); var userPassword = md5.digest('hex'); // No password if (typeof password === 'string') { command.pwd = userPassword; } // Force write using primary commandOptions.readPreference = ReadPreference.primary; // Execute the command self.command(command, commandOptions, function(err, result) { if (err && err.ok === 0 && err.code === undefined) return handleCallback(callback, { code: -5000 }, null); if (err) return handleCallback(callback, err, null); handleCallback( callback, !result.ok ? toError(result) : null, result.ok ? [{ user: username, pwd: '' }] : null ); }); }; var addUser = function(self, username, password, options, callback) { // Did the user destroy the topology if (self.serverConfig && self.serverConfig.isDestroyed()) return callback(new MongoError('topology was destroyed')); // Attempt to execute auth command _executeAuthCreateUserCommand(self, username, password, options, function(err, r) { // We need to perform the backward compatible insert operation if (err && err.code === -5000) { var finalOptions = writeConcern(shallowClone(options), self, options); // Use node md5 generator var md5 = crypto.createHash('md5'); // Generate keys used for authentication md5.update(username + ':mongo:' + password); var userPassword = md5.digest('hex'); // If we have another db set var db = options.dbName ? new Db(options.dbName, self.s.topology, self.s.options) : self; // Fetch a user collection var collection = db.collection(Db.SYSTEM_USER_COLLECTION); // Check if we are inserting the first user collection.count({}, finalOptions, function(err, count) { // We got an error (f.ex not authorized) if (err != null) return handleCallback(callback, err, null); // Check if the user exists and update i collection .find({ user: username }, { dbName: options['dbName'] }, finalOptions) .toArray(function(err) { // We got an error (f.ex not authorized) if (err != null) return handleCallback(callback, err, null); // Add command keys finalOptions.upsert = true; // We have a user, let's update the password or upsert if not collection.update( { user: username }, { $set: { user: username, pwd: userPassword } }, finalOptions, function(err) { if (count === 0 && err) return handleCallback(callback, null, [{ user: username, pwd: userPassword }]); if (err) return handleCallback(callback, err, null); handleCallback(callback, null, [{ user: username, pwd: userPassword }]); } ); }); }); return; } if (err) return handleCallback(callback, err); handleCallback(callback, err, r); }); }; /** * Add a user to the database. * @method * @param {string} username The username. * @param {string} password The password. * @param {object} [options=null] Optional settings. * @param {(number|string)} [options.w=null] The write concern. * @param {number} [options.wtimeout=null] The write concern timeout. * @param {boolean} [options.j=false] Specify a journal write concern. * @param {object} [options.customData=null] Custom data associated with the user (only Mongodb 2.6 or higher) * @param {object[]} [options.roles=null] Roles associated with the created user (only Mongodb 2.6 or higher) * @param {ClientSession} [options.session] optional session to use for this operation * @param {Db~resultCallback} [callback] The command result callback * @return {Promise} returns Promise if no callback passed */ Db.prototype.addUser = function(username, password, options, callback) { // Unpack the parameters var args = Array.prototype.slice.call(arguments, 2); callback = typeof args[args.length - 1] === 'function' ? args.pop() : undefined; options = args.length ? args.shift() || {} : {}; return executeOperation(this.s.topology, addUser, [this, username, password, options, callback]); }; define.classMethod('addUser', { callback: true, promise: true }); var _executeAuthRemoveUserCommand = function(self, username, options, callback) { if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; // Did the user destroy the topology if (self.serverConfig && self.serverConfig.isDestroyed()) return callback(new MongoError('topology was destroyed')); // Get the error options var commandOptions = { writeCommand: true }; if (options['dbName']) commandOptions.dbName = options['dbName']; // Get additional values var maxTimeMS = typeof options.maxTimeMS === 'number' ? options.maxTimeMS : null; // Add maxTimeMS to options if set if (maxTimeMS != null) commandOptions.maxTimeMS = maxTimeMS; // Build the command to execute var command = { dropUser: username }; // Apply write concern to command command = writeConcern(command, self, options); // Force write using primary commandOptions.readPreference = ReadPreference.primary; // Execute the command self.command(command, commandOptions, function(err, result) { if (err && !err.ok && err.code === undefined) return handleCallback(callback, { code: -5000 }); if (err) return handleCallback(callback, err, null); handleCallback(callback, null, result.ok ? true : false); }); }; var removeUser = function(self, username, options, callback) { // Attempt to execute command _executeAuthRemoveUserCommand(self, username, options, function(err, result) { if (err && err.code === -5000) { var finalOptions = writeConcern(shallowClone(options), self, options); // If we have another db set var db = options.dbName ? new Db(options.dbName, self.s.topology, self.s.options) : self; // Fetch a user collection var collection = db.collection(Db.SYSTEM_USER_COLLECTION); // Locate the user collection.findOne({ user: username }, finalOptions, function(err, user) { if (user == null) return handleCallback(callback, err, false); collection.remove({ user: username }, finalOptions, function(err) { handleCallback(callback, err, true); }); }); return; } if (err) return handleCallback(callback, err); handleCallback(callback, err, result); }); }; define.classMethod('removeUser', { callback: true, promise: true }); /** * Remove a user from a database * @method * @param {string} username The username. * @param {object} [options=null] Optional settings. * @param {(number|string)} [options.w=null] The write concern. * @param {number} [options.wtimeout=null] The write concern timeout. * @param {boolean} [options.j=false] Specify a journal write concern. * @param {ClientSession} [options.session] optional session to use for this operation * @param {Db~resultCallback} [callback] The command result callback * @return {Promise} returns Promise if no callback passed */ Db.prototype.removeUser = function(username, options, callback) { var args = Array.prototype.slice.call(arguments, 1); callback = typeof args[args.length - 1] === 'function' ? args.pop() : undefined; options = args.length ? args.shift() || {} : {}; return executeOperation(this.s.topology, removeUser, [this, username, options, callback]); }; /** * Set the current profiling level of MongoDB * * @param {string} level The new profiling level (off, slow_only, all). * @param {Object} [options] Optional settings * @param {ClientSession} [options.session] optional session to use for this operation * @param {Db~resultCallback} [callback] The command result callback. * @return {Promise} returns Promise if no callback passed */ Db.prototype.setProfilingLevel = function(level, options, callback) { if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; return executeOperation(this.s.topology, setProfilingLevel, [this, level, options, callback]); }; var setProfilingLevel = function(self, level, options, callback) { var command = {}; var profile = 0; if (level === 'off') { profile = 0; } else if (level === 'slow_only') { profile = 1; } else if (level === 'all') { profile = 2; } else { return callback(new Error('Error: illegal profiling level value ' + level)); } // Set up the profile number command['profile'] = profile; self.command(command, options, function(err, doc) { if (err == null && doc.ok === 1) return callback(null, level); return err != null ? callback(err, null) : callback(new Error('Error with profile command'), null); }); }; define.classMethod('setProfilingLevel', { callback: true, promise: true }); /** * Retrive the current profiling information for MongoDB * * @param {Object} [options] Optional settings * @param {ClientSession} [options.session] optional session to use for this operation * @param {Db~resultCallback} [callback] The command result callback. * @return {Promise} returns Promise if no callback passed * @deprecated Query the system.profile collection directly. */ Db.prototype.profilingInfo = function(options, callback) { if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; return executeOperation(this.s.topology, profilingInfo, [this, options, callback]); }; var profilingInfo = function(self, options, callback) { try { self .collection('system.profile') .find({}, null, options) .toArray(callback); } catch (err) { return callback(err, null); } }; define.classMethod('profilingInfo', { callback: true, promise: true }); /** * Retrieve the current profiling Level for MongoDB * * @param {Object} [options] Optional settings * @param {ClientSession} [options.session] optional session to use for this operation * @param {Db~resultCallback} [callback] The command result callback * @return {Promise} returns Promise if no callback passed */ Db.prototype.profilingLevel = function(options, callback) { if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; return executeOperation(this.s.topology, profilingLevel, [this, options, callback]); }; var profilingLevel = function(self, options, callback) { self.command({ profile: -1 }, options, function(err, doc) { if (err == null && doc.ok === 1) { var was = doc.was; if (was === 0) return callback(null, 'off'); if (was === 1) return callback(null, 'slow_only'); if (was === 2) return callback(null, 'all'); return callback(new Error('Error: illegal profiling level value ' + was), null); } else { err != null ? callback(err, null) : callback(new Error('Error with profile command'), null); } }); }; define.classMethod('profilingLevel', { callback: true, promise: true }); /** * Retrieves this collections index info. * @method * @param {string} name The name of the collection. * @param {object} [options=null] Optional settings. * @param {boolean} [options.full=false] Returns the full raw index information. * @param {(ReadPreference|string)} [options.readPreference=null] The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). * @param {ClientSession} [options.session] optional session to use for this operation * @param {Db~resultCallback} [callback] The command result callback * @return {Promise} returns Promise if no callback passed */ Db.prototype.indexInformation = function(name, options, callback) { if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; return executeOperation(this.s.topology, indexInformation, [this, name, options, callback]); }; var indexInformation = function(self, name, options, callback) { // If we specified full information var full = options['full'] == null ? false : options['full']; // Did the user destroy the topology if (self.serverConfig && self.serverConfig.isDestroyed()) return callback(new MongoError('topology was destroyed')); // Process all the results from the index command and collection var processResults = function(indexes) { // Contains all the information var info = {}; // Process all the indexes for (var i = 0; i < indexes.length; i++) { var index = indexes[i]; // Let's unpack the object info[index.name] = []; for (var name in index.key) { info[index.name].push([name, index.key[name]]); } } return info; }; // Get the list of indexes of the specified collection self .collection(name) .listIndexes(options) .toArray(function(err, indexes) { if (err) return callback(toError(err)); if (!Array.isArray(indexes)) return handleCallback(callback, null, []); if (full) return handleCallback(callback, null, indexes); handleCallback(callback, null, processResults(indexes)); }); }; define.classMethod('indexInformation', { callback: true, promise: true }); var createCreateIndexCommand = function(db, name, fieldOrSpec, options) { var indexParameters = parseIndexOptions(fieldOrSpec); var fieldHash = indexParameters.fieldHash; // Generate the index name var indexName = typeof options.name === 'string' ? options.name : indexParameters.name; var selector = { ns: db.databaseName + '.' + name, key: fieldHash, name: indexName }; // Ensure we have a correct finalUnique var finalUnique = options == null || 'object' === typeof options ? false : options; // Set up options options = options == null || typeof options === 'boolean' ? {} : options; // Add all the options var keysToOmit = Object.keys(selector); for (var optionName in options) { if (keysToOmit.indexOf(optionName) === -1) { selector[optionName] = options[optionName]; } } if (selector['unique'] == null) selector['unique'] = finalUnique; // Remove any write concern operations var removeKeys = ['w', 'wtimeout', 'j', 'fsync', 'readPreference']; for (var i = 0; i < removeKeys.length; i++) { delete selector[removeKeys[i]]; } // Return the command creation selector return selector; }; var createIndexUsingCreateIndexes = function(self, name, fieldOrSpec, options, callback) { // Build the index var indexParameters = parseIndexOptions(fieldOrSpec); // Generate the index name var indexName = typeof options.name === 'string' ? options.name : indexParameters.name; // Set up the index var indexes = [{ name: indexName, key: indexParameters.fieldHash }]; // merge all the options var keysToOmit = Object.keys(indexes[0]).concat([ 'w', 'wtimeout', 'j', 'fsync', 'readPreference', 'session' ]); for (var optionName in options) { if (keysToOmit.indexOf(optionName) === -1) { indexes[0][optionName] = options[optionName]; } } // Get capabilities var capabilities = self.s.topology.capabilities(); // Did the user pass in a collation, check if our write server supports it if (indexes[0].collation && capabilities && !capabilities.commandsTakeCollation) { // Create a new error var error = new MongoError(f('server/primary/mongos does not support collation')); error.code = 67; // Return the error return callback(error); } // Create command, apply write concern to command var cmd = writeConcern({ createIndexes: name, indexes: indexes }, self, options); // Decorate command with writeConcern if supported decorateWithWriteConcern(cmd, self, options); // ReadPreference primary options.readPreference = ReadPreference.PRIMARY; // Build the command self.command(cmd, options, function(err, result) { if (err) return handleCallback(callback, err, null); if (result.ok === 0) return handleCallback(callback, toError(result), null); // Return the indexName for backward compatibility handleCallback(callback, null, indexName); }); }; // Validate the database name var validateDatabaseName = function(databaseName) { if (typeof databaseName !== 'string') throw MongoError.create({ message: 'database name must be a string', driver: true }); if (databaseName.length === 0) throw MongoError.create({ message: 'database name cannot be the empty string', driver: true }); if (databaseName === '$external') return; var invalidChars = [' ', '.', '$', '/', '\\']; for (var i = 0; i < invalidChars.length; i++) { if (databaseName.indexOf(invalidChars[i]) !== -1) throw MongoError.create({ message: "database names cannot contain the character '" + invalidChars[i] + "'", driver: true }); } }; // Get write concern var writeConcern = function(target, db, options) { if (options.w != null || options.j != null || options.fsync != null) { var opts = {}; if (options.w) opts.w = options.w; if (options.wtimeout) opts.wtimeout = options.wtimeout; if (options.j) opts.j = options.j; if (options.fsync) opts.fsync = options.fsync; target.writeConcern = opts; } else if ( db.writeConcern.w != null || db.writeConcern.j != null || db.writeConcern.fsync != null ) { target.writeConcern = db.writeConcern; } return target; }; // Add listeners to topology var createListener = function(self, e, object) { var listener = function(err) { if (object.listeners(e).length > 0) { object.emit(e, err, self); // Emit on all associated db's if available for (var i = 0; i < self.s.children.length; i++) { self.s.children[i].emit(e, err, self.s.children[i]); } } }; return listener; }; /** * Unref all sockets * @method */ Db.prototype.unref = function() { this.s.topology.unref(); }; /** * Db close event * * Emitted after a socket closed against a single server or mongos proxy. * * @event Db#close * @type {MongoError} */ /** * Db reconnect event * * * Server: Emitted when the driver has reconnected and re-authenticated. * * ReplicaSet: N/A * * Mongos: Emitted when the driver reconnects and re-authenticates successfully against a Mongos. * * @event Db#reconnect * @type {object} */ /** * Db error event * * Emitted after an error occurred against a single server or mongos proxy. * * @event Db#error * @type {MongoError} */ /** * Db timeout event * * Emitted after a socket timeout occurred against a single server or mongos proxy. * * @event Db#timeout * @type {MongoError} */ /** * Db parseError event * * The parseError event is emitted if the driver detects illegal or corrupt BSON being received from the server. * * @event Db#parseError * @type {MongoError} */ /** * Db fullsetup event, emitted when all servers in the topology have been connected to at start up time. * * * Server: Emitted when the driver has connected to the single server and has authenticated. * * ReplSet: Emitted after the driver has attempted to connect to all replicaset members. * * Mongos: Emitted after the driver has attempted to connect to all mongos proxies. * * @event Db#fullsetup * @type {Db} */ // Constants Db.SYSTEM_NAMESPACE_COLLECTION = 'system.namespaces'; Db.SYSTEM_INDEX_COLLECTION = 'system.indexes'; Db.SYSTEM_PROFILE_COLLECTION = 'system.profile'; Db.SYSTEM_USER_COLLECTION = 'system.users'; Db.SYSTEM_COMMAND_COLLECTION = '$cmd'; Db.SYSTEM_JS_COLLECTION = 'system.js'; module.exports = Db;