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.
		
		
		
		
		
			
		
			
				
					288 lines
				
				8.9 KiB
			
		
		
			
		
	
	
					288 lines
				
				8.9 KiB
			| 
											3 years ago
										 | 'use strict'; | ||
|  | 
 | ||
|  | const SkipPopulateValue = require('./SkipPopulateValue'); | ||
|  | const assignRawDocsToIdStructure = require('./assignRawDocsToIdStructure'); | ||
|  | const get = require('../get'); | ||
|  | const getVirtual = require('./getVirtual'); | ||
|  | const leanPopulateMap = require('./leanPopulateMap'); | ||
|  | const lookupLocalFields = require('./lookupLocalFields'); | ||
|  | const mpath = require('mpath'); | ||
|  | const sift = require('sift').default; | ||
|  | const utils = require('../../utils'); | ||
|  | 
 | ||
|  | module.exports = function assignVals(o) { | ||
|  |   // Options that aren't explicitly listed in `populateOptions`
 | ||
|  |   const userOptions = Object.assign({}, get(o, 'allOptions.options.options'), get(o, 'allOptions.options')); | ||
|  |   // `o.options` contains options explicitly listed in `populateOptions`, like
 | ||
|  |   // `match` and `limit`.
 | ||
|  |   const populateOptions = Object.assign({}, o.options, userOptions, { | ||
|  |     justOne: o.justOne | ||
|  |   }); | ||
|  |   populateOptions.$nullIfNotFound = o.isVirtual; | ||
|  |   const populatedModel = o.populatedModel; | ||
|  | 
 | ||
|  |   const originalIds = [].concat(o.rawIds); | ||
|  | 
 | ||
|  |   // replace the original ids in our intermediate _ids structure
 | ||
|  |   // with the documents found by query
 | ||
|  |   o.allIds = [].concat(o.allIds); | ||
|  |   assignRawDocsToIdStructure(o.rawIds, o.rawDocs, o.rawOrder, populateOptions); | ||
|  | 
 | ||
|  |   // now update the original documents being populated using the
 | ||
|  |   // result structure that contains real documents.
 | ||
|  |   const docs = o.docs; | ||
|  |   const rawIds = o.rawIds; | ||
|  |   const options = o.options; | ||
|  |   const count = o.count && o.isVirtual; | ||
|  |   let i; | ||
|  | 
 | ||
|  |   function setValue(val) { | ||
|  |     if (count) { | ||
|  |       return val; | ||
|  |     } | ||
|  |     if (val instanceof SkipPopulateValue) { | ||
|  |       return val.val; | ||
|  |     } | ||
|  |     if (val === void 0) { | ||
|  |       return val; | ||
|  |     } | ||
|  | 
 | ||
|  |     const _allIds = o.allIds[i]; | ||
|  | 
 | ||
|  |     if (o.justOne === true && Array.isArray(val)) { | ||
|  |       // Might be an embedded discriminator (re: gh-9244) with multiple models, so make sure to pick the right
 | ||
|  |       // model before assigning.
 | ||
|  |       const ret = []; | ||
|  |       for (const doc of val) { | ||
|  |         const _docPopulatedModel = leanPopulateMap.get(doc); | ||
|  |         if (_docPopulatedModel == null || _docPopulatedModel === populatedModel) { | ||
|  |           ret.push(doc); | ||
|  |         } | ||
|  |       } | ||
|  |       // Since we don't want to have to create a new mongoosearray, make sure to
 | ||
|  |       // modify the array in place
 | ||
|  |       while (val.length > ret.length) { | ||
|  |         Array.prototype.pop.apply(val, []); | ||
|  |       } | ||
|  |       for (let i = 0; i < ret.length; ++i) { | ||
|  |         val[i] = ret[i]; | ||
|  |       } | ||
|  | 
 | ||
|  |       return valueFilter(val[0], options, populateOptions, _allIds); | ||
|  |     } else if (o.justOne === false && !Array.isArray(val)) { | ||
|  |       return valueFilter([val], options, populateOptions, _allIds); | ||
|  |     } | ||
|  |     return valueFilter(val, options, populateOptions, _allIds); | ||
|  |   } | ||
|  | 
 | ||
|  |   for (i = 0; i < docs.length; ++i) { | ||
|  |     const _path = o.path.endsWith('.$*') ? o.path.slice(0, -3) : o.path; | ||
|  |     const existingVal = mpath.get(_path, docs[i], lookupLocalFields); | ||
|  |     if (existingVal == null && !getVirtual(o.originalModel.schema, _path)) { | ||
|  |       continue; | ||
|  |     } | ||
|  | 
 | ||
|  |     let valueToSet; | ||
|  |     if (count) { | ||
|  |       valueToSet = numDocs(rawIds[i]); | ||
|  |     } else if (Array.isArray(o.match)) { | ||
|  |       valueToSet = Array.isArray(rawIds[i]) ? | ||
|  |         rawIds[i].filter(sift(o.match[i])) : | ||
|  |         [rawIds[i]].filter(sift(o.match[i]))[0]; | ||
|  |     } else { | ||
|  |       valueToSet = rawIds[i]; | ||
|  |     } | ||
|  | 
 | ||
|  |     // If we're populating a map, the existing value will be an object, so
 | ||
|  |     // we need to transform again
 | ||
|  |     const originalSchema = o.originalModel.schema; | ||
|  |     const isDoc = get(docs[i], '$__', null) != null; | ||
|  |     let isMap = isDoc ? | ||
|  |       existingVal instanceof Map : | ||
|  |       utils.isPOJO(existingVal); | ||
|  |     // If we pass the first check, also make sure the local field's schematype
 | ||
|  |     // is map (re: gh-6460)
 | ||
|  |     isMap = isMap && get(originalSchema._getSchema(_path), '$isSchemaMap'); | ||
|  |     if (!o.isVirtual && isMap) { | ||
|  |       const _keys = existingVal instanceof Map ? | ||
|  |         Array.from(existingVal.keys()) : | ||
|  |         Object.keys(existingVal); | ||
|  |       valueToSet = valueToSet.reduce((cur, v, i) => { | ||
|  |         cur.set(_keys[i], v); | ||
|  |         return cur; | ||
|  |       }, new Map()); | ||
|  |     } | ||
|  | 
 | ||
|  |     if (isDoc && Array.isArray(valueToSet)) { | ||
|  |       for (const val of valueToSet) { | ||
|  |         if (val != null && val.$__ != null) { | ||
|  |           val.$__.parent = docs[i]; | ||
|  |         } | ||
|  |       } | ||
|  |     } else if (isDoc && valueToSet != null && valueToSet.$__ != null) { | ||
|  |       valueToSet.$__.parent = docs[i]; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (o.isVirtual && isDoc) { | ||
|  |       docs[i].populated(_path, o.justOne ? originalIds[0] : originalIds, o.allOptions); | ||
|  |       // If virtual populate and doc is already init-ed, need to walk through
 | ||
|  |       // the actual doc to set rather than setting `_doc` directly
 | ||
|  |       mpath.set(_path, valueToSet, docs[i], setValue); | ||
|  |       continue; | ||
|  |     } | ||
|  | 
 | ||
|  |     const parts = _path.split('.'); | ||
|  |     let cur = docs[i]; | ||
|  |     const curPath = parts[0]; | ||
|  |     for (let j = 0; j < parts.length - 1; ++j) { | ||
|  |       // If we get to an array with a dotted path, like `arr.foo`, don't set
 | ||
|  |       // `foo` on the array.
 | ||
|  |       if (Array.isArray(cur) && !utils.isArrayIndex(parts[j])) { | ||
|  |         break; | ||
|  |       } | ||
|  | 
 | ||
|  |       if (parts[j] === '$*') { | ||
|  |         break; | ||
|  |       } | ||
|  | 
 | ||
|  |       if (cur[parts[j]] == null) { | ||
|  |         // If nothing to set, avoid creating an unnecessary array. Otherwise
 | ||
|  |         // we'll end up with a single doc in the array with only defaults.
 | ||
|  |         // See gh-8342, gh-8455
 | ||
|  |         const schematype = originalSchema._getSchema(curPath); | ||
|  |         if (valueToSet == null && schematype != null && schematype.$isMongooseArray) { | ||
|  |           break; | ||
|  |         } | ||
|  |         cur[parts[j]] = {}; | ||
|  |       } | ||
|  |       cur = cur[parts[j]]; | ||
|  |       // If the property in MongoDB is a primitive, we won't be able to populate
 | ||
|  |       // the nested path, so skip it. See gh-7545
 | ||
|  |       if (typeof cur !== 'object') { | ||
|  |         break; | ||
|  |       } | ||
|  |     } | ||
|  |     if (docs[i].$__) { | ||
|  |       docs[i].populated(_path, o.allIds[i], o.allOptions); | ||
|  |     } | ||
|  | 
 | ||
|  |     // If lean, need to check that each individual virtual respects
 | ||
|  |     // `justOne`, because you may have a populated virtual with `justOne`
 | ||
|  |     // underneath an array. See gh-6867
 | ||
|  |     mpath.set(_path, valueToSet, docs[i], lookupLocalFields, setValue, false); | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | function numDocs(v) { | ||
|  |   if (Array.isArray(v)) { | ||
|  |     // If setting underneath an array of populated subdocs, we may have an
 | ||
|  |     // array of arrays. See gh-7573
 | ||
|  |     if (v.some(el => Array.isArray(el))) { | ||
|  |       return v.map(el => numDocs(el)); | ||
|  |     } | ||
|  |     return v.length; | ||
|  |   } | ||
|  |   return v == null ? 0 : 1; | ||
|  | } | ||
|  | 
 | ||
|  | /*! | ||
|  |  * 1) Apply backwards compatible find/findOne behavior to sub documents | ||
|  |  * | ||
|  |  *    find logic: | ||
|  |  *      a) filter out non-documents | ||
|  |  *      b) remove _id from sub docs when user specified | ||
|  |  * | ||
|  |  *    findOne | ||
|  |  *      a) if no doc found, set to null | ||
|  |  *      b) remove _id from sub docs when user specified | ||
|  |  * | ||
|  |  * 2) Remove _ids when specified by users query. | ||
|  |  * | ||
|  |  * background: | ||
|  |  * _ids are left in the query even when user excludes them so | ||
|  |  * that population mapping can occur. | ||
|  |  */ | ||
|  | 
 | ||
|  | function valueFilter(val, assignmentOpts, populateOptions, allIds) { | ||
|  |   const userSpecifiedTransform = typeof populateOptions.transform === 'function'; | ||
|  |   const transform = userSpecifiedTransform ? populateOptions.transform : noop; | ||
|  |   if (Array.isArray(val)) { | ||
|  |     // find logic
 | ||
|  |     const ret = []; | ||
|  |     const numValues = val.length; | ||
|  |     for (let i = 0; i < numValues; ++i) { | ||
|  |       let subdoc = val[i]; | ||
|  |       const _allIds = Array.isArray(allIds) ? allIds[i] : allIds; | ||
|  |       if (!isPopulatedObject(subdoc) && (!populateOptions.retainNullValues || subdoc != null) && !userSpecifiedTransform) { | ||
|  |         continue; | ||
|  |       } else if (userSpecifiedTransform) { | ||
|  |         subdoc = transform(isPopulatedObject(subdoc) ? subdoc : null, _allIds); | ||
|  |       } | ||
|  |       maybeRemoveId(subdoc, assignmentOpts); | ||
|  |       ret.push(subdoc); | ||
|  |       if (assignmentOpts.originalLimit && | ||
|  |           ret.length >= assignmentOpts.originalLimit) { | ||
|  |         break; | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     // Since we don't want to have to create a new mongoosearray, make sure to
 | ||
|  |     // modify the array in place
 | ||
|  |     while (val.length > ret.length) { | ||
|  |       Array.prototype.pop.apply(val, []); | ||
|  |     } | ||
|  |     for (let i = 0; i < ret.length; ++i) { | ||
|  |       val[i] = ret[i]; | ||
|  |     } | ||
|  |     return val; | ||
|  |   } | ||
|  | 
 | ||
|  |   // findOne
 | ||
|  |   if (isPopulatedObject(val) || utils.isPOJO(val)) { | ||
|  |     maybeRemoveId(val, assignmentOpts); | ||
|  |     return transform(val, allIds); | ||
|  |   } | ||
|  |   if (val instanceof Map) { | ||
|  |     return val; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (populateOptions.justOne === false) { | ||
|  |     return []; | ||
|  |   } | ||
|  | 
 | ||
|  |   return val == null ? transform(val, allIds) : transform(null, allIds); | ||
|  | } | ||
|  | 
 | ||
|  | /*! | ||
|  |  * Remove _id from `subdoc` if user specified "lean" query option | ||
|  |  */ | ||
|  | 
 | ||
|  | function maybeRemoveId(subdoc, assignmentOpts) { | ||
|  |   if (subdoc != null && assignmentOpts.excludeId) { | ||
|  |     if (typeof subdoc.$__setValue === 'function') { | ||
|  |       delete subdoc._doc._id; | ||
|  |     } else { | ||
|  |       delete subdoc._id; | ||
|  |     } | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | /*! | ||
|  |  * Determine if `obj` is something we can set a populated path to. Can be a | ||
|  |  * document, a lean document, or an array/map that contains docs. | ||
|  |  */ | ||
|  | 
 | ||
|  | function isPopulatedObject(obj) { | ||
|  |   if (obj == null) { | ||
|  |     return false; | ||
|  |   } | ||
|  | 
 | ||
|  |   return Array.isArray(obj) || | ||
|  |     obj.$isMongooseMap || | ||
|  |     obj.$__ != null || | ||
|  |     leanPopulateMap.has(obj); | ||
|  | } | ||
|  | 
 | ||
|  | function noop(v) { | ||
|  |   return v; | ||
|  | } |