diff options
Diffstat (limited to 'common/src/main/webapp/usageguide/appserver/node_modules/mongodb/lib/db.js')
-rw-r--r-- | common/src/main/webapp/usageguide/appserver/node_modules/mongodb/lib/db.js | 1881 |
1 files changed, 1881 insertions, 0 deletions
diff --git a/common/src/main/webapp/usageguide/appserver/node_modules/mongodb/lib/db.js b/common/src/main/webapp/usageguide/appserver/node_modules/mongodb/lib/db.js new file mode 100644 index 0000000..c4d30b2 --- /dev/null +++ b/common/src/main/webapp/usageguide/appserver/node_modules/mongodb/lib/db.js @@ -0,0 +1,1881 @@ +"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('./read_preference') + , f = require('util').format + , Admin = require('./admin') + , Code = require('mongodb-core').BSON.Code + , CoreReadPreference = require('mongodb-core').ReadPreference + , MongoError = require('mongodb-core').MongoError + , ObjectID = require('mongodb-core').ObjectID + , Define = require('./metadata') + , Logger = require('mongodb-core').Logger + , Collection = require('./collection') + , crypto = require('crypto') + , assign = require('./utils').assign; + +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']; + +/** + * @fileOverview The **Db** class is a class that represents a MongoDB Database. + * + * @example + * var MongoClient = require('mongodb').MongoClient, + * test = require('assert'); + * // Connection url + * var url = 'mongodb://localhost:27017/test'; + * // Connect using MongoClient + * MongoClient.connect(url, function(err, db) { + * // Get an additional db + * var testDb = db.db('test'); + * db.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']; + +/** + * 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#authenticated + * @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; + + // No promise library selected fall back + if(!promiseLibrary) { + promiseLibrary = typeof global.Promise == 'function' ? + global.Promise : require('es6-promise').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 + // Authsource if any + , authSource: options.authSource + // 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; + } +}); + +/** + * The callback format for the Db.open method + * @callback Db~openCallback + * @param {MongoError} error An error instance representing the error during the execution. + * @param {Db} db The Db instance if the open method was successful. + */ + +// Internal method +var open = function(self, callback) { + self.s.topology.connect(self, self.s.options, function(err) { + if(callback == null) return; + var internalCallback = callback; + callback == null; + + if(err) { + self.close(); + return internalCallback(err); + } + + internalCallback(null, self); + }); +} + +/** + * Open the database + * @method + * @param {Db~openCallback} [callback] Callback + * @return {Promise} returns Promise if no callback passed + */ +Db.prototype.open = function(callback) { + var self = this; + // We provided a callback leg + if(typeof callback == 'function') return open(self, callback); + // Return promise + return new self.s.promiseLibrary(function(resolve, reject) { + open(self, function(err, db) { + if(err) return reject(err); + resolve(db); + }) + }); +} + +define.classMethod('open', {callback: true, promise:true}); + +/** + * Converts provided read preference to CoreReadPreference + * @param {(ReadPreference|string|object)} readPreference the user provided read preference + * @return {CoreReadPreference} + */ +var convertReadPreference = function(readPreference) { + if(readPreference && typeof readPreference == 'string') { + return new CoreReadPreference(readPreference); + } else if(readPreference instanceof ReadPreference) { + return new CoreReadPreference(readPreference.mode, readPreference.tags, {maxStalenessSeconds: readPreference.maxStalenessSeconds}); + } else if(readPreference && typeof readPreference == 'object') { + var mode = readPreference.mode || readPreference.preference; + if (mode && typeof mode == 'string') { + readPreference = new CoreReadPreference(mode, readPreference.tags, {maxStalenessSeconds: readPreference.maxStalenessSeconds}); + } + } + 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 = CoreReadPreference.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 {Db~resultCallback} [callback] The command result callback + * @return {Promise} returns Promise if no callback passed + */ +Db.prototype.command = function(command, options, callback) { + var self = this; + // Change the callback + if(typeof options == 'function') callback = options, options = {}; + // Clone the options + options = shallowClone(options); + + // Do we have a callback + if(typeof callback == 'function') return executeCommand(self, command, options, callback); + // Return a promise + return new this.s.promiseLibrary(function(resolve, reject) { + executeCommand(self, command, options, function(err, r) { + if(err) return reject(err); + resolve(r); + }); + }); +} + +define.classMethod('command', {callback: true, promise:true}); + +/** + * The callback format for results + * @callback Db~noResultCallback + * @param {MongoError} error An error instance representing the error during the execution. + * @param {null} result Is not set to a value + */ + +/** + * Close the db and its underlying connections + * @method + * @param {boolean} force Force close, emitting no events + * @param {Db~noResultCallback} [callback] The result callback + * @return {Promise} returns Promise if no callback passed + */ +Db.prototype.close = function(force, callback) { + if(typeof force == 'function') callback = force, force = false; + this.s.topology.close(force); + var self = this; + + // Fire close event if any listeners + if(this.listeners('close').length > 0) { + this.emit('close'); + + // If it's the top level db emit close on all children + if(this.parentDb == null) { + // Fire close on all children + for(var i = 0; i < this.s.children.length; i++) { + this.s.children[i].emit('close'); + } + } + + // Remove listeners after emit + self.removeAllListeners('close'); + } + + // Close parent db if set + if(this.s.parentDb) this.s.parentDb.close(); + // Callback after next event loop tick + if(typeof callback == 'function') return process.nextTick(function() { + handleCallback(callback, null); + }) + + // Return dummy promise + return new this.s.promiseLibrary(function(resolve) { + resolve(); + }); +} + +define.classMethod('close', {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. + */ + +/** + * 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; + } + + // 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(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}).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}) + .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. + * + * @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 {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 {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 {object} [options.collation=null] Specify collation (MongoDB 3.4 or higher) settings for update operation (see 3.4 documentation for available fields). + * @param {Db~collectionResultCallback} [callback] The results callback + * @return {Promise} returns Promise if no callback passed + */ +Db.prototype.createCollection = function(name, options, callback) { + var self = this; + var args = Array.prototype.slice.call(arguments, 0); + callback = args.pop(); + if(typeof callback != 'function') args.push(callback); + name = args.length ? args.shift() : null; + options = args.length ? args.shift() || {} : {}; + + // Do we have a promisesLibrary + options.promiseLibrary = options.promiseLibrary || this.s.promiseLibrary; + + // Check if the callback is in fact a string + if(typeof callback == 'string') name = callback; + + // Execute the fallback callback + if(typeof callback == 'function') return createCollection(self, name, options, callback); + return new this.s.promiseLibrary(function(resolve, reject) { + createCollection(self, name, options, function(err, r) { + if(err) return reject(err); + resolve(r); + }); + }); +} + +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 {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). + * @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); + } + + // 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 CoreReadPreference(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 {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 self = this; + var args = Array.prototype.slice.call(arguments, 1); + callback = args.pop(); + if(typeof callback != 'function') args.push(callback); + parameters = args.length ? args.shift() : parameters; + options = args.length ? args.shift() || {} : {}; + + // Check if the callback is in fact a string + if(typeof callback == 'function') return evaluate(self, code, parameters, options, callback); + // Execute the command + return new this.s.promiseLibrary(function(resolve, reject) { + evaluate(self, code, parameters, options, function(err, r) { + if(err) return reject(err); + resolve(r); + }); + }); +}; + +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 {Db~collectionResultCallback} [callback] The results callback + * @return {Promise} returns Promise if no callback passed + */ +Db.prototype.renameCollection = function(fromCollection, toCollection, options, callback) { + var self = this; + if(typeof options == 'function') callback = options, options = {}; + options = options || {}; + // Add return new collection + options.new_collection = true; + + // Check if the callback is in fact a string + if(typeof callback == 'function') { + return this.collection(fromCollection).rename(toCollection, options, callback); + } + + // Return a promise + return new this.s.promiseLibrary(function(resolve, reject) { + self.collection(fromCollection).rename(toCollection, options, function(err, r) { + if(err) return reject(err); + resolve(r); + }); + }); +}; + +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 {Db~resultCallback} [callback] The results callback + * @return {Promise} returns Promise if no callback passed + */ +Db.prototype.dropCollection = function(name, options, callback) { + var self = this; + if(typeof options == 'function') callback = options, options = {}; + options = options || {}; + + // Command to execute + var cmd = {'drop':name} + + // Decorate with write concern + decorateWithWriteConcern(cmd, self, options); + + // options + options = assign({}, this.s.options, {readPreference: ReadPreference.PRIMARY}); + + // Check if the callback is in fact a string + if(typeof callback == 'function') return this.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); + }); + + // Clone the options + options = shallowClone(self.s.options); + // Set readPreference PRIMARY + options.readPreference = ReadPreference.PRIMARY; + + // Execute the command + return new this.s.promiseLibrary(function(resolve, reject) { + // Execute command + self.command(cmd, options, function(err, result) { + // Did the user destroy the topology + if(self.serverConfig && self.serverConfig.isDestroyed()) return reject(new MongoError('topology was destroyed')); + if(err) return reject(err); + if(result.ok) return resolve(true); + resolve(false); + }); + }); +}; + +define.classMethod('dropCollection', {callback: true, promise:true}); + +/** + * Drop a database, removing it permanently from the server. + * + * @method + * @param {Db~resultCallback} [callback] The results callback + * @return {Promise} returns Promise if no callback passed + */ +Db.prototype.dropDatabase = function(options, callback) { + var self = this; + if(typeof options == 'function') callback = options, options = {}; + options = options || {}; + // Drop database command + var cmd = {'dropDatabase':1}; + + // Decorate with write concern + decorateWithWriteConcern(cmd, self, options); + + // Ensure primary only + options = assign({}, this.s.options, {readPreference: ReadPreference.PRIMARY}); + + // Check if the callback is in fact a string + if(typeof callback == 'function') return this.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); + }); + + // Execute the command + return new this.s.promiseLibrary(function(resolve, reject) { + // Execute command + self.command(cmd, options, function(err, result) { + // Did the user destroy the topology + if(self.serverConfig && self.serverConfig.isDestroyed()) return reject(new MongoError('topology was destroyed')); + if(err) return reject(err); + if(result.ok) return resolve(true); + resolve(false); + }); + }); +} + +define.classMethod('dropDatabase', {callback: true, promise:true}); + +/** + * The callback format for the collections method. + * @callback Db~collectionsResultCallback + * @param {MongoError} error An error instance representing the error during the execution. + * @param {Collection[]} collections An array of all the collections objects for the db instance. + */ +var collections = function(self, callback) { + // Let's get the collection names + self.listCollections().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.replace(self.s.databaseName + ".", ''), self.s.pkFactory, self.s.options); + })); + }); +} + +/** + * Fetch all collections for the current db. + * + * @method + * @param {Db~collectionsResultCallback} [callback] The results callback + * @return {Promise} returns Promise if no callback passed + */ +Db.prototype.collections = function(callback) { + var self = this; + + // Return the callback + if(typeof callback == 'function') return collections(self, callback); + // Return the promise + return new self.s.promiseLibrary(function(resolve, reject) { + collections(self, function(err, r) { + if(err) return reject(err); + resolve(r); + }); + }); +}; + +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 {Db~resultCallback} [callback] The command result callback + * @return {Promise} returns Promise if no callback passed + */ +Db.prototype.executeDbAdminCommand = function(selector, options, callback) { + var self = this; + if(typeof options == 'function') callback = options, options = {}; + options = options || {}; + + // Return the callback + if(typeof callback == 'function') { + // Convert read preference + if(options.readPreference) { + options.readPreference = convertReadPreference(options.readPreference) + } + + return 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); + }); + } + + // Return promise + return new self.s.promiseLibrary(function(resolve, reject) { + self.s.topology.command('admin.$cmd', selector, options, function(err, result) { + // Did the user destroy the topology + if(self.serverConfig && self.serverConfig.isDestroyed()) return reject(new MongoError('topology was destroyed')); + if(err) return reject(err); + resolve(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 {Db~resultCallback} [callback] The command result callback + * @return {Promise} returns Promise if no callback passed + */ +Db.prototype.createIndex = function(name, fieldOrSpec, options, callback) { + var self = this; + var args = Array.prototype.slice.call(arguments, 2); + callback = args.pop(); + if(typeof callback != 'function') args.push(callback); + options = args.length ? args.shift() || {} : {}; + options = typeof callback === 'function' ? options : callback; + options = options == null ? {} : options; + // Shallow clone the options + options = shallowClone(options); + // Run only against primary + options.readPreference = ReadPreference.PRIMARY; + + // If we have a callback fallback + if(typeof callback == 'function') return createIndex(self, name, fieldOrSpec, options, callback); + // Return a promise + return new this.s.promiseLibrary(function(resolve, reject) { + createIndex(self, name, fieldOrSpec, options, function(err, r) { + if(err) return reject(err); + resolve(r); + }); + }); +}; + +var createIndex = function(self, name, fieldOrSpec, options, callback) { + // Get the write concern options + var finalOptions = writeConcern({}, 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) + // 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) { + 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 {Db~resultCallback} [callback] The command result callback + * @return {Promise} returns Promise if no callback passed + */ +Db.prototype.ensureIndex = function(name, fieldOrSpec, options, callback) { + var self = this; + if(typeof options == 'function') callback = options, options = {}; + options = options || {}; + + // If we have a callback fallback + if(typeof callback == 'function') return ensureIndex(self, name, fieldOrSpec, options, callback); + + // Return a promise + return new this.s.promiseLibrary(function(resolve, reject) { + ensureIndex(self, name, fieldOrSpec, options, function(err, r) { + if(err) return reject(err); + resolve(r); + }); + }); +}; + +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')); + + // 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); +} + +/** + * Create a new Db instance sharing the current socket connections. Be aware that the new db instances are + * related in a parent-child relationship to the original instance so that events are correctly emitted on child + * db instances. Child db instances are cached so performing db('db1') twice will return the same instance. + * You can control these behaviors with the options noListener and returnNonCachedInstance. + * + * @method + * @param {string} name The name of the database we want to use. + * @param {object} [options=null] Optional settings. + * @param {boolean} [options.noListener=false] Do not make the db an event listener to the original connection. + * @param {boolean} [options.returnNonCachedInstance=false] Control if you want to return a cached instance or have a new one created + * @return {Db} + */ +Db.prototype.db = function(dbName, options) { + options = options || {}; + + // Copy the options and add out internal override of the not shared flag + var finalOptions = assign({}, this.options, options); + + // Do we have the db in the cache already + if(this.s.dbCache[dbName] && finalOptions.returnNonCachedInstance !== true) { + return this.s.dbCache[dbName]; + } + + // Add current db as parentDb + if(finalOptions.noListener == null || finalOptions.noListener == false) { + finalOptions.parentDb = this; + } + + // Add promiseLibrary + finalOptions.promiseLibrary = this.s.promiseLibrary; + + // Return the db object + var db = new Db(dbName, this.s.topology, finalOptions) + + // Add as child + if(finalOptions.noListener == null || finalOptions.noListener == false) { + this.addChild(db); + } + + // Add the db to the cache + this.s.dbCache[dbName] = db; + // Return the database + return db; +}; + +define.classMethod('db', {callback: false, promise:false, returns: [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 ? self.db(options.dbName) : self; + + // Fetch a user collection + var collection = db.collection(Db.SYSTEM_USER_COLLECTION); + + // Check if we are inserting the first user + collection.count({}, 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']}).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 {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 self = this; + var args = Array.prototype.slice.call(arguments, 2); + callback = args.pop(); + if(typeof callback != 'function') args.push(callback); + options = args.length ? args.shift() || {} : {}; + + // If we have a callback fallback + if(typeof callback == 'function') return addUser(self, username, password, options, callback); + + // Return a promise + return new this.s.promiseLibrary(function(resolve, reject) { + addUser(self, username, password, options, function(err, r) { + if(err) return reject(err); + resolve(r); + }); + }); +}; + +define.classMethod('addUser', {callback: true, promise:true}); + +var _executeAuthRemoveUserCommand = function(self, username, options, callback) { + if(typeof options == 'function') callback = 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 ? self.db(options.dbName) : self; + + // Fetch a user collection + var collection = db.collection(Db.SYSTEM_USER_COLLECTION); + + // Locate the user + collection.findOne({user: username}, {}, 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 {Db~resultCallback} [callback] The command result callback + * @return {Promise} returns Promise if no callback passed + */ +Db.prototype.removeUser = function(username, options, callback) { + // Unpack the parameters + var self = this; + var args = Array.prototype.slice.call(arguments, 1); + callback = args.pop(); + if(typeof callback != 'function') args.push(callback); + options = args.length ? args.shift() || {} : {}; + + // If we have a callback fallback + if(typeof callback == 'function') return removeUser(self, username, options, callback); + + // Return a promise + return new this.s.promiseLibrary(function(resolve, reject) { + removeUser(self, username, options, function(err, r) { + if(err) return reject(err); + resolve(r); + }); + }); +}; + +var authenticate = 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')); + + // the default db to authenticate against is 'self' + // if authententicate is called from a retry context, it may be another one, like admin + var authdb = options.dbName ? options.dbName : self.databaseName; + authdb = self.authSource ? self.authSource : authdb; + authdb = options.authdb ? options.authdb : authdb; + authdb = options.authSource ? options.authSource : authdb; + + // Callback + var _callback = function(err, result) { + if(self.listeners('authenticated').length > 0) { + self.emit('authenticated', err, result); + } + + // Return to caller + handleCallback(callback, err, result); + } + + // authMechanism + var authMechanism = options.authMechanism || ''; + authMechanism = authMechanism.toUpperCase(); + + // If classic auth delegate to auth command + if(authMechanism == 'MONGODB-CR') { + self.s.topology.auth('mongocr', authdb, username, password, function(err) { + if(err) return handleCallback(callback, err, false); + _callback(null, true); + }); + } else if(authMechanism == 'PLAIN') { + self.s.topology.auth('plain', authdb, username, password, function(err) { + if(err) return handleCallback(callback, err, false); + _callback(null, true); + }); + } else if(authMechanism == 'MONGODB-X509') { + self.s.topology.auth('x509', authdb, username, password, function(err) { + if(err) return handleCallback(callback, err, false); + _callback(null, true); + }); + } else if(authMechanism == 'SCRAM-SHA-1') { + self.s.topology.auth('scram-sha-1', authdb, username, password, function(err) { + if(err) return handleCallback(callback, err, false); + _callback(null, true); + }); + } else if(authMechanism == 'GSSAPI') { + if(process.platform == 'win32') { + self.s.topology.auth('sspi', authdb, username, password, options, function(err) { + if(err) return handleCallback(callback, err, false); + _callback(null, true); + }); + } else { + self.s.topology.auth('gssapi', authdb, username, password, options, function(err) { + if(err) return handleCallback(callback, err, false); + _callback(null, true); + }); + } + } else if(authMechanism == 'DEFAULT') { + self.s.topology.auth('default', authdb, username, password, function(err) { + if(err) return handleCallback(callback, err, false); + _callback(null, true); + }); + } else { + handleCallback(callback, MongoError.create({message: f("authentication mechanism %s not supported", options.authMechanism), driver:true})); + } +} + +/** + * Authenticate a user against the server. + * @method + * @param {string} username The username. + * @param {string} [password] The password. + * @param {object} [options=null] Optional settings. + * @param {string} [options.authMechanism=MONGODB-CR] The authentication mechanism to use, GSSAPI, MONGODB-CR, MONGODB-X509, PLAIN + * @param {Db~resultCallback} [callback] The command result callback + * @return {Promise} returns Promise if no callback passed + */ +Db.prototype.authenticate = function(username, password, options, callback) { + if(typeof options == 'function') callback = options, options = {}; + var self = this; + // Shallow copy the options + options = shallowClone(options); + + // Set default mechanism + if(!options.authMechanism) { + options.authMechanism = 'DEFAULT'; + } else if(options.authMechanism != 'GSSAPI' + && options.authMechanism != 'DEFAULT' + && options.authMechanism != 'MONGODB-CR' + && options.authMechanism != 'MONGODB-X509' + && options.authMechanism != 'SCRAM-SHA-1' + && options.authMechanism != 'PLAIN') { + return handleCallback(callback, MongoError.create({message: "only DEFAULT, GSSAPI, PLAIN, MONGODB-X509, SCRAM-SHA-1 or MONGODB-CR is supported by authMechanism", driver:true})); + } + + // If we have a callback fallback + if(typeof callback == 'function') return authenticate(self, username, password, options, function(err, r) { + // Support failed auth method + if(err && err.message && err.message.indexOf('saslStart') != -1) err.code = 59; + // Reject error + if(err) return callback(err, r); + callback(null, r); + }); + + // Return a promise + return new this.s.promiseLibrary(function(resolve, reject) { + authenticate(self, username, password, options, function(err, r) { + // Support failed auth method + if(err && err.message && err.message.indexOf('saslStart') != -1) err.code = 59; + // Reject error + if(err) return reject(err); + resolve(r); + }); + }); +}; + +define.classMethod('authenticate', {callback: true, promise:true}); + +/** + * Logout user from server, fire off on all connections and remove all auth info + * @method + * @param {object} [options=null] Optional settings. + * @param {string} [options.dbName=null] Logout against different database than current. + * @param {Db~resultCallback} [callback] The command result callback + * @return {Promise} returns Promise if no callback passed + */ +Db.prototype.logout = function(options, callback) { + var self = this; + if(typeof options == 'function') callback = options, options = {}; + options = options || {}; + + // Establish the correct database name + var dbName = this.s.authSource ? this.s.authSource : this.s.databaseName; + dbName = options.dbName ? options.dbName : dbName; + + // If we have a callback + if(typeof callback == 'function') { + return self.s.topology.logout(dbName, function(err) { + if(err) return callback(err); + callback(null, true); + }); + } + + // Return a promise + return new this.s.promiseLibrary(function(resolve, reject) { + self.s.topology.logout(dbName, function(err) { + if(err) return reject(err); + resolve(true); + }); + }); +} + +define.classMethod('logout', {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 {Db~resultCallback} [callback] The command result callback + * @return {Promise} returns Promise if no callback passed + */ +Db.prototype.indexInformation = function(name, options, callback) { + var self = this; + if(typeof options == 'function') callback = options, options = {}; + options = options || {}; + + // If we have a callback fallback + if(typeof callback == 'function') return indexInformation(self, name, options, callback); + + // Return a promise + return new this.s.promiseLibrary(function(resolve, reject) { + indexInformation(self, name, options, function(err, r) { + if(err) return reject(err); + resolve(r); + }); + }); +}; + +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().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]); + for(var optionName in options) { + if(keysToOmit.indexOf(optionName) == -1) { + indexes[0][optionName] = options[optionName]; + } + + // Remove any write concern operations + var removeKeys = ['w', 'wtimeout', 'j', 'fsync', 'readPreference']; + for(var i = 0; i < removeKeys.length; i++) { + delete indexes[0][removeKeys[i]]; + } + } + + // 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 authenticated event + * + * Emitted after all server members in the topology (single server, replicaset or mongos) have successfully authenticated. + * + * @event Db#authenticated + * @type {object} + */ + +/** + * 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; |