db_ops.js 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006
  1. 'use strict';
  2. const applyWriteConcern = require('../utils').applyWriteConcern;
  3. const Code = require('mongodb-core').BSON.Code;
  4. const resolveReadPreference = require('../utils').resolveReadPreference;
  5. const crypto = require('crypto');
  6. const debugOptions = require('../utils').debugOptions;
  7. const handleCallback = require('../utils').handleCallback;
  8. const MongoError = require('mongodb-core').MongoError;
  9. const parseIndexOptions = require('../utils').parseIndexOptions;
  10. const ReadPreference = require('mongodb-core').ReadPreference;
  11. const toError = require('../utils').toError;
  12. const CONSTANTS = require('../constants');
  13. const count = require('./collection_ops').count;
  14. const findOne = require('./collection_ops').findOne;
  15. const remove = require('./collection_ops').remove;
  16. const updateOne = require('./collection_ops').updateOne;
  17. let collection;
  18. function loadCollection() {
  19. if (!collection) {
  20. collection = require('../collection');
  21. }
  22. return collection;
  23. }
  24. let db;
  25. function loadDb() {
  26. if (!db) {
  27. db = require('../db');
  28. }
  29. return db;
  30. }
  31. const debugFields = [
  32. 'authSource',
  33. 'w',
  34. 'wtimeout',
  35. 'j',
  36. 'native_parser',
  37. 'forceServerObjectId',
  38. 'serializeFunctions',
  39. 'raw',
  40. 'promoteLongs',
  41. 'promoteValues',
  42. 'promoteBuffers',
  43. 'bufferMaxEntries',
  44. 'numberOfRetries',
  45. 'retryMiliSeconds',
  46. 'readPreference',
  47. 'pkFactory',
  48. 'parentDb',
  49. 'promiseLibrary',
  50. 'noListener'
  51. ];
  52. // Filter out any write concern options
  53. const illegalCommandFields = [
  54. 'w',
  55. 'wtimeout',
  56. 'j',
  57. 'fsync',
  58. 'autoIndexId',
  59. 'strict',
  60. 'serializeFunctions',
  61. 'pkFactory',
  62. 'raw',
  63. 'readPreference',
  64. 'session'
  65. ];
  66. /**
  67. * Add a user to the database.
  68. * @method
  69. * @param {Db} db The Db instance on which to add a user.
  70. * @param {string} username The username.
  71. * @param {string} password The password.
  72. * @param {object} [options] Optional settings. See Db.prototype.addUser for a list of options.
  73. * @param {Db~resultCallback} [callback] The command result callback
  74. */
  75. function addUser(db, username, password, options, callback) {
  76. let Db = loadDb();
  77. // Did the user destroy the topology
  78. if (db.serverConfig && db.serverConfig.isDestroyed())
  79. return callback(new MongoError('topology was destroyed'));
  80. // Attempt to execute auth command
  81. executeAuthCreateUserCommand(db, username, password, options, (err, r) => {
  82. // We need to perform the backward compatible insert operation
  83. if (err && err.code === -5000) {
  84. const finalOptions = applyWriteConcern(Object.assign({}, options), { db }, options);
  85. // Use node md5 generator
  86. const md5 = crypto.createHash('md5');
  87. // Generate keys used for authentication
  88. md5.update(username + ':mongo:' + password);
  89. const userPassword = md5.digest('hex');
  90. // If we have another db set
  91. const db = options.dbName ? new Db(options.dbName, db.s.topology, db.s.options) : db;
  92. // Fetch a user collection
  93. const collection = db.collection(CONSTANTS.SYSTEM_USER_COLLECTION);
  94. // Check if we are inserting the first user
  95. count(collection, {}, finalOptions, (err, count) => {
  96. // We got an error (f.ex not authorized)
  97. if (err != null) return handleCallback(callback, err, null);
  98. // Check if the user exists and update i
  99. const findOptions = Object.assign({ projection: { dbName: 1 } }, finalOptions);
  100. collection.find({ user: username }, findOptions).toArray(err => {
  101. // We got an error (f.ex not authorized)
  102. if (err != null) return handleCallback(callback, err, null);
  103. // Add command keys
  104. finalOptions.upsert = true;
  105. // We have a user, let's update the password or upsert if not
  106. updateOne(
  107. collection,
  108. { user: username },
  109. { $set: { user: username, pwd: userPassword } },
  110. finalOptions,
  111. err => {
  112. if (count === 0 && err)
  113. return handleCallback(callback, null, [{ user: username, pwd: userPassword }]);
  114. if (err) return handleCallback(callback, err, null);
  115. handleCallback(callback, null, [{ user: username, pwd: userPassword }]);
  116. }
  117. );
  118. });
  119. });
  120. return;
  121. }
  122. if (err) return handleCallback(callback, err);
  123. handleCallback(callback, err, r);
  124. });
  125. }
  126. /**
  127. * Fetch all collections for the current db.
  128. *
  129. * @method
  130. * @param {Db} db The Db instance on which to fetch collections.
  131. * @param {object} [options] Optional settings. See Db.prototype.collections for a list of options.
  132. * @param {Db~collectionsResultCallback} [callback] The results callback
  133. */
  134. function collections(db, options, callback) {
  135. let Collection = loadCollection();
  136. options = Object.assign({}, options, { nameOnly: true });
  137. // Let's get the collection names
  138. db.listCollections({}, options).toArray((err, documents) => {
  139. if (err != null) return handleCallback(callback, err, null);
  140. // Filter collections removing any illegal ones
  141. documents = documents.filter(doc => {
  142. return doc.name.indexOf('$') === -1;
  143. });
  144. // Return the collection objects
  145. handleCallback(
  146. callback,
  147. null,
  148. documents.map(d => {
  149. return new Collection(
  150. db,
  151. db.s.topology,
  152. db.s.databaseName,
  153. d.name,
  154. db.s.pkFactory,
  155. db.s.options
  156. );
  157. })
  158. );
  159. });
  160. }
  161. /**
  162. * Create a new collection on a server with the specified options. Use this to create capped collections.
  163. * More information about command options available at https://docs.mongodb.com/manual/reference/command/create/
  164. *
  165. * @method
  166. * @param {Db} db The Db instance on which to create the collection.
  167. * @param {string} name The collection name to create.
  168. * @param {object} [options] Optional settings. See Db.prototype.createCollection for a list of options.
  169. * @param {Db~collectionResultCallback} [callback] The results callback
  170. */
  171. function createCollection(db, name, options, callback) {
  172. let Collection = loadCollection();
  173. // Get the write concern options
  174. const finalOptions = applyWriteConcern(Object.assign({}, options), { db }, options);
  175. // Did the user destroy the topology
  176. if (db.serverConfig && db.serverConfig.isDestroyed()) {
  177. return callback(new MongoError('topology was destroyed'));
  178. }
  179. const listCollectionOptions = Object.assign({}, finalOptions, { nameOnly: true });
  180. // Check if we have the name
  181. db
  182. .listCollections({ name }, listCollectionOptions)
  183. .setReadPreference(ReadPreference.PRIMARY)
  184. .toArray((err, collections) => {
  185. if (err != null) return handleCallback(callback, err, null);
  186. if (collections.length > 0 && finalOptions.strict) {
  187. return handleCallback(
  188. callback,
  189. MongoError.create({
  190. message: `Collection ${name} already exists. Currently in strict mode.`,
  191. driver: true
  192. }),
  193. null
  194. );
  195. } else if (collections.length > 0) {
  196. try {
  197. return handleCallback(
  198. callback,
  199. null,
  200. new Collection(db, db.s.topology, db.s.databaseName, name, db.s.pkFactory, options)
  201. );
  202. } catch (err) {
  203. return handleCallback(callback, err);
  204. }
  205. }
  206. // Create collection command
  207. const cmd = { create: name };
  208. // Decorate command with writeConcern if supported
  209. applyWriteConcern(cmd, { db }, options);
  210. // Add all optional parameters
  211. for (let n in options) {
  212. if (
  213. options[n] != null &&
  214. typeof options[n] !== 'function' &&
  215. illegalCommandFields.indexOf(n) === -1
  216. ) {
  217. cmd[n] = options[n];
  218. }
  219. }
  220. // Force a primary read Preference
  221. finalOptions.readPreference = ReadPreference.PRIMARY;
  222. // Execute command
  223. executeCommand(db, cmd, finalOptions, err => {
  224. if (err) return handleCallback(callback, err);
  225. try {
  226. return handleCallback(
  227. callback,
  228. null,
  229. new Collection(db, db.s.topology, db.s.databaseName, name, db.s.pkFactory, options)
  230. );
  231. } catch (err) {
  232. return handleCallback(callback, err);
  233. }
  234. });
  235. });
  236. }
  237. /**
  238. * Creates an index on the db and collection.
  239. * @method
  240. * @param {Db} db The Db instance on which to create an index.
  241. * @param {string} name Name of the collection to create the index on.
  242. * @param {(string|object)} fieldOrSpec Defines the index.
  243. * @param {object} [options] Optional settings. See Db.prototype.createIndex for a list of options.
  244. * @param {Db~resultCallback} [callback] The command result callback
  245. */
  246. function createIndex(db, name, fieldOrSpec, options, callback) {
  247. // Get the write concern options
  248. let finalOptions = Object.assign({}, { readPreference: ReadPreference.PRIMARY }, options);
  249. finalOptions = applyWriteConcern(finalOptions, { db }, options);
  250. // Ensure we have a callback
  251. if (finalOptions.writeConcern && typeof callback !== 'function') {
  252. throw MongoError.create({
  253. message: 'Cannot use a writeConcern without a provided callback',
  254. driver: true
  255. });
  256. }
  257. // Did the user destroy the topology
  258. if (db.serverConfig && db.serverConfig.isDestroyed())
  259. return callback(new MongoError('topology was destroyed'));
  260. // Attempt to run using createIndexes command
  261. createIndexUsingCreateIndexes(db, name, fieldOrSpec, finalOptions, (err, result) => {
  262. if (err == null) return handleCallback(callback, err, result);
  263. /**
  264. * The following errors mean that the server recognized `createIndex` as a command so we don't need to fallback to an insert:
  265. * 67 = 'CannotCreateIndex' (malformed index options)
  266. * 85 = 'IndexOptionsConflict' (index already exists with different options)
  267. * 86 = 'IndexKeySpecsConflict' (index already exists with the same name)
  268. * 11000 = 'DuplicateKey' (couldn't build unique index because of dupes)
  269. * 11600 = 'InterruptedAtShutdown' (interrupted at shutdown)
  270. * 197 = 'InvalidIndexSpecificationOption' (`_id` with `background: true`)
  271. */
  272. if (
  273. err.code === 67 ||
  274. err.code === 11000 ||
  275. err.code === 85 ||
  276. err.code === 86 ||
  277. err.code === 11600 ||
  278. err.code === 197
  279. ) {
  280. return handleCallback(callback, err, result);
  281. }
  282. // Create command
  283. const doc = createCreateIndexCommand(db, name, fieldOrSpec, options);
  284. // Set no key checking
  285. finalOptions.checkKeys = false;
  286. // Insert document
  287. db.s.topology.insert(
  288. `${db.s.databaseName}.${CONSTANTS.SYSTEM_INDEX_COLLECTION}`,
  289. doc,
  290. finalOptions,
  291. (err, result) => {
  292. if (callback == null) return;
  293. if (err) return handleCallback(callback, err);
  294. if (result == null) return handleCallback(callback, null, null);
  295. if (result.result.writeErrors)
  296. return handleCallback(callback, MongoError.create(result.result.writeErrors[0]), null);
  297. handleCallback(callback, null, doc.name);
  298. }
  299. );
  300. });
  301. }
  302. // Add listeners to topology
  303. function createListener(db, e, object) {
  304. function listener(err) {
  305. if (object.listeners(e).length > 0) {
  306. object.emit(e, err, db);
  307. // Emit on all associated db's if available
  308. for (let i = 0; i < db.s.children.length; i++) {
  309. db.s.children[i].emit(e, err, db.s.children[i]);
  310. }
  311. }
  312. }
  313. return listener;
  314. }
  315. /**
  316. * Drop a collection from the database, removing it permanently. New accesses will create a new collection.
  317. *
  318. * @method
  319. * @param {Db} db The Db instance on which to drop the collection.
  320. * @param {string} name Name of collection to drop
  321. * @param {Object} [options] Optional settings. See Db.prototype.dropCollection for a list of options.
  322. * @param {Db~resultCallback} [callback] The results callback
  323. */
  324. function dropCollection(db, name, options, callback) {
  325. executeCommand(db, name, options, (err, result) => {
  326. // Did the user destroy the topology
  327. if (db.serverConfig && db.serverConfig.isDestroyed()) {
  328. return callback(new MongoError('topology was destroyed'));
  329. }
  330. if (err) return handleCallback(callback, err);
  331. if (result.ok) return handleCallback(callback, null, true);
  332. handleCallback(callback, null, false);
  333. });
  334. }
  335. /**
  336. * Drop a database, removing it permanently from the server.
  337. *
  338. * @method
  339. * @param {Db} db The Db instance to drop.
  340. * @param {Object} cmd The command document.
  341. * @param {Object} [options] Optional settings. See Db.prototype.dropDatabase for a list of options.
  342. * @param {Db~resultCallback} [callback] The results callback
  343. */
  344. function dropDatabase(db, cmd, options, callback) {
  345. executeCommand(db, cmd, options, (err, result) => {
  346. // Did the user destroy the topology
  347. if (db.serverConfig && db.serverConfig.isDestroyed()) {
  348. return callback(new MongoError('topology was destroyed'));
  349. }
  350. if (callback == null) return;
  351. if (err) return handleCallback(callback, err, null);
  352. handleCallback(callback, null, result.ok ? true : false);
  353. });
  354. }
  355. /**
  356. * Ensures that an index exists. If it does not, creates it.
  357. *
  358. * @method
  359. * @param {Db} db The Db instance on which to ensure the index.
  360. * @param {string} name The index name
  361. * @param {(string|object)} fieldOrSpec Defines the index.
  362. * @param {object} [options] Optional settings. See Db.prototype.ensureIndex for a list of options.
  363. * @param {Db~resultCallback} [callback] The command result callback
  364. */
  365. function ensureIndex(db, name, fieldOrSpec, options, callback) {
  366. // Get the write concern options
  367. const finalOptions = applyWriteConcern({}, { db }, options);
  368. // Create command
  369. const selector = createCreateIndexCommand(db, name, fieldOrSpec, options);
  370. const index_name = selector.name;
  371. // Did the user destroy the topology
  372. if (db.serverConfig && db.serverConfig.isDestroyed())
  373. return callback(new MongoError('topology was destroyed'));
  374. // Merge primary readPreference
  375. finalOptions.readPreference = ReadPreference.PRIMARY;
  376. // Check if the index already exists
  377. indexInformation(db, name, finalOptions, (err, indexInformation) => {
  378. if (err != null && err.code !== 26) return handleCallback(callback, err, null);
  379. // If the index does not exist, create it
  380. if (indexInformation == null || !indexInformation[index_name]) {
  381. createIndex(db, name, fieldOrSpec, options, callback);
  382. } else {
  383. if (typeof callback === 'function') return handleCallback(callback, null, index_name);
  384. }
  385. });
  386. }
  387. /**
  388. * Evaluate JavaScript on the server
  389. *
  390. * @method
  391. * @param {Db} db The Db instance.
  392. * @param {Code} code JavaScript to execute on server.
  393. * @param {(object|array)} parameters The parameters for the call.
  394. * @param {object} [options] Optional settings. See Db.prototype.eval for a list of options.
  395. * @param {Db~resultCallback} [callback] The results callback
  396. * @deprecated Eval is deprecated on MongoDB 3.2 and forward
  397. */
  398. function evaluate(db, code, parameters, options, callback) {
  399. let finalCode = code;
  400. let finalParameters = [];
  401. // Did the user destroy the topology
  402. if (db.serverConfig && db.serverConfig.isDestroyed())
  403. return callback(new MongoError('topology was destroyed'));
  404. // If not a code object translate to one
  405. if (!(finalCode && finalCode._bsontype === 'Code')) finalCode = new Code(finalCode);
  406. // Ensure the parameters are correct
  407. if (parameters != null && !Array.isArray(parameters) && typeof parameters !== 'function') {
  408. finalParameters = [parameters];
  409. } else if (parameters != null && Array.isArray(parameters) && typeof parameters !== 'function') {
  410. finalParameters = parameters;
  411. }
  412. // Create execution selector
  413. let cmd = { $eval: finalCode, args: finalParameters };
  414. // Check if the nolock parameter is passed in
  415. if (options['nolock']) {
  416. cmd['nolock'] = options['nolock'];
  417. }
  418. // Set primary read preference
  419. options.readPreference = new ReadPreference(ReadPreference.PRIMARY);
  420. // Execute the command
  421. executeCommand(db, cmd, options, (err, result) => {
  422. if (err) return handleCallback(callback, err, null);
  423. if (result && result.ok === 1) return handleCallback(callback, null, result.retval);
  424. if (result)
  425. return handleCallback(
  426. callback,
  427. MongoError.create({ message: `eval failed: ${result.errmsg}`, driver: true }),
  428. null
  429. );
  430. handleCallback(callback, err, result);
  431. });
  432. }
  433. /**
  434. * Execute a command
  435. *
  436. * @method
  437. * @param {Db} db The Db instance on which to execute the command.
  438. * @param {object} command The command hash
  439. * @param {object} [options] Optional settings. See Db.prototype.command for a list of options.
  440. * @param {Db~resultCallback} [callback] The command result callback
  441. */
  442. function executeCommand(db, command, options, callback) {
  443. // Did the user destroy the topology
  444. if (db.serverConfig && db.serverConfig.isDestroyed())
  445. return callback(new MongoError('topology was destroyed'));
  446. // Get the db name we are executing against
  447. const dbName = options.dbName || options.authdb || db.s.databaseName;
  448. // Convert the readPreference if its not a write
  449. options.readPreference = resolveReadPreference(options, { db, default: ReadPreference.primary });
  450. // Debug information
  451. if (db.s.logger.isDebug())
  452. db.s.logger.debug(
  453. `executing command ${JSON.stringify(
  454. command
  455. )} against ${dbName}.$cmd with options [${JSON.stringify(
  456. debugOptions(debugFields, options)
  457. )}]`
  458. );
  459. // Execute command
  460. db.s.topology.command(`${dbName}.$cmd`, command, options, (err, result) => {
  461. if (err) return handleCallback(callback, err);
  462. if (options.full) return handleCallback(callback, null, result);
  463. handleCallback(callback, null, result.result);
  464. });
  465. }
  466. /**
  467. * Runs a command on the database as admin.
  468. *
  469. * @method
  470. * @param {Db} db The Db instance on which to execute the command.
  471. * @param {object} command The command hash
  472. * @param {object} [options] Optional settings. See Db.prototype.executeDbAdminCommand for a list of options.
  473. * @param {Db~resultCallback} [callback] The command result callback
  474. */
  475. function executeDbAdminCommand(db, command, options, callback) {
  476. db.s.topology.command('admin.$cmd', command, options, (err, result) => {
  477. // Did the user destroy the topology
  478. if (db.serverConfig && db.serverConfig.isDestroyed()) {
  479. return callback(new MongoError('topology was destroyed'));
  480. }
  481. if (err) return handleCallback(callback, err);
  482. handleCallback(callback, null, result.result);
  483. });
  484. }
  485. /**
  486. * Retrieves this collections index info.
  487. *
  488. * @method
  489. * @param {Db} db The Db instance on which to retrieve the index info.
  490. * @param {string} name The name of the collection.
  491. * @param {object} [options] Optional settings. See Db.prototype.indexInformation for a list of options.
  492. * @param {Db~resultCallback} [callback] The command result callback
  493. */
  494. function indexInformation(db, name, options, callback) {
  495. // If we specified full information
  496. const full = options['full'] == null ? false : options['full'];
  497. // Did the user destroy the topology
  498. if (db.serverConfig && db.serverConfig.isDestroyed())
  499. return callback(new MongoError('topology was destroyed'));
  500. // Process all the results from the index command and collection
  501. function processResults(indexes) {
  502. // Contains all the information
  503. let info = {};
  504. // Process all the indexes
  505. for (let i = 0; i < indexes.length; i++) {
  506. const index = indexes[i];
  507. // Let's unpack the object
  508. info[index.name] = [];
  509. for (let name in index.key) {
  510. info[index.name].push([name, index.key[name]]);
  511. }
  512. }
  513. return info;
  514. }
  515. // Get the list of indexes of the specified collection
  516. db
  517. .collection(name)
  518. .listIndexes(options)
  519. .toArray((err, indexes) => {
  520. if (err) return callback(toError(err));
  521. if (!Array.isArray(indexes)) return handleCallback(callback, null, []);
  522. if (full) return handleCallback(callback, null, indexes);
  523. handleCallback(callback, null, processResults(indexes));
  524. });
  525. }
  526. // Transformation methods for cursor results
  527. function listCollectionsTransforms(databaseName) {
  528. const matching = `${databaseName}.`;
  529. return {
  530. doc: doc => {
  531. const index = doc.name.indexOf(matching);
  532. // Remove database name if available
  533. if (doc.name && index === 0) {
  534. doc.name = doc.name.substr(index + matching.length);
  535. }
  536. return doc;
  537. }
  538. };
  539. }
  540. /**
  541. * Retrive the current profiling information for MongoDB
  542. *
  543. * @method
  544. * @param {Db} db The Db instance on which to retrieve the profiling info.
  545. * @param {Object} [options] Optional settings. See Db.protoype.profilingInfo for a list of options.
  546. * @param {Db~resultCallback} [callback] The command result callback.
  547. * @deprecated Query the system.profile collection directly.
  548. */
  549. function profilingInfo(db, options, callback) {
  550. try {
  551. db
  552. .collection('system.profile')
  553. .find({}, options)
  554. .toArray(callback);
  555. } catch (err) {
  556. return callback(err, null);
  557. }
  558. }
  559. /**
  560. * Retrieve the current profiling level for MongoDB
  561. *
  562. * @method
  563. * @param {Db} db The Db instance on which to retrieve the profiling level.
  564. * @param {Object} [options] Optional settings. See Db.prototype.profilingLevel for a list of options.
  565. * @param {Db~resultCallback} [callback] The command result callback
  566. */
  567. function profilingLevel(db, options, callback) {
  568. executeCommand(db, { profile: -1 }, options, (err, doc) => {
  569. if (err == null && doc.ok === 1) {
  570. const was = doc.was;
  571. if (was === 0) return callback(null, 'off');
  572. if (was === 1) return callback(null, 'slow_only');
  573. if (was === 2) return callback(null, 'all');
  574. return callback(new Error('Error: illegal profiling level value ' + was), null);
  575. } else {
  576. err != null ? callback(err, null) : callback(new Error('Error with profile command'), null);
  577. }
  578. });
  579. }
  580. /**
  581. * Remove a user from a database
  582. *
  583. * @method
  584. * @param {Db} db The Db instance on which to remove the user.
  585. * @param {string} username The username.
  586. * @param {object} [options] Optional settings. See Db.prototype.removeUser for a list of options.
  587. * @param {Db~resultCallback} [callback] The command result callback
  588. */
  589. function removeUser(db, username, options, callback) {
  590. let Db = loadDb();
  591. // Attempt to execute command
  592. executeAuthRemoveUserCommand(db, username, options, (err, result) => {
  593. if (err && err.code === -5000) {
  594. const finalOptions = applyWriteConcern(Object.assign({}, options), { db }, options);
  595. // If we have another db set
  596. const db = options.dbName ? new Db(options.dbName, db.s.topology, db.s.options) : db;
  597. // Fetch a user collection
  598. const collection = db.collection(CONSTANTS.SYSTEM_USER_COLLECTION);
  599. // Locate the user
  600. findOne(collection, { user: username }, finalOptions, (err, user) => {
  601. if (user == null) return handleCallback(callback, err, false);
  602. remove(collection, { user: username }, finalOptions, err => {
  603. handleCallback(callback, err, true);
  604. });
  605. });
  606. return;
  607. }
  608. if (err) return handleCallback(callback, err);
  609. handleCallback(callback, err, result);
  610. });
  611. }
  612. /**
  613. * Set the current profiling level of MongoDB
  614. *
  615. * @method
  616. * @param {Db} db The Db instance on which to execute the command.
  617. * @param {string} level The new profiling level (off, slow_only, all).
  618. * @param {Object} [options] Optional settings. See Db.prototype.setProfilingLevel for a list of options.
  619. * @param {Db~resultCallback} [callback] The command result callback.
  620. */
  621. function setProfilingLevel(db, level, options, callback) {
  622. const command = {};
  623. let profile = 0;
  624. if (level === 'off') {
  625. profile = 0;
  626. } else if (level === 'slow_only') {
  627. profile = 1;
  628. } else if (level === 'all') {
  629. profile = 2;
  630. } else {
  631. return callback(new Error('Error: illegal profiling level value ' + level));
  632. }
  633. // Set up the profile number
  634. command['profile'] = profile;
  635. executeCommand(db, command, options, (err, doc) => {
  636. if (err == null && doc.ok === 1) return callback(null, level);
  637. return err != null
  638. ? callback(err, null)
  639. : callback(new Error('Error with profile command'), null);
  640. });
  641. }
  642. // Validate the database name
  643. function validateDatabaseName(databaseName) {
  644. if (typeof databaseName !== 'string')
  645. throw MongoError.create({ message: 'database name must be a string', driver: true });
  646. if (databaseName.length === 0)
  647. throw MongoError.create({ message: 'database name cannot be the empty string', driver: true });
  648. if (databaseName === '$external') return;
  649. const invalidChars = [' ', '.', '$', '/', '\\'];
  650. for (let i = 0; i < invalidChars.length; i++) {
  651. if (databaseName.indexOf(invalidChars[i]) !== -1)
  652. throw MongoError.create({
  653. message: "database names cannot contain the character '" + invalidChars[i] + "'",
  654. driver: true
  655. });
  656. }
  657. }
  658. /**
  659. * Create the command object for Db.prototype.createIndex.
  660. *
  661. * @param {Db} db The Db instance on which to create the command.
  662. * @param {string} name Name of the collection to create the index on.
  663. * @param {(string|object)} fieldOrSpec Defines the index.
  664. * @param {Object} [options] Optional settings. See Db.prototype.createIndex for a list of options.
  665. * @return {Object} The insert command object.
  666. */
  667. function createCreateIndexCommand(db, name, fieldOrSpec, options) {
  668. const indexParameters = parseIndexOptions(fieldOrSpec);
  669. const fieldHash = indexParameters.fieldHash;
  670. // Generate the index name
  671. const indexName = typeof options.name === 'string' ? options.name : indexParameters.name;
  672. const selector = {
  673. ns: db.databaseName + '.' + name,
  674. key: fieldHash,
  675. name: indexName
  676. };
  677. // Ensure we have a correct finalUnique
  678. const finalUnique = options == null || 'object' === typeof options ? false : options;
  679. // Set up options
  680. options = options == null || typeof options === 'boolean' ? {} : options;
  681. // Add all the options
  682. const keysToOmit = Object.keys(selector);
  683. for (let optionName in options) {
  684. if (keysToOmit.indexOf(optionName) === -1) {
  685. selector[optionName] = options[optionName];
  686. }
  687. }
  688. if (selector['unique'] == null) selector['unique'] = finalUnique;
  689. // Remove any write concern operations
  690. const removeKeys = ['w', 'wtimeout', 'j', 'fsync', 'readPreference', 'session'];
  691. for (let i = 0; i < removeKeys.length; i++) {
  692. delete selector[removeKeys[i]];
  693. }
  694. // Return the command creation selector
  695. return selector;
  696. }
  697. /**
  698. * Create index using the createIndexes command.
  699. *
  700. * @param {Db} db The Db instance on which to execute the command.
  701. * @param {string} name Name of the collection to create the index on.
  702. * @param {(string|object)} fieldOrSpec Defines the index.
  703. * @param {Object} [options] Optional settings. See Db.prototype.createIndex for a list of options.
  704. * @param {Db~resultCallback} [callback] The command result callback.
  705. */
  706. function createIndexUsingCreateIndexes(db, name, fieldOrSpec, options, callback) {
  707. // Build the index
  708. const indexParameters = parseIndexOptions(fieldOrSpec);
  709. // Generate the index name
  710. const indexName = typeof options.name === 'string' ? options.name : indexParameters.name;
  711. // Set up the index
  712. const indexes = [{ name: indexName, key: indexParameters.fieldHash }];
  713. // merge all the options
  714. const keysToOmit = Object.keys(indexes[0]).concat([
  715. 'writeConcern',
  716. 'w',
  717. 'wtimeout',
  718. 'j',
  719. 'fsync',
  720. 'readPreference',
  721. 'session'
  722. ]);
  723. for (let optionName in options) {
  724. if (keysToOmit.indexOf(optionName) === -1) {
  725. indexes[0][optionName] = options[optionName];
  726. }
  727. }
  728. // Get capabilities
  729. const capabilities = db.s.topology.capabilities();
  730. // Did the user pass in a collation, check if our write server supports it
  731. if (indexes[0].collation && capabilities && !capabilities.commandsTakeCollation) {
  732. // Create a new error
  733. const error = new MongoError('server/primary/mongos does not support collation');
  734. error.code = 67;
  735. // Return the error
  736. return callback(error);
  737. }
  738. // Create command, apply write concern to command
  739. const cmd = applyWriteConcern({ createIndexes: name, indexes }, { db }, options);
  740. // ReadPreference primary
  741. options.readPreference = ReadPreference.PRIMARY;
  742. // Build the command
  743. executeCommand(db, cmd, options, (err, result) => {
  744. if (err) return handleCallback(callback, err, null);
  745. if (result.ok === 0) return handleCallback(callback, toError(result), null);
  746. // Return the indexName for backward compatibility
  747. handleCallback(callback, null, indexName);
  748. });
  749. }
  750. /**
  751. * Run the createUser command.
  752. *
  753. * @param {Db} db The Db instance on which to execute the command.
  754. * @param {string} username The username of the user to add.
  755. * @param {string} password The password of the user to add.
  756. * @param {object} [options] Optional settings. See Db.prototype.addUser for a list of options.
  757. * @param {Db~resultCallback} [callback] The command result callback
  758. */
  759. function executeAuthCreateUserCommand(db, username, password, options, callback) {
  760. // Special case where there is no password ($external users)
  761. if (typeof username === 'string' && password != null && typeof password === 'object') {
  762. options = password;
  763. password = null;
  764. }
  765. // Unpack all options
  766. if (typeof options === 'function') {
  767. callback = options;
  768. options = {};
  769. }
  770. // Error out if we digestPassword set
  771. if (options.digestPassword != null) {
  772. return callback(
  773. toError(
  774. "The digestPassword option is not supported via add_user. Please use db.command('createUser', ...) instead for this option."
  775. )
  776. );
  777. }
  778. // Get additional values
  779. const customData = options.customData != null ? options.customData : {};
  780. let roles = Array.isArray(options.roles) ? options.roles : [];
  781. const maxTimeMS = typeof options.maxTimeMS === 'number' ? options.maxTimeMS : null;
  782. // If not roles defined print deprecated message
  783. if (roles.length === 0) {
  784. console.log('Creating a user without roles is deprecated in MongoDB >= 2.6');
  785. }
  786. // Get the error options
  787. const commandOptions = { writeCommand: true };
  788. if (options['dbName']) commandOptions.dbName = options['dbName'];
  789. // Add maxTimeMS to options if set
  790. if (maxTimeMS != null) commandOptions.maxTimeMS = maxTimeMS;
  791. // Check the db name and add roles if needed
  792. if (
  793. (db.databaseName.toLowerCase() === 'admin' || options.dbName === 'admin') &&
  794. !Array.isArray(options.roles)
  795. ) {
  796. roles = ['root'];
  797. } else if (!Array.isArray(options.roles)) {
  798. roles = ['dbOwner'];
  799. }
  800. const digestPassword = db.s.topology.lastIsMaster().maxWireVersion >= 7;
  801. // Build the command to execute
  802. let command = {
  803. createUser: username,
  804. customData: customData,
  805. roles: roles,
  806. digestPassword
  807. };
  808. // Apply write concern to command
  809. command = applyWriteConcern(command, { db }, options);
  810. let userPassword = password;
  811. if (!digestPassword) {
  812. // Use node md5 generator
  813. const md5 = crypto.createHash('md5');
  814. // Generate keys used for authentication
  815. md5.update(username + ':mongo:' + password);
  816. userPassword = md5.digest('hex');
  817. }
  818. // No password
  819. if (typeof password === 'string') {
  820. command.pwd = userPassword;
  821. }
  822. // Force write using primary
  823. commandOptions.readPreference = ReadPreference.primary;
  824. // Execute the command
  825. executeCommand(db, command, commandOptions, (err, result) => {
  826. if (err && err.ok === 0 && err.code === undefined)
  827. return handleCallback(callback, { code: -5000 }, null);
  828. if (err) return handleCallback(callback, err, null);
  829. handleCallback(
  830. callback,
  831. !result.ok ? toError(result) : null,
  832. result.ok ? [{ user: username, pwd: '' }] : null
  833. );
  834. });
  835. }
  836. /**
  837. * Run the dropUser command.
  838. *
  839. * @param {Db} db The Db instance on which to execute the command.
  840. * @param {string} username The username of the user to remove.
  841. * @param {object} [options] Optional settings. See Db.prototype.removeUser for a list of options.
  842. * @param {Db~resultCallback} [callback] The command result callback
  843. */
  844. function executeAuthRemoveUserCommand(db, username, options, callback) {
  845. if (typeof options === 'function') (callback = options), (options = {});
  846. options = options || {};
  847. // Did the user destroy the topology
  848. if (db.serverConfig && db.serverConfig.isDestroyed())
  849. return callback(new MongoError('topology was destroyed'));
  850. // Get the error options
  851. const commandOptions = { writeCommand: true };
  852. if (options['dbName']) commandOptions.dbName = options['dbName'];
  853. // Get additional values
  854. const maxTimeMS = typeof options.maxTimeMS === 'number' ? options.maxTimeMS : null;
  855. // Add maxTimeMS to options if set
  856. if (maxTimeMS != null) commandOptions.maxTimeMS = maxTimeMS;
  857. // Build the command to execute
  858. let command = {
  859. dropUser: username
  860. };
  861. // Apply write concern to command
  862. command = applyWriteConcern(command, { db }, options);
  863. // Force write using primary
  864. commandOptions.readPreference = ReadPreference.primary;
  865. // Execute the command
  866. executeCommand(db, command, commandOptions, (err, result) => {
  867. if (err && !err.ok && err.code === undefined) return handleCallback(callback, { code: -5000 });
  868. if (err) return handleCallback(callback, err, null);
  869. handleCallback(callback, null, result.ok ? true : false);
  870. });
  871. }
  872. module.exports = {
  873. addUser,
  874. collections,
  875. createCollection,
  876. createListener,
  877. createIndex,
  878. dropCollection,
  879. dropDatabase,
  880. ensureIndex,
  881. evaluate,
  882. executeCommand,
  883. executeDbAdminCommand,
  884. listCollectionsTransforms,
  885. indexInformation,
  886. profilingInfo,
  887. profilingLevel,
  888. removeUser,
  889. setProfilingLevel,
  890. validateDatabaseName
  891. };