Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

db_ops.js 33KB

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