getModelsMapForPopulate.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729
  1. 'use strict';
  2. const MongooseError = require('../../error/index');
  3. const SkipPopulateValue = require('./SkipPopulateValue');
  4. const get = require('../get');
  5. const getDiscriminatorByValue = require('../discriminator/getDiscriminatorByValue');
  6. const getConstructorName = require('../getConstructorName');
  7. const getSchemaTypes = require('./getSchemaTypes');
  8. const getVirtual = require('./getVirtual');
  9. const lookupLocalFields = require('./lookupLocalFields');
  10. const mpath = require('mpath');
  11. const modelNamesFromRefPath = require('./modelNamesFromRefPath');
  12. const utils = require('../../utils');
  13. const modelSymbol = require('../symbols').modelSymbol;
  14. const populateModelSymbol = require('../symbols').populateModelSymbol;
  15. const schemaMixedSymbol = require('../../schema/symbols').schemaMixedSymbol;
  16. module.exports = function getModelsMapForPopulate(model, docs, options) {
  17. let doc;
  18. const len = docs.length;
  19. const map = [];
  20. const modelNameFromQuery = options.model && options.model.modelName || options.model;
  21. let schema;
  22. let refPath;
  23. let modelNames;
  24. const available = {};
  25. const modelSchema = model.schema;
  26. // Populating a nested path should always be a no-op re: #9073.
  27. // People shouldn't do this, but apparently they do.
  28. if (options._localModel != null && options._localModel.schema.nested[options.path]) {
  29. return [];
  30. }
  31. const _virtualRes = getVirtual(model.schema, options.path);
  32. const virtual = _virtualRes == null ? null : _virtualRes.virtual;
  33. if (virtual != null) {
  34. return _virtualPopulate(model, docs, options, _virtualRes);
  35. }
  36. let allSchemaTypes = getSchemaTypes(model, modelSchema, null, options.path);
  37. allSchemaTypes = Array.isArray(allSchemaTypes) ? allSchemaTypes : [allSchemaTypes].filter(v => v != null);
  38. if (allSchemaTypes.length === 0 && options.strictPopulate !== false && options._localModel != null) {
  39. return new MongooseError('Cannot populate path `' + (options._fullPath || options.path) +
  40. '` because it is not in your schema. Set the `strictPopulate` option ' +
  41. 'to false to override.');
  42. }
  43. for (let i = 0; i < len; i++) {
  44. doc = docs[i];
  45. let justOne = null;
  46. const docSchema = doc != null && doc.$__ != null ? doc.$__schema : modelSchema;
  47. schema = getSchemaTypes(model, docSchema, doc, options.path);
  48. // Special case: populating a path that's a DocumentArray unless
  49. // there's an explicit `ref` or `refPath` re: gh-8946
  50. if (schema != null &&
  51. schema.$isMongooseDocumentArray &&
  52. schema.options.ref == null &&
  53. schema.options.refPath == null) {
  54. continue;
  55. }
  56. const isUnderneathDocArray = schema && schema.$isUnderneathDocArray;
  57. if (isUnderneathDocArray && get(options, 'options.sort') != null) {
  58. return new MongooseError('Cannot populate with `sort` on path ' + options.path +
  59. ' because it is a subproperty of a document array');
  60. }
  61. modelNames = null;
  62. let isRefPath = false;
  63. let normalizedRefPath = null;
  64. let schemaOptions = null;
  65. let modelNamesInOrder = null;
  66. if (schema != null && schema.instance === 'Embedded') {
  67. if (schema.options.ref) {
  68. const data = {
  69. localField: options.path + '._id',
  70. foreignField: '_id',
  71. justOne: true
  72. };
  73. const res = _getModelNames(doc, schema, modelNameFromQuery, model);
  74. const unpopulatedValue = mpath.get(options.path, doc);
  75. const id = mpath.get('_id', unpopulatedValue);
  76. addModelNamesToMap(model, map, available, res.modelNames, options, data, id, doc, schemaOptions, unpopulatedValue);
  77. }
  78. // No-op if no `ref` set. See gh-11538
  79. continue;
  80. }
  81. if (Array.isArray(schema)) {
  82. const schemasArray = schema;
  83. for (const _schema of schemasArray) {
  84. let _modelNames;
  85. let res;
  86. try {
  87. res = _getModelNames(doc, _schema, modelNameFromQuery, model);
  88. _modelNames = res.modelNames;
  89. isRefPath = isRefPath || res.isRefPath;
  90. normalizedRefPath = normalizedRefPath || res.refPath;
  91. justOne = res.justOne;
  92. } catch (error) {
  93. return error;
  94. }
  95. if (isRefPath && !res.isRefPath) {
  96. continue;
  97. }
  98. if (!_modelNames) {
  99. continue;
  100. }
  101. modelNames = modelNames || [];
  102. for (const modelName of _modelNames) {
  103. if (modelNames.indexOf(modelName) === -1) {
  104. modelNames.push(modelName);
  105. }
  106. }
  107. }
  108. } else {
  109. try {
  110. const res = _getModelNames(doc, schema, modelNameFromQuery, model);
  111. modelNames = res.modelNames;
  112. isRefPath = res.isRefPath;
  113. normalizedRefPath = normalizedRefPath || res.refPath;
  114. justOne = res.justOne;
  115. schemaOptions = get(schema, 'options.populate', null);
  116. // Dedupe, because `refPath` can return duplicates of the same model name,
  117. // and that causes perf issues.
  118. if (isRefPath) {
  119. modelNamesInOrder = modelNames;
  120. modelNames = Array.from(new Set(modelNames));
  121. }
  122. } catch (error) {
  123. return error;
  124. }
  125. if (!modelNames) {
  126. continue;
  127. }
  128. }
  129. const data = {};
  130. const localField = options.path;
  131. const foreignField = '_id';
  132. // `justOne = null` means we don't know from the schema whether the end
  133. // result should be an array or a single doc. This can result from
  134. // populating a POJO using `Model.populate()`
  135. if ('justOne' in options && options.justOne !== void 0) {
  136. justOne = options.justOne;
  137. } else if (schema && !schema[schemaMixedSymbol]) {
  138. // Skip Mixed types because we explicitly don't do casting on those.
  139. if (options.path.endsWith('.' + schema.path) || options.path === schema.path) {
  140. justOne = Array.isArray(schema) ?
  141. schema.every(schema => !schema.$isMongooseArray) :
  142. !schema.$isMongooseArray;
  143. }
  144. }
  145. if (!modelNames) {
  146. continue;
  147. }
  148. data.isVirtual = false;
  149. data.justOne = justOne;
  150. data.localField = localField;
  151. data.foreignField = foreignField;
  152. // Get local fields
  153. const ret = _getLocalFieldValues(doc, localField, model, options, null, schema);
  154. const id = String(utils.getValue(foreignField, doc));
  155. options._docs[id] = Array.isArray(ret) ? ret.slice() : ret;
  156. let match = get(options, 'match', null);
  157. const hasMatchFunction = typeof match === 'function';
  158. if (hasMatchFunction) {
  159. match = match.call(doc, doc);
  160. }
  161. data.match = match;
  162. data.hasMatchFunction = hasMatchFunction;
  163. data.isRefPath = isRefPath;
  164. data.modelNamesInOrder = modelNamesInOrder;
  165. if (isRefPath) {
  166. const embeddedDiscriminatorModelNames = _findRefPathForDiscriminators(doc,
  167. modelSchema, data, options, normalizedRefPath, ret);
  168. modelNames = embeddedDiscriminatorModelNames || modelNames;
  169. }
  170. try {
  171. addModelNamesToMap(model, map, available, modelNames, options, data, ret, doc, schemaOptions);
  172. } catch (err) {
  173. return err;
  174. }
  175. }
  176. return map;
  177. function _getModelNames(doc, schema, modelNameFromQuery, model) {
  178. let modelNames;
  179. let isRefPath = false;
  180. let justOne = null;
  181. if (schema && schema.instance === 'Array') {
  182. schema = schema.caster;
  183. }
  184. if (schema && schema.$isSchemaMap) {
  185. schema = schema.$__schemaType;
  186. }
  187. const ref = schema && schema.options && schema.options.ref;
  188. refPath = schema && schema.options && schema.options.refPath;
  189. if (schema != null &&
  190. schema[schemaMixedSymbol] &&
  191. !ref &&
  192. !refPath &&
  193. !modelNameFromQuery) {
  194. return { modelNames: null };
  195. }
  196. if (modelNameFromQuery) {
  197. modelNames = [modelNameFromQuery]; // query options
  198. } else if (refPath != null) {
  199. if (typeof refPath === 'function') {
  200. const subdocPath = options.path.slice(0, options.path.length - schema.path.length - 1);
  201. const vals = mpath.get(subdocPath, doc, lookupLocalFields);
  202. const subdocsBeingPopulated = Array.isArray(vals) ?
  203. utils.array.flatten(vals) :
  204. (vals ? [vals] : []);
  205. modelNames = new Set();
  206. for (const subdoc of subdocsBeingPopulated) {
  207. refPath = refPath.call(subdoc, subdoc, options.path);
  208. modelNamesFromRefPath(refPath, doc, options.path, modelSchema, options._queryProjection).
  209. forEach(name => modelNames.add(name));
  210. }
  211. modelNames = Array.from(modelNames);
  212. } else {
  213. modelNames = modelNamesFromRefPath(refPath, doc, options.path, modelSchema, options._queryProjection);
  214. }
  215. isRefPath = true;
  216. } else {
  217. let ref;
  218. let refPath;
  219. let schemaForCurrentDoc;
  220. let discriminatorValue;
  221. let modelForCurrentDoc = model;
  222. const discriminatorKey = model.schema.options.discriminatorKey;
  223. if (!schema && discriminatorKey && (discriminatorValue = utils.getValue(discriminatorKey, doc))) {
  224. // `modelNameForFind` is the discriminator value, so we might need
  225. // find the discriminated model name
  226. const discriminatorModel = getDiscriminatorByValue(model.discriminators, discriminatorValue) || model;
  227. if (discriminatorModel != null) {
  228. modelForCurrentDoc = discriminatorModel;
  229. } else {
  230. try {
  231. modelForCurrentDoc = _getModelFromConn(model.db, discriminatorValue);
  232. } catch (error) {
  233. return error;
  234. }
  235. }
  236. schemaForCurrentDoc = modelForCurrentDoc.schema._getSchema(options.path);
  237. if (schemaForCurrentDoc && schemaForCurrentDoc.caster) {
  238. schemaForCurrentDoc = schemaForCurrentDoc.caster;
  239. }
  240. } else {
  241. schemaForCurrentDoc = schema;
  242. }
  243. if (schemaForCurrentDoc != null) {
  244. justOne = !schemaForCurrentDoc.$isMongooseArray && !schemaForCurrentDoc._arrayPath;
  245. }
  246. if ((ref = get(schemaForCurrentDoc, 'options.ref')) != null) {
  247. if (schemaForCurrentDoc != null &&
  248. typeof ref === 'function' &&
  249. options.path.endsWith('.' + schemaForCurrentDoc.path)) {
  250. // Ensure correct context for ref functions: subdoc, not top-level doc. See gh-8469
  251. modelNames = new Set();
  252. const subdocPath = options.path.slice(0, options.path.length - schemaForCurrentDoc.path.length - 1);
  253. const vals = mpath.get(subdocPath, doc, lookupLocalFields);
  254. const subdocsBeingPopulated = Array.isArray(vals) ?
  255. utils.array.flatten(vals) :
  256. (vals ? [vals] : []);
  257. for (const subdoc of subdocsBeingPopulated) {
  258. modelNames.add(handleRefFunction(ref, subdoc));
  259. }
  260. if (subdocsBeingPopulated.length === 0) {
  261. modelNames = [handleRefFunction(ref, doc)];
  262. } else {
  263. modelNames = Array.from(modelNames);
  264. }
  265. } else {
  266. ref = handleRefFunction(ref, doc);
  267. modelNames = [ref];
  268. }
  269. } else if ((schemaForCurrentDoc = get(schema, 'options.refPath')) != null) {
  270. isRefPath = true;
  271. if (typeof refPath === 'function') {
  272. const subdocPath = options.path.slice(0, options.path.length - schemaForCurrentDoc.path.length - 1);
  273. const vals = mpath.get(subdocPath, doc, lookupLocalFields);
  274. const subdocsBeingPopulated = Array.isArray(vals) ?
  275. utils.array.flatten(vals) :
  276. (vals ? [vals] : []);
  277. modelNames = new Set();
  278. for (const subdoc of subdocsBeingPopulated) {
  279. refPath = refPath.call(subdoc, subdoc, options.path);
  280. modelNamesFromRefPath(refPath, doc, options.path, modelSchema, options._queryProjection).
  281. forEach(name => modelNames.add(name));
  282. }
  283. modelNames = Array.from(modelNames);
  284. } else {
  285. modelNames = modelNamesFromRefPath(refPath, doc, options.path, modelSchema, options._queryProjection);
  286. }
  287. }
  288. }
  289. if (!modelNames) {
  290. // `Model.populate()` on a POJO with no known local model. Default to using the `Model`
  291. if (options._localModel == null) {
  292. modelNames = [model.modelName];
  293. } else {
  294. return { modelNames: modelNames, justOne: justOne, isRefPath: isRefPath, refPath: refPath };
  295. }
  296. }
  297. if (!Array.isArray(modelNames)) {
  298. modelNames = [modelNames];
  299. }
  300. return { modelNames: modelNames, justOne: justOne, isRefPath: isRefPath, refPath: refPath };
  301. }
  302. };
  303. /*!
  304. * ignore
  305. */
  306. function _virtualPopulate(model, docs, options, _virtualRes) {
  307. const map = [];
  308. const available = {};
  309. const virtual = _virtualRes.virtual;
  310. for (const doc of docs) {
  311. let modelNames = null;
  312. const data = {};
  313. // localField and foreignField
  314. let localField;
  315. const virtualPrefix = _virtualRes.nestedSchemaPath ?
  316. _virtualRes.nestedSchemaPath + '.' : '';
  317. if (typeof virtual.options.localField === 'function') {
  318. localField = virtualPrefix + virtual.options.localField.call(doc, doc);
  319. } else if (Array.isArray(virtual.options.localField)) {
  320. localField = virtual.options.localField.map(field => virtualPrefix + field);
  321. } else {
  322. localField = virtualPrefix + virtual.options.localField;
  323. }
  324. data.count = virtual.options.count;
  325. if (virtual.options.skip != null && !options.hasOwnProperty('skip')) {
  326. options.skip = virtual.options.skip;
  327. }
  328. if (virtual.options.limit != null && !options.hasOwnProperty('limit')) {
  329. options.limit = virtual.options.limit;
  330. }
  331. if (virtual.options.perDocumentLimit != null && !options.hasOwnProperty('perDocumentLimit')) {
  332. options.perDocumentLimit = virtual.options.perDocumentLimit;
  333. }
  334. let foreignField = virtual.options.foreignField;
  335. if (!localField || !foreignField) {
  336. return new MongooseError('If you are populating a virtual, you must set the ' +
  337. 'localField and foreignField options');
  338. }
  339. if (typeof localField === 'function') {
  340. localField = localField.call(doc, doc);
  341. }
  342. if (typeof foreignField === 'function') {
  343. foreignField = foreignField.call(doc, doc);
  344. }
  345. data.isRefPath = false;
  346. // `justOne = null` means we don't know from the schema whether the end
  347. // result should be an array or a single doc. This can result from
  348. // populating a POJO using `Model.populate()`
  349. let justOne = null;
  350. if ('justOne' in options && options.justOne !== void 0) {
  351. justOne = options.justOne;
  352. }
  353. if (virtual.options.refPath) {
  354. modelNames =
  355. modelNamesFromRefPath(virtual.options.refPath, doc, options.path);
  356. justOne = !!virtual.options.justOne;
  357. data.isRefPath = true;
  358. } else if (virtual.options.ref) {
  359. let normalizedRef;
  360. if (typeof virtual.options.ref === 'function' && !virtual.options.ref[modelSymbol]) {
  361. normalizedRef = virtual.options.ref.call(doc, doc);
  362. } else {
  363. normalizedRef = virtual.options.ref;
  364. }
  365. justOne = !!virtual.options.justOne;
  366. // When referencing nested arrays, the ref should be an Array
  367. // of modelNames.
  368. if (Array.isArray(normalizedRef)) {
  369. modelNames = normalizedRef;
  370. } else {
  371. modelNames = [normalizedRef];
  372. }
  373. }
  374. data.isVirtual = true;
  375. data.virtual = virtual;
  376. data.justOne = justOne;
  377. // `match`
  378. let match = get(options, 'match', null) ||
  379. get(data, 'virtual.options.match', null) ||
  380. get(data, 'virtual.options.options.match', null);
  381. let hasMatchFunction = typeof match === 'function';
  382. if (hasMatchFunction) {
  383. match = match.call(doc, doc);
  384. }
  385. if (Array.isArray(localField) && Array.isArray(foreignField) && localField.length === foreignField.length) {
  386. match = Object.assign({}, match);
  387. for (let i = 1; i < localField.length; ++i) {
  388. match[foreignField[i]] = convertTo_id(mpath.get(localField[i], doc, lookupLocalFields), model.schema);
  389. hasMatchFunction = true;
  390. }
  391. localField = localField[0];
  392. foreignField = foreignField[0];
  393. }
  394. data.localField = localField;
  395. data.foreignField = foreignField;
  396. data.match = match;
  397. data.hasMatchFunction = hasMatchFunction;
  398. // Get local fields
  399. const ret = _getLocalFieldValues(doc, localField, model, options, virtual);
  400. try {
  401. addModelNamesToMap(model, map, available, modelNames, options, data, ret, doc);
  402. } catch (err) {
  403. return err;
  404. }
  405. }
  406. return map;
  407. }
  408. /*!
  409. * ignore
  410. */
  411. function addModelNamesToMap(model, map, available, modelNames, options, data, ret, doc, schemaOptions, unpopulatedValue) {
  412. // `PopulateOptions#connection`: if the model is passed as a string, the
  413. // connection matters because different connections have different models.
  414. const connection = options.connection != null ? options.connection : model.db;
  415. unpopulatedValue = unpopulatedValue === void 0 ? ret : unpopulatedValue;
  416. if (Array.isArray(unpopulatedValue)) {
  417. unpopulatedValue = utils.cloneArrays(unpopulatedValue);
  418. }
  419. if (modelNames == null) {
  420. return;
  421. }
  422. let k = modelNames.length;
  423. while (k--) {
  424. const modelName = modelNames[k];
  425. if (modelName == null) {
  426. continue;
  427. }
  428. let Model;
  429. if (options.model && options.model[modelSymbol]) {
  430. Model = options.model;
  431. } else if (modelName[modelSymbol]) {
  432. Model = modelName;
  433. } else {
  434. try {
  435. Model = _getModelFromConn(connection, modelName);
  436. } catch (err) {
  437. if (ret !== void 0) {
  438. throw err;
  439. }
  440. Model = null;
  441. }
  442. }
  443. let ids = ret;
  444. const flat = Array.isArray(ret) ? utils.array.flatten(ret) : [];
  445. const modelNamesForRefPath = data.modelNamesInOrder ? data.modelNamesInOrder : modelNames;
  446. if (data.isRefPath && Array.isArray(ret) && flat.length === modelNamesForRefPath.length) {
  447. ids = flat.filter((val, i) => modelNamesForRefPath[i] === modelName);
  448. }
  449. const perDocumentLimit = options.perDocumentLimit == null ?
  450. get(options, 'options.perDocumentLimit', null) :
  451. options.perDocumentLimit;
  452. if (!available[modelName] || perDocumentLimit != null) {
  453. const currentOptions = {
  454. model: Model
  455. };
  456. if (data.isVirtual && get(data.virtual, 'options.options')) {
  457. currentOptions.options = utils.clone(data.virtual.options.options);
  458. } else if (schemaOptions != null) {
  459. currentOptions.options = Object.assign({}, schemaOptions);
  460. }
  461. utils.merge(currentOptions, options);
  462. // Used internally for checking what model was used to populate this
  463. // path.
  464. options[populateModelSymbol] = Model;
  465. available[modelName] = {
  466. model: Model,
  467. options: currentOptions,
  468. match: data.hasMatchFunction ? [data.match] : data.match,
  469. docs: [doc],
  470. ids: [ids],
  471. allIds: [ret],
  472. unpopulatedValues: [unpopulatedValue],
  473. localField: new Set([data.localField]),
  474. foreignField: new Set([data.foreignField]),
  475. justOne: data.justOne,
  476. isVirtual: data.isVirtual,
  477. virtual: data.virtual,
  478. count: data.count,
  479. [populateModelSymbol]: Model
  480. };
  481. map.push(available[modelName]);
  482. } else {
  483. available[modelName].localField.add(data.localField);
  484. available[modelName].foreignField.add(data.foreignField);
  485. available[modelName].docs.push(doc);
  486. available[modelName].ids.push(ids);
  487. available[modelName].allIds.push(ret);
  488. available[modelName].unpopulatedValues.push(unpopulatedValue);
  489. if (data.hasMatchFunction) {
  490. available[modelName].match.push(data.match);
  491. }
  492. }
  493. }
  494. }
  495. function _getModelFromConn(conn, modelName) {
  496. /* If this connection has a parent from `useDb()`, bubble up to parent's models */
  497. if (conn.models[modelName] == null && conn._parent != null) {
  498. return _getModelFromConn(conn._parent, modelName);
  499. }
  500. return conn.model(modelName);
  501. }
  502. /*!
  503. * ignore
  504. */
  505. function handleRefFunction(ref, doc) {
  506. if (typeof ref === 'function' && !ref[modelSymbol]) {
  507. return ref.call(doc, doc);
  508. }
  509. return ref;
  510. }
  511. /*!
  512. * ignore
  513. */
  514. function _getLocalFieldValues(doc, localField, model, options, virtual, schema) {
  515. // Get Local fields
  516. const localFieldPathType = model.schema._getPathType(localField);
  517. const localFieldPath = localFieldPathType === 'real' ?
  518. model.schema.path(localField) :
  519. localFieldPathType.schema;
  520. const localFieldGetters = localFieldPath && localFieldPath.getters ?
  521. localFieldPath.getters : [];
  522. localField = localFieldPath != null && localFieldPath.instance === 'Embedded' ? localField + '._id' : localField;
  523. const _populateOptions = get(options, 'options', {});
  524. const getters = 'getters' in _populateOptions ?
  525. _populateOptions.getters :
  526. get(virtual, 'options.getters', false);
  527. if (localFieldGetters.length !== 0 && getters) {
  528. const hydratedDoc = (doc.$__ != null) ? doc : model.hydrate(doc);
  529. const localFieldValue = utils.getValue(localField, doc);
  530. if (Array.isArray(localFieldValue)) {
  531. const localFieldHydratedValue = utils.getValue(localField.split('.').slice(0, -1), hydratedDoc);
  532. return localFieldValue.map((localFieldArrVal, localFieldArrIndex) =>
  533. localFieldPath.applyGetters(localFieldArrVal, localFieldHydratedValue[localFieldArrIndex]));
  534. } else {
  535. return localFieldPath.applyGetters(localFieldValue, hydratedDoc);
  536. }
  537. } else {
  538. return convertTo_id(mpath.get(localField, doc, lookupLocalFields), schema);
  539. }
  540. }
  541. /*!
  542. * Retrieve the _id of `val` if a Document or Array of Documents.
  543. *
  544. * @param {Array|Document|Any} val
  545. * @return {Array|Document|Any}
  546. */
  547. function convertTo_id(val, schema) {
  548. if (val != null && val.$__ != null) {
  549. return val._id;
  550. }
  551. if (val != null && val._id != null && (schema == null || !schema.$isSchemaMap)) {
  552. return val._id;
  553. }
  554. if (Array.isArray(val)) {
  555. const rawVal = val.__array != null ? val.__array : val;
  556. for (let i = 0; i < rawVal.length; ++i) {
  557. if (rawVal[i] != null && rawVal[i].$__ != null) {
  558. rawVal[i] = rawVal[i]._id;
  559. }
  560. }
  561. if (utils.isMongooseArray(val) && val.$schema()) {
  562. return val.$schema()._castForPopulate(val, val.$parent());
  563. }
  564. return [].concat(val);
  565. }
  566. // `populate('map')` may be an object if populating on a doc that hasn't
  567. // been hydrated yet
  568. if (getConstructorName(val) === 'Object' &&
  569. // The intent here is we should only flatten the object if we expect
  570. // to get a Map in the end. Avoid doing this for mixed types.
  571. (schema == null || schema[schemaMixedSymbol] == null)) {
  572. const ret = [];
  573. for (const key of Object.keys(val)) {
  574. ret.push(val[key]);
  575. }
  576. return ret;
  577. }
  578. // If doc has already been hydrated, e.g. `doc.populate('map')`
  579. // then `val` will already be a map
  580. if (val instanceof Map) {
  581. return Array.from(val.values());
  582. }
  583. return val;
  584. }
  585. /*!
  586. * ignore
  587. */
  588. function _findRefPathForDiscriminators(doc, modelSchema, data, options, normalizedRefPath, ret) {
  589. // Re: gh-8452. Embedded discriminators may not have `refPath`, so clear
  590. // out embedded discriminator docs that don't have a `refPath` on the
  591. // populated path.
  592. if (!data.isRefPath || normalizedRefPath == null) {
  593. return;
  594. }
  595. const pieces = normalizedRefPath.split('.');
  596. let cur = '';
  597. let modelNames = void 0;
  598. for (let i = 0; i < pieces.length; ++i) {
  599. const piece = pieces[i];
  600. cur = cur + (cur.length === 0 ? '' : '.') + piece;
  601. const schematype = modelSchema.path(cur);
  602. if (schematype != null &&
  603. schematype.$isMongooseArray &&
  604. schematype.caster.discriminators != null &&
  605. Object.keys(schematype.caster.discriminators).length !== 0) {
  606. const subdocs = utils.getValue(cur, doc);
  607. const remnant = options.path.substring(cur.length + 1);
  608. const discriminatorKey = schematype.caster.schema.options.discriminatorKey;
  609. modelNames = [];
  610. for (const subdoc of subdocs) {
  611. const discriminatorName = utils.getValue(discriminatorKey, subdoc);
  612. const discriminator = schematype.caster.discriminators[discriminatorName];
  613. const discriminatorSchema = discriminator && discriminator.schema;
  614. if (discriminatorSchema == null) {
  615. continue;
  616. }
  617. const _path = discriminatorSchema.path(remnant);
  618. if (_path == null || _path.options.refPath == null) {
  619. const docValue = utils.getValue(data.localField.substring(cur.length + 1), subdoc);
  620. ret.forEach((v, i) => {
  621. if (v === docValue) {
  622. ret[i] = SkipPopulateValue(v);
  623. }
  624. });
  625. continue;
  626. }
  627. const modelName = utils.getValue(pieces.slice(i + 1).join('.'), subdoc);
  628. modelNames.push(modelName);
  629. }
  630. }
  631. }
  632. return modelNames;
  633. }