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.
		
		
		
		
		
			
		
			
				
					541 lines
				
				19 KiB
			
		
		
			
		
	
	
					541 lines
				
				19 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								'use strict';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const MongooseError = require('../../error/index');
							 | 
						||
| 
								 | 
							
								const SkipPopulateValue = require('./SkipPopulateValue');
							 | 
						||
| 
								 | 
							
								const get = require('../get');
							 | 
						||
| 
								 | 
							
								const getDiscriminatorByValue = require('../discriminator/getDiscriminatorByValue');
							 | 
						||
| 
								 | 
							
								const isPathExcluded = require('../projection/isPathExcluded');
							 | 
						||
| 
								 | 
							
								const getConstructorName = require('../getConstructorName');
							 | 
						||
| 
								 | 
							
								const getSchemaTypes = require('./getSchemaTypes');
							 | 
						||
| 
								 | 
							
								const getVirtual = require('./getVirtual');
							 | 
						||
| 
								 | 
							
								const lookupLocalFields = require('./lookupLocalFields');
							 | 
						||
| 
								 | 
							
								const mpath = require('mpath');
							 | 
						||
| 
								 | 
							
								const normalizeRefPath = require('./normalizeRefPath');
							 | 
						||
| 
								 | 
							
								const util = require('util');
							 | 
						||
| 
								 | 
							
								const utils = require('../../utils');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const modelSymbol = require('../symbols').modelSymbol;
							 | 
						||
| 
								 | 
							
								const populateModelSymbol = require('../symbols').populateModelSymbol;
							 | 
						||
