UNPKG

mingo

Version:

MongoDB query language for in-memory objects

197 lines (196 loc) 4.76 kB
import { ComputeOptions, evalExpr } from "../core/_internal"; import { Query } from "../query"; import { assert, compare as mingoCmp, ensureArray, flatten, intersection, isArray, isBoolean, isDate, isEmpty, isEqual, isNil, isNumber, isObject, isOperator, isRegExp, isString, resolve, truthy, typeOf } from "../util/_internal"; function elemMatchPredicate(criteria, options) { let format = (x) => x; let wrap = true; for (const k of Object.keys(criteria)) { wrap &&= isOperator(k) && "$and" !== k && "$or" !== k && "$nor" !== k; if (!wrap) break; } if (wrap) { criteria = { field: criteria }; format = (x) => ({ field: x }); } const q = new Query(criteria, options); return (v) => q.test(format(v)); } function processQuery(selector, value, options, predicate) { const pathArray = selector.split("."); const depth = Math.max(1, pathArray.length - 1); const copts = ComputeOptions.init(options).update({ depth }); const opts = { unwrapArray: true, pathArray }; if (predicate === $elemMatch) { value = elemMatchPredicate(value, options); } return (o) => { const lhs = resolve(o, selector, opts); return predicate(lhs, value, copts); }; } function processExpression(obj, expr, options, predicate) { assert( isArray(expr) && expr.length === 2, `${predicate.name} expects array(2)` ); const [lhs, rhs] = evalExpr(obj, expr, options); return predicate(lhs, rhs, options); } function $eq(a, b, options) { if (isEqual(a, b)) return true; if (isNil(a) && isNil(b)) return true; if (isArray(a)) { const depth = options?.local?.depth ?? 1; return a.some((v) => isEqual(v, b)) || flatten(a, depth).some((v) => isEqual(v, b)); } return false; } function $ne(a, b, options) { return !$eq(a, b, options); } function $in(a, b, _options) { if (isNil(a)) return b.some((v) => v === null); return intersection([ensureArray(a), b]).length > 0; } function $nin(a, b, options) { return !$in(a, b, options); } function $lt(a, b, _options) { return compare(a, b, (x, y) => mingoCmp(x, y) < 0); } function $lte(a, b, _options) { return compare(a, b, (x, y) => mingoCmp(x, y) <= 0); } function $gt(a, b, _options) { return compare(a, b, (x, y) => mingoCmp(x, y) > 0); } function $gte(a, b, _options) { return compare(a, b, (x, y) => mingoCmp(x, y) >= 0); } function $mod(a, b, _options) { return ensureArray(a).some( ((x) => b.length === 2 && x % b[0] === b[1]) ); } function $regex(a, b, options) { const lhs = ensureArray(a); const match = (x) => isString(x) && truthy(b.exec(x), options?.useStrictMode); return lhs.some(match) || flatten(lhs, 1).some(match); } function $all(values, rhs, options) { if (!isArray(values) || !isArray(rhs) || !values.length || !rhs.length) { return false; } let matched = true; for (const expr of rhs) { if (!matched) break; if (isObject(expr) && Object.keys(expr)[0] === "$elemMatch") { const criteria = expr["$elemMatch"]; const pred = elemMatchPredicate(criteria, options); matched = $elemMatch(values, pred, options); } else if (isRegExp(expr)) { matched = values.some((s) => isString(s) && expr.test(s)); } else { matched = values.some((v) => isEqual(expr, v)); } } return matched; } function $size(a, b, _options) { return Array.isArray(a) && a.length === b; } function $elemMatch(a, b, _options) { if (isArray(a) && !isEmpty(a)) { for (let i = 0, len = a.length; i < len; i++) if (b(a[i])) return true; } return false; } const isNull = (a) => a === null; const compareFuncs = { array: isArray, boolean: isBoolean, bool: isBoolean, date: isDate, number: isNumber, int: isNumber, long: isNumber, double: isNumber, decimal: isNumber, null: isNull, object: isObject, regexp: isRegExp, regex: isRegExp, string: isString, // added for completeness undefined: isNil, // deprecated // Mongo identifiers 1: isNumber, //double 2: isString, 3: isObject, 4: isArray, 6: isNil, // deprecated 8: isBoolean, 9: isDate, 10: isNull, 11: isRegExp, 16: isNumber, //int 18: isNumber, //long 19: isNumber //decimal }; function compareType(a, b, _) { const f = compareFuncs[b]; return f ? f(a) : false; } function $type(a, b, options) { return isArray(b) ? b.findIndex((t) => compareType(a, t, options)) >= 0 : compareType(a, b, options); } function compare(a, b, f) { for (const v of ensureArray(a)) { if (typeOf(v) === typeOf(b) && f(v, b)) return true; } return false; } export { $all, $elemMatch, $eq, $gt, $gte, $in, $lt, $lte, $mod, $ne, $nin, $regex, $size, $type, processExpression, processQuery };