js-node-arango
Version:
NodeJS ArangoDB Provider
274 lines • 10 kB
JavaScript
"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