Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

array.js 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. 'use strict';
  2. /*!
  3. * Module dependencies.
  4. */
  5. const $exists = require('./operators/exists');
  6. const $type = require('./operators/type');
  7. const MongooseError = require('../error/mongooseError');
  8. const SchemaType = require('../schematype');
  9. const CastError = SchemaType.CastError;
  10. const Types = {
  11. Array: SchemaArray,
  12. Boolean: require('./boolean'),
  13. Date: require('./date'),
  14. Number: require('./number'),
  15. String: require('./string'),
  16. ObjectId: require('./objectid'),
  17. Buffer: require('./buffer'),
  18. Map: require('./map')
  19. };
  20. const Mixed = require('./mixed');
  21. const cast = require('../cast');
  22. const get = require('../helpers/get');
  23. const util = require('util');
  24. const utils = require('../utils');
  25. const castToNumber = require('./operators/helpers').castToNumber;
  26. const geospatial = require('./operators/geospatial');
  27. const getDiscriminatorByValue = require('../queryhelpers').getDiscriminatorByValue;
  28. let MongooseArray;
  29. let EmbeddedDoc;
  30. /**
  31. * Array SchemaType constructor
  32. *
  33. * @param {String} key
  34. * @param {SchemaType} cast
  35. * @param {Object} options
  36. * @inherits SchemaType
  37. * @api public
  38. */
  39. function SchemaArray(key, cast, options, schemaOptions) {
  40. // lazy load
  41. EmbeddedDoc || (EmbeddedDoc = require('../types').Embedded);
  42. let typeKey = 'type';
  43. if (schemaOptions && schemaOptions.typeKey) {
  44. typeKey = schemaOptions.typeKey;
  45. }
  46. this.schemaOptions = schemaOptions;
  47. if (cast) {
  48. let castOptions = {};
  49. if (utils.isPOJO(cast)) {
  50. if (cast[typeKey]) {
  51. // support { type: Woot }
  52. castOptions = utils.clone(cast); // do not alter user arguments
  53. delete castOptions[typeKey];
  54. cast = cast[typeKey];
  55. } else {
  56. cast = Mixed;
  57. }
  58. }
  59. if (cast === Object) {
  60. cast = Mixed;
  61. }
  62. // support { type: 'String' }
  63. const name = typeof cast === 'string'
  64. ? cast
  65. : utils.getFunctionName(cast);
  66. const caster = name in Types
  67. ? Types[name]
  68. : cast;
  69. this.casterConstructor = caster;
  70. if (typeof caster === 'function' &&
  71. !caster.$isArraySubdocument &&
  72. !caster.$isSchemaMap) {
  73. this.caster = new caster(null, castOptions);
  74. } else {
  75. this.caster = caster;
  76. }
  77. if (!(this.caster instanceof EmbeddedDoc)) {
  78. this.caster.path = key;
  79. }
  80. }
  81. this.$isMongooseArray = true;
  82. SchemaType.call(this, key, options, 'Array');
  83. let defaultArr;
  84. let fn;
  85. if (this.defaultValue != null) {
  86. defaultArr = this.defaultValue;
  87. fn = typeof defaultArr === 'function';
  88. }
  89. if (!('defaultValue' in this) || this.defaultValue !== void 0) {
  90. const defaultFn = function() {
  91. let arr = [];
  92. if (fn) {
  93. arr = defaultArr.call(this);
  94. } else if (defaultArr != null) {
  95. arr = arr.concat(defaultArr);
  96. }
  97. // Leave it up to `cast()` to convert the array
  98. return arr;
  99. };
  100. defaultFn.$runBeforeSetters = true;
  101. this.default(defaultFn);
  102. }
  103. }
  104. /**
  105. * This schema type's name, to defend against minifiers that mangle
  106. * function names.
  107. *
  108. * @api public
  109. */
  110. SchemaArray.schemaName = 'Array';
  111. /**
  112. * Options for all arrays.
  113. *
  114. * - `castNonArrays`: `true` by default. If `false`, Mongoose will throw a CastError when a value isn't an array. If `true`, Mongoose will wrap the provided value in an array before casting.
  115. *
  116. * @static options
  117. * @api public
  118. */
  119. SchemaArray.options = { castNonArrays: true };
  120. /*!
  121. * Inherits from SchemaType.
  122. */
  123. SchemaArray.prototype = Object.create(SchemaType.prototype);
  124. SchemaArray.prototype.constructor = SchemaArray;
  125. /*!
  126. * ignore
  127. */
  128. SchemaArray._checkRequired = SchemaType.prototype.checkRequired;
  129. /**
  130. * Override the function the required validator uses to check whether an array
  131. * passes the `required` check.
  132. *
  133. * ####Example:
  134. *
  135. * // Require non-empty array to pass `required` check
  136. * mongoose.Schema.Types.Array.checkRequired(v => Array.isArray(v) && v.length);
  137. *
  138. * const M = mongoose.model({ arr: { type: Array, required: true } });
  139. * new M({ arr: [] }).validateSync(); // `null`, validation fails!
  140. *
  141. * @param {Function} fn
  142. * @return {Function}
  143. * @function checkRequired
  144. * @static
  145. * @api public
  146. */
  147. SchemaArray.checkRequired = SchemaType.checkRequired;
  148. /**
  149. * Check if the given value satisfies the `required` validator.
  150. *
  151. * @param {Any} value
  152. * @param {Document} doc
  153. * @return {Boolean}
  154. * @api public
  155. */
  156. SchemaArray.prototype.checkRequired = function checkRequired(value, doc) {
  157. if (SchemaType._isRef(this, value, doc, true)) {
  158. return !!value;
  159. }
  160. // `require('util').inherits()` does **not** copy static properties, and
  161. // plugins like mongoose-float use `inherits()` for pre-ES6.
  162. const _checkRequired = typeof this.constructor.checkRequired == 'function' ?
  163. this.constructor.checkRequired() :
  164. SchemaArray.checkRequired();
  165. return _checkRequired(value);
  166. };
  167. /**
  168. * Adds an enum validator if this is an array of strings. Equivalent to
  169. * `SchemaString.prototype.enum()`
  170. *
  171. * @param {String|Object} [args...] enumeration values
  172. * @return {SchemaType} this
  173. */
  174. SchemaArray.prototype.enum = function() {
  175. const instance = get(this, 'caster.instance');
  176. if (instance !== 'String') {
  177. throw new Error('`enum` can only be set on an array of strings, not ' + instance);
  178. }
  179. this.caster.enum.apply(this.caster, arguments);
  180. return this;
  181. };
  182. /**
  183. * Overrides the getters application for the population special-case
  184. *
  185. * @param {Object} value
  186. * @param {Object} scope
  187. * @api private
  188. */
  189. SchemaArray.prototype.applyGetters = function(value, scope) {
  190. if (this.caster.options && this.caster.options.ref) {
  191. // means the object id was populated
  192. return value;
  193. }
  194. return SchemaType.prototype.applyGetters.call(this, value, scope);
  195. };
  196. /**
  197. * Casts values for set().
  198. *
  199. * @param {Object} value
  200. * @param {Document} doc document that triggers the casting
  201. * @param {Boolean} init whether this is an initialization cast
  202. * @api private
  203. */
  204. SchemaArray.prototype.cast = function(value, doc, init) {
  205. // lazy load
  206. MongooseArray || (MongooseArray = require('../types').Array);
  207. let i;
  208. let l;
  209. if (Array.isArray(value)) {
  210. if (!value.length && doc) {
  211. const indexes = doc.schema.indexedPaths();
  212. const arrayPath = this.path;
  213. for (i = 0, l = indexes.length; i < l; ++i) {
  214. const pathIndex = indexes[i][0][arrayPath];
  215. if (pathIndex === '2dsphere' || pathIndex === '2d') {
  216. return;
  217. }
  218. }
  219. // Special case: if this index is on the parent of what looks like
  220. // GeoJSON, skip setting the default to empty array re: #1668, #3233
  221. const arrayGeojsonPath = this.path.endsWith('.coordinates') ?
  222. this.path.substr(0, this.path.lastIndexOf('.')) : null;
  223. if (arrayGeojsonPath != null) {
  224. for (i = 0, l = indexes.length; i < l; ++i) {
  225. const pathIndex = indexes[i][0][arrayGeojsonPath];
  226. if (pathIndex === '2dsphere') {
  227. return;
  228. }
  229. }
  230. }
  231. }
  232. if (!(value && value.isMongooseArray)) {
  233. value = new MongooseArray(value, this.path, doc);
  234. } else if (value && value.isMongooseArray) {
  235. // We need to create a new array, otherwise change tracking will
  236. // update the old doc (gh-4449)
  237. value = new MongooseArray(value, this.path, doc);
  238. }
  239. if (this.caster && this.casterConstructor !== Mixed) {
  240. try {
  241. for (i = 0, l = value.length; i < l; i++) {
  242. value[i] = this.caster.cast(value[i], doc, init);
  243. }
  244. } catch (e) {
  245. // rethrow
  246. throw new CastError('[' + e.kind + ']', util.inspect(value), this.path, e);
  247. }
  248. }
  249. return value;
  250. }
  251. if (init || SchemaArray.options.castNonArrays) {
  252. // gh-2442: if we're loading this from the db and its not an array, mark
  253. // the whole array as modified.
  254. if (!!doc && !!init) {
  255. doc.markModified(this.path);
  256. }
  257. return this.cast([value], doc, init);
  258. }
  259. throw new CastError('Array', util.inspect(value), this.path);
  260. };
  261. /*!
  262. * Ignore
  263. */
  264. SchemaArray.prototype.discriminator = function(name, schema) {
  265. let arr = this; // eslint-disable-line consistent-this
  266. while (arr.$isMongooseArray && !arr.$isMongooseDocumentArray) {
  267. arr = arr.casterConstructor;
  268. if (arr == null || typeof arr === 'function') {
  269. throw new MongooseError('You can only add an embedded discriminator on ' +
  270. 'a document array, ' + this.path + ' is a plain array');
  271. }
  272. }
  273. return arr.discriminator(name, schema);
  274. };
  275. /*!
  276. * ignore
  277. */
  278. SchemaArray.prototype.clone = function() {
  279. const options = Object.assign({}, this.options);
  280. const schematype = new this.constructor(this.path, this.caster, options, this.schemaOptions);
  281. schematype.validators = this.validators.slice();
  282. return schematype;
  283. };
  284. /**
  285. * Casts values for queries.
  286. *
  287. * @param {String} $conditional
  288. * @param {any} [value]
  289. * @api private
  290. */
  291. SchemaArray.prototype.castForQuery = function($conditional, value) {
  292. let handler;
  293. let val;
  294. if (arguments.length === 2) {
  295. handler = this.$conditionalHandlers[$conditional];
  296. if (!handler) {
  297. throw new Error('Can\'t use ' + $conditional + ' with Array.');
  298. }
  299. val = handler.call(this, value);
  300. } else {
  301. val = $conditional;
  302. let Constructor = this.casterConstructor;
  303. if (val &&
  304. Constructor.discriminators &&
  305. Constructor.schema &&
  306. Constructor.schema.options &&
  307. Constructor.schema.options.discriminatorKey) {
  308. if (typeof val[Constructor.schema.options.discriminatorKey] === 'string' &&
  309. Constructor.discriminators[val[Constructor.schema.options.discriminatorKey]]) {
  310. Constructor = Constructor.discriminators[val[Constructor.schema.options.discriminatorKey]];
  311. } else {
  312. const constructorByValue = getDiscriminatorByValue(Constructor, val[Constructor.schema.options.discriminatorKey]);
  313. if (constructorByValue) {
  314. Constructor = constructorByValue;
  315. }
  316. }
  317. }
  318. const proto = this.casterConstructor.prototype;
  319. let method = proto && (proto.castForQuery || proto.cast);
  320. if (!method && Constructor.castForQuery) {
  321. method = Constructor.castForQuery;
  322. }
  323. const caster = this.caster;
  324. if (Array.isArray(val)) {
  325. this.setters.reverse().forEach(setter => {
  326. val = setter.call(this, val, this);
  327. });
  328. val = val.map(function(v) {
  329. if (utils.isObject(v) && v.$elemMatch) {
  330. return v;
  331. }
  332. if (method) {
  333. v = method.call(caster, v);
  334. return v;
  335. }
  336. if (v != null) {
  337. v = new Constructor(v);
  338. return v;
  339. }
  340. return v;
  341. });
  342. } else if (method) {
  343. val = method.call(caster, val);
  344. } else if (val != null) {
  345. val = new Constructor(val);
  346. }
  347. }
  348. return val;
  349. };
  350. function cast$all(val) {
  351. if (!Array.isArray(val)) {
  352. val = [val];
  353. }
  354. val = val.map(function(v) {
  355. if (utils.isObject(v)) {
  356. const o = {};
  357. o[this.path] = v;
  358. return cast(this.casterConstructor.schema, o)[this.path];
  359. }
  360. return v;
  361. }, this);
  362. return this.castForQuery(val);
  363. }
  364. function cast$elemMatch(val) {
  365. const keys = Object.keys(val);
  366. const numKeys = keys.length;
  367. for (let i = 0; i < numKeys; ++i) {
  368. const key = keys[i];
  369. const value = val[key];
  370. if (key.startsWith('$') && value) {
  371. val[key] = this.castForQuery(key, value);
  372. }
  373. }
  374. // Is this an embedded discriminator and is the discriminator key set?
  375. // If so, use the discriminator schema. See gh-7449
  376. const discriminatorKey = get(this,
  377. 'casterConstructor.schema.options.discriminatorKey');
  378. const discriminators = get(this, 'casterConstructor.schema.discriminators', {});
  379. if (discriminatorKey != null &&
  380. val[discriminatorKey] != null &&
  381. discriminators[val[discriminatorKey]] != null) {
  382. return cast(discriminators[val[discriminatorKey]], val);
  383. }
  384. return cast(this.casterConstructor.schema, val);
  385. }
  386. const handle = SchemaArray.prototype.$conditionalHandlers = {};
  387. handle.$all = cast$all;
  388. handle.$options = String;
  389. handle.$elemMatch = cast$elemMatch;
  390. handle.$geoIntersects = geospatial.cast$geoIntersects;
  391. handle.$or = handle.$and = function(val) {
  392. if (!Array.isArray(val)) {
  393. throw new TypeError('conditional $or/$and require array');
  394. }
  395. const ret = [];
  396. for (let i = 0; i < val.length; ++i) {
  397. ret.push(cast(this.casterConstructor.schema, val[i]));
  398. }
  399. return ret;
  400. };
  401. handle.$near =
  402. handle.$nearSphere = geospatial.cast$near;
  403. handle.$within =
  404. handle.$geoWithin = geospatial.cast$within;
  405. handle.$size =
  406. handle.$minDistance =
  407. handle.$maxDistance = castToNumber;
  408. handle.$exists = $exists;
  409. handle.$type = $type;
  410. handle.$eq =
  411. handle.$gt =
  412. handle.$gte =
  413. handle.$lt =
  414. handle.$lte =
  415. handle.$ne =
  416. handle.$regex = SchemaArray.prototype.castForQuery;
  417. // `$in` is special because you can also include an empty array in the query
  418. // like `$in: [1, []]`, see gh-5913
  419. handle.$nin = SchemaType.prototype.$conditionalHandlers.$nin;
  420. handle.$in = SchemaType.prototype.$conditionalHandlers.$in;
  421. /*!
  422. * Module exports.
  423. */
  424. module.exports = SchemaArray;