query.js 8.1 KB

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