123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209 |
- 'use strict';
- const applyWriteConcern = require('../utils').applyWriteConcern;
- const Code = require('../core').BSON.Code;
- const decorateWithCollation = require('../utils').decorateWithCollation;
- const decorateWithReadConcern = require('../utils').decorateWithReadConcern;
- const executeCommand = require('./db_ops').executeCommand;
- const handleCallback = require('../utils').handleCallback;
- const isObject = require('../utils').isObject;
- const loadDb = require('../dynamic_loaders').loadDb;
- const OperationBase = require('./operation').OperationBase;
- const ReadPreference = require('../core').ReadPreference;
- const toError = require('../utils').toError;
- const Aspect = require('./operation').Aspect;
- const defineAspects = require('./operation').defineAspects;
- const decorateWithExplain = require('../utils').decorateWithExplain;
- const maxWireVersion = require('../core/utils').maxWireVersion;
- const MongoError = require('../error').MongoError;
- const exclusionList = [
- 'explain',
- 'readPreference',
- 'session',
- 'bypassDocumentValidation',
- 'w',
- 'wtimeout',
- 'j',
- 'writeConcern'
- ];
- /**
- * Run Map Reduce across a collection. Be aware that the inline option for out will return an array of results not a collection.
- *
- * @class
- * @property {Collection} a Collection instance.
- * @property {(function|string)} map The mapping function.
- * @property {(function|string)} reduce The reduce function.
- * @property {object} [options] Optional settings. See Collection.prototype.mapReduce for a list of options.
- */
- class MapReduceOperation extends OperationBase {
- /**
- * Constructs a MapReduce operation.
- *
- * @param {Collection} a Collection instance.
- * @param {(function|string)} map The mapping function.
- * @param {(function|string)} reduce The reduce function.
- * @param {object} [options] Optional settings. See Collection.prototype.mapReduce for a list of options.
- */
- constructor(collection, map, reduce, options) {
- super(options);
- this.collection = collection;
- this.map = map;
- this.reduce = reduce;
- }
- /**
- * Execute the operation.
- *
- * @param {Collection~resultCallback} [callback] The command result callback
- */
- execute(callback) {
- const coll = this.collection;
- const map = this.map;
- const reduce = this.reduce;
- let options = this.options;
- let mapCommandHash = {
- mapReduce: coll.collectionName,
- map: map,
- reduce: reduce
- };
- // Add any other options passed in
- for (let n in options) {
- if ('scope' === n) {
- mapCommandHash[n] = processScope(options[n]);
- } else {
- // Only include if not in exclusion list
- if (exclusionList.indexOf(n) === -1) {
- mapCommandHash[n] = options[n];
- }
- }
- }
- options = Object.assign({}, options);
- // Ensure we have the right read preference inheritance
- options.readPreference = ReadPreference.resolve(coll, options);
- // If we have a read preference and inline is not set as output fail hard
- if (
- options.readPreference !== false &&
- options.readPreference !== 'primary' &&
- options['out'] &&
- options['out'].inline !== 1 &&
- options['out'] !== 'inline'
- ) {
- // Force readPreference to primary
- options.readPreference = 'primary';
- // Decorate command with writeConcern if supported
- applyWriteConcern(mapCommandHash, { db: coll.s.db, collection: coll }, options);
- } else {
- decorateWithReadConcern(mapCommandHash, coll, options);
- }
- // Is bypassDocumentValidation specified
- if (options.bypassDocumentValidation === true) {
- mapCommandHash.bypassDocumentValidation = options.bypassDocumentValidation;
- }
- // Have we specified collation
- try {
- decorateWithCollation(mapCommandHash, coll, options);
- } catch (err) {
- return callback(err, null);
- }
- if (this.explain) {
- if (maxWireVersion(coll.s.topology) < 9) {
- callback(new MongoError(`server does not support explain on mapReduce`));
- return;
- }
- mapCommandHash = decorateWithExplain(mapCommandHash, this.explain);
- }
- // Execute command
- executeCommand(coll.s.db, mapCommandHash, options, (err, result) => {
- if (err) return handleCallback(callback, err);
- // Check if we have an error
- if (1 !== result.ok || result.err || result.errmsg) {
- return handleCallback(callback, toError(result));
- }
- // If an explain operation was executed, don't process the server results
- if (this.explain) return callback(undefined, result);
- // Create statistics value
- const stats = {};
- if (result.timeMillis) stats['processtime'] = result.timeMillis;
- if (result.counts) stats['counts'] = result.counts;
- if (result.timing) stats['timing'] = result.timing;
- // invoked with inline?
- if (result.results) {
- // If we wish for no verbosity
- if (options['verbose'] == null || !options['verbose']) {
- return handleCallback(callback, null, result.results);
- }
- return handleCallback(callback, null, { results: result.results, stats: stats });
- }
- // The returned collection
- let collection = null;
- // If we have an object it's a different db
- if (result.result != null && typeof result.result === 'object') {
- const doc = result.result;
- // Return a collection from another db
- let Db = loadDb();
- collection = new Db(doc.db, coll.s.db.s.topology, coll.s.db.s.options).collection(
- doc.collection
- );
- } else {
- // Create a collection object that wraps the result collection
- collection = coll.s.db.collection(result.result);
- }
- // If we wish for no verbosity
- if (options['verbose'] == null || !options['verbose']) {
- return handleCallback(callback, err, collection);
- }
- // Return stats as third set of values
- handleCallback(callback, err, { collection: collection, stats: stats });
- });
- }
- }
- /**
- * Functions that are passed as scope args must
- * be converted to Code instances.
- * @ignore
- */
- function processScope(scope) {
- if (!isObject(scope) || scope._bsontype === 'ObjectID') {
- return scope;
- }
- const keys = Object.keys(scope);
- let key;
- const new_scope = {};
- for (let i = keys.length - 1; i >= 0; i--) {
- key = keys[i];
- if ('function' === typeof scope[key]) {
- new_scope[key] = new Code(String(scope[key]));
- } else {
- new_scope[key] = processScope(scope[key]);
- }
- }
- return new_scope;
- }
- defineAspects(MapReduceOperation, [Aspect.EXPLAINABLE]);
- module.exports = MapReduceOperation;
|