UNPKG

js-node-arango

Version:
274 lines 10 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ArangoBaseQueryBuilder = void 0; class ArangoBaseQueryBuilder { constructor(options) { this.options = options; this.bindVars = {}; // eslint-disable-next-line no-use-before-define this.additionalQueries = new Map(); this.withClause = ''; this.selector = ''; this.filter = ''; this.sort = ''; this.limit = ''; this.doc = 'doc'; this.returnClause = 'doc'; this.inlineQueries = ''; this._isRootQuery = true; this._tick = 0; // eslint-disable-next-line no-use-before-define this._parent = undefined; } prepareQuery() { this.buildEverything(); let query = ''; if (this.options.withCount) { query += `${this.withClause}LET total = FIRST(${this.selector}${this.filter}COLLECT WITH COUNT INTO length RETURN length) ` + `LET docs = (${this.selector}${this.filter}${this.sort}${this.limit}${this.inlineQueries}RETURN ${this.returnClause}) ` + 'RETURN { docs, total }'; } else { query += `${this.withClause}${this.selector}${this.filter}${this.sort}${this.limit}${this.inlineQueries}RETURN ${this.returnClause}`; } return { query, bindVars: this.bindVars }; } setDocSelector(doc) { this.checkSafe(doc); this.doc = doc; return this; } getDocSelector() { return this.doc; } addAdditionalQuery(varName, q) { this.checkSafe(varName); if (varName === this.doc) throw new Error('Additional query variable name cannot be same with doc selector'); q.setParentQuery(this); this.additionalQueries.set(varName, q); return this; } buildSelector() { throw new Error('Override this method'); } buildFilter() { if (this.options.filterBy) { const filters = this.extractFilters(this.options.filterBy.filter); this.filter = `FILTER ${filters.map((e) => e.query).join(' && ')} `; for (let filter of filters) { this.bindVars = Object.assign(Object.assign({}, filter.bindVars), this.bindVars); } } } buildSort() { if (this.options.sortBy) { const sortFieldKeyName = `sortField_${this.getNextGlobalTick()}`; const sortOrderKeyName = `sortOrder_${this.getNextGlobalTick()}`; this.sort = `SORT ${this.doc}.@${sortFieldKeyName} @${sortOrderKeyName} `; this.bindVars[sortFieldKeyName] = this.options.sortBy.field.split('.'); this.bindVars[sortOrderKeyName] = this.options.sortBy.order; } } buildLimit() { if (this.options.limitBy) { const offsetKeyName = `limitByOffset_${this.getNextGlobalTick()}`; const limitKeyName = `limitByLimit_${this.getNextGlobalTick()}`; this.limit = `LIMIT @${offsetKeyName}, @${limitKeyName} `; this.bindVars[offsetKeyName] = this.options.limitBy.offset; this.bindVars[limitKeyName] = this.options.limitBy.limit; } } buildWithClause() { if (this._isRootQuery) { const targets = this.gatherRelatedCollectionsRecursively(); if (targets.length > 0) { this.withClause = `WITH ${this.handleCommaSeparated(targets, 'target')} `; } } } gatherRelatedCollectionsRecursively() { let targets = []; if (this.options.relatedCollections) { targets = this.concatArrays(targets, this.options.relatedCollections); } for (let elem of this.additionalQueries) { targets = this.concatArrays(targets, elem[1].gatherRelatedCollectionsRecursively()); } return targets; } buildReturnClause() { if (this.additionalQueries.size === 0) { this.returnClause = this.doc; return; } this.returnClause = `MERGE(${this.doc}, { ${Array.from(this.additionalQueries.keys()).join(', ')} })`; } buildAdditionalQueries() { var _a; if (this.additionalQueries.size === 0) { return; } const queries = []; for (let elem of this.additionalQueries) { const innerAqlQuery = elem[1].prepareQuery(); queries.push(`LET ${elem[0]} = ${elem[1].options.withCount || ((_a = elem[1].options.limitBy) === null || _a === void 0 ? void 0 : _a.limit) === 1 ? 'FIRST' : ''}(${innerAqlQuery.query}) `); this.bindVars = Object.assign(Object.assign({}, innerAqlQuery.bindVars), this.bindVars); } this.inlineQueries = queries.join(''); } buildEverything() { this.buildSelector(); this.buildFilter(); this.buildSort(); this.buildLimit(); this.buildWithClause(); this.buildReturnClause(); this.buildAdditionalQueries(); } isFilterPrimitiveType(filter) { const checkPrimitive = function (input) { return (typeof input === 'string' || typeof input === 'number' || typeof input === 'boolean' || input === undefined || input === null); }; if (!Array.isArray(filter)) { return checkPrimitive(filter); } return filter.every((elem) => checkPrimitive(elem)); } isFilterProperFilterObject(filter) { return (filter.hasOwnProperty('@value') && this.isFilterPrimitiveType(filter['@value']) && (filter.hasOwnProperty('@operator') || filter.hasOwnProperty('@docNameToFilter'))); } /** * Flattens the provided filter object and returns an array with dot notation representation. * @example * ```js * // Given object: * const filter = { * gitRepoInfo: { * webhook: 12, * owner: { * name: 'Tospaa', * } * }, * state: 'InTest', * } * // Expected output: * [ * { * query: 'doc.gitRepoInfo.webhook == \@docgitRepoInfowebhook', * vars: { * bindVarName: 'docgitRepoInfowebhook', * value: 12 * } * }, * { * query: 'doc.gitRepoInfo.owner.name == \@docgitRepoInfoownername', * vars: { * bindVarName: 'docgitRepoInfoownername', * value: 'Tospaa' * } * }, * { * query: 'doc.gitRepoInfo.state == \@docgitRepoInfostate', * vars: { * bindVarName: 'docgitRepoInfostate', * value: 'InTest' * } * } * ] * ``` * @param filter Object provided to be flattened. * @param prevKey For recursive use only * @returns An array of filters */ extractFilters(filter, prevKey) { var _a, _b, _c; prevKey !== null && prevKey !== void 0 ? prevKey : (prevKey = ''); const filters = []; for (let key in filter) { if (this.isFilterPrimitiveType(filter[key]) || this.isFilterProperFilterObject(filter[key])) { const objectKey = `${prevKey}${key}`; const docPath = `path_${this.getNextGlobalTick()}`; const bindVarName = `filter_${this.getNextGlobalTick()}`; const query = `${(_a = filter[key]['@docNameToFilter']) !== null && _a !== void 0 ? _a : this.doc}.@${docPath} ${(_b = filter[key]['@operator']) !== null && _b !== void 0 ? _b : '=='} @${bindVarName}`; const bindVars = { [docPath]: objectKey.split('.'), [bindVarName]: (_c = filter[key]['@value']) !== null && _c !== void 0 ? _c : filter[key], }; filters.push({ query, bindVars }); continue; } prevKey += `${key}.`; filters.push(...this.extractFilters(filter[key], prevKey)); } return filters; } handleCommaSeparated(values, keyNameInput) { this.checkSafe(keyNameInput); if (values.length === 0) return ''; let statement = ''; for (let i = 0; i < values.length; i++) { const keyName = `${keyNameInput}_${this.getNextGlobalTick()}`; statement += `@@${keyName}`; if (i + 1 !== values.length) statement += ', '; this.bindVars[`@${keyName}`] = values[i]; } return statement; } checkSafe(phrase) { if (!/^[A-Za-z][A-Za-z0-9\-_]*$/.test(phrase)) throw new Error('This phrase does not seem safe and cannot be used in the query: ' + phrase); } concatArrays(array1, array2) { if (Array.isArray(array2)) { return array1.concat(array2); } array1.push(array2); return array1; } isRootQuery() { return this._isRootQuery; } setParentQuery(parent) { this._isRootQuery = false; this._parent = parent; } getParentQuery() { return this._parent; } getTopmostParentQuery() { if (this._parent === undefined) return this; if (this._parent.isRootQuery()) return this._parent; let parent = this._parent; while (true) { const localParent = parent.getParentQuery(); if (!localParent) { return parent; } if (localParent.isRootQuery()) { return localParent; } parent = localParent; } } getNextTick() { this._tick += 1; return this._tick; } getNextGlobalTick() { return this.getTopmostParentQuery().getNextTick(); } } exports.ArangoBaseQueryBuilder = ArangoBaseQueryBuilder; //# sourceMappingURL=base-query-builder.js.map