UNPKG

@bcc-code/feathers-arangodb

Version:
248 lines (247 loc) 9.75 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.QueryBuilder = exports.LogicalOperator = void 0; const isObject_1 = __importDefault(require("lodash/isObject")); const get_1 = __importDefault(require("lodash/get")); const set_1 = __importDefault(require("lodash/set")); const arangojs_1 = require("arangojs"); const searchBuilder_1 = require("./searchBuilder"); const logger_1 = __importDefault(require("./logger")); const errors_1 = require("@feathersjs/errors"); var LogicalOperator; (function (LogicalOperator) { LogicalOperator["And"] = " AND "; LogicalOperator["Or"] = " OR "; })(LogicalOperator || (exports.LogicalOperator = LogicalOperator = {})); class QueryBuilder { ; constructor(params, docName = "doc", returnDocName = "doc", searchFields = []) { this.reserved = [ "$select", "$limit", "$skip", "$sort", "$in", "$nin", "$lt", "$lte", "$gt", "$gte", "$ne", "$not", "$or", "$and", "$aql", "$resolve", "$search", "$elemMatch", ]; this.bindVars = {}; this.maxLimit = 1000000000; // A billion records... this._limit = -1; this._countNeed = ""; this._skip = 0; this.docName = "doc"; this.returnDocName = "doc"; this.varCount = 0; this.searchFields = searchFields; this.docName = docName; this.returnDocName = returnDocName; this.create(params); } getParameterizedPath(path, basePath) { const pathArray = path.split('.').map((field) => (0, arangojs_1.aql) `[${field}]`); return arangojs_1.aql.join([ arangojs_1.aql.literal(basePath), ...pathArray ], ''); } projectRecursive(o) { const result = Object.keys(o).map((field, ind) => { const v = (0, get_1.default)(o, field); return arangojs_1.aql.join([ (0, arangojs_1.aql) `[${field}]:`, (0, isObject_1.default)(v) ? arangojs_1.aql.join([ arangojs_1.aql.literal("{"), this.projectRecursive(v), arangojs_1.aql.literal("}"), ]) : this.getParameterizedPath(v, this.returnDocName), ], " "); }); return arangojs_1.aql.join(result, ", "); } selectBuilder(params) { var _a; const select = (_a = params.query) === null || _a === void 0 ? void 0 : _a.$select; if (!(select === null || select === void 0 ? void 0 : select.length)) return arangojs_1.aql.literal(`RETURN ${this.returnDocName}`); const ret = {}; select.forEach((fieldName) => { (0, set_1.default)(ret, fieldName, fieldName); }); (0, set_1.default)(ret, '_key', '_key'); return arangojs_1.aql.join([ (0, arangojs_1.aql) `RETURN {`, this.projectRecursive(ret), (0, arangojs_1.aql) `}`, ], " "); } create(params) { this.returnFilter = this.selectBuilder(params); const query = (0, get_1.default)(params, "query", null); logger_1.default.debug("Query object received from client:", query); this._runCheck(query); return this; } _runCheck(query) { if (!isQueryObject(query)) return this; if (query.$limit !== undefined) this._limit = parseIntTypeSafe(query.$limit); if (query.$skip !== undefined) this._skip = parseIntTypeSafe(query.$skip); if (query.$sort !== undefined) this._sort = this.addSort(query.$sort); if (query.$search !== undefined) this._search = this.addSearch(this.searchFields, query.$search); this._filter = this._aqlFilterFromFeathersQuery(query, arangojs_1.aql.literal(this.docName)); } _aqlFilterFromFeathersQuery(feathersQuery, aqlFilterVar) { if (typeof feathersQuery !== "object" || feathersQuery === null) { return arangojs_1.aql.join([aqlFilterVar, (0, arangojs_1.aql) `${feathersQuery}`], " == "); } const aqlFilters = []; for (const [key, value] of Object.entries(feathersQuery)) { let operator; switch (key) { case "$in": operator = " ANY == "; break; case "$nin": operator = " NONE == "; break; case "$not": case "$ne": operator = " != "; break; case "$lt": operator = " > "; break; case "$lte": operator = " >= "; break; case "$gt": operator = " < "; break; case "$gte": operator = " <= "; break; case "$elemMatch": aqlFilters.push(this._aqlFilterArrayElement(value, aqlFilterVar)); continue; case "$or": aqlFilters.push(this._aqlFilterFromFeathersQueryArray(value, aqlFilterVar, LogicalOperator.Or)); continue; case "$and": aqlFilters.push(this._aqlFilterFromFeathersQueryArray(value, aqlFilterVar, LogicalOperator.And)); continue; case "$size": aqlFilters.push(this._aqlFilterFromFeathersQuery(value, (0, arangojs_1.aql) `LENGTH(${aqlFilterVar})`)); continue; } if (operator) { aqlFilters.push(arangojs_1.aql.join([ (0, arangojs_1.aql) `${value}`, aqlFilterVar, ], operator)); continue; } if (!this.reserved.includes(key)) { aqlFilters.push(this._aqlFilterFromFeathersQuery(value, arangojs_1.aql.join([aqlFilterVar, (0, arangojs_1.aql) `[${key}]`], ''))); } } return this._joinAqlFiltersWithOperator(aqlFilters, LogicalOperator.And); } _aqlFilterArrayElement(elementQuery, aqlFilterVar) { const elementFilter = (0, arangojs_1.aql) `FILTER ${this._aqlFilterFromFeathersQuery(elementQuery, (0, arangojs_1.aql) `CURRENT`)}`; return (0, arangojs_1.aql) `LENGTH(${aqlFilterVar}[* ${elementFilter} RETURN CURRENT])`; } _aqlFilterFromFeathersQueryArray(feathersQueries, aqlFilterVar, operator) { const aqlFilters = feathersQueries.map((f) => this._aqlFilterFromFeathersQuery(f, aqlFilterVar)); return this._joinAqlFiltersWithOperator(aqlFilters, operator); } _joinAqlFiltersWithOperator(aqlFilters, operator) { const filtered = aqlFilters.filter((c) => c !== undefined); if (!filtered.length) return undefined; const combined = arangojs_1.aql.join(filtered, operator); if (operator === LogicalOperator.And) return combined; return (0, arangojs_1.aql) `(${combined})`; } addSort(sort) { if (!isQueryObject(sort)) throw new errors_1.BadRequest("Sort has incorrect type"); if (Object.keys(sort).length > 0) { return arangojs_1.aql.join(Object.keys(sort).map((key) => { return arangojs_1.aql.join([ this.getParameterizedPath(key, this.docName), arangojs_1.aql.literal(parseIntTypeSafe(sort[key]) === -1 ? "DESC" : "") ], ' '); }), ", "); } } addSearch(searchFields, search) { if (!searchFields.length) throw new errors_1.BadRequest('A search has been attempted on a collection where no search logic has been defined'); if (!(0, searchBuilder_1.isQueryTypeCorrect)(search)) { throw new errors_1.BadRequest('Invalid query type'); } return (0, searchBuilder_1.generateFuzzyStatement)(searchFields, search); } get limit() { if (this._limit === -1 && this._skip === 0) return (0, arangojs_1.aql) ``; const realLimit = this._limit > -1 ? this._limit : this.maxLimit; return (0, arangojs_1.aql) `LIMIT ${this._skip}, ${realLimit}`; } get sort() { if (this._search) { return (0, arangojs_1.aql) `SORT BM25(${arangojs_1.aql.literal(this.docName)}) desc`; } if (this._sort) { return (0, arangojs_1.aql) `SORT ${this._sort}`; } return (0, arangojs_1.aql) ``; } get filter() { const filterParts = []; if (this._search) { filterParts.push((0, arangojs_1.aql) `(${this._search})`); } if (this._filter) { filterParts.push((0, arangojs_1.aql) `(${this._filter})`); } if (!filterParts.length) return undefined; return arangojs_1.aql.join(filterParts, LogicalOperator.And); } } exports.QueryBuilder = QueryBuilder; function isQueryObject(query) { if (!query) return false; return typeof query === "object"; } function parseIntTypeSafe(value) { if (typeof value === "number") return value; if (typeof value !== "string") return NaN; return parseInt(value); }