2023-10-03 11:14:36 +08:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
const Promise = require('bluebird');
|
|
|
|
const { parseArgs, shuffle } = require('./util');
|
|
|
|
|
|
|
|
class Query {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Query constructor.
|
|
|
|
*
|
|
|
|
* @param {Array} data
|
|
|
|
*/
|
|
|
|
constructor(data) {
|
|
|
|
this.data = data;
|
|
|
|
this.length = data.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the number of elements.
|
|
|
|
*
|
|
|
|
* @return Number
|
|
|
|
*/
|
|
|
|
count() {
|
|
|
|
return this.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Iterates over all documents.
|
|
|
|
*
|
|
|
|
* @param {Function} iterator
|
|
|
|
*/
|
|
|
|
forEach(iterator) {
|
|
|
|
const { data, length } = this;
|
|
|
|
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
|
|
iterator(data[i], i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an array containing all documents.
|
|
|
|
*
|
|
|
|
* @return {Array}
|
|
|
|
*/
|
|
|
|
toArray() {
|
|
|
|
return this.data;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the document at the specified index. `num` can be a positive or
|
|
|
|
* negative number.
|
|
|
|
*
|
|
|
|
* @param {Number} i
|
|
|
|
* @return {Document|Object}
|
|
|
|
*/
|
|
|
|
eq(i) {
|
|
|
|
const index = i < 0 ? this.length + i : i;
|
|
|
|
return this.data[index];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the first document.
|
|
|
|
*
|
|
|
|
* @return {Document|Object}
|
|
|
|
*/
|
|
|
|
first() {
|
|
|
|
return this.eq(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the last document.
|
|
|
|
*
|
|
|
|
* @return {Document|Object}
|
|
|
|
*/
|
|
|
|
last() {
|
|
|
|
return this.eq(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the specified range of documents.
|
|
|
|
*
|
|
|
|
* @param {Number} start
|
|
|
|
* @param {Number} [end]
|
|
|
|
* @return {Query}
|
|
|
|
*/
|
|
|
|
slice(start, end) {
|
|
|
|
return new this.constructor(this.data.slice(start, end));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Limits the number of documents returned.
|
|
|
|
*
|
|
|
|
* @param {Number} i
|
|
|
|
* @return {Query}
|
|
|
|
*/
|
|
|
|
limit(i) {
|
|
|
|
return this.slice(0, i);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Specifies the number of items to skip.
|
|
|
|
*
|
|
|
|
* @param {Number} i
|
|
|
|
* @return {Query}
|
|
|
|
*/
|
|
|
|
skip(i) {
|
|
|
|
return this.slice(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns documents in a reversed order.
|
|
|
|
*
|
|
|
|
* @return {Query}
|
|
|
|
*/
|
|
|
|
reverse() {
|
|
|
|
return new this.constructor(this.data.slice().reverse());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns documents in random order.
|
|
|
|
*
|
|
|
|
* @return {Query}
|
|
|
|
*/
|
|
|
|
shuffle() {
|
|
|
|
return new this.constructor(shuffle(this.data));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Finds matching documents.
|
|
|
|
*
|
|
|
|
* @param {Object} query
|
|
|
|
* @param {Object} [options]
|
|
|
|
* @param {Number} [options.limit=0] Limits the number of documents returned.
|
|
|
|
* @param {Number} [options.skip=0] Skips the first elements.
|
|
|
|
* @param {Boolean} [options.lean=false] Returns a plain JavaScript object.
|
|
|
|
* @return {Query|Array}
|
|
|
|
*/
|
|
|
|
find(query, options = {}) {
|
|
|
|
const filter = this._schema._execQuery(query);
|
|
|
|
const { data, length } = this;
|
|
|
|
const { lean = false } = options;
|
|
|
|
let { limit = length, skip } = options;
|
|
|
|
const arr = [];
|
|
|
|
|
|
|
|
for (let i = 0; limit && i < length; i++) {
|
|
|
|
const item = data[i];
|
|
|
|
|
|
|
|
if (filter(item)) {
|
|
|
|
if (skip) {
|
|
|
|
skip--;
|
|
|
|
} else {
|
|
|
|
arr.push(lean ? item.toObject() : item);
|
|
|
|
limit--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return lean ? arr : new this.constructor(arr);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Finds the first matching documents.
|
|
|
|
*
|
|
|
|
* @param {Object} query
|
|
|
|
* @param {Object} [options]
|
|
|
|
* @param {Number} [options.skip=0] Skips the first elements.
|
|
|
|
* @param {Boolean} [options.lean=false] Returns a plain JavaScript object.
|
|
|
|
* @return {Document|Object}
|
|
|
|
*/
|
|
|
|
findOne(query, options = {}) {
|
|
|
|
options.limit = 1;
|
|
|
|
|
|
|
|
const result = this.find(query, options);
|
|
|
|
return options.lean ? result[0] : result.data[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sorts documents.
|
|
|
|
*
|
|
|
|
* Example:
|
|
|
|
*
|
|
|
|
* ``` js
|
|
|
|
* query.sort('date', -1);
|
|
|
|
* query.sort({date: -1, title: 1});
|
|
|
|
* query.sort('-date title');
|
|
|
|
* ```
|
|
|
|
*
|
|
|
|
* If the `order` equals to `-1`, `desc` or `descending`, the data will be
|
|
|
|
* returned in reversed order.
|
|
|
|
*
|
|
|
|
* @param {String|Object} orderby
|
|
|
|
* @param {String|Number} [order]
|
|
|
|
* @return {Query}
|
|
|
|
*/
|
|
|
|
sort(orderby, order) {
|
|
|
|
const sort = parseArgs(orderby, order);
|
|
|
|
const fn = this._schema._execSort(sort);
|
|
|
|
|
|
|
|
return new this.constructor(this.data.slice().sort(fn));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates an array of values by iterating each element in the collection.
|
|
|
|
*
|
|
|
|
* @param {Function} iterator
|
|
|
|
* @return {Array}
|
|
|
|
*/
|
|
|
|
map(iterator) {
|
|
|
|
const { data, length } = this;
|
|
|
|
const result = new Array(length);
|
|
|
|
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
|
|
result[i] = iterator(data[i], i);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reduces a collection to a value which is the accumulated result of iterating
|
|
|
|
* each element in the collection.
|
|
|
|
*
|
|
|
|
* @param {Function} iterator
|
|
|
|
* @param {*} [initial] By default, the initial value is the first document.
|
|
|
|
* @return {*}
|
|
|
|
*/
|
|
|
|
reduce(iterator, initial) {
|
|
|
|
const { data, length } = this;
|
|
|
|
let result, i;
|
|
|
|
|
|
|
|
if (initial === undefined) {
|
|
|
|
i = 1;
|
|
|
|
result = data[0];
|
|
|
|
} else {
|
|
|
|
i = 0;
|
|
|
|
result = initial;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (; i < length; i++) {
|
|
|
|
result = iterator(result, data[i], i);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reduces a collection to a value which is the accumulated result of iterating
|
|
|
|
* each element in the collection from right to left.
|
|
|
|
*
|
|
|
|
* @param {Function} iterator
|
|
|
|
* @param {*} [initial] By default, the initial value is the last document.
|
|
|
|
* @return {*}
|
|
|
|
*/
|
|
|
|
reduceRight(iterator, initial) {
|
|
|
|
const { data, length } = this;
|
|
|
|
let result, i;
|
|
|
|
|
|
|
|
if (initial === undefined) {
|
|
|
|
i = length - 2;
|
|
|
|
result = data[length - 1];
|
|
|
|
} else {
|
|
|
|
i = length - 1;
|
|
|
|
result = initial;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (; i >= 0; i--) {
|
|
|
|
result = iterator(result, data[i], i);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a new array with all documents that pass the test implemented by the
|
|
|
|
* provided function.
|
|
|
|
*
|
|
|
|
* @param {Function} iterator
|
|
|
|
* @return {Query}
|
|
|
|
*/
|
|
|
|
filter(iterator) {
|
|
|
|
const { data, length } = this;
|
|
|
|
const arr = [];
|
|
|
|
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
|
|
const item = data[i];
|
|
|
|
if (iterator(item, i)) arr.push(item);
|
|
|
|
}
|
|
|
|
|
|
|
|
return new this.constructor(arr);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Tests whether all documents pass the test implemented by the provided
|
|
|
|
* function.
|
|
|
|
*
|
|
|
|
* @param {Function} iterator
|
|
|
|
* @return {Boolean}
|
|
|
|
*/
|
|
|
|
every(iterator) {
|
|
|
|
const { data, length } = this;
|
|
|
|
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
|
|
if (!iterator(data[i], i)) return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Tests whether some documents pass the test implemented by the provided
|
|
|
|
* function.
|
|
|
|
*
|
|
|
|
* @param {Function} iterator
|
|
|
|
* @return {Boolean}
|
|
|
|
*/
|
|
|
|
some(iterator) {
|
|
|
|
const { data, length } = this;
|
|
|
|
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
|
|
if (iterator(data[i], i)) return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update all documents.
|
|
|
|
*
|
|
|
|
* @param {Object} data
|
|
|
|
* @param {Function} [callback]
|
|
|
|
* @return {Promise}
|
|
|
|
*/
|
|
|
|
update(data, callback) {
|
|
|
|
const model = this._model;
|
|
|
|
const stack = this._schema._parseUpdate(data);
|
|
|
|
|
|
|
|
return Promise.mapSeries(this.data, item => model._updateWithStack(item._id, stack)).asCallback(callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Replace all documents.
|
|
|
|
*
|
|
|
|
* @param {Object} data
|
|
|
|
* @param {Function} [callback]
|
|
|
|
* @return {Promise}
|
|
|
|
*/
|
|
|
|
replace(data, callback) {
|
|
|
|
const model = this._model;
|
|
|
|
|
|
|
|
return Promise.map(this.data, item => model.replaceById(item._id, data)).asCallback(callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove all documents.
|
|
|
|
*
|
|
|
|
* @param {Function} [callback]
|
|
|
|
* @return {Promise}
|
|
|
|
*/
|
|
|
|
remove(callback) {
|
|
|
|
const model = this._model;
|
|
|
|
|
|
|
|
return Promise.mapSeries(this.data, item => model.removeById(item._id)).asCallback(callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Populates document references.
|
|
|
|
*
|
|
|
|
* @param {String|Object} expr
|
|
|
|
* @return {Query}
|
|
|
|
*/
|
|
|
|
populate(expr) {
|
|
|
|
const stack = this._schema._parsePopulate(expr);
|
|
|
|
const { data, length } = this;
|
|
|
|
const model = this._model;
|
|
|
|
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
|
|
data[i] = model._populate(data[i], stack);
|
|
|
|
}
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Query.prototype.size = Query.prototype.count;
|
|
|
|
|
|
|
|
Query.prototype.each = Query.prototype.forEach;
|
|
|
|
|
|
|
|
Query.prototype.random = Query.prototype.shuffle;
|
|
|
|
|
|
|
|
module.exports = Query;
|