UNPKG

ddl-manager

Version:

store postgres procedures and triggers in files

339 lines (298 loc) 9.71 kB
import { Sql, Expression as ExpressionSyntax, FunctionCall, ColumnReference as ColumnLink, CaseWhen as CaseWhenSyntax, ArrayLiteral as PgArray, Extract as ExtractSyntax, Operand, BinaryOperator, PreUnaryOperator, PostUnaryOperator, SubExpression, CastTo, SquareBrackets, EqualAny, In } from "psql-lang"; import { IExpressionElement, FuncCall, Operator, CaseWhen, Expression, UnknownExpressionElement, OrderByItem, OrderBy, Extract } from "../ast"; import { UnknownExpressionElementParser } from "./UnknownExpressionElementParser"; import { ColumnReferenceParser } from "./ColumnReferenceParser"; import { ArrayElement } from "../ast/expression/ArrayElement"; import {strict} from "assert"; const funcsWithoutOrderBy = [ // 1 + 1 = 1 + 1 "count", // a + b = b + a "sum", // Math.max(a, b) = Math.max(b, a) "max", // Math.min(a, b) = Math.max(b, a) "min", // x || y = y || x "bool_or", // x && y = y && x "bool_and" ]; export class ExpressionParser { private columnReferenceParser = new ColumnReferenceParser(); private unknownExpressionElementParser = new UnknownExpressionElementParser(); parse(input: string | ExpressionSyntax | Operand): Expression { const sql = toOperand(input); const elements = this.parseElements(sql); if ( elements.length === 1 && elements[0] instanceof Expression ) { return elements[0] as Expression; } return new Expression(elements); } private parseElements(elemSyntax: any): IExpressionElement[] { if ( elemSyntax instanceof SubExpression ) { return [new Expression( this.parseElements( elemSyntax.row.subExpression ), true )]; } return this.tryParseBinaryOperator( elemSyntax) || [ this.tryParseFunctionCall(elemSyntax) || this.tryParsePgArray(elemSyntax) || this.tryParseExtract(elemSyntax) || this.tryParseCaseWhen(elemSyntax) || this.tryParseColumnRef(elemSyntax) || this.parseUnknown(elemSyntax) ]; } private tryParseBinaryOperator(elemSyntax: any) { if ( elemSyntax instanceof BinaryOperator ) { return [ ...this.parseElements( elemSyntax.row.left ), new Operator( elemSyntax.row.operator ), ...this.parseElements( elemSyntax.row.right ) ]; } if ( elemSyntax instanceof PreUnaryOperator ) { return [ new Operator( elemSyntax.row.preOperator ), ...this.parseElements( elemSyntax.row.operand ) ]; } if ( elemSyntax instanceof PostUnaryOperator ) { return [ ...this.parseElements( elemSyntax.row.operand ), new Operator( elemSyntax.row.postOperator ) ]; } if ( elemSyntax instanceof CastTo ) { return [ ...this.parseElements( elemSyntax.row.cast ), new Operator("::"), UnknownExpressionElement.fromSql(elemSyntax.row.to.toString()) ]; } if ( elemSyntax instanceof SquareBrackets ) { return [ ...this.parseElements( elemSyntax.row.operand ), UnknownExpressionElement.fromSql( "[" + elemSyntax.row.index.toString() + "]" ) ]; } if ( elemSyntax instanceof EqualAny ) { return [ ...this.parseElements( elemSyntax.row.operand ), new Operator("="), UnknownExpressionElement.fromSql( "any(" + elemSyntax.row.equalAny.toString() + ")", this.unknownExpressionElementParser.buildColumnsMap( elemSyntax.row.equalAny ) ) ]; } if ( elemSyntax instanceof In ) { const unknownSql = Array.isArray(elemSyntax.row.in) ? `in (${elemSyntax.row.in.join(", ")})` : `in (${elemSyntax.row.in})`; return [ ...this.parseElements( elemSyntax.row.operand ), UnknownExpressionElement.fromSql( unknownSql, this.unknownExpressionElementParser.buildColumnsMap( elemSyntax.row.in ) ) ]; } } private tryParseFunctionCall(funcCallSyntax: any) { if ( !(funcCallSyntax instanceof FunctionCall) ) { return; } const funcNameSyntax = funcCallSyntax.row.call; const funcName = funcNameSyntax.toString(); let args = funcCallSyntax.row.arguments.map(argSql => this.parseFunctionCallArgument( funcName, argSql ) ); let where: Expression | undefined; if ( funcCallSyntax.row.filter ) { where = this.parse( funcCallSyntax.row.filter ); } let orderByItems: OrderByItem[] = []; if ( funcCallSyntax.row.orderBy ) { funcCallSyntax.row.orderBy.forEach(itemSyntax => { const nulls = itemSyntax.row.nulls as ("first" | "last" | undefined); const vector = itemSyntax.row.vector as ("asc" | "desc" | undefined); const expression = this.parse( itemSyntax.row.expression ); const item = new OrderByItem({ type: vector, expression, nulls }); orderByItems.push(item); }) } let distinct = funcCallSyntax.row.form == "distinct"; if ( funcsWithoutOrderBy.includes(funcName) ) { orderByItems = []; } if ( funcName === "sum" ) { orderByItems = []; } // min(distinct x) = min(x) // max(distinct x) = max(x) // bool_or(distinct x) = bool_or(x) // bool_and(distinct x) = bool_and(x) const funcsWithoutDistinct = [ "max", "min", "bool_or", "bool_and" ]; if ( funcsWithoutDistinct.includes(funcName) ) { distinct = false; } // count(id_client) = count(id_partner) = count(*) // count(distinct id_client) != count(id_partner) if ( funcName === "count" && !distinct ) { args = [Expression.unknown("*")]; } const funcCall = new FuncCall( funcName, args, where, distinct, orderByItems.length ? new OrderBy(orderByItems) : undefined ); return funcCall; } private tryParsePgArray(elemSyntax: any) { if ( !(elemSyntax instanceof PgArray) ) { return; } const content = elemSyntax.row.array.map(expression => this.parse(expression) ); return new ArrayElement(content); } private tryParseExtract(elemSyntax: any) { if ( !(elemSyntax instanceof ExtractSyntax) ) { return; } const extract = elemSyntax.row.extract; const from = this.parse( elemSyntax.row.from ); return new Extract(extract, from); } private tryParseCaseWhen(elemSyntax: any) { if ( !(elemSyntax instanceof CaseWhenSyntax) ) { return; } return new CaseWhen({ cases: elemSyntax.row.case.map(caseSyntax => ({ when: this.parse( caseSyntax.row.when ), then: this.parse( caseSyntax.row.then ) })), else: elemSyntax.row.else ? this.parse( elemSyntax.row.else ) : undefined }); } private tryParseColumnRef(elemSyntax: any) { if ( !(elemSyntax instanceof ColumnLink) ) { return; } strict.ok( !elemSyntax.row.allColumns ); return this.columnReferenceParser.parse(elemSyntax); } private parseUnknown(elemSyntax: any) { return this.unknownExpressionElementParser.parse(elemSyntax); } private parseFunctionCallArgument( funcName: string, argSql: Operand ) { if ( funcName === "count" && (argSql.toString()).trim() === "*" ) { return new Expression([ new UnknownExpressionElement( new ColumnLink({ row: {column: []} }) ) ]); } return this.parse(argSql); } } function toOperand(input: string | ExpressionSyntax | Operand) { if ( typeof input === "string" ) { return Sql.code(input).parse(ExpressionSyntax).operand(); } if ( input instanceof ExpressionSyntax ) { return input.operand(); } return input; }