common_functions.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. 'use strict';
  2. const applyRetryableWrites = require('../utils').applyRetryableWrites;
  3. const applyWriteConcern = require('../utils').applyWriteConcern;
  4. const decorateWithCollation = require('../utils').decorateWithCollation;
  5. const decorateWithReadConcern = require('../utils').decorateWithReadConcern;
  6. const executeCommand = require('./db_ops').executeCommand;
  7. const formattedOrderClause = require('../utils').formattedOrderClause;
  8. const handleCallback = require('../utils').handleCallback;
  9. const MongoError = require('../core').MongoError;
  10. const ReadPreference = require('../core').ReadPreference;
  11. const toError = require('../utils').toError;
  12. const CursorState = require('../core/cursor').CursorState;
  13. const maxWireVersion = require('../core/utils').maxWireVersion;
  14. /**
  15. * Build the count command.
  16. *
  17. * @method
  18. * @param {collectionOrCursor} an instance of a collection or cursor
  19. * @param {object} query The query for the count.
  20. * @param {object} [options] Optional settings. See Collection.prototype.count and Cursor.prototype.count for a list of options.
  21. */
  22. function buildCountCommand(collectionOrCursor, query, options) {
  23. const skip = options.skip;
  24. const limit = options.limit;
  25. let hint = options.hint;
  26. const maxTimeMS = options.maxTimeMS;
  27. query = query || {};
  28. // Final query
  29. const cmd = {
  30. count: options.collectionName,
  31. query: query
  32. };
  33. if (collectionOrCursor.s.numberOfRetries) {
  34. // collectionOrCursor is a cursor
  35. if (collectionOrCursor.options.hint) {
  36. hint = collectionOrCursor.options.hint;
  37. } else if (collectionOrCursor.cmd.hint) {
  38. hint = collectionOrCursor.cmd.hint;
  39. }
  40. decorateWithCollation(cmd, collectionOrCursor, collectionOrCursor.cmd);
  41. } else {
  42. decorateWithCollation(cmd, collectionOrCursor, options);
  43. }
  44. // Add limit, skip and maxTimeMS if defined
  45. if (typeof skip === 'number') cmd.skip = skip;
  46. if (typeof limit === 'number') cmd.limit = limit;
  47. if (typeof maxTimeMS === 'number') cmd.maxTimeMS = maxTimeMS;
  48. if (hint) cmd.hint = hint;
  49. // Do we have a readConcern specified
  50. decorateWithReadConcern(cmd, collectionOrCursor);
  51. return cmd;
  52. }
  53. /**
  54. * Find and update a document.
  55. *
  56. * @method
  57. * @param {Collection} a Collection instance.
  58. * @param {object} query Query object to locate the object to modify.
  59. * @param {array} sort If multiple docs match, choose the first one in the specified sort order as the object to manipulate.
  60. * @param {object} doc The fields/vals to be updated.
  61. * @param {object} [options] Optional settings. See Collection.prototype.findAndModify for a list of options.
  62. * @param {Collection~findAndModifyCallback} [callback] The command result callback
  63. * @deprecated use findOneAndUpdate, findOneAndReplace or findOneAndDelete instead
  64. */
  65. function findAndModify(coll, query, sort, doc, options, callback) {
  66. // Create findAndModify command object
  67. const queryObject = {
  68. findAndModify: coll.collectionName,
  69. query: query
  70. };
  71. sort = formattedOrderClause(sort);
  72. if (sort) {
  73. queryObject.sort = sort;
  74. }
  75. queryObject.new = options.new ? true : false;
  76. queryObject.remove = options.remove ? true : false;
  77. queryObject.upsert = options.upsert ? true : false;
  78. const projection = options.projection || options.fields;
  79. if (projection) {
  80. queryObject.fields = projection;
  81. }
  82. if (options.arrayFilters) {
  83. queryObject.arrayFilters = options.arrayFilters;
  84. delete options.arrayFilters;
  85. }
  86. if (doc && !options.remove) {
  87. queryObject.update = doc;
  88. }
  89. if (options.maxTimeMS) queryObject.maxTimeMS = options.maxTimeMS;
  90. // Either use override on the function, or go back to default on either the collection
  91. // level or db
  92. options.serializeFunctions = options.serializeFunctions || coll.s.serializeFunctions;
  93. // No check on the documents
  94. options.checkKeys = false;
  95. // Final options for retryable writes and write concern
  96. let finalOptions = Object.assign({}, options);
  97. finalOptions = applyRetryableWrites(finalOptions, coll.s.db);
  98. finalOptions = applyWriteConcern(finalOptions, { db: coll.s.db, collection: coll }, options);
  99. // Decorate the findAndModify command with the write Concern
  100. if (finalOptions.writeConcern) {
  101. queryObject.writeConcern = finalOptions.writeConcern;
  102. }
  103. // Have we specified bypassDocumentValidation
  104. if (finalOptions.bypassDocumentValidation === true) {
  105. queryObject.bypassDocumentValidation = finalOptions.bypassDocumentValidation;
  106. }
  107. finalOptions.readPreference = ReadPreference.primary;
  108. // Have we specified collation
  109. try {
  110. decorateWithCollation(queryObject, coll, finalOptions);
  111. } catch (err) {
  112. return callback(err, null);
  113. }
  114. // Execute the command
  115. executeCommand(coll.s.db, queryObject, finalOptions, (err, result) => {
  116. if (err) return handleCallback(callback, err, null);
  117. return handleCallback(callback, null, result);
  118. });
  119. }
  120. /**
  121. * Retrieves this collections index info.
  122. *
  123. * @method
  124. * @param {Db} db The Db instance on which to retrieve the index info.
  125. * @param {string} name The name of the collection.
  126. * @param {object} [options] Optional settings. See Db.prototype.indexInformation for a list of options.
  127. * @param {Db~resultCallback} [callback] The command result callback
  128. */
  129. function indexInformation(db, name, options, callback) {
  130. // If we specified full information
  131. const full = options['full'] == null ? false : options['full'];
  132. // Did the user destroy the topology
  133. if (db.serverConfig && db.serverConfig.isDestroyed())
  134. return callback(new MongoError('topology was destroyed'));
  135. // Process all the results from the index command and collection
  136. function processResults(indexes) {
  137. // Contains all the information
  138. let info = {};
  139. // Process all the indexes
  140. for (let i = 0; i < indexes.length; i++) {
  141. const index = indexes[i];
  142. // Let's unpack the object
  143. info[index.name] = [];
  144. for (let name in index.key) {
  145. info[index.name].push([name, index.key[name]]);
  146. }
  147. }
  148. return info;
  149. }
  150. // Get the list of indexes of the specified collection
  151. db.collection(name)
  152. .listIndexes(options)
  153. .toArray((err, indexes) => {
  154. if (err) return callback(toError(err));
  155. if (!Array.isArray(indexes)) return handleCallback(callback, null, []);
  156. if (full) return handleCallback(callback, null, indexes);
  157. handleCallback(callback, null, processResults(indexes));
  158. });
  159. }
  160. function prepareDocs(coll, docs, options) {
  161. const forceServerObjectId =
  162. typeof options.forceServerObjectId === 'boolean'
  163. ? options.forceServerObjectId
  164. : coll.s.db.options.forceServerObjectId;
  165. // no need to modify the docs if server sets the ObjectId
  166. if (forceServerObjectId === true) {
  167. return docs;
  168. }
  169. return docs.map(doc => {
  170. if (forceServerObjectId !== true && doc._id == null) {
  171. doc._id = coll.s.pkFactory.createPk();
  172. }
  173. return doc;
  174. });
  175. }
  176. // Get the next available document from the cursor, returns null if no more documents are available.
  177. function nextObject(cursor, callback) {
  178. if (cursor.s.state === CursorState.CLOSED || (cursor.isDead && cursor.isDead())) {
  179. return handleCallback(
  180. callback,
  181. MongoError.create({ message: 'Cursor is closed', driver: true })
  182. );
  183. }
  184. if (cursor.s.state === CursorState.INIT && cursor.cmd && cursor.cmd.sort) {
  185. try {
  186. cursor.cmd.sort = formattedOrderClause(cursor.cmd.sort);
  187. } catch (err) {
  188. return handleCallback(callback, err);
  189. }
  190. }
  191. // Get the next object
  192. cursor._next((err, doc) => {
  193. cursor.s.state = CursorState.OPEN;
  194. if (err) return handleCallback(callback, err);
  195. handleCallback(callback, null, doc);
  196. });
  197. }
  198. function insertDocuments(coll, docs, options, callback) {
  199. if (typeof options === 'function') (callback = options), (options = {});
  200. options = options || {};
  201. // Ensure we are operating on an array op docs
  202. docs = Array.isArray(docs) ? docs : [docs];
  203. // Final options for retryable writes and write concern
  204. let finalOptions = Object.assign({}, options);
  205. finalOptions = applyRetryableWrites(finalOptions, coll.s.db);
  206. finalOptions = applyWriteConcern(finalOptions, { db: coll.s.db, collection: coll }, options);
  207. // If keep going set unordered
  208. if (finalOptions.keepGoing === true) finalOptions.ordered = false;
  209. finalOptions.serializeFunctions = options.serializeFunctions || coll.s.serializeFunctions;
  210. docs = prepareDocs(coll, docs, options);
  211. // File inserts
  212. coll.s.topology.insert(coll.s.namespace, docs, finalOptions, (err, result) => {
  213. if (callback == null) return;
  214. if (err) return handleCallback(callback, err);
  215. if (result == null) return handleCallback(callback, null, null);
  216. if (result.result.code) return handleCallback(callback, toError(result.result));
  217. if (result.result.writeErrors)
  218. return handleCallback(callback, toError(result.result.writeErrors[0]));
  219. // Add docs to the list
  220. result.ops = docs;
  221. // Return the results
  222. handleCallback(callback, null, result);
  223. });
  224. }
  225. function removeDocuments(coll, selector, options, callback) {
  226. if (typeof options === 'function') {
  227. (callback = options), (options = {});
  228. } else if (typeof selector === 'function') {
  229. callback = selector;
  230. options = {};
  231. selector = {};
  232. }
  233. // Create an empty options object if the provided one is null
  234. options = options || {};
  235. // Final options for retryable writes and write concern
  236. let finalOptions = Object.assign({}, options);
  237. finalOptions = applyRetryableWrites(finalOptions, coll.s.db);
  238. finalOptions = applyWriteConcern(finalOptions, { db: coll.s.db, collection: coll }, options);
  239. // If selector is null set empty
  240. if (selector == null) selector = {};
  241. // Build the op
  242. const op = { q: selector, limit: 0 };
  243. if (options.single) {
  244. op.limit = 1;
  245. } else if (finalOptions.retryWrites) {
  246. finalOptions.retryWrites = false;
  247. }
  248. if (options.hint) {
  249. op.hint = options.hint;
  250. }
  251. // Have we specified collation
  252. try {
  253. decorateWithCollation(finalOptions, coll, options);
  254. } catch (err) {
  255. return callback(err, null);
  256. }
  257. if (options.explain !== undefined && maxWireVersion(coll.s.topology) < 3) {
  258. return callback
  259. ? callback(new MongoError(`server does not support explain on remove`))
  260. : undefined;
  261. }
  262. // Execute the remove
  263. coll.s.topology.remove(coll.s.namespace, [op], finalOptions, (err, result) => {
  264. if (callback == null) return;
  265. if (err) return handleCallback(callback, err, null);
  266. if (result == null) return handleCallback(callback, null, null);
  267. if (result.result.code) return handleCallback(callback, toError(result.result));
  268. if (result.result.writeErrors) {
  269. return handleCallback(callback, toError(result.result.writeErrors[0]));
  270. }
  271. // Return the results
  272. handleCallback(callback, null, result);
  273. });
  274. }
  275. function updateDocuments(coll, selector, document, options, callback) {
  276. if ('function' === typeof options) (callback = options), (options = null);
  277. if (options == null) options = {};
  278. if (!('function' === typeof callback)) callback = null;
  279. // If we are not providing a selector or document throw
  280. if (selector == null || typeof selector !== 'object')
  281. return callback(toError('selector must be a valid JavaScript object'));
  282. if (document == null || typeof document !== 'object')
  283. return callback(toError('document must be a valid JavaScript object'));
  284. // Final options for retryable writes and write concern
  285. let finalOptions = Object.assign({}, options);
  286. finalOptions = applyRetryableWrites(finalOptions, coll.s.db);
  287. finalOptions = applyWriteConcern(finalOptions, { db: coll.s.db, collection: coll }, options);
  288. // Do we return the actual result document
  289. // Either use override on the function, or go back to default on either the collection
  290. // level or db
  291. finalOptions.serializeFunctions = options.serializeFunctions || coll.s.serializeFunctions;
  292. // Execute the operation
  293. const op = { q: selector, u: document };
  294. op.upsert = options.upsert !== void 0 ? !!options.upsert : false;
  295. op.multi = options.multi !== void 0 ? !!options.multi : false;
  296. if (options.hint) {
  297. op.hint = options.hint;
  298. }
  299. if (finalOptions.arrayFilters) {
  300. op.arrayFilters = finalOptions.arrayFilters;
  301. delete finalOptions.arrayFilters;
  302. }
  303. if (finalOptions.retryWrites && op.multi) {
  304. finalOptions.retryWrites = false;
  305. }
  306. // Have we specified collation
  307. try {
  308. decorateWithCollation(finalOptions, coll, options);
  309. } catch (err) {
  310. return callback(err, null);
  311. }
  312. if (options.explain !== undefined && maxWireVersion(coll.s.topology) < 3) {
  313. return callback
  314. ? callback(new MongoError(`server does not support explain on update`))
  315. : undefined;
  316. }
  317. // Update options
  318. coll.s.topology.update(coll.s.namespace, [op], finalOptions, (err, result) => {
  319. if (callback == null) return;
  320. if (err) return handleCallback(callback, err, null);
  321. if (result == null) return handleCallback(callback, null, null);
  322. if (result.result.code) return handleCallback(callback, toError(result.result));
  323. if (result.result.writeErrors)
  324. return handleCallback(callback, toError(result.result.writeErrors[0]));
  325. // Return the results
  326. handleCallback(callback, null, result);
  327. });
  328. }
  329. module.exports = {
  330. buildCountCommand,
  331. findAndModify,
  332. indexInformation,
  333. nextObject,
  334. prepareDocs,
  335. insertDocuments,
  336. removeDocuments,
  337. updateDocuments
  338. };