import { WhereQuery } from "./WhereQuery"; import { ComplexExpression, BinaryExpression } from "./Expression"; import { OrderProperty } from "./OrderProperty"; import { utils } from "./utils"; import { ODATA_PARAMS, EXPRESSION_TYPE } from "./constants"; import { Query } from "./Query"; export class QueryBuilder { constructor(private query: Query) { } build(): { [key: string]: string } { const queryParameters = {}; const whereClause = this.query.filter; if (whereClause instanceof WhereQuery) { const expression = whereClause.getExpression(); if (expression instanceof ComplexExpression) { const result = this.buildComplexExpression(expression, null); if (result) { queryParameters[ODATA_PARAMS.$filter] = result; } } } const selectClause = this.query.selectProperties; if (selectClause instanceof Array) { queryParameters[ODATA_PARAMS.$select] = selectClause.join(", "); } const expandClause = this.query.expandProperties; if (expandClause instanceof Array) queryParameters[ODATA_PARAMS.$expand] = expandClause.join(", "); const orderByClause = this.query.orderProperties; if (orderByClause instanceof Array) { const order = []; for (const i in orderByClause) { if (orderByClause[i] instanceof OrderProperty) order.push(orderByClause[i].getValue()); } queryParameters[ODATA_PARAMS.$orderby] = order.join(", "); } const take = this.query.takeValue; if (utils.isNumber(take)) queryParameters[ODATA_PARAMS.$top] = take; const skip = this.query.skipValue; if (utils.isNumber(skip)) queryParameters[ODATA_PARAMS.$skip] = skip; const search = this.query.searchValue; if (search) queryParameters[ODATA_PARAMS.$search] = search; if (this.query.inlineCount !== null) { queryParameters[ODATA_PARAMS.$count] = this.query.inlineCount; } return queryParameters; } private buildComplexExpression(expression: ComplexExpression, parent: string): string { if (!(expression instanceof ComplexExpression)) throw new Error("Invalid complex expression"); let counter = 0; let result = ""; const subExpressions = expression.getExpressions(); if (!subExpressions.length) { return result; } while (true) { const currentExp = subExpressions[counter]; if (currentExp instanceof BinaryExpression) { result += this.buildBinaryExpression(currentExp); } else if (currentExp instanceof ComplexExpression) { const complexResult = this.buildComplexExpression(currentExp, expression.type); if (complexResult) { result += "(" + complexResult + ")"; } } if (expression.getType() === EXPRESSION_TYPE.not) result = expression.getType() + " " + result; // TODOSDK: Rethink this! if (expression.getType() === EXPRESSION_TYPE.any) { const exprVal = []; for (let index = 0; index < subExpressions.length; index++) { if (subExpressions[index].type == EXPRESSION_TYPE.contains || subExpressions[index].type == EXPRESSION_TYPE.startsWith || subExpressions[index].type == EXPRESSION_TYPE.endsWith) exprVal.push(subExpressions[index].type.toLowerCase() + "(x, '" + (subExpressions[index] as BinaryExpression).constant + "')"); else exprVal.push("x " + subExpressions[index].type.toLowerCase() + " " + this.checkType((subExpressions[index] as BinaryExpression).constant)); } result = (currentExp as BinaryExpression).parameter + "/" + expression.getType() + "(x:" + exprVal.join(" " + parent + " ") + ")"; } counter++; if (counter == subExpressions.length) break; result += " " + expression.getType() + " "; } return result; } private buildBinaryExpression(expression: BinaryExpression): string { let result: string = null; const type = expression.getType(); switch (type) { case EXPRESSION_TYPE.eq: case EXPRESSION_TYPE.ne: case EXPRESSION_TYPE.lt: case EXPRESSION_TYPE.le: case EXPRESSION_TYPE.gt: case EXPRESSION_TYPE.ge: { const constant = expression.getConstant(); result = `(${expression.getParameter()} ${type} ${this.checkType(constant)})`; break; } case EXPRESSION_TYPE.contains: case EXPRESSION_TYPE.startsWith: case EXPRESSION_TYPE.endsWith: { const expressionParameter = this.applyCaseSensitiveSettings(expression, expression.getParameter()); const expressionConstant = this.checkType(expression.getConstant(), true); const expressionValue = this.applyCaseSensitiveSettings(expression, expressionConstant); result = `${type.toLowerCase()}(${expressionParameter}, ${expressionValue})`; break; } case EXPRESSION_TYPE.in: { const valueArray = expression.getConstant(); if (!Array.isArray(valueArray)) { throw new Error("Parameter is not an array!"); } result = `${expression.getParameter()} in (${valueArray.join(", ")})`; break; } default: { const msg = `Unsupported operator ${type}`; throw new Error(msg); } } return result; } private applyCaseSensitiveSettings(expression: BinaryExpression, value: string): string { if (expression.isCaseSensitive()) { return value; } return `tolower(${value})`; } private checkType(constant: any, treatAsString = false): string { if (this.isGuid(constant) && !treatAsString) return constant; if (constant == null) return null; constant = constant.replace(/[+/?%#&]/g, function ($0) { return encodeURIComponent($0); }); // escapes the apostrophes constant = constant.replace(/'/g, "''"); return "'" + constant + "'"; } private isGuid(constant: string): boolean { return /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(constant); } }