UNPKG

7.89 kBJavaScriptView Raw
1'use strict';
2
3/*!
4 * Module dependencies
5 */
6
7const get = require('lodash.get');
8const isDefiningProjection = require('./helpers/projection/isDefiningProjection');
9const 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
19exports.preparePopulationOptions = function preparePopulationOptions(query, options) {
20 const pop = utils.object.vals(query.options.populate);
21
22 // lean options should trickle through all queries
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
41exports.preparePopulationOptionsMQ = function preparePopulationOptionsMQ(query, options) {
42 const pop = utils.object.vals(query._mongooseOptions.populate);
43
44 // lean options should trickle through all queries
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 * returns discriminator by discriminatorMapping.value
75 *
76 * @param {Model} model
77 * @param {string} value
78 */
79function 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
98exports.getDiscriminatorByValue = getDiscriminatorByValue;
99
100/*!
101 * If the document is a mapped discriminator type, it returns a model instance for that type, otherwise,
102 * it returns an instance of the given model.
103 *
104 * @param {Model} model
105 * @param {Object} doc
106 * @param {Object} fields
107 *
108 * @return {Model}
109 */
110exports.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
142function 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 * ignore
161 */
162
163exports.applyPaths = function applyPaths(fields, schema) {
164 // determine if query is selecting or excluding fields
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 // Skip `$meta` and `$slice`
180 if (!isDefiningProjection(field)) {
181 continue;
182 }
183 exclude = field === 0;
184 break;
185 }
186 }
187
188 // if selecting, apply default schematype select:true fields
189 // if excluding, apply schematype select:false fields
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 // forced inclusion
200 delete fields[plusPath];
201 }
202
203 if (typeof type.selected !== 'boolean') return;
204
205 if (hasPlusPath) {
206 // forced inclusion
207 delete fields[plusPath];
208
209 // if there are other fields being included, add this one
210 // if no other included fields, leave this out (implied inclusion)
211 if (exclude === false && keys.length > 1 && !~keys.indexOf(path)) {
212 fields[path] = 1;
213 }
214
215 return;
216 }
217
218 // check for parent exclusions
219 const pieces = path.split('.');
220 const root = pieces[0];
221 if (~excluded.indexOf(root)) {
222 return;
223 }
224
225 // Special case: if user has included a parent path of a discriminator key,
226 // don't explicitly project in the discriminator key because that will
227 // project out everything else under the parent path
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 // Any leftover plus paths must in the schema, so delete them (gh-7017)
266 for (const key of Object.keys(fields || {})) {
267 if (key.charAt(0) === '+') {
268 delete fields[key];
269 }
270 }
271
272 // user didn't specify fields, implies returning all fields.
273 // only need to apply excluded fields and delete any plus paths
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 // avoid recursion
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 // array of subdocs?
295 if (type.schema) {
296 analyzeSchema(type.schema, path);
297 }
298 });
299
300 stack.pop();
301 }
302};
303
304/*!
305 * Set each path query option to lean
306 *
307 * @param {Object} option
308 */
309
310function makeLean(val) {
311 return function(option) {
312 option.options || (option.options = {});
313 option.options.lean = val;
314 };
315}
316
317/*!
318 * Handle the `WriteOpResult` from the server
319 */
320
321exports.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};