UNPKG

8 kBJavaScriptView Raw
1'use strict';
2
3/*!
4 * Module dependencies
5 */
6
7const checkEmbeddedDiscriminatorKeyProjection =
8 require('./helpers/discriminator/checkEmbeddedDiscriminatorKeyProjection');
9const get = require('./helpers/get');
10const getDiscriminatorByValue =
11 require('./helpers/discriminator/getDiscriminatorByValue');
12const isDefiningProjection = require('./helpers/projection/isDefiningProjection');
13const clone = require('./helpers/clone');
14
15/*!
16 * Prepare a set of path options for query population.
17 *
18 * @param {Query} query
19 * @param {Object} options
20 * @return {Array}
21 */
22
23exports.preparePopulationOptions = function preparePopulationOptions(query, options) {
24 const _populate = query.options.populate;
25 const pop = Object.keys(_populate).reduce((vals, key) => vals.concat([_populate[key]]), []);
26
27 // lean options should trickle through all queries
28 if (options.lean != null) {
29 pop.
30 filter(p => get(p, 'options.lean') == null).
31 forEach(makeLean(options.lean));
32 }
33
34 return pop;
35};
36
37/*!
38 * Prepare a set of path options for query population. This is the MongooseQuery
39 * version
40 *
41 * @param {Query} query
42 * @param {Object} options
43 * @return {Array}
44 */
45
46exports.preparePopulationOptionsMQ = function preparePopulationOptionsMQ(query, options) {
47 const _populate = query._mongooseOptions.populate;
48 const pop = Object.keys(_populate).reduce((vals, key) => vals.concat([_populate[key]]), []);
49
50 // lean options should trickle through all queries
51 if (options.lean != null) {
52 pop.
53 filter(p => get(p, 'options.lean') == null).
54 forEach(makeLean(options.lean));
55 }
56
57 const session = get(query, 'options.session', null);
58 if (session != null) {
59 pop.forEach(path => {
60 if (path.options == null) {
61 path.options = { session: session };
62 return;
63 }
64 if (!('session' in path.options)) {
65 path.options.session = session;
66 }
67 });
68 }
69
70 const projection = query._fieldsForExec();
71 pop.forEach(p => {
72 p._queryProjection = projection;
73 });
74
75 return pop;
76};
77
78/*!
79 * If the document is a mapped discriminator type, it returns a model instance for that type, otherwise,
80 * it returns an instance of the given model.
81 *
82 * @param {Model} model
83 * @param {Object} doc
84 * @param {Object} fields
85 *
86 * @return {Document}
87 */
88exports.createModel = function createModel(model, doc, fields, userProvidedFields) {
89 model.hooks.execPreSync('createModel', doc);
90 const discriminatorMapping = model.schema ?
91 model.schema.discriminatorMapping :
92 null;
93
94 const key = discriminatorMapping && discriminatorMapping.isRoot ?
95 discriminatorMapping.key :
96 null;
97
98 const value = doc[key];
99 if (key && value && model.discriminators) {
100 const discriminator = model.discriminators[value] || getDiscriminatorByValue(model, value);
101 if (discriminator) {
102 const _fields = clone(userProvidedFields);
103 exports.applyPaths(_fields, discriminator.schema);
104 return new discriminator(undefined, _fields, true);
105 }
106 }
107
108 return new model(undefined, fields, {
109 skipId: true,
110 isNew: false,
111 willInit: true
112 });
113};
114
115/*!
116 * ignore
117 */
118
119exports.applyPaths = function applyPaths(fields, schema) {
120 // determine if query is selecting or excluding fields
121 let exclude;
122 let keys;
123 let keyIndex;
124
125 if (fields) {
126 keys = Object.keys(fields);
127 keyIndex = keys.length;
128
129 while (keyIndex--) {
130 if (keys[keyIndex][0] === '+') {
131 continue;
132 }
133 const field = fields[keys[keyIndex]];
134 // Skip `$meta` and `$slice`
135 if (!isDefiningProjection(field)) {
136 continue;
137 }
138 exclude = !field;
139 break;
140 }
141 }
142
143 // if selecting, apply default schematype select:true fields
144 // if excluding, apply schematype select:false fields
145
146 const selected = [];
147 const excluded = [];
148 const stack = [];
149
150 analyzeSchema(schema);
151
152 switch (exclude) {
153 case true:
154 for (const fieldName of excluded) {
155 fields[fieldName] = 0;
156 }
157 break;
158 case false:
159 if (schema &&
160 schema.paths['_id'] &&
161 schema.paths['_id'].options &&
162 schema.paths['_id'].options.select === false) {
163 fields._id = 0;
164 }
165
166 for (const fieldName of selected) {
167 fields[fieldName] = fields[fieldName] || 1;
168 }
169 break;
170 case undefined:
171 if (fields == null) {
172 break;
173 }
174 // Any leftover plus paths must in the schema, so delete them (gh-7017)
175 for (const key of Object.keys(fields || {})) {
176 if (key.startsWith('+')) {
177 delete fields[key];
178 }
179 }
180
181 // user didn't specify fields, implies returning all fields.
182 // only need to apply excluded fields and delete any plus paths
183 for (const fieldName of excluded) {
184 fields[fieldName] = 0;
185 }
186 break;
187 }
188
189 function analyzeSchema(schema, prefix) {
190 prefix || (prefix = '');
191
192 // avoid recursion
193 if (stack.indexOf(schema) !== -1) {
194 return [];
195 }
196 stack.push(schema);
197
198 const addedPaths = [];
199 schema.eachPath(function(path, type) {
200 if (prefix) path = prefix + '.' + path;
201
202 const addedPath = analyzePath(path, type);
203 if (addedPath != null) {
204 addedPaths.push(addedPath);
205 }
206
207 // nested schemas
208 if (type.schema) {
209 const _addedPaths = analyzeSchema(type.schema, path);
210
211 // Special case: if discriminator key is the only field that would
212 // be projected in, remove it.
213 if (exclude === false) {
214 checkEmbeddedDiscriminatorKeyProjection(fields, path, type.schema,
215 selected, _addedPaths);
216 }
217 }
218 });
219
220 stack.pop();
221 return addedPaths;
222 }
223
224 function analyzePath(path, type) {
225 const plusPath = '+' + path;
226 const hasPlusPath = fields && plusPath in fields;
227 if (hasPlusPath) {
228 // forced inclusion
229 delete fields[plusPath];
230 }
231
232 if (typeof type.selected !== 'boolean') return;
233
234 if (hasPlusPath) {
235 // forced inclusion
236 delete fields[plusPath];
237
238 // if there are other fields being included, add this one
239 // if no other included fields, leave this out (implied inclusion)
240 if (exclude === false && keys.length > 1 && !~keys.indexOf(path)) {
241 fields[path] = 1;
242 }
243
244 return;
245 }
246
247 // check for parent exclusions
248 const pieces = path.split('.');
249 let cur = '';
250 for (let i = 0; i < pieces.length; ++i) {
251 cur += cur.length ? '.' + pieces[i] : pieces[i];
252 if (excluded.indexOf(cur) !== -1) {
253 return;
254 }
255 }
256
257 // Special case: if user has included a parent path of a discriminator key,
258 // don't explicitly project in the discriminator key because that will
259 // project out everything else under the parent path
260 if (!exclude && get(type, 'options.$skipDiscriminatorCheck', false)) {
261 let cur = '';
262 for (let i = 0; i < pieces.length; ++i) {
263 cur += (cur.length === 0 ? '' : '.') + pieces[i];
264 const projection = get(fields, cur, false);
265 if (projection && typeof projection !== 'object') {
266 return;
267 }
268 }
269 }
270
271 (type.selected ? selected : excluded).push(path);
272 return path;
273 }
274};
275
276/*!
277 * Set each path query option to lean
278 *
279 * @param {Object} option
280 */
281
282function makeLean(val) {
283 return function(option) {
284 option.options || (option.options = {});
285 option.options.lean = val;
286 };
287}
288
289/*!
290 * Handle the `WriteOpResult` from the server
291 */
292
293exports.handleDeleteWriteOpResult = function handleDeleteWriteOpResult(callback) {
294 return function _handleDeleteWriteOpResult(error, res) {
295 if (error) {
296 return callback(error);
297 }
298 const mongooseResult = Object.assign({}, res.result);
299 if (get(res, 'result.n', null) != null) {
300 mongooseResult.deletedCount = res.result.n;
301 }
302 if (res.deletedCount != null) {
303 mongooseResult.deletedCount = res.deletedCount;
304 }
305 return callback(null, mongooseResult);
306 };
307};