query.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. 'use strict';
  2. const Promise = require('bluebird');
  3. const { parseArgs, shuffle } = require('./util');
  4. class Query {
  5. /**
  6. * Query constructor.
  7. *
  8. * @param {Array} data
  9. */
  10. constructor(data) {
  11. this.data = data;
  12. this.length = data.length;
  13. }
  14. /**
  15. * Returns the number of elements.
  16. *
  17. * @return Number
  18. */
  19. count() {
  20. return this.length;
  21. }
  22. /**
  23. * Iterates over all documents.
  24. *
  25. * @param {Function} iterator
  26. */
  27. forEach(iterator) {
  28. const { data, length } = this;
  29. for (let i = 0; i < length; i++) {
  30. iterator(data[i], i);
  31. }
  32. }
  33. /**
  34. * Returns an array containing all documents.
  35. *
  36. * @return {Array}
  37. */
  38. toArray() {
  39. return this.data;
  40. }
  41. /**
  42. * Returns the document at the specified index. `num` can be a positive or
  43. * negative number.
  44. *
  45. * @param {Number} i
  46. * @return {Document|Object}
  47. */
  48. eq(i) {
  49. const index = i < 0 ? this.length + i : i;
  50. return this.data[index];
  51. }
  52. /**
  53. * Returns the first document.
  54. *
  55. * @return {Document|Object}
  56. */
  57. first() {
  58. return this.eq(0);
  59. }
  60. /**
  61. * Returns the last document.
  62. *
  63. * @return {Document|Object}
  64. */
  65. last() {
  66. return this.eq(-1);
  67. }
  68. /**
  69. * Returns the specified range of documents.
  70. *
  71. * @param {Number} start
  72. * @param {Number} [end]
  73. * @return {Query}
  74. */
  75. slice(start, end) {
  76. return new this.constructor(this.data.slice(start, end));
  77. }
  78. /**
  79. * Limits the number of documents returned.
  80. *
  81. * @param {Number} i
  82. * @return {Query}
  83. */
  84. limit(i) {
  85. return this.slice(0, i);
  86. }
  87. /**
  88. * Specifies the number of items to skip.
  89. *
  90. * @param {Number} i
  91. * @return {Query}
  92. */
  93. skip(i) {
  94. return this.slice(i);
  95. }
  96. /**
  97. * Returns documents in a reversed order.
  98. *
  99. * @return {Query}
  100. */
  101. reverse() {
  102. return new this.constructor(this.data.slice().reverse());
  103. }
  104. /**
  105. * Returns documents in random order.
  106. *
  107. * @return {Query}
  108. */
  109. shuffle() {
  110. return new this.constructor(shuffle(this.data));
  111. }
  112. /**
  113. * Finds matching documents.
  114. *
  115. * @param {Object} query
  116. * @param {Object} [options]
  117. * @param {Number} [options.limit=0] Limits the number of documents returned.
  118. * @param {Number} [options.skip=0] Skips the first elements.
  119. * @param {Boolean} [options.lean=false] Returns a plain JavaScript object.
  120. * @return {Query|Array}
  121. */
  122. find(query, options = {}) {
  123. const filter = this._schema._execQuery(query);
  124. const { data, length } = this;
  125. const { lean = false } = options;
  126. let { limit = length, skip } = options;
  127. const arr = [];
  128. for (let i = 0; limit && i < length; i++) {
  129. const item = data[i];
  130. if (filter(item)) {
  131. if (skip) {
  132. skip--;
  133. } else {
  134. arr.push(lean ? item.toObject() : item);
  135. limit--;
  136. }
  137. }
  138. }
  139. return lean ? arr : new this.constructor(arr);
  140. }
  141. /**
  142. * Finds the first matching documents.
  143. *
  144. * @param {Object} query
  145. * @param {Object} [options]
  146. * @param {Number} [options.skip=0] Skips the first elements.
  147. * @param {Boolean} [options.lean=false] Returns a plain JavaScript object.
  148. * @return {Document|Object}
  149. */
  150. findOne(query, options = {}) {
  151. options.limit = 1;
  152. const result = this.find(query, options);
  153. return options.lean ? result[0] : result.data[0];
  154. }
  155. /**
  156. * Sorts documents.
  157. *
  158. * Example:
  159. *
  160. * ``` js
  161. * query.sort('date', -1);
  162. * query.sort({date: -1, title: 1});
  163. * query.sort('-date title');
  164. * ```
  165. *
  166. * If the `order` equals to `-1`, `desc` or `descending`, the data will be
  167. * returned in reversed order.
  168. *
  169. * @param {String|Object} orderby
  170. * @param {String|Number} [order]
  171. * @return {Query}
  172. */
  173. sort(orderby, order) {
  174. const sort = parseArgs(orderby, order);
  175. const fn = this._schema._execSort(sort);
  176. return new this.constructor(this.data.slice().sort(fn));
  177. }
  178. /**
  179. * Creates an array of values by iterating each element in the collection.
  180. *
  181. * @param {Function} iterator
  182. * @return {Array}
  183. */
  184. map(iterator) {
  185. const { data, length } = this;
  186. const result = new Array(length);
  187. for (let i = 0; i < length; i++) {
  188. result[i] = iterator(data[i], i);
  189. }
  190. return result;
  191. }
  192. /**
  193. * Reduces a collection to a value which is the accumulated result of iterating
  194. * each element in the collection.
  195. *
  196. * @param {Function} iterator
  197. * @param {*} [initial] By default, the initial value is the first document.
  198. * @return {*}
  199. */
  200. reduce(iterator, initial) {
  201. const { data, length } = this;
  202. let result, i;
  203. if (initial === undefined) {
  204. i = 1;
  205. result = data[0];
  206. } else {
  207. i = 0;
  208. result = initial;
  209. }
  210. for (; i < length; i++) {
  211. result = iterator(result, data[i], i);
  212. }
  213. return result;
  214. }
  215. /**
  216. * Reduces a collection to a value which is the accumulated result of iterating
  217. * each element in the collection from right to left.
  218. *
  219. * @param {Function} iterator
  220. * @param {*} [initial] By default, the initial value is the last document.
  221. * @return {*}
  222. */
  223. reduceRight(iterator, initial) {
  224. const { data, length } = this;
  225. let result, i;
  226. if (initial === undefined) {
  227. i = length - 2;
  228. result = data[length - 1];
  229. } else {
  230. i = length - 1;
  231. result = initial;
  232. }
  233. for (; i >= 0; i--) {
  234. result = iterator(result, data[i], i);
  235. }
  236. return result;
  237. }
  238. /**
  239. * Creates a new array with all documents that pass the test implemented by the
  240. * provided function.
  241. *
  242. * @param {Function} iterator
  243. * @return {Query}
  244. */
  245. filter(iterator) {
  246. const { data, length } = this;
  247. const arr = [];
  248. for (let i = 0; i < length; i++) {
  249. const item = data[i];
  250. if (iterator(item, i)) arr.push(item);
  251. }
  252. return new this.constructor(arr);
  253. }
  254. /**
  255. * Tests whether all documents pass the test implemented by the provided
  256. * function.
  257. *
  258. * @param {Function} iterator
  259. * @return {Boolean}
  260. */
  261. every(iterator) {
  262. const { data, length } = this;
  263. for (let i = 0; i < length; i++) {
  264. if (!iterator(data[i], i)) return false;
  265. }
  266. return true;
  267. }
  268. /**
  269. * Tests whether some documents pass the test implemented by the provided
  270. * function.
  271. *
  272. * @param {Function} iterator
  273. * @return {Boolean}
  274. */
  275. some(iterator) {
  276. const { data, length } = this;
  277. for (let i = 0; i < length; i++) {
  278. if (iterator(data[i], i)) return true;
  279. }
  280. return false;
  281. }
  282. /**
  283. * Update all documents.
  284. *
  285. * @param {Object} data
  286. * @param {Function} [callback]
  287. * @return {Promise}
  288. */
  289. update(data, callback) {
  290. const model = this._model;
  291. const stack = this._schema._parseUpdate(data);
  292. return Promise.mapSeries(this.data, item => model._updateWithStack(item._id, stack)).asCallback(callback);
  293. }
  294. /**
  295. * Replace all documents.
  296. *
  297. * @param {Object} data
  298. * @param {Function} [callback]
  299. * @return {Promise}
  300. */
  301. replace(data, callback) {
  302. const model = this._model;
  303. return Promise.map(this.data, item => model.replaceById(item._id, data)).asCallback(callback);
  304. }
  305. /**
  306. * Remove all documents.
  307. *
  308. * @param {Function} [callback]
  309. * @return {Promise}
  310. */
  311. remove(callback) {
  312. const model = this._model;
  313. return Promise.mapSeries(this.data, item => model.removeById(item._id)).asCallback(callback);
  314. }
  315. /**
  316. * Populates document references.
  317. *
  318. * @param {String|Object} expr
  319. * @return {Query}
  320. */
  321. populate(expr) {
  322. const stack = this._schema._parsePopulate(expr);
  323. const { data, length } = this;
  324. const model = this._model;
  325. for (let i = 0; i < length; i++) {
  326. data[i] = model._populate(data[i], stack);
  327. }
  328. return this;
  329. }
  330. }
  331. Query.prototype.size = Query.prototype.count;
  332. Query.prototype.each = Query.prototype.forEach;
  333. Query.prototype.random = Query.prototype.shuffle;
  334. module.exports = Query;