UNPKG

46.9 kBJavaScriptView Raw
1'use strict';
2
3/*!
4 * Module dependencies.
5 */
6
7const EventEmitter = require('events').EventEmitter;
8const Kareem = require('kareem');
9const SchemaType = require('./schematype');
10const VirtualType = require('./virtualtype');
11const applyTimestampsToChildren = require('./helpers/update/applyTimestampsToChildren');
12const applyTimestampsToUpdate = require('./helpers/update/applyTimestampsToUpdate');
13const get = require('lodash.get');
14const getIndexes = require('./helpers/schema/getIndexes');
15const handleTimestampOption = require('./helpers/schema/handleTimestampOption');
16const merge = require('./helpers/schema/merge');
17const mpath = require('mpath');
18const readPref = require('./driver').get().ReadPreference;
19const util = require('util');
20const utils = require('./utils');
21const validateRef = require('./helpers/populate/validateRef');
22
23let MongooseTypes;
24
25const allMiddleware = require('./helpers/query/applyQueryMiddleware').
26 middlewareFunctions.
27 concat(require('./helpers/model/applyHooks').middlewareFunctions);
28
29let id = 0;
30
31/**
32 * Schema constructor.
33 *
34 * ####Example:
35 *
36 * var child = new Schema({ name: String });
37 * var schema = new Schema({ name: String, age: Number, children: [child] });
38 * var Tree = mongoose.model('Tree', schema);
39 *
40 * // setting schema options
41 * new Schema({ name: String }, { _id: false, autoIndex: false })
42 *
43 * ####Options:
44 *
45 * - [autoIndex](/docs/guide.html#autoIndex): bool - defaults to null (which means use the connection's autoIndex option)
46 * - [autoCreate](/docs/guide.html#autoCreate): bool - defaults to null (which means use the connection's autoCreate option)
47 * - [bufferCommands](/docs/guide.html#bufferCommands): bool - defaults to true
48 * - [capped](/docs/guide.html#capped): bool - defaults to false
49 * - [collection](/docs/guide.html#collection): string - no default
50 * - [id](/docs/guide.html#id): bool - defaults to true
51 * - [_id](/docs/guide.html#_id): bool - defaults to true
52 * - `minimize`: bool - controls [document#toObject](#document_Document-toObject) behavior when called manually - defaults to true
53 * - [read](/docs/guide.html#read): string
54 * - [writeConcern](/docs/guide.html#writeConcern): object - defaults to null, use to override [the MongoDB server's default write concern settings](https://docs.mongodb.com/manual/reference/write-concern/)
55 * - [shardKey](/docs/guide.html#shardKey): bool - defaults to `null`
56 * - [strict](/docs/guide.html#strict): bool - defaults to true
57 * - [toJSON](/docs/guide.html#toJSON) - object - no default
58 * - [toObject](/docs/guide.html#toObject) - object - no default
59 * - [typeKey](/docs/guide.html#typeKey) - string - defaults to 'type'
60 * - [useNestedStrict](/docs/guide.html#useNestedStrict) - boolean - defaults to false
61 * - [validateBeforeSave](/docs/guide.html#validateBeforeSave) - bool - defaults to `true`
62 * - [versionKey](/docs/guide.html#versionKey): string - defaults to "__v"
63 * - [collation](/docs/guide.html#collation): object - defaults to null (which means use no collation)
64 * - [selectPopulatedPaths](/docs/guide.html#selectPopulatedPaths): boolean - defaults to `true`
65 *
66 * ####Note:
67 *
68 * _When nesting schemas, (`children` in the example above), always declare the child schema first before passing it into its parent._
69 *
70 * @param {Object} definition
71 * @param {Object} [options]
72 * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
73 * @event `init`: Emitted after the schema is compiled into a `Model`.
74 * @api public
75 */
76
77function Schema(obj, options) {
78 if (!(this instanceof Schema)) {
79 return new Schema(obj, options);
80 }
81
82 this.obj = obj;
83 this.paths = {};
84 this.aliases = {};
85 this.subpaths = {};
86 this.virtuals = {};
87 this.singleNestedPaths = {};
88 this.nested = {};
89 this.inherits = {};
90 this.callQueue = [];
91 this._indexes = [];
92 this.methods = {};
93 this.methodOptions = {};
94 this.statics = {};
95 this.tree = {};
96 this.query = {};
97 this.childSchemas = [];
98 this.plugins = [];
99 // For internal debugging. Do not use this to try to save a schema in MDB.
100 this.$id = ++id;
101
102 this.s = {
103 hooks: new Kareem()
104 };
105
106 this.options = this.defaultOptions(options);
107
108 // build paths
109 if (obj) {
110 this.add(obj);
111 }
112
113 // check if _id's value is a subdocument (gh-2276)
114 const _idSubDoc = obj && obj._id && utils.isObject(obj._id);
115
116 // ensure the documents get an auto _id unless disabled
117 const auto_id = !this.paths['_id'] &&
118 (!this.options.noId && this.options._id) && !_idSubDoc;
119
120 if (auto_id) {
121 const _obj = {_id: {auto: true}};
122 _obj._id[this.options.typeKey] = Schema.ObjectId;
123 this.add(_obj);
124 }
125
126 this.setupTimestamp(this.options.timestamps);
127}
128
129/*!
130 * Create virtual properties with alias field
131 */
132function aliasFields(schema, paths) {
133 paths = paths || Object.keys(schema.paths);
134 for (const path of paths) {
135 const options = get(schema.paths[path], 'options');
136 if (options == null) {
137 continue;
138 }
139
140 const prop = schema.paths[path].path;
141 const alias = options.alias;
142
143 if (!alias) {
144 continue;
145 }
146
147 if (typeof alias !== 'string') {
148 throw new Error('Invalid value for alias option on ' + prop + ', got ' + alias);
149 }
150
151 schema.aliases[alias] = prop;
152
153 schema.
154 virtual(alias).
155 get((function(p) {
156 return function() {
157 if (typeof this.get === 'function') {
158 return this.get(p);
159 }
160 return this[p];
161 };
162 })(prop)).
163 set((function(p) {
164 return function(v) {
165 return this.set(p, v);
166 };
167 })(prop));
168 }
169}
170
171/*!
172 * Inherit from EventEmitter.
173 */
174Schema.prototype = Object.create(EventEmitter.prototype);
175Schema.prototype.constructor = Schema;
176Schema.prototype.instanceOfSchema = true;
177
178/*!
179 * ignore
180 */
181
182Object.defineProperty(Schema.prototype, '$schemaType', {
183 configurable: false,
184 enumerable: false,
185 writable: true
186});
187
188/**
189 * Array of child schemas (from document arrays and single nested subdocs)
190 * and their corresponding compiled models. Each element of the array is
191 * an object with 2 properties: `schema` and `model`.
192 *
193 * This property is typically only useful for plugin authors and advanced users.
194 * You do not need to interact with this property at all to use mongoose.
195 *
196 * @api public
197 * @property childSchemas
198 * @memberOf Schema
199 * @instance
200 */
201
202Object.defineProperty(Schema.prototype, 'childSchemas', {
203 configurable: false,
204 enumerable: true,
205 writable: true
206});
207
208/**
209 * The original object passed to the schema constructor
210 *
211 * ####Example:
212 *
213 * var schema = new Schema({ a: String }).add({ b: String });
214 * schema.obj; // { a: String }
215 *
216 * @api public
217 * @property obj
218 * @memberOf Schema
219 * @instance
220 */
221
222Schema.prototype.obj;
223
224/**
225 * Schema as flat paths
226 *
227 * ####Example:
228 * {
229 * '_id' : SchemaType,
230 * , 'nested.key' : SchemaType,
231 * }
232 *
233 * @api private
234 * @property paths
235 * @memberOf Schema
236 * @instance
237 */
238
239Schema.prototype.paths;
240
241/**
242 * Schema as a tree
243 *
244 * ####Example:
245 * {
246 * '_id' : ObjectId
247 * , 'nested' : {
248 * 'key' : String
249 * }
250 * }
251 *
252 * @api private
253 * @property tree
254 * @memberOf Schema
255 * @instance
256 */
257
258Schema.prototype.tree;
259
260/**
261 * Returns a deep copy of the schema
262 *
263 * @return {Schema} the cloned schema
264 * @api public
265 * @memberOf Schema
266 * @instance
267 */
268
269Schema.prototype.clone = function() {
270 const s = new Schema({}, this._userProvidedOptions);
271 s.obj = this.obj;
272 s.options = utils.clone(this.options);
273 s.callQueue = this.callQueue.map(function(f) { return f; });
274 s.methods = utils.clone(this.methods);
275 s.methodOptions = utils.clone(this.methodOptions);
276 s.statics = utils.clone(this.statics);
277 s.query = utils.clone(this.query);
278 s.plugins = Array.prototype.slice.call(this.plugins);
279 s._indexes = utils.clone(this._indexes);
280 s.s.hooks = this.s.hooks.clone();
281 s._originalSchema = this._originalSchema == null ?
282 this._originalSchema :
283 this._originalSchema.clone();
284
285 s.tree = utils.clone(this.tree);
286 s.paths = utils.clone(this.paths);
287 s.nested = utils.clone(this.nested);
288 s.subpaths = utils.clone(this.subpaths);
289 s.childSchemas = this.childSchemas.slice();
290 s.singleNestedPaths = utils.clone(this.singleNestedPaths);
291
292 s.virtuals = utils.clone(this.virtuals);
293 s.$globalPluginsApplied = this.$globalPluginsApplied;
294 s.$isRootDiscriminator = this.$isRootDiscriminator;
295
296 if (this.discriminatorMapping != null) {
297 s.discriminatorMapping = Object.assign({}, this.discriminatorMapping);
298 }
299 if (s.discriminators != null) {
300 s.discriminators = Object.assign({}, this.discriminators);
301 }
302
303 s.aliases = Object.assign({}, this.aliases);
304
305 // Bubble up `init` for backwards compat
306 s.on('init', v => this.emit('init', v));
307
308 return s;
309};
310
311/**
312 * Returns default options for this schema, merged with `options`.
313 *
314 * @param {Object} options
315 * @return {Object}
316 * @api private
317 */
318
319Schema.prototype.defaultOptions = function(options) {
320 if (options && options.safe === false) {
321 options.safe = {w: 0};
322 }
323
324 if (options && options.safe && options.safe.w === 0) {
325 // if you turn off safe writes, then versioning goes off as well
326 options.versionKey = false;
327 }
328
329 this._userProvidedOptions = options == null ? {} : utils.clone(options);
330
331 options = utils.options({
332 strict: true,
333 bufferCommands: true,
334 capped: false, // { size, max, autoIndexId }
335 versionKey: '__v',
336 discriminatorKey: '__t',
337 minimize: true,
338 autoIndex: null,
339 shardKey: null,
340 read: null,
341 validateBeforeSave: true,
342 // the following are only applied at construction time
343 noId: false, // deprecated, use { _id: false }
344 _id: true,
345 noVirtualId: false, // deprecated, use { id: false }
346 id: true,
347 typeKey: 'type'
348 }, utils.clone(options));
349
350 if (options.read) {
351 options.read = readPref(options.read);
352 }
353
354 return options;
355};
356
357/**
358 * Adds key path / schema type pairs to this schema.
359 *
360 * ####Example:
361 *
362 * var ToySchema = new Schema;
363 * ToySchema.add({ name: 'string', color: 'string', price: 'number' });
364 *
365 * @param {Object} obj
366 * @param {String} prefix
367 * @api public
368 */
369
370Schema.prototype.add = function add(obj, prefix) {
371 if (obj instanceof Schema) {
372 merge(this, obj);
373 return;
374 }
375
376 prefix = prefix || '';
377 const keys = Object.keys(obj);
378
379 for (let i = 0; i < keys.length; ++i) {
380 const key = keys[i];
381
382 if (obj[key] == null) {
383 throw new TypeError('Invalid value for schema path `' + prefix + key + '`');
384 }
385
386 if (Array.isArray(obj[key]) && obj[key].length === 1 && obj[key][0] == null) {
387 throw new TypeError('Invalid value for schema Array path `' + prefix + key + '`');
388 }
389
390 if (utils.isObject(obj[key]) &&
391 (!obj[key].constructor || utils.getFunctionName(obj[key].constructor) === 'Object') &&
392 (!obj[key][this.options.typeKey] || (this.options.typeKey === 'type' && obj[key].type.type))) {
393 if (Object.keys(obj[key]).length) {
394 // nested object { last: { name: String }}
395 this.nested[prefix + key] = true;
396 this.add(obj[key], prefix + key + '.');
397 } else {
398 if (prefix) {
399 this.nested[prefix.substr(0, prefix.length - 1)] = true;
400 }
401 this.path(prefix + key, obj[key]); // mixed type
402 }
403 } else {
404 if (prefix) {
405 this.nested[prefix.substr(0, prefix.length - 1)] = true;
406 }
407 this.path(prefix + key, obj[key]);
408 }
409 }
410
411 const addedKeys = Object.keys(obj).
412 map(key => prefix ? prefix + key : key);
413 aliasFields(this, addedKeys);
414};
415
416/**
417 * Reserved document keys.
418 *
419 * Keys in this object are names that are rejected in schema declarations b/c they conflict with mongoose functionality. Using these key name will throw an error.
420 *
421 * on, emit, _events, db, get, set, init, isNew, errors, schema, options, modelName, collection, _pres, _posts, toObject
422 *
423 * _NOTE:_ Use of these terms as method names is permitted, but play at your own risk, as they may be existing mongoose document methods you are stomping on.
424 *
425 * var schema = new Schema(..);
426 * schema.methods.init = function () {} // potentially breaking
427 */
428
429Schema.reserved = Object.create(null);
430Schema.prototype.reserved = Schema.reserved;
431const reserved = Schema.reserved;
432// Core object
433reserved['prototype'] =
434// EventEmitter
435reserved.emit =
436reserved.on =
437reserved.once =
438reserved.listeners =
439reserved.removeListener =
440// document properties and functions
441reserved.collection =
442reserved.db =
443reserved.errors =
444reserved.init =
445reserved.isModified =
446reserved.isNew =
447reserved.get =
448reserved.modelName =
449reserved.save =
450reserved.schema =
451reserved.toObject =
452reserved.validate =
453reserved.remove =
454// hooks.js
455reserved._pres = reserved._posts = 1;
456
457/*!
458 * Document keys to print warnings for
459 */
460
461const warnings = {};
462warnings.increment = '`increment` should not be used as a schema path name ' +
463 'unless you have disabled versioning.';
464
465/**
466 * Gets/sets schema paths.
467 *
468 * Sets a path (if arity 2)
469 * Gets a path (if arity 1)
470 *
471 * ####Example
472 *
473 * schema.path('name') // returns a SchemaType
474 * schema.path('name', Number) // changes the schemaType of `name` to Number
475 *
476 * @param {String} path
477 * @param {Object} constructor
478 * @api public
479 */
480
481Schema.prototype.path = function(path, obj) {
482 if (obj === undefined) {
483 if (this.paths[path]) {
484 return this.paths[path];
485 }
486 if (this.subpaths[path]) {
487 return this.subpaths[path];
488 }
489 if (this.singleNestedPaths[path]) {
490 return this.singleNestedPaths[path];
491 }
492
493 // Look for maps
494 for (const _path of Object.keys(this.paths)) {
495 if (!_path.includes('.$*')) {
496 continue;
497 }
498 const re = new RegExp('^' + _path.replace(/\.\$\*/g, '.[^.]+') + '$');
499 if (re.test(path)) {
500 return this.paths[_path];
501 }
502 }
503
504 // subpaths?
505 return /\.\d+\.?.*$/.test(path)
506 ? getPositionalPath(this, path)
507 : undefined;
508 }
509
510 // some path names conflict with document methods
511 if (reserved[path]) {
512 throw new Error('`' + path + '` may not be used as a schema pathname');
513 }
514
515 if (warnings[path]) {
516 console.log('WARN: ' + warnings[path]);
517 }
518
519 if (typeof obj === 'object' && 'ref' in obj) {
520 validateRef(obj.ref, path);
521 }
522
523 // update the tree
524 const subpaths = path.split(/\./);
525 const last = subpaths.pop();
526 let branch = this.tree;
527
528 subpaths.forEach(function(sub, i) {
529 if (!branch[sub]) {
530 branch[sub] = {};
531 }
532 if (typeof branch[sub] !== 'object') {
533 const msg = 'Cannot set nested path `' + path + '`. '
534 + 'Parent path `'
535 + subpaths.slice(0, i).concat([sub]).join('.')
536 + '` already set to type ' + branch[sub].name
537 + '.';
538 throw new Error(msg);
539 }
540 branch = branch[sub];
541 });
542
543 branch[last] = utils.clone(obj);
544
545 this.paths[path] = Schema.interpretAsType(path, obj, this.options);
546 const schemaType = this.paths[path];
547
548 if (schemaType.$isSchemaMap) {
549 // Maps can have arbitrary keys, so `$*` is internal shorthand for "any key"
550 // The '$' is to imply this path should never be stored in MongoDB so we
551 // can easily build a regexp out of this path, and '*' to imply "any key."
552 const mapPath = path + '.$*';
553 this.paths[path + '.$*'] = Schema.interpretAsType(mapPath,
554 obj.of || { type: {} }, this.options);
555 schemaType.$__schemaType = this.paths[path + '.$*'];
556 }
557
558 if (schemaType.$isSingleNested) {
559 for (const key in schemaType.schema.paths) {
560 this.singleNestedPaths[path + '.' + key] = schemaType.schema.paths[key];
561 }
562 for (const key in schemaType.schema.singleNestedPaths) {
563 this.singleNestedPaths[path + '.' + key] =
564 schemaType.schema.singleNestedPaths[key];
565 }
566
567 this.childSchemas.push({
568 schema: schemaType.schema,
569 model: schemaType.caster
570 });
571 } else if (schemaType.$isMongooseDocumentArray) {
572 this.childSchemas.push({
573 schema: schemaType.schema,
574 model: schemaType.casterConstructor
575 });
576 }
577
578 return this;
579};
580
581/**
582 * Converts type arguments into Mongoose Types.
583 *
584 * @param {String} path
585 * @param {Object} obj constructor
586 * @api private
587 */
588
589Schema.interpretAsType = function(path, obj, options) {
590 if (obj instanceof SchemaType) {
591 return obj;
592 }
593
594 if (obj.constructor) {
595 const constructorName = utils.getFunctionName(obj.constructor);
596 if (constructorName !== 'Object') {
597 const oldObj = obj;
598 obj = {};
599 obj[options.typeKey] = oldObj;
600 }
601 }
602
603 // Get the type making sure to allow keys named "type"
604 // and default to mixed if not specified.
605 // { type: { type: String, default: 'freshcut' } }
606 let type = obj[options.typeKey] && (options.typeKey !== 'type' || !obj.type.type)
607 ? obj[options.typeKey]
608 : {};
609 let name;
610
611 if (utils.getFunctionName(type.constructor) === 'Object' || type === 'mixed') {
612 return new MongooseTypes.Mixed(path, obj);
613 }
614
615 if (Array.isArray(type) || Array === type || type === 'array') {
616 // if it was specified through { type } look for `cast`
617 let cast = (Array === type || type === 'array')
618 ? obj.cast
619 : type[0];
620
621 if (cast && cast.instanceOfSchema) {
622 return new MongooseTypes.DocumentArray(path, cast, obj);
623 }
624 if (cast &&
625 cast[options.typeKey] &&
626 cast[options.typeKey].instanceOfSchema) {
627 return new MongooseTypes.DocumentArray(path, cast[options.typeKey], obj, cast);
628 }
629
630 if (Array.isArray(cast)) {
631 return new MongooseTypes.Array(path, Schema.interpretAsType(path, cast, options), obj);
632 }
633
634 if (typeof cast === 'string') {
635 cast = MongooseTypes[cast.charAt(0).toUpperCase() + cast.substring(1)];
636 } else if (cast && (!cast[options.typeKey] || (options.typeKey === 'type' && cast.type.type))
637 && utils.getFunctionName(cast.constructor) === 'Object') {
638 if (Object.keys(cast).length) {
639 // The `minimize` and `typeKey` options propagate to child schemas
640 // declared inline, like `{ arr: [{ val: { $type: String } }] }`.
641 // See gh-3560
642 const childSchemaOptions = {minimize: options.minimize};
643 if (options.typeKey) {
644 childSchemaOptions.typeKey = options.typeKey;
645 }
646 //propagate 'strict' option to child schema
647 if (options.hasOwnProperty('strict')) {
648 childSchemaOptions.strict = options.strict;
649 }
650 const childSchema = new Schema(cast, childSchemaOptions);
651 childSchema.$implicitlyCreated = true;
652 return new MongooseTypes.DocumentArray(path, childSchema, obj);
653 } else {
654 // Special case: empty object becomes mixed
655 return new MongooseTypes.Array(path, MongooseTypes.Mixed, obj);
656 }
657 }
658
659 if (cast) {
660 type = cast[options.typeKey] && (options.typeKey !== 'type' || !cast.type.type)
661 ? cast[options.typeKey]
662 : cast;
663
664 name = typeof type === 'string'
665 ? type
666 : type.schemaName || utils.getFunctionName(type);
667
668 if (!(name in MongooseTypes)) {
669 throw new TypeError('Undefined type `' + name + '` at array `' + path +
670 '`');
671 }
672 }
673
674 return new MongooseTypes.Array(path, cast || MongooseTypes.Mixed, obj, options);
675 }
676
677 if (type && type.instanceOfSchema) {
678 return new MongooseTypes.Embedded(type, path, obj);
679 }
680
681 if (Buffer.isBuffer(type)) {
682 name = 'Buffer';
683 } else {
684 name = typeof type === 'string'
685 ? type
686 // If not string, `type` is a function. Outside of IE, function.name
687 // gives you the function name. In IE, you need to compute it
688 : type.schemaName || utils.getFunctionName(type);
689 }
690
691 if (name) {
692 name = name.charAt(0).toUpperCase() + name.substring(1);
693 }
694
695 if (undefined == MongooseTypes[name]) {
696 throw new TypeError('Undefined type `' + name + '` at `' + path +
697 '`\n Did you try nesting Schemas? ' +
698 'You can only nest using refs or arrays.');
699 }
700
701 return new MongooseTypes[name](path, obj);
702};
703
704/**
705 * Iterates the schemas paths similar to Array#forEach.
706 *
707 * The callback is passed the pathname and schemaType as arguments on each iteration.
708 *
709 * @param {Function} fn callback function
710 * @return {Schema} this
711 * @api public
712 */
713
714Schema.prototype.eachPath = function(fn) {
715 const keys = Object.keys(this.paths);
716 const len = keys.length;
717
718 for (let i = 0; i < len; ++i) {
719 fn(keys[i], this.paths[keys[i]]);
720 }
721
722 return this;
723};
724
725/**
726 * Returns an Array of path strings that are required by this schema.
727 *
728 * @api public
729 * @param {Boolean} invalidate refresh the cache
730 * @return {Array}
731 */
732
733Schema.prototype.requiredPaths = function requiredPaths(invalidate) {
734 if (this._requiredpaths && !invalidate) {
735 return this._requiredpaths;
736 }
737
738 const paths = Object.keys(this.paths);
739 let i = paths.length;
740 const ret = [];
741
742 while (i--) {
743 const path = paths[i];
744 if (this.paths[path].isRequired) {
745 ret.push(path);
746 }
747 }
748 this._requiredpaths = ret;
749 return this._requiredpaths;
750};
751
752/**
753 * Returns indexes from fields and schema-level indexes (cached).
754 *
755 * @api private
756 * @return {Array}
757 */
758
759Schema.prototype.indexedPaths = function indexedPaths() {
760 if (this._indexedpaths) {
761 return this._indexedpaths;
762 }
763 this._indexedpaths = this.indexes();
764 return this._indexedpaths;
765};
766
767/**
768 * Returns the pathType of `path` for this schema.
769 *
770 * Given a path, returns whether it is a real, virtual, nested, or ad-hoc/undefined path.
771 *
772 * @param {String} path
773 * @return {String}
774 * @api public
775 */
776
777Schema.prototype.pathType = function(path) {
778 if (path in this.paths) {
779 return 'real';
780 }
781 if (path in this.virtuals) {
782 return 'virtual';
783 }
784 if (path in this.nested) {
785 return 'nested';
786 }
787 if (path in this.subpaths) {
788 return 'real';
789 }
790 if (path in this.singleNestedPaths) {
791 return 'real';
792 }
793
794 // Look for maps
795 for (const _path of Object.keys(this.paths)) {
796 if (!_path.includes('.$*')) {
797 continue;
798 }
799 const re = new RegExp('^' + _path.replace(/\.\$\*/g, '.[^.]+') + '$');
800 if (re.test(path)) {
801 return this.paths[_path];
802 }
803 }
804
805 if (/\.\d+\.|\.\d+$/.test(path)) {
806 return getPositionalPathType(this, path);
807 }
808 return 'adhocOrUndefined';
809};
810
811/**
812 * Returns true iff this path is a child of a mixed schema.
813 *
814 * @param {String} path
815 * @return {Boolean}
816 * @api private
817 */
818
819Schema.prototype.hasMixedParent = function(path) {
820 const subpaths = path.split(/\./g);
821 path = '';
822 for (let i = 0; i < subpaths.length; ++i) {
823 path = i > 0 ? path + '.' + subpaths[i] : subpaths[i];
824 if (path in this.paths &&
825 this.paths[path] instanceof MongooseTypes.Mixed) {
826 return true;
827 }
828 }
829
830 return false;
831};
832
833/**
834 * Setup updatedAt and createdAt timestamps to documents if enabled
835 *
836 * @param {Boolean|Object} timestamps timestamps options
837 * @api private
838 */
839Schema.prototype.setupTimestamp = function(timestamps) {
840 const childHasTimestamp = this.childSchemas.
841 find(s => s.schema.options.timestamps != null);
842
843 if (timestamps == null && !childHasTimestamp) {
844 return;
845 }
846
847 const createdAt = handleTimestampOption(timestamps, 'createdAt');
848 const updatedAt = handleTimestampOption(timestamps, 'updatedAt');
849 const schemaAdditions = {};
850
851 this.$timestamps = { createdAt: createdAt, updatedAt: updatedAt };
852
853 if (updatedAt && !this.paths[updatedAt]) {
854 schemaAdditions[updatedAt] = Date;
855 }
856
857 if (createdAt && !this.paths[createdAt]) {
858 schemaAdditions[createdAt] = Date;
859 }
860
861 this.add(schemaAdditions);
862
863 this.pre('save', function(next) {
864 const defaultTimestamp = (this.ownerDocument ? this.ownerDocument() : this).
865 constructor.base.now();
866 const auto_id = this._id && this._id.auto;
867
868 if (createdAt && !this.get(createdAt) && this.isSelected(createdAt)) {
869 this.set(createdAt, auto_id ? this._id.getTimestamp() : defaultTimestamp);
870 }
871
872 if (updatedAt && (this.isNew || this.isModified())) {
873 let ts = defaultTimestamp;
874 if (this.isNew) {
875 if (createdAt != null) {
876 ts = this.get(createdAt);
877 } else if (auto_id) {
878 ts = this._id.getTimestamp();
879 }
880 }
881 this.set(updatedAt, ts);
882 }
883
884 next();
885 });
886
887 this.methods.initializeTimestamps = function() {
888 if (createdAt && !this.get(createdAt)) {
889 this.set(createdAt, new Date());
890 }
891 if (updatedAt && !this.get(updatedAt)) {
892 this.set(updatedAt, new Date());
893 }
894 return this;
895 };
896
897 this.pre('findOneAndUpdate', _setTimestampsOnUpdate);
898 this.pre('update', _setTimestampsOnUpdate);
899 this.pre('updateOne', _setTimestampsOnUpdate);
900 this.pre('updateMany', _setTimestampsOnUpdate);
901
902 function _setTimestampsOnUpdate(next) {
903 const now = this.model.base.now();
904 applyTimestampsToUpdate(now, createdAt, updatedAt, this.getUpdate(),
905 this.options, this.schema);
906 applyTimestampsToChildren(now, this.getUpdate(), this.model.schema);
907 next();
908 }
909};
910
911/*!
912 * ignore
913 */
914
915function getPositionalPathType(self, path) {
916 const subpaths = path.split(/\.(\d+)\.|\.(\d+)$/).filter(Boolean);
917 if (subpaths.length < 2) {
918 return self.paths[subpaths[0]];
919 }
920
921 let val = self.path(subpaths[0]);
922 let isNested = false;
923 if (!val) {
924 return val;
925 }
926
927 const last = subpaths.length - 1;
928 let subpath;
929 let i = 1;
930
931 for (; i < subpaths.length; ++i) {
932 isNested = false;
933 subpath = subpaths[i];
934
935 if (i === last && val && !/\D/.test(subpath)) {
936 if (val.$isMongooseDocumentArray) {
937 const oldVal = val;
938 val = new SchemaType(subpath, {
939 required: get(val, 'schemaOptions.required', false)
940 });
941 val.cast = function(value, doc, init) {
942 return oldVal.cast(value, doc, init)[0];
943 };
944 val.$isMongooseDocumentArrayElement = true;
945 val.caster = oldVal.caster;
946 val.schema = oldVal.schema;
947 } else if (val instanceof MongooseTypes.Array) {
948 // StringSchema, NumberSchema, etc
949 val = val.caster;
950 } else {
951 val = undefined;
952 }
953 break;
954 }
955
956 // ignore if its just a position segment: path.0.subpath
957 if (!/\D/.test(subpath)) {
958 continue;
959 }
960
961 if (!(val && val.schema)) {
962 val = undefined;
963 break;
964 }
965
966 const type = val.schema.pathType(subpath);
967 isNested = (type === 'nested');
968 val = val.schema.path(subpath);
969 }
970
971 self.subpaths[path] = val;
972 if (val) {
973 return 'real';
974 }
975 if (isNested) {
976 return 'nested';
977 }
978 return 'adhocOrUndefined';
979}
980
981
982/*!
983 * ignore
984 */
985
986function getPositionalPath(self, path) {
987 getPositionalPathType(self, path);
988 return self.subpaths[path];
989}
990
991/**
992 * Adds a method call to the queue.
993 *
994 * @param {String} name name of the document method to call later
995 * @param {Array} args arguments to pass to the method
996 * @api public
997 */
998
999Schema.prototype.queue = function(name, args) {
1000 this.callQueue.push([name, args]);
1001 return this;
1002};
1003
1004/**
1005 * Defines a pre hook for the document.
1006 *
1007 * ####Example
1008 *
1009 * var toySchema = new Schema(..);
1010 *
1011 * toySchema.pre('save', function (next) {
1012 * if (!this.created) this.created = new Date;
1013 * next();
1014 * })
1015 *
1016 * toySchema.pre('validate', function (next) {
1017 * if (this.name !== 'Woody') this.name = 'Woody';
1018 * next();
1019 * })
1020 *
1021 * @param {String} method
1022 * @param {Object} [options]
1023 * @param {Boolean} [options.document] If `name` is a hook for both document and query middleware, set to `true` to run on document middleware.
1024 * @param {Boolean} [options.query] If `name` is a hook for both document and query middleware, set to `true` to run on query middleware.
1025 * @param {Function} callback
1026 * @see hooks.js https://github.com/bnoguchi/hooks-js/tree/31ec571cef0332e21121ee7157e0cf9728572cc3
1027 * @api public
1028 */
1029
1030Schema.prototype.pre = function(name) {
1031 if (name instanceof RegExp) {
1032 const remainingArgs = Array.prototype.slice.call(arguments, 1);
1033 for (const fn of allMiddleware) {
1034 if (name.test(fn)) {
1035 this.pre.apply(this, [fn].concat(remainingArgs));
1036 }
1037 }
1038 return this;
1039 }
1040 this.s.hooks.pre.apply(this.s.hooks, arguments);
1041 return this;
1042};
1043
1044/**
1045 * Defines a post hook for the document
1046 *
1047 * var schema = new Schema(..);
1048 * schema.post('save', function (doc) {
1049 * console.log('this fired after a document was saved');
1050 * });
1051 *
1052 * schema.post('find', function(docs) {
1053 * console.log('this fired after you run a find query');
1054 * });
1055 *
1056 * var Model = mongoose.model('Model', schema);
1057 *
1058 * var m = new Model(..);
1059 * m.save(function(err) {
1060 * console.log('this fires after the `post` hook');
1061 * });
1062 *
1063 * m.find(function(err, docs) {
1064 * console.log('this fires after the post find hook');
1065 * });
1066 *
1067 * @param {String} method name of the method to hook
1068 * @param {Object} [options]
1069 * @param {Boolean} [options.document] If `name` is a hook for both document and query middleware, set to `true` to run on document middleware.
1070 * @param {Boolean} [options.query] If `name` is a hook for both document and query middleware, set to `true` to run on query middleware.
1071 * @param {Function} fn callback
1072 * @see middleware http://mongoosejs.com/docs/middleware.html
1073 * @see kareem http://npmjs.org/package/kareem
1074 * @api public
1075 */
1076
1077Schema.prototype.post = function(name) {
1078 if (name instanceof RegExp) {
1079 const remainingArgs = Array.prototype.slice.call(arguments, 1);
1080 for (const fn of allMiddleware) {
1081 if (name.test(fn)) {
1082 this.post.apply(this, [fn].concat(remainingArgs));
1083 }
1084 }
1085 return this;
1086 }
1087 this.s.hooks.post.apply(this.s.hooks, arguments);
1088 return this;
1089};
1090
1091/**
1092 * Registers a plugin for this schema.
1093 *
1094 * @param {Function} plugin callback
1095 * @param {Object} [opts]
1096 * @see plugins
1097 * @api public
1098 */
1099
1100Schema.prototype.plugin = function(fn, opts) {
1101 if (typeof fn !== 'function') {
1102 throw new Error('First param to `schema.plugin()` must be a function, ' +
1103 'got "' + (typeof fn) + '"');
1104 }
1105
1106 if (opts &&
1107 opts.deduplicate) {
1108 for (let i = 0; i < this.plugins.length; ++i) {
1109 if (this.plugins[i].fn === fn) {
1110 return this;
1111 }
1112 }
1113 }
1114 this.plugins.push({ fn: fn, opts: opts });
1115
1116 fn(this, opts);
1117 return this;
1118};
1119
1120/**
1121 * Adds an instance method to documents constructed from Models compiled from this schema.
1122 *
1123 * ####Example
1124 *
1125 * var schema = kittySchema = new Schema(..);
1126 *
1127 * schema.method('meow', function () {
1128 * console.log('meeeeeoooooooooooow');
1129 * })
1130 *
1131 * var Kitty = mongoose.model('Kitty', schema);
1132 *
1133 * var fizz = new Kitty;
1134 * fizz.meow(); // meeeeeooooooooooooow
1135 *
1136 * If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as methods.
1137 *
1138 * schema.method({
1139 * purr: function () {}
1140 * , scratch: function () {}
1141 * });
1142 *
1143 * // later
1144 * fizz.purr();
1145 * fizz.scratch();
1146 *
1147 * NOTE: `Schema.method()` adds instance methods to the `Schema.methods` object. You can also add instance methods directly to the `Schema.methods` object as seen in the [guide](./guide.html#methods)
1148 *
1149 * @param {String|Object} method name
1150 * @param {Function} [fn]
1151 * @api public
1152 */
1153
1154Schema.prototype.method = function(name, fn, options) {
1155 if (typeof name !== 'string') {
1156 for (const i in name) {
1157 this.methods[i] = name[i];
1158 this.methodOptions[i] = utils.clone(options);
1159 }
1160 } else {
1161 this.methods[name] = fn;
1162 this.methodOptions[name] = utils.clone(options);
1163 }
1164 return this;
1165};
1166
1167/**
1168 * Adds static "class" methods to Models compiled from this schema.
1169 *
1170 * ####Example
1171 *
1172 * var schema = new Schema(..);
1173 * schema.static('findByName', function (name, callback) {
1174 * return this.find({ name: name }, callback);
1175 * });
1176 *
1177 * var Drink = mongoose.model('Drink', schema);
1178 * Drink.findByName('sanpellegrino', function (err, drinks) {
1179 * //
1180 * });
1181 *
1182 * If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as statics.
1183 *
1184 * @param {String|Object} name
1185 * @param {Function} [fn]
1186 * @api public
1187 */
1188
1189Schema.prototype.static = function(name, fn) {
1190 if (typeof name !== 'string') {
1191 for (const i in name) {
1192 this.statics[i] = name[i];
1193 }
1194 } else {
1195 this.statics[name] = fn;
1196 }
1197 return this;
1198};
1199
1200/**
1201 * Defines an index (most likely compound) for this schema.
1202 *
1203 * ####Example
1204 *
1205 * schema.index({ first: 1, last: -1 })
1206 *
1207 * @param {Object} fields
1208 * @param {Object} [options] Options to pass to [MongoDB driver's `createIndex()` function](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#createIndex)
1209 * @param {String} [options.expires=null] Mongoose-specific syntactic sugar, uses [ms](https://www.npmjs.com/package/ms) to convert `expires` option into seconds for the `expireAfterSeconds` in the above link.
1210 * @api public
1211 */
1212
1213Schema.prototype.index = function(fields, options) {
1214 fields || (fields = {});
1215 options || (options = {});
1216
1217 if (options.expires) {
1218 utils.expires(options);
1219 }
1220
1221 this._indexes.push([fields, options]);
1222 return this;
1223};
1224
1225/**
1226 * Sets/gets a schema option.
1227 *
1228 * ####Example
1229 *
1230 * schema.set('strict'); // 'true' by default
1231 * schema.set('strict', false); // Sets 'strict' to false
1232 * schema.set('strict'); // 'false'
1233 *
1234 * @param {String} key option name
1235 * @param {Object} [value] if not passed, the current option value is returned
1236 * @see Schema ./
1237 * @api public
1238 */
1239
1240Schema.prototype.set = function(key, value, _tags) {
1241 if (arguments.length === 1) {
1242 return this.options[key];
1243 }
1244
1245 switch (key) {
1246 case 'read':
1247 this.options[key] = readPref(value, _tags);
1248 this._userProvidedOptions[key] = this.options[key];
1249 break;
1250 case 'safe':
1251 setSafe(this.options, value);
1252 this._userProvidedOptions[key] = this.options[key];
1253 break;
1254 case 'timestamps':
1255 this.setupTimestamp(value);
1256 this.options[key] = value;
1257 this._userProvidedOptions[key] = this.options[key];
1258 break;
1259 default:
1260 this.options[key] = value;
1261 this._userProvidedOptions[key] = this.options[key];
1262 break;
1263 }
1264
1265 return this;
1266};
1267
1268/*!
1269 * ignore
1270 */
1271
1272const safeDeprecationWarning = 'Mongoose: The `safe` option for schemas is ' +
1273 'deprecated. Use the `writeConcern` option instead: ' +
1274 'http://bit.ly/mongoose-write-concern';
1275
1276const setSafe = util.deprecate(function setSafe(options, value) {
1277 options.safe = value === false ?
1278 {w: 0} :
1279 value;
1280}, safeDeprecationWarning);
1281
1282/**
1283 * Gets a schema option.
1284 *
1285 * @param {String} key option name
1286 * @api public
1287 */
1288
1289Schema.prototype.get = function(key) {
1290 return this.options[key];
1291};
1292
1293/**
1294 * The allowed index types
1295 *
1296 * @receiver Schema
1297 * @static indexTypes
1298 * @api public
1299 */
1300
1301const indexTypes = '2d 2dsphere hashed text'.split(' ');
1302
1303Object.defineProperty(Schema, 'indexTypes', {
1304 get: function() {
1305 return indexTypes;
1306 },
1307 set: function() {
1308 throw new Error('Cannot overwrite Schema.indexTypes');
1309 }
1310});
1311
1312/**
1313 * Returns a list of indexes that this schema declares, via `schema.index()`
1314 * or by `index: true` in a path's options.
1315 *
1316 * @api public
1317 */
1318
1319Schema.prototype.indexes = function() {
1320 return getIndexes(this);
1321};
1322
1323/**
1324 * Creates a virtual type with the given name.
1325 *
1326 * @param {String} name
1327 * @param {Object} [options]
1328 * @return {VirtualType}
1329 */
1330
1331Schema.prototype.virtual = function(name, options) {
1332 if (options && options.ref) {
1333 if (!options.localField) {
1334 throw new Error('Reference virtuals require `localField` option');
1335 }
1336
1337 if (!options.foreignField) {
1338 throw new Error('Reference virtuals require `foreignField` option');
1339 }
1340
1341 this.pre('init', function(obj) {
1342 if (mpath.has(name, obj)) {
1343 const _v = mpath.get(name, obj);
1344 if (!this.$$populatedVirtuals) {
1345 this.$$populatedVirtuals = {};
1346 }
1347
1348 if (options.justOne) {
1349 this.$$populatedVirtuals[name] = Array.isArray(_v) ?
1350 _v[0] :
1351 _v;
1352 } else {
1353 this.$$populatedVirtuals[name] = Array.isArray(_v) ?
1354 _v :
1355 _v == null ? [] : [_v];
1356 }
1357
1358 mpath.unset(name, obj);
1359 }
1360 });
1361
1362 const virtual = this.virtual(name);
1363 virtual.options = options;
1364 return virtual.
1365 get(function() {
1366 if (!this.$$populatedVirtuals) {
1367 this.$$populatedVirtuals = {};
1368 }
1369 if (name in this.$$populatedVirtuals) {
1370 return this.$$populatedVirtuals[name];
1371 }
1372 return null;
1373 }).
1374 set(function(_v) {
1375 if (!this.$$populatedVirtuals) {
1376 this.$$populatedVirtuals = {};
1377 }
1378
1379 if (options.justOne) {
1380 this.$$populatedVirtuals[name] = Array.isArray(_v) ?
1381 _v[0] :
1382 _v;
1383
1384 if (typeof this.$$populatedVirtuals[name] !== 'object') {
1385 this.$$populatedVirtuals[name] = null;
1386 }
1387 } else {
1388 this.$$populatedVirtuals[name] = Array.isArray(_v) ?
1389 _v :
1390 _v == null ? [] : [_v];
1391
1392 this.$$populatedVirtuals[name] = this.$$populatedVirtuals[name].filter(function(doc) {
1393 return doc && typeof doc === 'object';
1394 });
1395 }
1396 });
1397 }
1398
1399 const virtuals = this.virtuals;
1400 const parts = name.split('.');
1401
1402 if (this.pathType(name) === 'real') {
1403 throw new Error('Virtual path "' + name + '"' +
1404 ' conflicts with a real path in the schema');
1405 }
1406
1407 virtuals[name] = parts.reduce(function(mem, part, i) {
1408 mem[part] || (mem[part] = (i === parts.length - 1)
1409 ? new VirtualType(options, name)
1410 : {});
1411 return mem[part];
1412 }, this.tree);
1413
1414 return virtuals[name];
1415};
1416
1417/**
1418 * Returns the virtual type with the given `name`.
1419 *
1420 * @param {String} name
1421 * @return {VirtualType}
1422 */
1423
1424Schema.prototype.virtualpath = function(name) {
1425 return this.virtuals[name];
1426};
1427
1428/**
1429 * Removes the given `path` (or [`paths`]).
1430 *
1431 * @param {String|Array} path
1432 *
1433 * @api public
1434 */
1435Schema.prototype.remove = function(path) {
1436 if (typeof path === 'string') {
1437 path = [path];
1438 }
1439 if (Array.isArray(path)) {
1440 path.forEach(function(name) {
1441 if (this.path(name)) {
1442 delete this.paths[name];
1443
1444 const pieces = name.split('.');
1445 const last = pieces.pop();
1446 let branch = this.tree;
1447 for (let i = 0; i < pieces.length; ++i) {
1448 branch = branch[pieces[i]];
1449 }
1450 delete branch[last];
1451 }
1452 }, this);
1453 }
1454};
1455
1456/**
1457 * Loads an ES6 class into a schema. Maps [setters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set) + [getters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get), [static methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static),
1458 * and [instance methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Class_body_and_method_definitions)
1459 * to schema [virtuals](http://mongoosejs.com/docs/guide.html#virtuals),
1460 * [statics](http://mongoosejs.com/docs/guide.html#statics), and
1461 * [methods](http://mongoosejs.com/docs/guide.html#methods).
1462 *
1463 * ####Example:
1464 *
1465 * ```javascript
1466 * const md5 = require('md5');
1467 * const userSchema = new Schema({ email: String });
1468 * class UserClass {
1469 * // `gravatarImage` becomes a virtual
1470 * get gravatarImage() {
1471 * const hash = md5(this.email.toLowerCase());
1472 * return `https://www.gravatar.com/avatar/${hash}`;
1473 * }
1474 *
1475 * // `getProfileUrl()` becomes a document method
1476 * getProfileUrl() {
1477 * return `https://mysite.com/${this.email}`;
1478 * }
1479 *
1480 * // `findByEmail()` becomes a static
1481 * static findByEmail(email) {
1482 * return this.findOne({ email });
1483 * }
1484 * }
1485 *
1486 * // `schema` will now have a `gravatarImage` virtual, a `getProfileUrl()` method,
1487 * // and a `findByEmail()` static
1488 * userSchema.loadClass(UserClass);
1489 * ```
1490 *
1491 * @param {Function} model
1492 * @param {Boolean} [virtualsOnly] if truthy, only pulls virtuals from the class, not methods or statics
1493 */
1494Schema.prototype.loadClass = function(model, virtualsOnly) {
1495 if (model === Object.prototype ||
1496 model === Function.prototype ||
1497 model.prototype.hasOwnProperty('$isMongooseModelPrototype')) {
1498 return this;
1499 }
1500
1501 this.loadClass(Object.getPrototypeOf(model));
1502
1503 // Add static methods
1504 if (!virtualsOnly) {
1505 Object.getOwnPropertyNames(model).forEach(function(name) {
1506 if (name.match(/^(length|name|prototype)$/)) {
1507 return;
1508 }
1509 const method = Object.getOwnPropertyDescriptor(model, name);
1510 if (typeof method.value === 'function') {
1511 this.static(name, method.value);
1512 }
1513 }, this);
1514 }
1515
1516 // Add methods and virtuals
1517 Object.getOwnPropertyNames(model.prototype).forEach(function(name) {
1518 if (name.match(/^(constructor)$/)) {
1519 return;
1520 }
1521 const method = Object.getOwnPropertyDescriptor(model.prototype, name);
1522 if (!virtualsOnly) {
1523 if (typeof method.value === 'function') {
1524 this.method(name, method.value);
1525 }
1526 }
1527 if (typeof method.get === 'function') {
1528 this.virtual(name).get(method.get);
1529 }
1530 if (typeof method.set === 'function') {
1531 this.virtual(name).set(method.set);
1532 }
1533 }, this);
1534
1535 return this;
1536};
1537
1538/*!
1539 * ignore
1540 */
1541
1542Schema.prototype._getSchema = function(path) {
1543 const _this = this;
1544 const pathschema = _this.path(path);
1545 const resultPath = [];
1546
1547 if (pathschema) {
1548 pathschema.$fullPath = path;
1549 return pathschema;
1550 }
1551
1552 function search(parts, schema) {
1553 let p = parts.length + 1;
1554 let foundschema;
1555 let trypath;
1556
1557 while (p--) {
1558 trypath = parts.slice(0, p).join('.');
1559 foundschema = schema.path(trypath);
1560 if (foundschema) {
1561 resultPath.push(trypath);
1562
1563 if (foundschema.caster) {
1564 // array of Mixed?
1565 if (foundschema.caster instanceof MongooseTypes.Mixed) {
1566 foundschema.caster.$fullPath = resultPath.join('.');
1567 return foundschema.caster;
1568 }
1569
1570 // Now that we found the array, we need to check if there
1571 // are remaining document paths to look up for casting.
1572 // Also we need to handle array.$.path since schema.path
1573 // doesn't work for that.
1574 // If there is no foundschema.schema we are dealing with
1575 // a path like array.$
1576 if (p !== parts.length && foundschema.schema) {
1577 let ret;
1578 if (parts[p] === '$' || isArrayFilter(parts[p])) {
1579 if (p + 1 === parts.length) {
1580 // comments.$
1581 return foundschema;
1582 }
1583 // comments.$.comments.$.title
1584 ret = search(parts.slice(p + 1), foundschema.schema);
1585 if (ret) {
1586 ret.$isUnderneathDocArray = ret.$isUnderneathDocArray ||
1587 !foundschema.schema.$isSingleNested;
1588 }
1589 return ret;
1590 }
1591 // this is the last path of the selector
1592 ret = search(parts.slice(p), foundschema.schema);
1593 if (ret) {
1594 ret.$isUnderneathDocArray = ret.$isUnderneathDocArray ||
1595 !foundschema.schema.$isSingleNested;
1596 }
1597 return ret;
1598 }
1599 }
1600
1601 foundschema.$fullPath = resultPath.join('.');
1602
1603 return foundschema;
1604 }
1605 }
1606 }
1607
1608 // look for arrays
1609 const parts = path.split('.');
1610 for (let i = 0; i < parts.length; ++i) {
1611 if (parts[i] === '$' || isArrayFilter(parts[i])) {
1612 // Re: gh-5628, because `schema.path()` doesn't take $ into account.
1613 parts[i] = '0';
1614 }
1615 }
1616 return search(parts, _this);
1617};
1618
1619/*!
1620 * ignore
1621 */
1622
1623Schema.prototype._getPathType = function(path) {
1624 const _this = this;
1625 const pathschema = _this.path(path);
1626
1627 if (pathschema) {
1628 return 'real';
1629 }
1630
1631 function search(parts, schema) {
1632 let p = parts.length + 1,
1633 foundschema,
1634 trypath;
1635
1636 while (p--) {
1637 trypath = parts.slice(0, p).join('.');
1638 foundschema = schema.path(trypath);
1639 if (foundschema) {
1640 if (foundschema.caster) {
1641 // array of Mixed?
1642 if (foundschema.caster instanceof MongooseTypes.Mixed) {
1643 return { schema: foundschema, pathType: 'mixed' };
1644 }
1645
1646 // Now that we found the array, we need to check if there
1647 // are remaining document paths to look up for casting.
1648 // Also we need to handle array.$.path since schema.path
1649 // doesn't work for that.
1650 // If there is no foundschema.schema we are dealing with
1651 // a path like array.$
1652 if (p !== parts.length && foundschema.schema) {
1653 if (parts[p] === '$' || isArrayFilter(parts[p])) {
1654 if (p === parts.length - 1) {
1655 return { schema: foundschema, pathType: 'nested' };
1656 }
1657 // comments.$.comments.$.title
1658 return search(parts.slice(p + 1), foundschema.schema);
1659 }
1660 // this is the last path of the selector
1661 return search(parts.slice(p), foundschema.schema);
1662 }
1663 return {
1664 schema: foundschema,
1665 pathType: foundschema.$isSingleNested ? 'nested' : 'array'
1666 };
1667 }
1668 return { schema: foundschema, pathType: 'real' };
1669 } else if (p === parts.length && schema.nested[trypath]) {
1670 return { schema: schema, pathType: 'nested' };
1671 }
1672 }
1673 return { schema: foundschema || schema, pathType: 'undefined' };
1674 }
1675
1676 // look for arrays
1677 return search(path.split('.'), _this);
1678};
1679
1680/*!
1681 * ignore
1682 */
1683
1684function isArrayFilter(piece) {
1685 return piece.indexOf('$[') === 0 &&
1686 piece.lastIndexOf(']') === piece.length - 1;
1687}
1688
1689/*!
1690 * Module exports.
1691 */
1692
1693module.exports = exports = Schema;
1694
1695// require down here because of reference issues
1696
1697/**
1698 * The various built-in Mongoose Schema Types.
1699 *
1700 * ####Example:
1701 *
1702 * var mongoose = require('mongoose');
1703 * var ObjectId = mongoose.Schema.Types.ObjectId;
1704 *
1705 * ####Types:
1706 *
1707 * - [String](#schema-string-js)
1708 * - [Number](#schema-number-js)
1709 * - [Boolean](#schema-boolean-js) | Bool
1710 * - [Array](#schema-array-js)
1711 * - [Buffer](#schema-buffer-js)
1712 * - [Date](#schema-date-js)
1713 * - [ObjectId](#schema-objectid-js) | Oid
1714 * - [Mixed](#schema-mixed-js)
1715 *
1716 * Using this exposed access to the `Mixed` SchemaType, we can use them in our schema.
1717 *
1718 * var Mixed = mongoose.Schema.Types.Mixed;
1719 * new mongoose.Schema({ _user: Mixed })
1720 *
1721 * @api public
1722 */
1723
1724Schema.Types = MongooseTypes = require('./schema/index');
1725
1726/*!
1727 * ignore
1728 */
1729
1730exports.ObjectId = MongooseTypes.ObjectId;