|
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006 |
- 'use strict';
-
- const applyWriteConcern = require('../utils').applyWriteConcern;
- const Code = require('mongodb-core').BSON.Code;
- const resolveReadPreference = require('../utils').resolveReadPreference;
- const crypto = require('crypto');
- const debugOptions = require('../utils').debugOptions;
- const handleCallback = require('../utils').handleCallback;
- const MongoError = require('mongodb-core').MongoError;
- const parseIndexOptions = require('../utils').parseIndexOptions;
- const ReadPreference = require('mongodb-core').ReadPreference;
- const toError = require('../utils').toError;
- const CONSTANTS = require('../constants');
-
- const count = require('./collection_ops').count;
- const findOne = require('./collection_ops').findOne;
- const remove = require('./collection_ops').remove;
- const updateOne = require('./collection_ops').updateOne;
-
- let collection;
- function loadCollection() {
- if (!collection) {
- collection = require('../collection');
- }
- return collection;
- }
- let db;
- function loadDb() {
- if (!db) {
- db = require('../db');
- }
- return db;
- }
-
- const 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
- const illegalCommandFields = [
- 'w',
- 'wtimeout',
- 'j',
- 'fsync',
- 'autoIndexId',
- 'strict',
- 'serializeFunctions',
- 'pkFactory',
- 'raw',
- 'readPreference',
- 'session'
- ];
-
- /**
- * Add a user to the database.
- * @method
- * @param {Db} db The Db instance on which to add a user.
- * @param {string} username The username.
- * @param {string} password The password.
- * @param {object} [options] Optional settings. See Db.prototype.addUser for a list of options.
- * @param {Db~resultCallback} [callback] The command result callback
- */
- function addUser(db, username, password, options, callback) {
- let Db = loadDb();
-
- // Did the user destroy the topology
- if (db.serverConfig && db.serverConfig.isDestroyed())
- return callback(new MongoError('topology was destroyed'));
- // Attempt to execute auth command
- executeAuthCreateUserCommand(db, username, password, options, (err, r) => {
- // We need to perform the backward compatible insert operation
- if (err && err.code === -5000) {
- const finalOptions = applyWriteConcern(Object.assign({}, options), { db }, options);
-
- // Use node md5 generator
- const md5 = crypto.createHash('md5');
- // Generate keys used for authentication
- md5.update(username + ':mongo:' + password);
- const userPassword = md5.digest('hex');
-
- // If we have another db set
- const dbToUse = options.dbName ? new Db(options.dbName, db.s.topology, db.s.options) : db;
-
- // Fetch a user collection
- const collection = dbToUse.collection(CONSTANTS.SYSTEM_USER_COLLECTION);
-
- // Check if we are inserting the first user
- count(collection, {}, finalOptions, (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
- const findOptions = Object.assign({ projection: { dbName: 1 } }, finalOptions);
- collection.find({ user: username }, findOptions).toArray(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
- updateOne(
- collection,
- { user: username },
- { $set: { user: username, pwd: userPassword } },
- finalOptions,
- 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);
- });
- }
-
- /**
- * Fetch all collections for the current db.
- *
- * @method
- * @param {Db} db The Db instance on which to fetch collections.
- * @param {object} [options] Optional settings. See Db.prototype.collections for a list of options.
- * @param {Db~collectionsResultCallback} [callback] The results callback
- */
- function collections(db, options, callback) {
- let Collection = loadCollection();
-
- options = Object.assign({}, options, { nameOnly: true });
- // Let's get the collection names
- db.listCollections({}, options).toArray((err, documents) => {
- if (err != null) return handleCallback(callback, err, null);
- // Filter collections removing any illegal ones
- documents = documents.filter(doc => {
- return doc.name.indexOf('$') === -1;
- });
-
- // Return the collection objects
- handleCallback(
- callback,
- null,
- documents.map(d => {
- return new Collection(
- db,
- db.s.topology,
- db.s.databaseName,
- d.name,
- db.s.pkFactory,
- db.s.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 {Db} db The Db instance on which to create the collection.
- * @param {string} name The collection name to create.
- * @param {object} [options] Optional settings. See Db.prototype.createCollection for a list of options.
- * @param {Db~collectionResultCallback} [callback] The results callback
- */
- function createCollection(db, name, options, callback) {
- let Collection = loadCollection();
-
- // Get the write concern options
- const finalOptions = applyWriteConcern(Object.assign({}, options), { db }, options);
-
- // Did the user destroy the topology
- if (db.serverConfig && db.serverConfig.isDestroyed()) {
- return callback(new MongoError('topology was destroyed'));
- }
-
- const listCollectionOptions = Object.assign({}, finalOptions, { nameOnly: true });
-
- // Check if we have the name
- db
- .listCollections({ name }, listCollectionOptions)
- .setReadPreference(ReadPreference.PRIMARY)
- .toArray((err, collections) => {
- if (err != null) return handleCallback(callback, err, null);
- if (collections.length > 0 && finalOptions.strict) {
- return handleCallback(
- callback,
- MongoError.create({
- message: `Collection ${name} already exists. Currently in strict mode.`,
- driver: true
- }),
- null
- );
- } else if (collections.length > 0) {
- try {
- return handleCallback(
- callback,
- null,
- new Collection(db, db.s.topology, db.s.databaseName, name, db.s.pkFactory, options)
- );
- } catch (err) {
- return handleCallback(callback, err);
- }
- }
-
- // Create collection command
- const cmd = { create: name };
-
- // Decorate command with writeConcern if supported
- applyWriteConcern(cmd, { db }, options);
-
- // Add all optional parameters
- for (let 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
- executeCommand(db, cmd, finalOptions, err => {
- if (err) return handleCallback(callback, err);
-
- try {
- return handleCallback(
- callback,
- null,
- new Collection(db, db.s.topology, db.s.databaseName, name, db.s.pkFactory, options)
- );
- } catch (err) {
- return handleCallback(callback, err);
- }
- });
- });
- }
-
- /**
- * Creates an index on the db and collection.
- * @method
- * @param {Db} db The Db instance on which to create an index.
- * @param {string} name Name of the collection to create the index on.
- * @param {(string|object)} fieldOrSpec Defines the index.
- * @param {object} [options] Optional settings. See Db.prototype.createIndex for a list of options.
- * @param {Db~resultCallback} [callback] The command result callback
- */
- function createIndex(db, name, fieldOrSpec, options, callback) {
- // Get the write concern options
- let finalOptions = Object.assign({}, { readPreference: ReadPreference.PRIMARY }, options);
- finalOptions = applyWriteConcern(finalOptions, { db }, 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 (db.serverConfig && db.serverConfig.isDestroyed())
- return callback(new MongoError('topology was destroyed'));
-
- // Attempt to run using createIndexes command
- createIndexUsingCreateIndexes(db, name, fieldOrSpec, finalOptions, (err, result) => {
- if (err == null) return handleCallback(callback, err, result);
-
- /**
- * The following errors mean that the server recognized `createIndex` as a command so we don't need to fallback to an insert:
- * 67 = 'CannotCreateIndex' (malformed index options)
- * 85 = 'IndexOptionsConflict' (index already exists with different options)
- * 86 = 'IndexKeySpecsConflict' (index already exists with the same name)
- * 11000 = 'DuplicateKey' (couldn't build unique index because of dupes)
- * 11600 = 'InterruptedAtShutdown' (interrupted at shutdown)
- * 197 = 'InvalidIndexSpecificationOption' (`_id` with `background: true`)
- */
- if (
- err.code === 67 ||
- err.code === 11000 ||
- err.code === 85 ||
- err.code === 86 ||
- err.code === 11600 ||
- err.code === 197
- ) {
- return handleCallback(callback, err, result);
- }
-
- // Create command
- const doc = createCreateIndexCommand(db, name, fieldOrSpec, options);
- // Set no key checking
- finalOptions.checkKeys = false;
- // Insert document
- db.s.topology.insert(
- `${db.s.databaseName}.${CONSTANTS.SYSTEM_INDEX_COLLECTION}`,
- doc,
- finalOptions,
- (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);
- }
- );
- });
- }
-
- // Add listeners to topology
- function createListener(db, e, object) {
- function listener(err) {
- if (object.listeners(e).length > 0) {
- object.emit(e, err, db);
-
- // Emit on all associated db's if available
- for (let i = 0; i < db.s.children.length; i++) {
- db.s.children[i].emit(e, err, db.s.children[i]);
- }
- }
- }
- return listener;
- }
-
- /**
- * Drop a collection from the database, removing it permanently. New accesses will create a new collection.
- *
- * @method
- * @param {Db} db The Db instance on which to drop the collection.
- * @param {string} name Name of collection to drop
- * @param {Object} [options] Optional settings. See Db.prototype.dropCollection for a list of options.
- * @param {Db~resultCallback} [callback] The results callback
- */
- function dropCollection(db, name, options, callback) {
- executeCommand(db, name, options, (err, result) => {
- // Did the user destroy the topology
- if (db.serverConfig && db.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);
- });
- }
-
- /**
- * Drop a database, removing it permanently from the server.
- *
- * @method
- * @param {Db} db The Db instance to drop.
- * @param {Object} cmd The command document.
- * @param {Object} [options] Optional settings. See Db.prototype.dropDatabase for a list of options.
- * @param {Db~resultCallback} [callback] The results callback
- */
- function dropDatabase(db, cmd, options, callback) {
- executeCommand(db, cmd, options, (err, result) => {
- // Did the user destroy the topology
- if (db.serverConfig && db.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);
- });
- }
-
- /**
- * Ensures that an index exists. If it does not, creates it.
- *
- * @method
- * @param {Db} db The Db instance on which to ensure the index.
- * @param {string} name The index name
- * @param {(string|object)} fieldOrSpec Defines the index.
- * @param {object} [options] Optional settings. See Db.prototype.ensureIndex for a list of options.
- * @param {Db~resultCallback} [callback] The command result callback
- */
- function ensureIndex(db, name, fieldOrSpec, options, callback) {
- // Get the write concern options
- const finalOptions = applyWriteConcern({}, { db }, options);
- // Create command
- const selector = createCreateIndexCommand(db, name, fieldOrSpec, options);
- const index_name = selector.name;
-
- // Did the user destroy the topology
- if (db.serverConfig && db.serverConfig.isDestroyed())
- return callback(new MongoError('topology was destroyed'));
-
- // Merge primary readPreference
- finalOptions.readPreference = ReadPreference.PRIMARY;
-
- // Check if the index already exists
- indexInformation(db, name, finalOptions, (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]) {
- createIndex(db, name, fieldOrSpec, options, callback);
- } else {
- if (typeof callback === 'function') return handleCallback(callback, null, index_name);
- }
- });
- }
-
- /**
- * Evaluate JavaScript on the server
- *
- * @method
- * @param {Db} db The Db instance.
- * @param {Code} code JavaScript to execute on server.
- * @param {(object|array)} parameters The parameters for the call.
- * @param {object} [options] Optional settings. See Db.prototype.eval for a list of options.
- * @param {Db~resultCallback} [callback] The results callback
- * @deprecated Eval is deprecated on MongoDB 3.2 and forward
- */
- function evaluate(db, code, parameters, options, callback) {
- let finalCode = code;
- let finalParameters = [];
-
- // Did the user destroy the topology
- if (db.serverConfig && db.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
- let 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
- executeCommand(db, cmd, options, (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: `eval failed: ${result.errmsg}`, driver: true }),
- null
- );
- handleCallback(callback, err, result);
- });
- }
-
- /**
- * Execute a command
- *
- * @method
- * @param {Db} db The Db instance on which to execute the command.
- * @param {object} command The command hash
- * @param {object} [options] Optional settings. See Db.prototype.command for a list of options.
- * @param {Db~resultCallback} [callback] The command result callback
- */
- function executeCommand(db, command, options, callback) {
- // Did the user destroy the topology
- if (db.serverConfig && db.serverConfig.isDestroyed())
- return callback(new MongoError('topology was destroyed'));
- // Get the db name we are executing against
- const dbName = options.dbName || options.authdb || db.s.databaseName;
-
- // Convert the readPreference if its not a write
- options.readPreference = resolveReadPreference(options, { db, default: ReadPreference.primary });
-
- // Debug information
- if (db.s.logger.isDebug())
- db.s.logger.debug(
- `executing command ${JSON.stringify(
- command
- )} against ${dbName}.$cmd with options [${JSON.stringify(
- debugOptions(debugFields, options)
- )}]`
- );
-
- // Execute command
- db.s.topology.command(`${dbName}.$cmd`, command, options, (err, result) => {
- if (err) return handleCallback(callback, err);
- if (options.full) return handleCallback(callback, null, result);
- handleCallback(callback, null, result.result);
- });
- }
-
- /**
- * Runs a command on the database as admin.
- *
- * @method
- * @param {Db} db The Db instance on which to execute the command.
- * @param {object} command The command hash
- * @param {object} [options] Optional settings. See Db.prototype.executeDbAdminCommand for a list of options.
- * @param {Db~resultCallback} [callback] The command result callback
- */
- function executeDbAdminCommand(db, command, options, callback) {
- db.s.topology.command('admin.$cmd', command, options, (err, result) => {
- // Did the user destroy the topology
- if (db.serverConfig && db.serverConfig.isDestroyed()) {
- return callback(new MongoError('topology was destroyed'));
- }
-
- if (err) return handleCallback(callback, err);
- handleCallback(callback, null, result.result);
- });
- }
-
- /**
- * Retrieves this collections index info.
- *
- * @method
- * @param {Db} db The Db instance on which to retrieve the index info.
- * @param {string} name The name of the collection.
- * @param {object} [options] Optional settings. See Db.prototype.indexInformation for a list of options.
- * @param {Db~resultCallback} [callback] The command result callback
- */
- function indexInformation(db, name, options, callback) {
- // If we specified full information
- const full = options['full'] == null ? false : options['full'];
-
- // Did the user destroy the topology
- if (db.serverConfig && db.serverConfig.isDestroyed())
- return callback(new MongoError('topology was destroyed'));
- // Process all the results from the index command and collection
- function processResults(indexes) {
- // Contains all the information
- let info = {};
- // Process all the indexes
- for (let i = 0; i < indexes.length; i++) {
- const index = indexes[i];
- // Let's unpack the object
- info[index.name] = [];
- for (let name in index.key) {
- info[index.name].push([name, index.key[name]]);
- }
- }
-
- return info;
- }
-
- // Get the list of indexes of the specified collection
- db
- .collection(name)
- .listIndexes(options)
- .toArray((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));
- });
- }
-
- // Transformation methods for cursor results
- function listCollectionsTransforms(databaseName) {
- const matching = `${databaseName}.`;
-
- return {
- doc: doc => {
- const 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;
- }
- };
- }
-
- /**
- * Retrieve the current profiling information for MongoDB
- *
- * @method
- * @param {Db} db The Db instance on which to retrieve the profiling info.
- * @param {Object} [options] Optional settings. See Db.protoype.profilingInfo for a list of options.
- * @param {Db~resultCallback} [callback] The command result callback.
- * @deprecated Query the system.profile collection directly.
- */
- function profilingInfo(db, options, callback) {
- try {
- db
- .collection('system.profile')
- .find({}, options)
- .toArray(callback);
- } catch (err) {
- return callback(err, null);
- }
- }
-
- /**
- * Retrieve the current profiling level for MongoDB
- *
- * @method
- * @param {Db} db The Db instance on which to retrieve the profiling level.
- * @param {Object} [options] Optional settings. See Db.prototype.profilingLevel for a list of options.
- * @param {Db~resultCallback} [callback] The command result callback
- */
- function profilingLevel(db, options, callback) {
- executeCommand(db, { profile: -1 }, options, (err, doc) => {
- if (err == null && doc.ok === 1) {
- const 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);
- }
- });
- }
-
- /**
- * Remove a user from a database
- *
- * @method
- * @param {Db} db The Db instance on which to remove the user.
- * @param {string} username The username.
- * @param {object} [options] Optional settings. See Db.prototype.removeUser for a list of options.
- * @param {Db~resultCallback} [callback] The command result callback
- */
- function removeUser(db, username, options, callback) {
- let Db = loadDb();
-
- // Attempt to execute command
- executeAuthRemoveUserCommand(db, username, options, (err, result) => {
- if (err && err.code === -5000) {
- const finalOptions = applyWriteConcern(Object.assign({}, options), { db }, options);
- // If we have another db set
- const db = options.dbName ? new Db(options.dbName, db.s.topology, db.s.options) : db;
-
- // Fetch a user collection
- const collection = db.collection(CONSTANTS.SYSTEM_USER_COLLECTION);
-
- // Locate the user
- findOne(collection, { user: username }, finalOptions, (err, user) => {
- if (user == null) return handleCallback(callback, err, false);
- remove(collection, { user: username }, finalOptions, err => {
- handleCallback(callback, err, true);
- });
- });
-
- return;
- }
-
- if (err) return handleCallback(callback, err);
- handleCallback(callback, err, result);
- });
- }
-
- /**
- * Set the current profiling level of MongoDB
- *
- * @method
- * @param {Db} db The Db instance on which to execute the command.
- * @param {string} level The new profiling level (off, slow_only, all).
- * @param {Object} [options] Optional settings. See Db.prototype.setProfilingLevel for a list of options.
- * @param {Db~resultCallback} [callback] The command result callback.
- */
- function setProfilingLevel(db, level, options, callback) {
- const command = {};
- let 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;
-
- executeCommand(db, command, options, (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);
- });
- }
-
- // Validate the database name
- function validateDatabaseName(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;
-
- const invalidChars = [' ', '.', '$', '/', '\\'];
- for (let 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
- });
- }
- }
-
- /**
- * Create the command object for Db.prototype.createIndex.
- *
- * @param {Db} db The Db instance on which to create the command.
- * @param {string} name Name of the collection to create the index on.
- * @param {(string|object)} fieldOrSpec Defines the index.
- * @param {Object} [options] Optional settings. See Db.prototype.createIndex for a list of options.
- * @return {Object} The insert command object.
- */
- function createCreateIndexCommand(db, name, fieldOrSpec, options) {
- const indexParameters = parseIndexOptions(fieldOrSpec);
- const fieldHash = indexParameters.fieldHash;
-
- // Generate the index name
- const indexName = typeof options.name === 'string' ? options.name : indexParameters.name;
- const selector = {
- ns: db.databaseName + '.' + name,
- key: fieldHash,
- name: indexName
- };
-
- // Ensure we have a correct finalUnique
- const finalUnique = options == null || 'object' === typeof options ? false : options;
- // Set up options
- options = options == null || typeof options === 'boolean' ? {} : options;
-
- // Add all the options
- const keysToOmit = Object.keys(selector);
- for (let optionName in options) {
- if (keysToOmit.indexOf(optionName) === -1) {
- selector[optionName] = options[optionName];
- }
- }
-
- if (selector['unique'] == null) selector['unique'] = finalUnique;
-
- // Remove any write concern operations
- const removeKeys = ['w', 'wtimeout', 'j', 'fsync', 'readPreference', 'session'];
- for (let i = 0; i < removeKeys.length; i++) {
- delete selector[removeKeys[i]];
- }
-
- // Return the command creation selector
- return selector;
- }
-
- /**
- * Create index using the createIndexes command.
- *
- * @param {Db} db The Db instance on which to execute the command.
- * @param {string} name Name of the collection to create the index on.
- * @param {(string|object)} fieldOrSpec Defines the index.
- * @param {Object} [options] Optional settings. See Db.prototype.createIndex for a list of options.
- * @param {Db~resultCallback} [callback] The command result callback.
- */
- function createIndexUsingCreateIndexes(db, name, fieldOrSpec, options, callback) {
- // Build the index
- const indexParameters = parseIndexOptions(fieldOrSpec);
- // Generate the index name
- const indexName = typeof options.name === 'string' ? options.name : indexParameters.name;
- // Set up the index
- const indexes = [{ name: indexName, key: indexParameters.fieldHash }];
- // merge all the options
- const keysToOmit = Object.keys(indexes[0]).concat([
- 'writeConcern',
- 'w',
- 'wtimeout',
- 'j',
- 'fsync',
- 'readPreference',
- 'session'
- ]);
-
- for (let optionName in options) {
- if (keysToOmit.indexOf(optionName) === -1) {
- indexes[0][optionName] = options[optionName];
- }
- }
-
- // Get capabilities
- const capabilities = db.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
- const error = new MongoError('server/primary/mongos does not support collation');
- error.code = 67;
- // Return the error
- return callback(error);
- }
-
- // Create command, apply write concern to command
- const cmd = applyWriteConcern({ createIndexes: name, indexes }, { db }, options);
-
- // ReadPreference primary
- options.readPreference = ReadPreference.PRIMARY;
-
- // Build the command
- executeCommand(db, cmd, options, (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);
- });
- }
-
- /**
- * Run the createUser command.
- *
- * @param {Db} db The Db instance on which to execute the command.
- * @param {string} username The username of the user to add.
- * @param {string} password The password of the user to add.
- * @param {object} [options] Optional settings. See Db.prototype.addUser for a list of options.
- * @param {Db~resultCallback} [callback] The command result callback
- */
- function executeAuthCreateUserCommand(db, 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) {
- return callback(
- toError(
- "The digestPassword option is not supported via add_user. Please use db.command('createUser', ...) instead for this option."
- )
- );
- }
-
- // Get additional values
- const customData = options.customData != null ? options.customData : {};
- let roles = Array.isArray(options.roles) ? options.roles : [];
- const 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
- const 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 (
- (db.databaseName.toLowerCase() === 'admin' || options.dbName === 'admin') &&
- !Array.isArray(options.roles)
- ) {
- roles = ['root'];
- } else if (!Array.isArray(options.roles)) {
- roles = ['dbOwner'];
- }
-
- const digestPassword = db.s.topology.lastIsMaster().maxWireVersion >= 7;
-
- // Build the command to execute
- let command = {
- createUser: username,
- customData: customData,
- roles: roles,
- digestPassword
- };
-
- // Apply write concern to command
- command = applyWriteConcern(command, { db }, options);
-
- let userPassword = password;
-
- if (!digestPassword) {
- // Use node md5 generator
- const md5 = crypto.createHash('md5');
- // Generate keys used for authentication
- md5.update(username + ':mongo:' + password);
- userPassword = md5.digest('hex');
- }
-
- // No password
- if (typeof password === 'string') {
- command.pwd = userPassword;
- }
-
- // Force write using primary
- commandOptions.readPreference = ReadPreference.primary;
-
- // Execute the command
- executeCommand(db, command, commandOptions, (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
- );
- });
- }
-
- /**
- * Run the dropUser command.
- *
- * @param {Db} db The Db instance on which to execute the command.
- * @param {string} username The username of the user to remove.
- * @param {object} [options] Optional settings. See Db.prototype.removeUser for a list of options.
- * @param {Db~resultCallback} [callback] The command result callback
- */
- function executeAuthRemoveUserCommand(db, username, options, callback) {
- if (typeof options === 'function') (callback = options), (options = {});
- options = options || {};
-
- // Did the user destroy the topology
- if (db.serverConfig && db.serverConfig.isDestroyed())
- return callback(new MongoError('topology was destroyed'));
- // Get the error options
- const commandOptions = { writeCommand: true };
- if (options['dbName']) commandOptions.dbName = options['dbName'];
-
- // Get additional values
- const 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
- let command = {
- dropUser: username
- };
-
- // Apply write concern to command
- command = applyWriteConcern(command, { db }, options);
-
- // Force write using primary
- commandOptions.readPreference = ReadPreference.primary;
-
- // Execute the command
- executeCommand(db, command, commandOptions, (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);
- });
- }
-
- module.exports = {
- addUser,
- collections,
- createCollection,
- createListener,
- createIndex,
- dropCollection,
- dropDatabase,
- ensureIndex,
- evaluate,
- executeCommand,
- executeDbAdminCommand,
- listCollectionsTransforms,
- indexInformation,
- profilingInfo,
- profilingLevel,
- removeUser,
- setProfilingLevel,
- validateDatabaseName
- };
|