1 | 'use strict';
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | const checkEmbeddedDiscriminatorKeyProjection =
|
8 | require('./helpers/discriminator/checkEmbeddedDiscriminatorKeyProjection');
|
9 | const get = require('./helpers/get');
|
10 | const getDiscriminatorByValue =
|
11 | require('./helpers/discriminator/getDiscriminatorByValue');
|
12 | const isDefiningProjection = require('./helpers/projection/isDefiningProjection');
|
13 | const clone = require('./helpers/clone');
|
14 | const isPathSelectedInclusive = require('./helpers/projection/isPathSelectedInclusive');
|
15 |
|
16 | /**
|
17 | * Prepare a set of path options for query population.
|
18 | *
|
19 | * @param {Query} query
|
20 | * @param {Object} options
|
21 | * @return {Array}
|
22 | */
|
23 |
|
24 | exports.preparePopulationOptions = function preparePopulationOptions(query, options) {
|
25 | const _populate = query.options.populate;
|
26 | const pop = Object.keys(_populate).reduce((vals, key) => vals.concat([_populate[key]]), []);
|
27 |
|
28 |
|
29 | if (options.lean != null) {
|
30 | pop
|
31 | .filter(p => (p && p.options && p.options.lean) == null)
|
32 | .forEach(makeLean(options.lean));
|
33 | }
|
34 |
|
35 | pop.forEach(opts => {
|
36 | opts._localModel = query.model;
|
37 | });
|
38 |
|
39 | return pop;
|
40 | };
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 | exports.preparePopulationOptionsMQ = function preparePopulationOptionsMQ(query, options) {
|
52 | const _populate = query._mongooseOptions.populate;
|
53 | const pop = Object.keys(_populate).reduce((vals, key) => vals.concat([_populate[key]]), []);
|
54 |
|
55 |
|
56 | if (options.lean != null) {
|
57 | pop
|
58 | .filter(p => (p && p.options && p.options.lean) == null)
|
59 | .forEach(makeLean(options.lean));
|
60 | }
|
61 |
|
62 | const session = query && query.options && query.options.session || null;
|
63 | if (session != null) {
|
64 | pop.forEach(path => {
|
65 | if (path.options == null) {
|
66 | path.options = { session: session };
|
67 | return;
|
68 | }
|
69 | if (!('session' in path.options)) {
|
70 | path.options.session = session;
|
71 | }
|
72 | });
|
73 | }
|
74 |
|
75 | const projection = query._fieldsForExec();
|
76 | pop.forEach(p => {
|
77 | p._queryProjection = projection;
|
78 | });
|
79 | pop.forEach(opts => {
|
80 | opts._localModel = query.model;
|
81 | });
|
82 |
|
83 | return pop;
|
84 | };
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 | exports.createModel = function createModel(model, doc, fields, userProvidedFields, options) {
|
97 | model.hooks.execPreSync('createModel', doc);
|
98 | const discriminatorMapping = model.schema ?
|
99 | model.schema.discriminatorMapping :
|
100 | null;
|
101 |
|
102 | const key = discriminatorMapping && discriminatorMapping.isRoot ?
|
103 | discriminatorMapping.key :
|
104 | null;
|
105 |
|
106 | const value = doc[key];
|
107 | if (key && value && model.discriminators) {
|
108 | const discriminator = model.discriminators[value] || getDiscriminatorByValue(model.discriminators, value);
|
109 | if (discriminator) {
|
110 | const _fields = clone(userProvidedFields);
|
111 | exports.applyPaths(_fields, discriminator.schema);
|
112 | return new discriminator(undefined, _fields, true);
|
113 | }
|
114 | }
|
115 |
|
116 | const _opts = {
|
117 | skipId: true,
|
118 | isNew: false,
|
119 | willInit: true
|
120 | };
|
121 | if (options != null && 'defaults' in options) {
|
122 | _opts.defaults = options.defaults;
|
123 | }
|
124 | return new model(undefined, fields, _opts);
|
125 | };
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 | exports.createModelAndInit = function createModelAndInit(model, doc, fields, userProvidedFields, options, populatedIds, callback) {
|
132 | const initOpts = populatedIds ?
|
133 | { populated: populatedIds } :
|
134 | undefined;
|
135 |
|
136 | const casted = exports.createModel(model, doc, fields, userProvidedFields, options);
|
137 | try {
|
138 | casted.$init(doc, initOpts, callback);
|
139 | } catch (error) {
|
140 | callback(error, casted);
|
141 | }
|
142 | };
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 | exports.applyPaths = function applyPaths(fields, schema, sanitizeProjection) {
|
149 |
|
150 | let exclude;
|
151 | let keys;
|
152 | const minusPathsToSkip = new Set();
|
153 |
|
154 | if (fields) {
|
155 | keys = Object.keys(fields);
|
156 |
|
157 |
|
158 | const minusPaths = [];
|
159 | for (let i = 0; i < keys.length; ++i) {
|
160 | const key = keys[i];
|
161 | if (keys[i][0] !== '-') {
|
162 | continue;
|
163 | }
|
164 |
|
165 | delete fields[key];
|
166 | if (key === '-_id') {
|
167 | fields['_id'] = 0;
|
168 | } else {
|
169 | minusPaths.push(key.slice(1));
|
170 | }
|
171 | }
|
172 |
|
173 | keys = Object.keys(fields);
|
174 | for (let keyIndex = 0; keyIndex < keys.length; ++keyIndex) {
|
175 | if (keys[keyIndex][0] === '+') {
|
176 | continue;
|
177 | }
|
178 | const field = fields[keys[keyIndex]];
|
179 |
|
180 | if (!isDefiningProjection(field)) {
|
181 | continue;
|
182 | }
|
183 | if (keys[keyIndex] === '_id' && keys.length > 1) {
|
184 | continue;
|
185 | }
|
186 | if (keys[keyIndex] === schema.options.discriminatorKey && keys.length > 1 && field != null && !field) {
|
187 | continue;
|
188 | }
|
189 | exclude = !field;
|
190 | break;
|
191 | }
|
192 |
|
193 |
|
194 |
|
195 | for (const path of minusPaths) {
|
196 | const type = schema.path(path);
|
197 |
|
198 |
|
199 |
|
200 |
|
201 | if ((!type || !type.selected) || exclude !== false) {
|
202 | fields[path] = 0;
|
203 | exclude = true;
|
204 | } else if (type && type.selected && exclude === false) {
|
205 |
|
206 |
|
207 | minusPathsToSkip.add(path);
|
208 | }
|
209 | }
|
210 | }
|
211 |
|
212 |
|
213 |
|
214 | const selected = [];
|
215 | const excluded = [];
|
216 | const stack = [];
|
217 |
|
218 | analyzeSchema(schema);
|
219 | switch (exclude) {
|
220 | case true:
|
221 | for (const fieldName of excluded) {
|
222 | fields[fieldName] = 0;
|
223 | }
|
224 | break;
|
225 | case false:
|
226 | if (schema &&
|
227 | schema.paths['_id'] &&
|
228 | schema.paths['_id'].options &&
|
229 | schema.paths['_id'].options.select === false) {
|
230 | fields._id = 0;
|
231 | }
|
232 |
|
233 | for (const fieldName of selected) {
|
234 | if (minusPathsToSkip.has(fieldName)) {
|
235 | continue;
|
236 | }
|
237 | if (isPathSelectedInclusive(fields, fieldName)) {
|
238 | continue;
|
239 | }
|
240 | fields[fieldName] = fields[fieldName] || 1;
|
241 | }
|
242 | break;
|
243 | case undefined:
|
244 | if (fields == null) {
|
245 | break;
|
246 | }
|
247 |
|
248 | for (const key of Object.keys(fields || {})) {
|
249 | if (key.startsWith('+')) {
|
250 | delete fields[key];
|
251 | }
|
252 | }
|
253 |
|
254 |
|
255 |
|
256 | for (const fieldName of excluded) {
|
257 | if (fields[fieldName] != null) {
|
258 |
|
259 |
|
260 | continue;
|
261 | }
|
262 | fields[fieldName] = 0;
|
263 | }
|
264 | break;
|
265 | }
|
266 |
|
267 | function analyzeSchema(schema, prefix) {
|
268 | prefix || (prefix = '');
|
269 |
|
270 |
|
271 | if (stack.indexOf(schema) !== -1) {
|
272 | return [];
|
273 | }
|
274 | stack.push(schema);
|
275 |
|
276 | const addedPaths = [];
|
277 | schema.eachPath(function(path, type) {
|
278 | if (prefix) path = prefix + '.' + path;
|
279 | if (type.$isSchemaMap || path.endsWith('.$*')) {
|
280 | const plusPath = '+' + path;
|
281 | const hasPlusPath = fields && plusPath in fields;
|
282 | if (type.options && type.options.select === false && !hasPlusPath) {
|
283 | excluded.push(path);
|
284 | }
|
285 | return;
|
286 | }
|
287 | let addedPath = analyzePath(path, type);
|
288 |
|
289 | if (addedPath == null && !Array.isArray(type) && type.$isMongooseArray && !type.$isMongooseDocumentArray) {
|
290 | addedPath = analyzePath(path, type.caster);
|
291 | }
|
292 | if (addedPath != null) {
|
293 | addedPaths.push(addedPath);
|
294 | }
|
295 |
|
296 |
|
297 | if (type.schema) {
|
298 | const _addedPaths = analyzeSchema(type.schema, path);
|
299 |
|
300 |
|
301 |
|
302 | if (exclude === false) {
|
303 | checkEmbeddedDiscriminatorKeyProjection(fields, path, type.schema,
|
304 | selected, _addedPaths);
|
305 | }
|
306 | }
|
307 | });
|
308 | stack.pop();
|
309 | return addedPaths;
|
310 | }
|
311 |
|
312 | function analyzePath(path, type) {
|
313 | if (fields == null) {
|
314 | return;
|
315 | }
|
316 |
|
317 |
|
318 | if (typeof type.selected !== 'boolean') {
|
319 | return;
|
320 | }
|
321 |
|
322 |
|
323 | if (type.selected === false && fields[path]) {
|
324 | if (sanitizeProjection) {
|
325 | fields[path] = 0;
|
326 | }
|
327 |
|
328 | return;
|
329 | }
|
330 |
|
331 |
|
332 |
|
333 |
|
334 | if (!exclude && type.selected && path === schema.options.discriminatorKey && fields[path] != null && !fields[path]) {
|
335 | delete fields[path];
|
336 | return;
|
337 | }
|
338 |
|
339 | if (exclude === false && type.selected && fields[path] != null && !fields[path]) {
|
340 | delete fields[path];
|
341 | return;
|
342 | }
|
343 |
|
344 | const plusPath = '+' + path;
|
345 | const hasPlusPath = fields && plusPath in fields;
|
346 | if (hasPlusPath) {
|
347 |
|
348 | delete fields[plusPath];
|
349 |
|
350 |
|
351 |
|
352 | if (exclude === false && keys.length > 1 && !~keys.indexOf(path) && !sanitizeProjection) {
|
353 | fields[path] = 1;
|
354 | } else if (exclude == null && sanitizeProjection && type.selected === false) {
|
355 | fields[path] = 0;
|
356 | }
|
357 |
|
358 | return;
|
359 | }
|
360 |
|
361 |
|
362 | const pieces = path.split('.');
|
363 | let cur = '';
|
364 | for (let i = 0; i < pieces.length; ++i) {
|
365 | cur += cur.length ? '.' + pieces[i] : pieces[i];
|
366 | if (excluded.indexOf(cur) !== -1) {
|
367 | return;
|
368 | }
|
369 | }
|
370 |
|
371 |
|
372 |
|
373 |
|
374 | if (!exclude && (type && type.options && type.options.$skipDiscriminatorCheck || false)) {
|
375 | let cur = '';
|
376 | for (let i = 0; i < pieces.length; ++i) {
|
377 | cur += (cur.length === 0 ? '' : '.') + pieces[i];
|
378 | const projection = get(fields, cur, false) || get(fields, cur + '.$', false);
|
379 | if (projection && typeof projection !== 'object') {
|
380 | return;
|
381 | }
|
382 | }
|
383 | }
|
384 |
|
385 | (type.selected ? selected : excluded).push(path);
|
386 | return path;
|
387 | }
|
388 | };
|
389 |
|
390 |
|
391 |
|
392 |
|
393 |
|
394 |
|
395 |
|
396 | function makeLean(val) {
|
397 | return function(option) {
|
398 | option.options || (option.options = {});
|
399 |
|
400 | if (val != null && Array.isArray(val.virtuals)) {
|
401 | val = Object.assign({}, val);
|
402 | val.virtuals = val.virtuals.
|
403 | filter(path => typeof path === 'string' && path.startsWith(option.path + '.')).
|
404 | map(path => path.slice(option.path.length + 1));
|
405 | }
|
406 |
|
407 | option.options.lean = val;
|
408 | };
|
409 | }
|