123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399 |
- 'use strict';
- const applyRetryableWrites = require('../utils').applyRetryableWrites;
- const applyWriteConcern = require('../utils').applyWriteConcern;
- const decorateWithCollation = require('../utils').decorateWithCollation;
- const decorateWithReadConcern = require('../utils').decorateWithReadConcern;
- const executeCommand = require('./db_ops').executeCommand;
- const formattedOrderClause = require('../utils').formattedOrderClause;
- const handleCallback = require('../utils').handleCallback;
- const MongoError = require('../core').MongoError;
- const ReadPreference = require('../core').ReadPreference;
- const toError = require('../utils').toError;
- const CursorState = require('../core/cursor').CursorState;
- const maxWireVersion = require('../core/utils').maxWireVersion;
- /**
- * Build the count command.
- *
- * @method
- * @param {collectionOrCursor} an instance of a collection or cursor
- * @param {object} query The query for the count.
- * @param {object} [options] Optional settings. See Collection.prototype.count and Cursor.prototype.count for a list of options.
- */
- function buildCountCommand(collectionOrCursor, query, options) {
- const skip = options.skip;
- const limit = options.limit;
- let hint = options.hint;
- const maxTimeMS = options.maxTimeMS;
- query = query || {};
- // Final query
- const cmd = {
- count: options.collectionName,
- query: query
- };
- if (collectionOrCursor.s.numberOfRetries) {
- // collectionOrCursor is a cursor
- if (collectionOrCursor.options.hint) {
- hint = collectionOrCursor.options.hint;
- } else if (collectionOrCursor.cmd.hint) {
- hint = collectionOrCursor.cmd.hint;
- }
- decorateWithCollation(cmd, collectionOrCursor, collectionOrCursor.cmd);
- } else {
- decorateWithCollation(cmd, collectionOrCursor, options);
- }
- // Add limit, skip and maxTimeMS if defined
- if (typeof skip === 'number') cmd.skip = skip;
- if (typeof limit === 'number') cmd.limit = limit;
- if (typeof maxTimeMS === 'number') cmd.maxTimeMS = maxTimeMS;
- if (hint) cmd.hint = hint;
- // Do we have a readConcern specified
- decorateWithReadConcern(cmd, collectionOrCursor);
- return cmd;
- }
- /**
- * Find and update a document.
- *
- * @method
- * @param {Collection} a Collection instance.
- * @param {object} query Query object to locate the object to modify.
- * @param {array} sort If multiple docs match, choose the first one in the specified sort order as the object to manipulate.
- * @param {object} doc The fields/vals to be updated.
- * @param {object} [options] Optional settings. See Collection.prototype.findAndModify for a list of options.
- * @param {Collection~findAndModifyCallback} [callback] The command result callback
- * @deprecated use findOneAndUpdate, findOneAndReplace or findOneAndDelete instead
- */
- function findAndModify(coll, query, sort, doc, options, callback) {
- // Create findAndModify command object
- const queryObject = {
- findAndModify: coll.collectionName,
- query: query
- };
- sort = formattedOrderClause(sort);
- if (sort) {
- queryObject.sort = sort;
- }
- queryObject.new = options.new ? true : false;
- queryObject.remove = options.remove ? true : false;
- queryObject.upsert = options.upsert ? true : false;
- const projection = options.projection || options.fields;
- if (projection) {
- queryObject.fields = projection;
- }
- if (options.arrayFilters) {
- queryObject.arrayFilters = options.arrayFilters;
- delete options.arrayFilters;
- }
- if (doc && !options.remove) {
- queryObject.update = doc;
- }
- if (options.maxTimeMS) queryObject.maxTimeMS = options.maxTimeMS;
- // Either use override on the function, or go back to default on either the collection
- // level or db
- options.serializeFunctions = options.serializeFunctions || coll.s.serializeFunctions;
- // No check on the documents
- options.checkKeys = false;
- // Final options for retryable writes and write concern
- let finalOptions = Object.assign({}, options);
- finalOptions = applyRetryableWrites(finalOptions, coll.s.db);
- finalOptions = applyWriteConcern(finalOptions, { db: coll.s.db, collection: coll }, options);
- // Decorate the findAndModify command with the write Concern
- if (finalOptions.writeConcern) {
- queryObject.writeConcern = finalOptions.writeConcern;
- }
- // Have we specified bypassDocumentValidation
- if (finalOptions.bypassDocumentValidation === true) {
- queryObject.bypassDocumentValidation = finalOptions.bypassDocumentValidation;
- }
- finalOptions.readPreference = ReadPreference.primary;
- // Have we specified collation
- try {
- decorateWithCollation(queryObject, coll, finalOptions);
- } catch (err) {
- return callback(err, null);
- }
- // Execute the command
- executeCommand(coll.s.db, queryObject, finalOptions, (err, result) => {
- if (err) return handleCallback(callback, err, null);
- return handleCallback(callback, null, result);
- });
- }
- /**
- * Retrieves this collections index info.
- *
- * @method
- * @param {Db} db The Db instance on which to retrieve the index info.
- * @param {string} name The name of the collection.
- * @param {object} [options] Optional settings. See Db.prototype.indexInformation for a list of options.
- * @param {Db~resultCallback} [callback] The command result callback
- */
- function indexInformation(db, name, options, callback) {
- // If we specified full information
- const full = options['full'] == null ? false : options['full'];
- // Did the user destroy the topology
- if (db.serverConfig && db.serverConfig.isDestroyed())
- return callback(new MongoError('topology was destroyed'));
- // Process all the results from the index command and collection
- function processResults(indexes) {
- // Contains all the information
- let info = {};
- // Process all the indexes
- for (let i = 0; i < indexes.length; i++) {
- const index = indexes[i];
- // Let's unpack the object
- info[index.name] = [];
- for (let name in index.key) {
- info[index.name].push([name, index.key[name]]);
- }
- }
- return info;
- }
- // Get the list of indexes of the specified collection
- db.collection(name)
- .listIndexes(options)
- .toArray((err, indexes) => {
- if (err) return callback(toError(err));
- if (!Array.isArray(indexes)) return handleCallback(callback, null, []);
- if (full) return handleCallback(callback, null, indexes);
- handleCallback(callback, null, processResults(indexes));
- });
- }
- function prepareDocs(coll, docs, options) {
- const forceServerObjectId =
- typeof options.forceServerObjectId === 'boolean'
- ? options.forceServerObjectId
- : coll.s.db.options.forceServerObjectId;
- // no need to modify the docs if server sets the ObjectId
- if (forceServerObjectId === true) {
- return docs;
- }
- return docs.map(doc => {
- if (forceServerObjectId !== true && doc._id == null) {
- doc._id = coll.s.pkFactory.createPk();
- }
- return doc;
- });
- }
- // Get the next available document from the cursor, returns null if no more documents are available.
- function nextObject(cursor, callback) {
- if (cursor.s.state === CursorState.CLOSED || (cursor.isDead && cursor.isDead())) {
- return handleCallback(
- callback,
- MongoError.create({ message: 'Cursor is closed', driver: true })
- );
- }
- if (cursor.s.state === CursorState.INIT && cursor.cmd && cursor.cmd.sort) {
- try {
- cursor.cmd.sort = formattedOrderClause(cursor.cmd.sort);
- } catch (err) {
- return handleCallback(callback, err);
- }
- }
- // Get the next object
- cursor._next((err, doc) => {
- cursor.s.state = CursorState.OPEN;
- if (err) return handleCallback(callback, err);
- handleCallback(callback, null, doc);
- });
- }
- function insertDocuments(coll, docs, options, callback) {
- if (typeof options === 'function') (callback = options), (options = {});
- options = options || {};
- // Ensure we are operating on an array op docs
- docs = Array.isArray(docs) ? docs : [docs];
- // Final options for retryable writes and write concern
- let finalOptions = Object.assign({}, options);
- finalOptions = applyRetryableWrites(finalOptions, coll.s.db);
- finalOptions = applyWriteConcern(finalOptions, { db: coll.s.db, collection: coll }, options);
- // If keep going set unordered
- if (finalOptions.keepGoing === true) finalOptions.ordered = false;
- finalOptions.serializeFunctions = options.serializeFunctions || coll.s.serializeFunctions;
- docs = prepareDocs(coll, docs, options);
- // File inserts
- coll.s.topology.insert(coll.s.namespace, docs, finalOptions, (err, result) => {
- if (callback == null) return;
- if (err) return handleCallback(callback, err);
- if (result == null) return handleCallback(callback, null, null);
- if (result.result.code) return handleCallback(callback, toError(result.result));
- if (result.result.writeErrors)
- return handleCallback(callback, toError(result.result.writeErrors[0]));
- // Add docs to the list
- result.ops = docs;
- // Return the results
- handleCallback(callback, null, result);
- });
- }
- function removeDocuments(coll, selector, options, callback) {
- if (typeof options === 'function') {
- (callback = options), (options = {});
- } else if (typeof selector === 'function') {
- callback = selector;
- options = {};
- selector = {};
- }
- // Create an empty options object if the provided one is null
- options = options || {};
- // Final options for retryable writes and write concern
- let finalOptions = Object.assign({}, options);
- finalOptions = applyRetryableWrites(finalOptions, coll.s.db);
- finalOptions = applyWriteConcern(finalOptions, { db: coll.s.db, collection: coll }, options);
- // If selector is null set empty
- if (selector == null) selector = {};
- // Build the op
- const op = { q: selector, limit: 0 };
- if (options.single) {
- op.limit = 1;
- } else if (finalOptions.retryWrites) {
- finalOptions.retryWrites = false;
- }
- if (options.hint) {
- op.hint = options.hint;
- }
- // Have we specified collation
- try {
- decorateWithCollation(finalOptions, coll, options);
- } catch (err) {
- return callback(err, null);
- }
- if (options.explain !== undefined && maxWireVersion(coll.s.topology) < 3) {
- return callback
- ? callback(new MongoError(`server does not support explain on remove`))
- : undefined;
- }
- // Execute the remove
- coll.s.topology.remove(coll.s.namespace, [op], finalOptions, (err, result) => {
- if (callback == null) return;
- if (err) return handleCallback(callback, err, null);
- if (result == null) return handleCallback(callback, null, null);
- if (result.result.code) return handleCallback(callback, toError(result.result));
- if (result.result.writeErrors) {
- return handleCallback(callback, toError(result.result.writeErrors[0]));
- }
- // Return the results
- handleCallback(callback, null, result);
- });
- }
- function updateDocuments(coll, selector, document, options, callback) {
- if ('function' === typeof options) (callback = options), (options = null);
- if (options == null) options = {};
- if (!('function' === typeof callback)) callback = null;
- // If we are not providing a selector or document throw
- if (selector == null || typeof selector !== 'object')
- return callback(toError('selector must be a valid JavaScript object'));
- if (document == null || typeof document !== 'object')
- return callback(toError('document must be a valid JavaScript object'));
- // Final options for retryable writes and write concern
- let finalOptions = Object.assign({}, options);
- finalOptions = applyRetryableWrites(finalOptions, coll.s.db);
- finalOptions = applyWriteConcern(finalOptions, { db: coll.s.db, collection: coll }, options);
- // Do we return the actual result document
- // Either use override on the function, or go back to default on either the collection
- // level or db
- finalOptions.serializeFunctions = options.serializeFunctions || coll.s.serializeFunctions;
- // Execute the operation
- const op = { q: selector, u: document };
- op.upsert = options.upsert !== void 0 ? !!options.upsert : false;
- op.multi = options.multi !== void 0 ? !!options.multi : false;
- if (options.hint) {
- op.hint = options.hint;
- }
- if (finalOptions.arrayFilters) {
- op.arrayFilters = finalOptions.arrayFilters;
- delete finalOptions.arrayFilters;
- }
- if (finalOptions.retryWrites && op.multi) {
- finalOptions.retryWrites = false;
- }
- // Have we specified collation
- try {
- decorateWithCollation(finalOptions, coll, options);
- } catch (err) {
- return callback(err, null);
- }
- if (options.explain !== undefined && maxWireVersion(coll.s.topology) < 3) {
- return callback
- ? callback(new MongoError(`server does not support explain on update`))
- : undefined;
- }
- // Update options
- coll.s.topology.update(coll.s.namespace, [op], finalOptions, (err, result) => {
- if (callback == null) return;
- if (err) return handleCallback(callback, err, null);
- if (result == null) return handleCallback(callback, null, null);
- if (result.result.code) return handleCallback(callback, toError(result.result));
- if (result.result.writeErrors)
- return handleCallback(callback, toError(result.result.writeErrors[0]));
- // Return the results
- handleCallback(callback, null, result);
- });
- }
- module.exports = {
- buildCountCommand,
- findAndModify,
- indexInformation,
- nextObject,
- prepareDocs,
- insertDocuments,
- removeDocuments,
- updateDocuments
- };
|