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 ki;
124 let field;
125
126 if (fields) {
127 keys = Object.keys(fields);
128 ki = keys.length;
129
130 while (ki--) {
131 if (keys[ki][0] === '+') {
132 continue;
133 }
134 field = fields[keys[ki]];
135 // Skip `$meta` and `$slice`
136 if (!isDefiningProjection(field)) {
137 continue;
138 }
139 exclude = field === 0;
140 break;
141 }
142 }
143
144 // if selecting, apply default schematype select:true fields
145 // if excluding, apply schematype select:false fields
146
147 const selected = [];
148 const excluded = [];
149 const stack = [];
150
151 const analyzePath = function(path, type) {
152 const plusPath = '+' + path;
153 const hasPlusPath = fields && plusPath in fields;
154 if (hasPlusPath) {
155 // forced inclusion
156 delete fields[plusPath];
157 }
158
159 if (typeof type.selected !== 'boolean') return;
160
161 if (hasPlusPath) {
162 // forced inclusion
163 delete fields[plusPath];
164
165 // if there are other fields being included, add this one
166 // if no other included fields, leave this out (implied inclusion)
167 if (exclude === false && keys.length > 1 && !~keys.indexOf(path)) {
168 fields[path] = 1;
169 }
170
171 return;
172 }
173
174 // check for parent exclusions
175 const pieces = path.split('.');
176 let cur = '';
177 for (let i = 0; i < pieces.length; ++i) {
178 cur += cur.length ? '.' + pieces[i] : pieces[i];
179 if (excluded.indexOf(cur) !== -1) {
180 return;
181 }
182 }
183
184 // Special case: if user has included a parent path of a discriminator key,
185 // don't explicitly project in the discriminator key because that will
186 // project out everything else under the parent path
187 if (!exclude && get(type, 'options.$skipDiscriminatorCheck', false)) {
188 let cur = '';
189 for (let i = 0; i < pieces.length; ++i) {
190 cur += (cur.length === 0 ? '' : '.') + pieces[i];
191 const projection = get(fields, cur, false);
192 if (projection && typeof projection !== 'object') {
193 return;
194 }
195 }
196 }
197
198 (type.selected ? selected : excluded).push(path);
199 return path;
200 };
201
202 analyzeSchema(schema);
203
204 switch (exclude) {
205 case true:
206 for (let i = 0; i < excluded.length; ++i) {
207 fields[excluded[i]] = 0;
208 }
209 break;
210 case false:
211 if (schema &&
212 schema.paths['_id'] &&
213 schema.paths['_id'].options &&
214 schema.paths['_id'].options.select === false) {
215 fields._id = 0;
216 }
217 for (let i = 0; i < selected.length; ++i) {
218 fields[selected[i]] = 1;
219 }
220 break;
221 case undefined:
222 if (fields == null) {
223 break;
224 }
225 // Any leftover plus paths must in the schema, so delete them (gh-7017)
226 for (const key of Object.keys(fields || {})) {
227 if (key.startsWith('+')) {
228 delete fields[key];
229 }
230 }
231
232 // user didn't specify fields, implies returning all fields.
233 // only need to apply excluded fields and delete any plus paths
234 for (let i = 0; i < excluded.length; ++i) {
235 fields[excluded[i]] = 0;
236 }
237 break;
238 }
239
240 function analyzeSchema(schema, prefix) {
241 prefix || (prefix = '');
242
243 // avoid recursion
244 if (stack.indexOf(schema) !== -1) {
245 return [];
246 }
247 stack.push(schema);
248
249 const addedPaths = [];
250 schema.eachPath(function(path, type) {
251 if (prefix) path = prefix + '.' + path;
252
253 const addedPath = analyzePath(path, type);
254 if (addedPath != null) {
255 addedPaths.push(addedPath);
256 }
257
258 // nested schemas
259 if (type.schema) {
260 const _addedPaths = analyzeSchema(type.schema, path);
261
262 // Special case: if discriminator key is the only field that would
263 // be projected in, remove it.
264 if (exclude === false) {
265 checkEmbeddedDiscriminatorKeyProjection(fields, path, type.schema,
266 selected, _addedPaths);
267 }
268 }
269 });
270
271 stack.pop();
272 return addedPaths;
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};