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 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  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. for (i = 0, l = indexes.length; i < l; ++i) {
  213. const pathIndex = indexes[i][0][this.path];
  214. if (pathIndex === '2dsphere' || pathIndex === '2d') {
  215. return;
  216. }
  217. }
  218. }
  219. if (!(value && value.isMongooseArray)) {
  220. value = new MongooseArray(value, this.path, doc);
  221. } else if (value && value.isMongooseArray) {
  222. // We need to create a new array, otherwise change tracking will
  223. // update the old doc (gh-4449)
  224. value = new MongooseArray(value, this.path, doc);
  225. }
  226. if (this.caster && this.casterConstructor !== Mixed) {
  227. try {
  228. for (i = 0, l = value.length; i < l; i++) {
  229. value[i] = this.caster.cast(value[i], doc, init);
  230. }
  231. } catch (e) {
  232. // rethrow
  233. throw new CastError('[' + e.kind + ']', util.inspect(value), this.path, e);
  234. }
  235. }
  236. return value;
  237. }
  238. if (init || SchemaArray.options.castNonArrays) {
  239. // gh-2442: if we're loading this from the db and its not an array, mark
  240. // the whole array as modified.
  241. if (!!doc && !!init) {
  242. doc.markModified(this.path);
  243. }
  244. return this.cast([value], doc, init);
  245. }
  246. throw new CastError('Array', util.inspect(value), this.path);
  247. };
  248. /*!
  249. * Ignore
  250. */
  251. SchemaArray.prototype.discriminator = function(name, schema) {
  252. let arr = this; // eslint-disable-line consistent-this
  253. while (arr.$isMongooseArray && !arr.$isMongooseDocumentArray) {
  254. arr = arr.casterConstructor;
  255. if (arr == null || typeof arr === 'function') {
  256. throw new MongooseError('You can only add an embedded discriminator on ' +
  257. 'a document array, ' + this.path + ' is a plain array');
  258. }
  259. }
  260. return arr.discriminator(name, schema);
  261. };
  262. /*!
  263. * ignore
  264. */
  265. SchemaArray.prototype.clone = function() {
  266. const options = Object.assign({}, this.options);
  267. const schematype = new this.constructor(this.path, this.caster, options, this.schemaOptions);
  268. schematype.validators = this.validators.slice();
  269. return schematype;
  270. };
  271. /**
  272. * Casts values for queries.
  273. *
  274. * @param {String} $conditional
  275. * @param {any} [value]
  276. * @api private
  277. */
  278. SchemaArray.prototype.castForQuery = function($conditional, value) {
  279. let handler;
  280. let val;
  281. if (arguments.length === 2) {
  282. handler = this.$conditionalHandlers[$conditional];
  283. if (!handler) {
  284. throw new Error('Can\'t use ' + $conditional + ' with Array.');
  285. }
  286. val = handler.call(this, value);
  287. } else {
  288. val = $conditional;
  289. let Constructor = this.casterConstructor;
  290. if (val &&
  291. Constructor.discriminators &&
  292. Constructor.schema &&
  293. Constructor.schema.options &&
  294. Constructor.schema.options.discriminatorKey) {
  295. if (typeof val[Constructor.schema.options.discriminatorKey] === 'string' &&
  296. Constructor.discriminators[val[Constructor.schema.options.discriminatorKey]]) {
  297. Constructor = Constructor.discriminators[val[Constructor.schema.options.discriminatorKey]];
  298. } else {
  299. const constructorByValue = getDiscriminatorByValue(Constructor, val[Constructor.schema.options.discriminatorKey]);
  300. if (constructorByValue) {
  301. Constructor = constructorByValue;
  302. }
  303. }
  304. }
  305. const proto = this.casterConstructor.prototype;
  306. let method = proto && (proto.castForQuery || proto.cast);
  307. if (!method && Constructor.castForQuery) {
  308. method = Constructor.castForQuery;
  309. }
  310. const caster = this.caster;
  311. if (Array.isArray(val)) {
  312. this.setters.reverse().forEach(setter => {
  313. val = setter.call(this, val, this);
  314. });
  315. val = val.map(function(v) {
  316. if (utils.isObject(v) && v.$elemMatch) {
  317. return v;
  318. }
  319. if (method) {
  320. v = method.call(caster, v);
  321. return v;
  322. }
  323. if (v != null) {
  324. v = new Constructor(v);
  325. return v;
  326. }
  327. return v;
  328. });
  329. } else if (method) {
  330. val = method.call(caster, val);
  331. } else if (val != null) {
  332. val = new Constructor(val);
  333. }
  334. }
  335. return val;
  336. };
  337. function cast$all(val) {
  338. if (!Array.isArray(val)) {
  339. val = [val];
  340. }
  341. val = val.map(function(v) {
  342. if (utils.isObject(v)) {
  343. const o = {};
  344. o[this.path] = v;
  345. return cast(this.casterConstructor.schema, o)[this.path];
  346. }
  347. return v;
  348. }, this);
  349. return this.castForQuery(val);
  350. }
  351. function cast$elemMatch(val) {
  352. const keys = Object.keys(val);
  353. const numKeys = keys.length;
  354. for (let i = 0; i < numKeys; ++i) {
  355. const key = keys[i];
  356. const value = val[key];
  357. if (key.indexOf('$') === 0 && value) {
  358. val[key] = this.castForQuery(key, value);
  359. }
  360. }
  361. // Is this an embedded discriminator and is the discriminator key set?
  362. // If so, use the discriminator schema. See gh-7449
  363. const discriminatorKey = get(this,
  364. 'casterConstructor.schema.options.discriminatorKey');
  365. const discriminators = get(this, 'casterConstructor.schema.discriminators', {});
  366. if (discriminatorKey != null &&
  367. val[discriminatorKey] != null &&
  368. discriminators[val[discriminatorKey]] != null) {
  369. return cast(discriminators[val[discriminatorKey]], val);
  370. }
  371. return cast(this.casterConstructor.schema, val);
  372. }
  373. const handle = SchemaArray.prototype.$conditionalHandlers = {};
  374. handle.$all = cast$all;
  375. handle.$options = String;
  376. handle.$elemMatch = cast$elemMatch;
  377. handle.$geoIntersects = geospatial.cast$geoIntersects;
  378. handle.$or = handle.$and = function(val) {
  379. if (!Array.isArray(val)) {
  380. throw new TypeError('conditional $or/$and require array');
  381. }
  382. const ret = [];
  383. for (let i = 0; i < val.length; ++i) {
  384. ret.push(cast(this.casterConstructor.schema, val[i]));
  385. }
  386. return ret;
  387. };
  388. handle.$near =
  389. handle.$nearSphere = geospatial.cast$near;
  390. handle.$within =
  391. handle.$geoWithin = geospatial.cast$within;
  392. handle.$size =
  393. handle.$minDistance =
  394. handle.$maxDistance = castToNumber;
  395. handle.$exists = $exists;
  396. handle.$type = $type;
  397. handle.$eq =
  398. handle.$gt =
  399. handle.$gte =
  400. handle.$lt =
  401. handle.$lte =
  402. handle.$ne =
  403. handle.$nin =
  404. handle.$regex = SchemaArray.prototype.castForQuery;
  405. // `$in` is special because you can also include an empty array in the query
  406. // like `$in: [1, []]`, see gh-5913
  407. handle.$in = SchemaType.prototype.$conditionalHandlers.$in;
  408. /*!
  409. * Module exports.
  410. */
  411. module.exports = SchemaArray;