UNPKG

7.46 kBJavaScriptView Raw
1'use strict';
2
3/*!
4 * Module dependencies
5 */
6
7const get = require('./helpers/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 return new model(undefined, fields, {
131 skipId: true,
132 isNew: false,
133 willInit: true
134 });
135};
136
137/*!
138 * ignore
139 */
140
141exports.applyPaths = function applyPaths(fields, schema) {
142 // determine if query is selecting or excluding fields
143 let exclude;
144 let keys;
145 let ki;
146 let field;
147
148 if (fields) {
149 keys = Object.keys(fields);
150 ki = keys.length;
151
152 while (ki--) {
153 if (keys[ki][0] === '+') {
154 continue;
155 }
156 field = fields[keys[ki]];
157 // Skip `$meta` and `$slice`
158 if (!isDefiningProjection(field)) {
159 continue;
160 }
161 exclude = field === 0;
162 break;
163 }
164 }
165
166 // if selecting, apply default schematype select:true fields
167 // if excluding, apply schematype select:false fields
168
169 const selected = [];
170 const excluded = [];
171 const stack = [];
172
173 const analyzePath = function(path, type) {
174 const plusPath = '+' + path;
175 const hasPlusPath = fields && plusPath in fields;
176 if (hasPlusPath) {
177 // forced inclusion
178 delete fields[plusPath];
179 }
180
181 if (typeof type.selected !== 'boolean') return;
182
183 if (hasPlusPath) {
184 // forced inclusion
185 delete fields[plusPath];
186
187 // if there are other fields being included, add this one
188 // if no other included fields, leave this out (implied inclusion)
189 if (exclude === false && keys.length > 1 && !~keys.indexOf(path)) {
190 fields[path] = 1;
191 }
192
193 return;
194 }
195
196 // check for parent exclusions
197 const pieces = path.split('.');
198 const root = pieces[0];
199 if (~excluded.indexOf(root)) {
200 return;
201 }
202
203 // Special case: if user has included a parent path of a discriminator key,
204 // don't explicitly project in the discriminator key because that will
205 // project out everything else under the parent path
206 if (!exclude && get(type, 'options.$skipDiscriminatorCheck', false)) {
207 let cur = '';
208 for (let i = 0; i < pieces.length; ++i) {
209 cur += (cur.length === 0 ? '' : '.') + pieces[i];
210 const projection = get(fields, cur, false);
211 if (projection && typeof projection !== 'object') {
212 return;
213 }
214 }
215 }
216
217 (type.selected ? selected : excluded).push(path);
218 };
219
220 analyzeSchema(schema);
221
222 switch (exclude) {
223 case true:
224 for (let i = 0; i < excluded.length; ++i) {
225 fields[excluded[i]] = 0;
226 }
227 break;
228 case false:
229 if (schema &&
230 schema.paths['_id'] &&
231 schema.paths['_id'].options &&
232 schema.paths['_id'].options.select === false) {
233 fields._id = 0;
234 }
235 for (let i = 0; i < selected.length; ++i) {
236 fields[selected[i]] = 1;
237 }
238 break;
239 case undefined:
240 if (fields == null) {
241 break;
242 }
243 // Any leftover plus paths must in the schema, so delete them (gh-7017)
244 for (const key of Object.keys(fields || {})) {
245 if (key.charAt(0) === '+') {
246 delete fields[key];
247 }
248 }
249
250 // user didn't specify fields, implies returning all fields.
251 // only need to apply excluded fields and delete any plus paths
252 for (let i = 0; i < excluded.length; ++i) {
253 fields[excluded[i]] = 0;
254 }
255 break;
256 }
257
258 function analyzeSchema(schema, prefix) {
259 prefix || (prefix = '');
260
261 // avoid recursion
262 if (stack.indexOf(schema) !== -1) {
263 return;
264 }
265 stack.push(schema);
266
267 schema.eachPath(function(path, type) {
268 if (prefix) path = prefix + '.' + path;
269
270 analyzePath(path, type);
271
272 // array of subdocs?
273 if (type.schema) {
274 analyzeSchema(type.schema, path);
275 }
276 });
277
278 stack.pop();
279 }
280};
281
282/*!
283 * Set each path query option to lean
284 *
285 * @param {Object} option
286 */
287
288function makeLean(val) {
289 return function(option) {
290 option.options || (option.options = {});
291 option.options.lean = val;
292 };
293}
294
295/*!
296 * Handle the `WriteOpResult` from the server
297 */
298
299exports.handleDeleteWriteOpResult = function handleDeleteWriteOpResult(callback) {
300 return function _handleDeleteWriteOpResult(error, res) {
301 if (error) {
302 return callback(error);
303 }
304 return callback(null,
305 Object.assign({}, res.result, {deletedCount: res.deletedCount }));
306 };
307};