| 
								 | 
							
								const schemaMixedSymbol = require('../../schema/symbols').schemaMixedSymbol;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								module.exports = function getModelsMapForPopulate(model, docs, options) {
							 | 
						||
| 
								 | 
							
								  let i;
							 | 
						||
| 
								 | 
							
								  let doc;
							 | 
						||
| 
								 | 
							
								  const len = docs.length;
							 | 
						||
| 
								 | 
							
								  const available = {};
							 | 
						||
| 
								 | 
							
								  const map = [];
							 | 
						||
| 
								 | 
							
								  const modelNameFromQuery = options.model && options.model.modelName || options.model;
							 | 
						||
| 
								 | 
							
								  let schema;
							 | 
						||
| 
								 | 
							
								  let refPath;
							 | 
						||
| 
								 | 
							
								  let Model;
							 | 
						||
| 
								 | 
							
								  let currentOptions;
							 | 
						||
| 
								 | 
							
								  let modelNames;
							 | 
						||
| 
								 | 
							
								  let modelName;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const originalModel = options.model;
							 | 
						||
| 
								 | 
							
								  let isVirtual = false;
							 | 
						||
| 
								 | 
							
								  const modelSchema = model.schema;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  let allSchemaTypes = getSchemaTypes(modelSchema, null, options.path);
							 | 
						||
| 
								 | 
							
								  allSchemaTypes = Array.isArray(allSchemaTypes) ? allSchemaTypes : [allSchemaTypes].filter(v => v != null);
							 | 
						||
| 
								 | 
							
								  const _firstWithRefPath = allSchemaTypes.find(schematype => get(schematype, 'options.refPath', null) != null);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  for (i = 0; i < len; i++) {
							 | 
						||
| 
								 | 
							
								    doc = docs[i];
							 | 
						||
| 
								 | 
							
								    let justOne = null;
							 | 
						||
| 
								 | 
							
								    schema = getSchemaTypes(modelSchema, doc, options.path);
							 | 
						||
| 
								 | 
							
								    // Special case: populating a path that's a DocumentArray unless
							 | 
						||
| 
								 | 
							
								    // there's an explicit `ref` or `refPath` re: gh-8946
							 | 
						||
| 
								 | 
							
								    if (schema != null &&
							 | 
						||
| 
								 | 
							
								        schema.$isMongooseDocumentArray &&
							 | 
						||
| 
								 | 
							
								        schema.options.ref == null &&
							 | 
						||
| 
								 | 
							
								        schema.options.refPath == null) {
							 | 
						||
| 
								 | 
							
								      continue;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    // Populating a nested path should always be a no-op re: #9073.
							 | 
						||
| 
								 | 
							
								    // People shouldn't do this, but apparently they do.
							 | 
						||
| 
								 | 
							
								    if (options._localModel != null && options._localModel.schema.nested[options.path]) {
							 | 
						||
| 
								 | 
							
								      continue;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const isUnderneathDocArray = schema && schema.$isUnderneathDocArray;
							 | 
						||
| 
								 | 
							
								    if (isUnderneathDocArray && get(options, 'options.sort') != null) {
							 | 
						||
| 
								 | 
							
								      return new MongooseError('Cannot populate with `sort` on path ' + options.path +
							 | 
						||
| 
								 | 
							
								        ' because it is a subproperty of a document array');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    modelNames = null;
							 | 
						||
| 
								 | 
							
								    let isRefPath = !!_firstWithRefPath;
							 | 
						||
| 
								 | 
							
								    let normalizedRefPath = _firstWithRefPath ? get(_firstWithRefPath, 'options.refPath', null) : null;
							 | 
						||
| 
								 | 
							
								    let schemaOptions = null;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (Array.isArray(schema)) {
							 | 
						||
| 
								 | 
							
								      const schemasArray = schema;
							 | 
						||
| 
								 | 
							
								      for (const _schema of schemasArray) {
							 | 
						||
| 
								 | 
							
								        let _modelNames;
							 | 
						||
| 
								 | 
							
								        let res;
							 | 
						||
| 
								 | 
							
								        try {
							 | 
						||
| 
								 | 
							
								          res = _getModelNames(doc, _schema);
							 | 
						||
| 
								 | 
							
								          _modelNames = res.modelNames;
							 | 
						||
| 
								 | 
							
								          isRefPath = isRefPath || res.isRefPath;
							 | 
						||
| 
								 | 
							
								          normalizedRefPath = normalizeRefPath(normalizedRefPath, doc, options.path) ||
							 | 
						||
| 
								 | 
							
								            res.refPath;
							 | 
						||
| 
								 | 
							
								          justOne = res.justOne;
							 | 
						||
| 
								 | 
							
								        } catch (error) {
							 | 
						||
| 
								 | 
							
								          return error;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (isRefPath && !res.isRefPath) {
							 | 
						||
| 
								 | 
							
								          continue;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        if (!_modelNames) {
							 | 
						||
| 
								 | 
							
								          continue;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        modelNames = modelNames || [];
							 | 
						||
| 
								 | 
							
								        for (const modelName of _modelNames) {
							 | 
						||
| 
								 | 
							
								          if (modelNames.indexOf(modelName) === -1) {
							 | 
						||
| 
								 | 
							
								            modelNames.push(modelName);
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      try {
							 | 
						||
| 
								 | 
							
								        const res = _getModelNames(doc, schema);
							 | 
						||
| 
								 | 
							
								        modelNames = res.modelNames;
							 | 
						||
| 
								 | 
							
								        isRefPath = res.isRefPath;
							 | 
						||
| 
								 | 
							
								        normalizedRefPath = res.refPath;
							 | 
						||
| 
								 | 
							
								        justOne = res.justOne;
							 | 
						||
| 
								 | 
							
								        schemaOptions = get(schema, 'options.populate', null);
							 | 
						||
| 
								 | 
							
								      } catch (error) {
							 | 
						||
| 
								 | 
							
								        return error;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (!modelNames) {
							 | 
						||
| 
								 | 
							
								        continue;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const _virtualRes = getVirtual(model.schema, options.path);
							 | 
						||
| 
								 | 
							
								    const virtual = _virtualRes == null ? null : _virtualRes.virtual;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    let localField;
							 | 
						||
| 
								 | 
							
								    let count = false;
							 | 
						||
| 
								 | 
							
								    if (virtual && virtual.options) {
							 | 
						||
| 
								 | 
							
								      const virtualPrefix = _virtualRes.nestedSchemaPath ?
							 | 
						||
| 
								 | 
							
								        _virtualRes.nestedSchemaPath + '.' : '';
							 | 
						||
| 
								 | 
							
								      if (typeof virtual.options.localField === 'function') {
							 | 
						||
| 
								 | 
							
								        localField = virtualPrefix + virtual.options.localField.call(doc, doc);
							 | 
						||
| 
								 | 
							
								      } else if (Array.isArray(virtual.options.localField)) {
							 | 
						||
| 
								 | 
							
								        localField = virtual.options.localField.map(field => virtualPrefix + field);
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        localField = virtualPrefix + virtual.options.localField;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      count = virtual.options.count;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (virtual.options.skip != null && !options.hasOwnProperty('skip')) {
							 | 
						||
| 
								 | 
							
								        options.skip = virtual.options.skip;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      if (virtual.options.limit != null && !options.hasOwnProperty('limit')) {
							 | 
						||
| 
								 | 
							
								        options.limit = virtual.options.limit;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      if (virtual.options.perDocumentLimit != null && !options.hasOwnProperty('perDocumentLimit')) {
							 | 
						||
| 
								 | 
							
								        options.perDocumentLimit = virtual.options.perDocumentLimit;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      localField = options.path;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    let foreignField = virtual && virtual.options ?
							 | 
						||
| 
								 | 
							
								      virtual.options.foreignField :
							 | 
						||
| 
								 | 
							
								      '_id';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // `justOne = null` means we don't know from the schema whether the end
							 | 
						||
| 
								 | 
							
								    // result should be an array or a single doc. This can result from
							 | 
						||
| 
								 | 
							
								    // populating a POJO using `Model.populate()`
							 | 
						||
| 
								 | 
							
								    if ('justOne' in options && options.justOne !== void 0) {
							 | 
						||
| 
								 | 
							
								      justOne = options.justOne;
							 | 
						||
| 
								 | 
							
								    } else if (virtual && virtual.options && virtual.options.refPath) {
							 | 
						||
| 
								 | 
							
								      const normalizedRefPath =
							 | 
						||
| 
								 | 
							
								        normalizeRefPath(virtual.options.refPath, doc, options.path);
							 | 
						||
| 
								 | 
							
								      justOne = !!virtual.options.justOne;
							 | 
						||
| 
								 | 
							
								      isVirtual = true;
							 | 
						||
| 
								 | 
							
								      const refValue = utils.getValue(normalizedRefPath, doc);
							 | 
						||
| 
								 | 
							
								      modelNames = Array.isArray(refValue) ? refValue : [refValue];
							 | 
						||
| 
								 | 
							
								    } else if (virtual && virtual.options && virtual.options.ref) {
							 | 
						||
| 
								 | 
							
								      let normalizedRef;
							 | 
						||
| 
								 | 
							
								      if (typeof virtual.options.ref === 'function') {
							 | 
						||
| 
								 | 
							
								        normalizedRef = virtual.options.ref.call(doc, doc);
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        normalizedRef = virtual.options.ref;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      justOne = !!virtual.options.justOne;
							 | 
						||
| 
								 | 
							
								      isVirtual = true;
							 | 
						||
| 
								 | 
							
								      if (!modelNames) {
							 | 
						||
| 
								 | 
							
								        modelNames = [].concat(normalizedRef);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    } else if (schema && !schema[schemaMixedSymbol]) {
							 | 
						||
| 
								 | 
							
								      // Skip Mixed types because we explicitly don't do casting on those.
							 | 
						||
| 
								 | 
							
								      if (options.path.endsWith('.' + schema.path)) {
							 | 
						||
| 
								 | 
							
								        justOne = Array.isArray(schema) ?
							 | 
						||
| 
								 | 
							
								          schema.every(schema => !schema.$isMongooseArray) :
							 | 
						||
| 
								 | 
							
								          !schema.$isMongooseArray;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (!modelNames) {
							 | 
						||
| 
								 | 
							
								      continue;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (virtual && (!localField || !foreignField)) {
							 | 
						||
| 
								 | 
							
								      return new MongooseError('If you are populating a virtual, you must set the ' +
							 | 
						||
| 
								 | 
							
								        'localField and foreignField options');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    options.isVirtual = isVirtual;
							 | 
						||
| 
								 | 
							
								    options.virtual = virtual;
							 | 
						||
| 
								 | 
							
								    if (typeof localField === 'function') {
							 | 
						||
| 
								 | 
							
								      localField = localField.call(doc, doc);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (typeof foreignField === 'function') {
							 | 
						||
| 
								 | 
							
								      foreignField = foreignField.call(doc);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    let match = get(options, 'match', null) ||
							 | 
						||
| 
								 | 
							
								      get(currentOptions, 'match', null) ||
							 | 
						||
| 
								 | 
							
								      get(options, 'virtual.options.match', null) ||
							 | 
						||
| 
								 | 
							
								      get(options, 'virtual.options.options.match', null);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    let hasMatchFunction = typeof match === 'function';
							 | 
						||
| 
								 | 
							
								    if (hasMatchFunction) {
							 | 
						||
| 
								 | 
							
								      match = match.call(doc, doc);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (Array.isArray(localField) && Array.isArray(foreignField) && localField.length === foreignField.length) {
							 | 
						||
| 
								 | 
							
								      match = Object.assign({}, match);
							 | 
						||
| 
								 | 
							
								      for (let i = 1; i < localField.length; ++i) {
							 | 
						||
| 
								 | 
							
								        match[foreignField[i]] = convertTo_id(mpath.get(localField[i], doc, lookupLocalFields), schema);
							 | 
						||
| 
								 | 
							
								        hasMatchFunction = true;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      localField = localField[0];
							 | 
						||
| 
								 | 
							
								      foreignField = foreignField[0];
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const localFieldPathType = modelSchema._getPathType(localField);
							 | 
						||
| 
								 | 
							
								    const localFieldPath = localFieldPathType === 'real' ? modelSchema.path(localField) : localFieldPathType.schema;
							 | 
						||
| 
								 | 
							
								    const localFieldGetters = localFieldPath && localFieldPath.getters ? localFieldPath.getters : [];
							 | 
						||
| 
								 | 
							
								    let ret;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const _populateOptions = get(options, 'options', {});
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const getters = 'getters' in _populateOptions ?
							 | 
						||
| 
								 | 
							
								      _populateOptions.getters :
							 | 
						||
| 
								 | 
							
								      options.isVirtual && get(virtual, 'options.getters', false);
							 | 
						||
| 
								 | 
							
								    if (localFieldGetters.length > 0 && getters) {
							 | 
						||
| 
								 | 
							
								      const hydratedDoc = (doc.$__ != null) ? doc : model.hydrate(doc);
							 | 
						||
| 
								 | 
							
								      const localFieldValue = mpath.get(localField, doc, lookupLocalFields);
							 | 
						||
| 
								 | 
							
								      if (Array.isArray(localFieldValue)) {
							 | 
						||
| 
								 | 
							
								        const localFieldHydratedValue = mpath.get(localField.split('.').slice(0, -1), hydratedDoc, lookupLocalFields);
							 | 
						||
| 
								 | 
							
								        ret = localFieldValue.map((localFieldArrVal, localFieldArrIndex) =>
							 | 
						||
| 
								 | 
							
								          localFieldPath.applyGetters(localFieldArrVal, localFieldHydratedValue[localFieldArrIndex]));
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        ret = localFieldPath.applyGetters(localFieldValue, hydratedDoc);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      ret = convertTo_id(mpath.get(localField, doc, lookupLocalFields), schema);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const id = String(utils.getValue(foreignField, doc));
							 | 
						||
| 
								 | 
							
								    options._docs[id] = Array.isArray(ret) ? ret.slice() : ret;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Re: gh-8452. Embedded discriminators may not have `refPath`, so clear
							 | 
						||
| 
								 | 
							
								    // out embedded discriminator docs that don't have a `refPath` on the
							 | 
						||
| 
								 | 
							
								    // populated path.
							 | 
						||
| 
								 | 
							
								    if (isRefPath && normalizedRefPath != null) {
							 | 
						||
| 
								 | 
							
								      const pieces = normalizedRefPath.split('.');
							 | 
						||
| 
								 | 
							
								      let cur = '';
							 | 
						||
| 
								 | 
							
								      for (let j = 0; j < pieces.length; ++j) {
							 | 
						||
| 
								 | 
							
								        const piece = pieces[j];
							 | 
						||
| 
								 | 
							
								        cur = cur + (cur.length === 0 ? '' : '.') + piece;
							 | 
						||
| 
								 | 
							
								        const schematype = modelSchema.path(cur);
							 | 
						||
| 
								 | 
							
								        if (schematype != null &&
							 | 
						||
| 
								 | 
							
								            schematype.$isMongooseArray &&
							 | 
						||
| 
								 | 
							
								            schematype.caster.discriminators != null &&
							 | 
						||
| 
								 | 
							
								            Object.keys(schematype.caster.discriminators).length > 0) {
							 | 
						||
| 
								 | 
							
								          const subdocs = utils.getValue(cur, doc);
							 | 
						||
| 
								 | 
							
								          const remnant = options.path.substr(cur.length + 1);
							 | 
						||
| 
								 | 
							
								          const discriminatorKey = schematype.caster.schema.options.discriminatorKey;
							 | 
						||
| 
								 | 
							
								          modelNames = [];
							 | 
						||
| 
								 | 
							
								          for (const subdoc of subdocs) {
							 | 
						||
| 
								 | 
							
								            const discriminatorName = utils.getValue(discriminatorKey, subdoc);
							 | 
						||
| 
								 | 
							
								            const discriminator = schematype.caster.discriminators[discriminatorName];
							 | 
						||
| 
								 | 
							
								            const discriminatorSchema = discriminator && discriminator.schema;
							 | 
						||
| 
								 | 
							
								            if (discriminatorSchema == null) {
							 | 
						||
| 
								 | 
							
								              continue;
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            const _path = discriminatorSchema.path(remnant);
							 | 
						||
| 
								 | 
							
								            if (_path == null || _path.options.refPath == null) {
							 | 
						||
| 
								 | 
							
								              const docValue = utils.getValue(localField.substr(cur.length + 1), subdoc);
							 | 
						||
| 
								 | 
							
								              ret = ret.map(v => v === docValue ? SkipPopulateValue(v) : v);
							 | 
						||
| 
								 | 
							
								              continue;
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            const modelName = utils.getValue(pieces.slice(j + 1).join('.'), subdoc);
							 | 
						||
| 
								 | 
							
								            modelNames.push(modelName);
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    let k = modelNames.length;
							 | 
						||
| 
								 | 
							
								    while (k--) {
							 | 
						||
| 
								 | 
							
								      modelName = modelNames[k];
							 | 
						||
| 
								 | 
							
								      if (modelName == null) {
							 | 
						||
| 
								 | 
							
								        continue;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // `PopulateOptions#connection`: if the model is passed as a string, the
							 | 
						||
| 
								 | 
							
								      // connection matters because different connections have different models.
							 | 
						||
| 
								 | 
							
								      const connection = options.connection != null ? options.connection : model.db;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      try {
							 | 
						||
| 
								 | 
							
								        Model = originalModel && originalModel[modelSymbol] ?
							 | 
						||
| 
								 | 
							
								          originalModel :
							 | 
						||
| 
								 | 
							
								          modelName[modelSymbol] ? modelName : connection.model(modelName);
							 | 
						||
| 
								 | 
							
								      } catch (error) {
							 | 
						||
| 
								 | 
							
								        // If `ret` is undefined, we'll add an empty entry to modelsMap. We shouldn't
							 | 
						||
| 
								 | 
							
								        // execute a query, but it is necessary to make sure `justOne` gets handled
							 | 
						||
| 
								 | 
							
								        // correctly for setting an empty array (see gh-8455)
							 | 
						||
| 
								 | 
							
								        if (ret !== undefined) {
							 | 
						||
| 
								 | 
							
								          return error;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      let ids = ret;
							 | 
						||
| 
								 | 
							
								      const flat = Array.isArray(ret) ? utils.array.flatten(ret) : [];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (isRefPath && Array.isArray(ret) && flat.length === modelNames.length) {
							 | 
						||
| 
								 | 
							
								        ids = flat.filter((val, i) => modelNames[i] === modelName);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (!available[modelName] || currentOptions.perDocumentLimit != null || get(currentOptions, 'options.perDocumentLimit') != null) {
							 | 
						||
| 
								 | 
							
								        currentOptions = {
							 | 
						||
| 
								 | 
							
								          model: Model
							 | 
						||
| 
								 | 
							
								        };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (isVirtual && get(virtual, 'options.options')) {
							 | 
						||
| 
								 | 
							
								          currentOptions.options = utils.clone(virtual.options.options);
							 | 
						||
| 
								 | 
							
								        } else if (schemaOptions != null) {
							 | 
						||
| 
								 | 
							
								          currentOptions.options = Object.assign({}, schemaOptions);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        utils.merge(currentOptions, options);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // Used internally for checking what model was used to populate this
							 | 
						||
| 
								 | 
							
								        // path.
							 | 
						||
| 
								 | 
							
								        options[populateModelSymbol] = Model;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        available[modelName] = {
							 | 
						||
| 
								 | 
							
								          model: Model,
							 | 
						||
| 
								 | 
							
								          options: currentOptions,
							 | 
						||
| 
								 | 
							
								          match: hasMatchFunction ? [match] : match,
							 | 
						||
| 
								 | 
							
								          docs: [doc],
							 | 
						||
| 
								 | 
							
								          ids: [ids],
							 | 
						||
| 
								 | 
							
								          allIds: [ret],
							 | 
						||
| 
								 | 
							
								          localField: new Set([localField]),
							 | 
						||
| 
								 | 
							
								          foreignField: new Set([foreignField]),
							 | 
						||
| 
								 | 
							
								          justOne: justOne,
							 | 
						||
| 
								 | 
							
								          isVirtual: isVirtual,
							 | 
						||
| 
								 | 
							
								          virtual: virtual,
							 | 
						||
| 
								 | 
							
								          count: count,
							 | 
						||
| 
								 | 
							
								          [populateModelSymbol]: Model
							 | 
						||
| 
								 | 
							
								        };
							 | 
						||
| 
								 | 
							
								        map.push(available[modelName]);
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        available[modelName].localField.add(localField);
							 | 
						||
| 
								 | 
							
								        available[modelName].foreignField.add(foreignField);
							 | 
						||
| 
								 | 
							
								        available[modelName].docs.push(doc);
							 | 
						||
| 
								 | 
							
								        available[modelName].ids.push(ids);
							 | 
						||
| 
								 | 
							
								        available[modelName].allIds.push(ret);
							 | 
						||
| 
								 | 
							
								        if (hasMatchFunction) {
							 | 
						||
| 
								 | 
							
								          available[modelName].match.push(match);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return map;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  function _getModelNames(doc, schema) {
							 | 
						||
| 
								 | 
							
								    let modelNames;
							 | 
						||
| 
								 | 
							
								    let discriminatorKey;
							 | 
						||
| 
								 | 
							
								    let isRefPath = false;
							 | 
						||
| 
								 | 
							
								    let justOne = null;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (schema && schema.caster) {
							 | 
						||
| 
								 | 
							
								      schema = schema.caster;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (schema && schema.$isSchemaMap) {
							 | 
						||
| 
								 | 
							
								      schema = schema.$__schemaType;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (!schema && model.discriminators) {
							 | 
						||
| 
								 | 
							
								      discriminatorKey = model.schema.discriminatorMapping.key;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    refPath = schema && schema.options && schema.options.refPath;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const normalizedRefPath = normalizeRefPath(refPath, doc, options.path);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (modelNameFromQuery) {
							 | 
						||
| 
								 | 
							
								      modelNames = [modelNameFromQuery]; // query options
							 | 
						||
| 
								 | 
							
								    } else if (normalizedRefPath) {
							 | 
						||
| 
								 | 
							
								      if (options._queryProjection != null && isPathExcluded(options._queryProjection, normalizedRefPath)) {
							 | 
						||
| 
								 | 
							
								        throw new MongooseError('refPath `' + normalizedRefPath +
							 | 
						||
| 
								 | 
							
								          '` must not be excluded in projection, got ' +
							 | 
						||
| 
								 | 
							
								          util.inspect(options._queryProjection));
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (modelSchema.virtuals.hasOwnProperty(normalizedRefPath) && doc.$__ == null) {
							 | 
						||
| 
								 | 
							
								        modelNames = [modelSchema.virtuals[normalizedRefPath].applyGetters(void 0, doc)];
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        modelNames = utils.getValue(normalizedRefPath, doc);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (Array.isArray(modelNames)) {
							 | 
						||
| 
								 | 
							
								        modelNames = utils.array.flatten(modelNames);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      isRefPath = true;
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      let modelForCurrentDoc = model;
							 | 
						||
| 
								 | 
							
								      let schemaForCurrentDoc;
							 | 
						||
| 
								 | 
							
								      let discriminatorValue;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (!schema && discriminatorKey && (discriminatorValue = utils.getValue(discriminatorKey, doc))) {
							 | 
						||
| 
								 | 
							
								        // `modelNameForFind` is the discriminator value, so we might need
							 | 
						||
| 
								 | 
							
								        // find the discriminated model name
							 | 
						||
| 
								 | 
							
								        const discriminatorModel = getDiscriminatorByValue(model.discriminators, discriminatorValue) || model;
							 | 
						||
| 
								 | 
							
								        if (discriminatorModel != null) {
							 | 
						||
| 
								 | 
							
								          modelForCurrentDoc = discriminatorModel;
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          try {
							 | 
						||
| 
								 | 
							
								            modelForCurrentDoc = model.db.model(discriminatorValue);
							 | 
						||
| 
								 | 
							
								          } catch (error) {
							 | 
						||
| 
								 | 
							
								            return error;
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        schemaForCurrentDoc = modelForCurrentDoc.schema._getSchema(options.path);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (schemaForCurrentDoc && schemaForCurrentDoc.caster) {
							 | 
						||
| 
								 | 
							
								          schemaForCurrentDoc = schemaForCurrentDoc.caster;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        schemaForCurrentDoc = schema;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      const _virtualRes = getVirtual(modelForCurrentDoc.schema, options.path);
							 | 
						||
| 
								 | 
							
								      const virtual = _virtualRes == null ? null : _virtualRes.virtual;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (schemaForCurrentDoc != null) {
							 | 
						||
| 
								 | 
							
								        justOne = !schemaForCurrentDoc.$isMongooseArray && !schemaForCurrentDoc._arrayPath;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      let ref;
							 | 
						||
| 
								 | 
							
								      let refPath;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if ((ref = get(schemaForCurrentDoc, 'options.ref')) != null) {
							 | 
						||
| 
								 | 
							
								        ref = handleRefFunction(ref, doc);
							 | 
						||
| 
								 | 
							
								        modelNames = [ref];
							 | 
						||
| 
								 | 
							
								      } else if ((ref = get(virtual, 'options.ref')) != null) {
							 | 
						||
| 
								 | 
							
								        ref = handleRefFunction(ref, doc);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // When referencing nested arrays, the ref should be an Array
							 | 
						||
| 
								 | 
							
								        // of modelNames.
							 | 
						||
| 
								 | 
							
								        if (Array.isArray(ref)) {
							 | 
						||
| 
								 | 
							
								          modelNames = ref;
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          modelNames = [ref];
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        isVirtual = true;
							 | 
						||
| 
								 | 
							
								      } else if ((refPath = get(schemaForCurrentDoc, 'options.refPath')) != null) {
							 | 
						||
| 
								 | 
							
								        isRefPath = true;
							 | 
						||
| 
								 | 
							
								        refPath = normalizeRefPath(refPath, doc, options.path);
							 | 
						||
| 
								 | 
							
								        modelNames = utils.getValue(refPath, doc);
							 | 
						||
| 
								 | 
							
								        if (Array.isArray(modelNames)) {
							 | 
						||
| 
								 | 
							
								          modelNames = utils.array.flatten(modelNames);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        // We may have a discriminator, in which case we don't want to
							 | 
						||
| 
								 | 
							
								        // populate using the base model by default
							 | 
						||
| 
								 | 
							
								        modelNames = discriminatorKey ? null : [model.modelName];
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (!modelNames) {
							 | 
						||
| 
								 | 
							
								      return { modelNames: modelNames, isRefPath: isRefPath, refPath: normalizedRefPath, justOne: justOne };
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (!Array.isArray(modelNames)) {
							 | 
						||
| 
								 | 
							
								      modelNames = [modelNames];
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return { modelNames: modelNames, isRefPath: isRefPath, refPath: normalizedRefPath, justOne: justOne };
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/*!
							 | 
						||
| 
								 | 
							
								 * ignore
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function handleRefFunction(ref, doc) {
							 | 
						||
| 
								 | 
							
								  if (typeof ref === 'function' && !ref[modelSymbol]) {
							 | 
						||
| 
								 | 
							
								    return ref.call(doc, doc);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return ref;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/*!
							 | 
						||
| 
								 | 
							
								 * Retrieve the _id of `val` if a Document or Array of Documents.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param {Array|Document|Any} val
							 | 
						||
| 
								 | 
							
								 * @return {Array|Document|Any}
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function convertTo_id(val, schema) {
							 | 
						||
| 
								 | 
							
								  if (val != null && val.$__ != null) {
							 | 
						||
| 
								 | 
							
								    return val._id;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if (val != null && val._id != null && (schema == null || !schema.$isSchemaMap)) {
							 | 
						||
| 
								 | 
							
								    return val._id;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (Array.isArray(val)) {
							 | 
						||
| 
								 | 
							
								    for (let i = 0; i < val.length; ++i) {
							 | 
						||
| 
								 | 
							
								      if (val[i] != null && val[i].$__ != null) {
							 | 
						||
| 
								 | 
							
								        val[i] = val[i]._id;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (val.isMongooseArray && val.$schema()) {
							 | 
						||
| 
								 | 
							
								      return val.$schema()._castForPopulate(val, val.$parent());
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return [].concat(val);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // `populate('map')` may be an object if populating on a doc that hasn't
							 | 
						||
| 
								 | 
							
								  // been hydrated yet
							 | 
						||
| 
								 | 
							
								  if (getConstructorName(val) === 'Object' &&
							 | 
						||
| 
								 | 
							
								      // The intent here is we should only flatten the object if we expect
							 | 
						||
| 
								 | 
							
								      // to get a Map in the end. Avoid doing this for mixed types.
							 | 
						||
| 
								 | 
							
								      (schema == null || schema[schemaMixedSymbol] == null)) {
							 | 
						||
| 
								 | 
							
								    const ret = [];
							 | 
						||
| 
								 | 
							
								    for (const key of Object.keys(val)) {
							 | 
						||
| 
								 | 
							
								      ret.push(val[key]);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return ret;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  // If doc has already been hydrated, e.g. `doc.populate('map').execPopulate()`
							 | 
						||
| 
								 | 
							
								  // then `val` will already be a map
							 | 
						||
| 
								 | 
							
								  if (val instanceof Map) {
							 | 
						||
| 
								 | 
							
								    return Array.from(val.values());
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return val;
							 | 
						||
| 
								 | 
							
								}
							 |