1 | 'use strict';
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | const get = require('lodash.get');
|
8 | const isDefiningProjection = require('./helpers/projection/isDefiningProjection');
|
9 | const utils = require('./utils');
|
10 |
|
11 | /*!
|
12 | * Prepare a set of path options for query population.
|
13 | *
|
14 | * @param {Query} query
|
15 | * @param {Object} options
|
16 | * @return {Array}
|
17 | */
|
18 |
|
19 | exports.preparePopulationOptions = function preparePopulationOptions(query, options) {
|
20 | const pop = utils.object.vals(query.options.populate);
|
21 |
|
22 |
|
23 | if (options.lean != null) {
|
24 | pop.
|
25 | filter(p => get(p, 'options.lean') == null).
|
26 | forEach(makeLean(options.lean));
|
27 | }
|
28 |
|
29 | return pop;
|
30 | };
|
31 |
|
32 | /*!
|
33 | * Prepare a set of path options for query population. This is the MongooseQuery
|
34 | * version
|
35 | *
|
36 | * @param {Query} query
|
37 | * @param {Object} options
|
38 | * @return {Array}
|
39 | */
|
40 |
|
41 | exports.preparePopulationOptionsMQ = function preparePopulationOptionsMQ(query, options) {
|
42 | const pop = utils.object.vals(query._mongooseOptions.populate);
|
43 |
|
44 |
|
45 | if (options.lean != null) {
|
46 | pop.
|
47 | filter(p => get(p, 'options.lean') == null).
|
48 | forEach(makeLean(options.lean));
|
49 | }
|
50 |
|
51 | const session = get(query, 'options.session', null);
|
52 | if (session != null) {
|
53 | pop.forEach(path => {
|
54 | if (path.options == null) {
|
55 | path.options = { session: session };
|
56 | return;
|
57 | }
|
58 | if (!('session' in path.options)) {
|
59 | path.options.session = session;
|
60 | }
|
61 | });
|
62 | }
|
63 |
|
64 | const projection = query._fieldsForExec();
|
65 | pop.forEach(p => {
|
66 | p._queryProjection = projection;
|
67 | });
|
68 |
|
69 | return pop;
|
70 | };
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 | function getDiscriminatorByValue(model, value) {
|
80 | let discriminator = null;
|
81 | if (!model.discriminators) {
|
82 | return discriminator;
|
83 | }
|
84 | for (const name in model.discriminators) {
|
85 | const it = model.discriminators[name];
|
86 | if (
|
87 | it.schema &&
|
88 | it.schema.discriminatorMapping &&
|
89 | it.schema.discriminatorMapping.value == value
|
90 | ) {
|
91 | discriminator = it;
|
92 | break;
|
93 | }
|
94 | }
|
95 | return discriminator;
|
96 | }
|
97 |
|
98 | exports.getDiscriminatorByValue = getDiscriminatorByValue;
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 | exports.createModel = function createModel(model, doc, fields, userProvidedFields) {
|
111 | model.hooks.execPreSync('createModel', doc);
|
112 | const discriminatorMapping = model.schema ?
|
113 | model.schema.discriminatorMapping :
|
114 | null;
|
115 |
|
116 | const key = discriminatorMapping && discriminatorMapping.isRoot ?
|
117 | discriminatorMapping.key :
|
118 | null;
|
119 |
|
120 | const value = doc[key];
|
121 | if (key && value && model.discriminators) {
|
122 | const discriminator = model.discriminators[value] || getDiscriminatorByValue(model, value);
|
123 | if (discriminator) {
|
124 | const _fields = utils.clone(userProvidedFields);
|
125 | exports.applyPaths(_fields, discriminator.schema);
|
126 | return new discriminator(undefined, _fields, true);
|
127 | }
|
128 | }
|
129 |
|
130 | const skipDefaults = gatherPaths(doc, {}, '');
|
131 | return new model(undefined, fields, {
|
132 | skipId: true,
|
133 | isNew: false,
|
134 | skipDefaults: skipDefaults
|
135 | });
|
136 | };
|
137 |
|
138 |
|
139 |
|
140 |
|
141 |
|
142 | function gatherPaths(obj, map, path) {
|
143 | for (const key of Object.keys(obj)) {
|
144 | const fullPath = path ? path + '.' + key : key;
|
145 | map[fullPath] = true;
|
146 | if (obj[key] != null &&
|
147 | typeof obj[key] === 'object' &&
|
148 | !Array.isArray(obj) &&
|
149 | !(obj instanceof Map) &&
|
150 | !obj[key]._bsontype &&
|
151 | !utils.isMongooseObject(obj[key])) {
|
152 | gatherPaths(obj[key], map, fullPath);
|
153 | }
|
154 | }
|
155 |
|
156 | return map;
|
157 | }
|
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 | exports.applyPaths = function applyPaths(fields, schema) {
|
164 |
|
165 | let exclude;
|
166 | let keys;
|
167 | let ki;
|
168 | let field;
|
169 |
|
170 | if (fields) {
|
171 | keys = Object.keys(fields);
|
172 | ki = keys.length;
|
173 |
|
174 | while (ki--) {
|
175 | if (keys[ki][0] === '+') {
|
176 | continue;
|
177 | }
|
178 | field = fields[keys[ki]];
|
179 |
|
180 | if (!isDefiningProjection(field)) {
|
181 | continue;
|
182 | }
|
183 | exclude = field === 0;
|
184 | break;
|
185 | }
|
186 | }
|
187 |
|
188 |
|
189 |
|
190 |
|
191 | const selected = [];
|
192 | const excluded = [];
|
193 | const stack = [];
|
194 |
|
195 | const analyzePath = function(path, type) {
|
196 | const plusPath = '+' + path;
|
197 | const hasPlusPath = fields && plusPath in fields;
|
198 | if (hasPlusPath) {
|
199 |
|
200 | delete fields[plusPath];
|
201 | }
|
202 |
|
203 | if (typeof type.selected !== 'boolean') return;
|
204 |
|
205 | if (hasPlusPath) {
|
206 |
|
207 | delete fields[plusPath];
|
208 |
|
209 |
|
210 |
|
211 | if (exclude === false && keys.length > 1 && !~keys.indexOf(path)) {
|
212 | fields[path] = 1;
|
213 | }
|
214 |
|
215 | return;
|
216 | }
|
217 |
|
218 |
|
219 | const pieces = path.split('.');
|
220 | const root = pieces[0];
|
221 | if (~excluded.indexOf(root)) {
|
222 | return;
|
223 | }
|
224 |
|
225 |
|
226 |
|
227 |
|
228 | if (!exclude && get(type, 'options.$skipDiscriminatorCheck', false)) {
|
229 | let cur = '';
|
230 | for (let i = 0; i < pieces.length; ++i) {
|
231 | cur += (cur.length === 0 ? '' : '.') + pieces[i];
|
232 | const projection = get(fields, cur, false);
|
233 | if (projection && typeof projection !== 'object') {
|
234 | return;
|
235 | }
|
236 | }
|
237 | }
|
238 |
|
239 | (type.selected ? selected : excluded).push(path);
|
240 | };
|
241 |
|
242 | analyzeSchema(schema);
|
243 |
|
244 | switch (exclude) {
|
245 | case true:
|
246 | for (let i = 0; i < excluded.length; ++i) {
|
247 | fields[excluded[i]] = 0;
|
248 | }
|
249 | break;
|
250 | case false:
|
251 | if (schema &&
|
252 | schema.paths['_id'] &&
|
253 | schema.paths['_id'].options &&
|
254 | schema.paths['_id'].options.select === false) {
|
255 | fields._id = 0;
|
256 | }
|
257 | for (let i = 0; i < selected.length; ++i) {
|
258 | fields[selected[i]] = 1;
|
259 | }
|
260 | break;
|
261 | case undefined:
|
262 | if (fields == null) {
|
263 | break;
|
264 | }
|
265 |
|
266 | for (const key of Object.keys(fields || {})) {
|
267 | if (key.charAt(0) === '+') {
|
268 | delete fields[key];
|
269 | }
|
270 | }
|
271 |
|
272 |
|
273 |
|
274 | for (let i = 0; i < excluded.length; ++i) {
|
275 | fields[excluded[i]] = 0;
|
276 | }
|
277 | break;
|
278 | }
|
279 |
|
280 | function analyzeSchema(schema, prefix) {
|
281 | prefix || (prefix = '');
|
282 |
|
283 |
|
284 | if (stack.indexOf(schema) !== -1) {
|
285 | return;
|
286 | }
|
287 | stack.push(schema);
|
288 |
|
289 | schema.eachPath(function(path, type) {
|
290 | if (prefix) path = prefix + '.' + path;
|
291 |
|
292 | analyzePath(path, type);
|
293 |
|
294 |
|
295 | if (type.schema) {
|
296 | analyzeSchema(type.schema, path);
|
297 | }
|
298 | });
|
299 |
|
300 | stack.pop();
|
301 | }
|
302 | };
|
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 |
|
309 |
|
310 | function makeLean(val) {
|
311 | return function(option) {
|
312 | option.options || (option.options = {});
|
313 | option.options.lean = val;
|
314 | };
|
315 | }
|
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 | exports.handleWriteOpResult = function handleWriteOpResult(callback) {
|
322 | return function _handleWriteOpResult(error, res) {
|
323 | if (error) {
|
324 | return callback(error);
|
325 | }
|
326 | return callback(null, res.result);
|
327 | };
|
328 | };
|