ddl-manager
Version:
store postgres procedures and triggers in files
449 lines (374 loc) • 12.7 kB
text/typescript
import { Operator } from "./Operator";
import { FuncCall } from "./FuncCall";
import { IExpressionElement } from "./interface";
import { TableReference } from "../../database/schema/TableReference";
import { TableID } from "../../database/schema/TableID";
import { AbstractExpressionElement } from "./AbstractExpressionElement";
import { Spaces } from "../Spaces";
import { IColumnsMap, UnknownExpressionElement } from "./UnknownExpressionElement";
import { ColumnReference } from "./ColumnReference";
import { Exists } from "./Exists";
import { CaseWhen } from "./CaseWhen";
import { strict } from "assert";
export type ConditionElementType = string | IExpressionElement;
export class Expression extends AbstractExpressionElement {
static and(conditions: ConditionElementType[]) {
return Expression.condition("and", conditions);
}
static or(conditions: ConditionElementType[]) {
return Expression.condition("or", conditions);
}
static unknown(sql: string, columnsMap: IColumnsMap = {}) {
const unknownElement = UnknownExpressionElement.fromSql(sql, columnsMap);
return new Expression([unknownElement]);
}
static funcCall(name: string, args: Expression[]) {
const funcElem = new FuncCall(
name,
args
);
return new Expression([funcElem]);
}
private static condition(operator: string, conditions: ConditionElementType[]): Expression {
const elements: IExpressionElement[] = [];
for (let i = 0, n = conditions.length; i < n; i++) {
if ( i > 0 ) {
elements.push( new Operator(operator) );
}
const sql = conditions[ i ];
if ( typeof sql === "string" ) {
elements.push(
UnknownExpressionElement.fromSql( sql )
);
}
else {
elements.push( sql );
}
}
return new Expression(elements).extrude();
}
readonly brackets: boolean;
readonly elements: IExpressionElement[];
constructor(
elements: IExpressionElement[] = [],
brackets = false
) {
super();
this.elements = elements;
this.brackets = brackets;
}
children() {
return this.elements;
}
and(otherExpression?: Expression): Expression {
if ( !otherExpression ) {
return this;
}
const canCombineToPlainCondition = (
this.onlyOperators("and") &&
otherExpression.onlyOperators("and")
);
if ( canCombineToPlainCondition ) {
return new Expression([
...this.elements,
new Operator("and"),
...otherExpression.elements
]);
}
return new Expression([
this,
new Operator("and"),
otherExpression
]);
}
getExplicitCastType(): string | undefined {
const twoLastElements = this.elements.slice(-2);
const isCasting = (
twoLastElements.length === 2 &&
twoLastElements[0].toString() === "::"
);
if ( isCasting ) {
return twoLastElements[1].toString();
}
}
isCoalesce() {
if ( !this.isFuncCall() ) {
return false;
}
const funcCall = this.getFuncCalls()[0] as FuncCall;
return funcCall.name === "coalesce";
}
isFuncCall(): boolean {
const elements = this.getElementsWithoutCasts();
const firstElem = elements[0];
return (
elements.length === 1 &&
(
firstElem instanceof FuncCall
||
firstElem instanceof Expression &&
firstElem.isFuncCall()
)
);
}
isThatFuncCall(funcName: string): boolean {
const elements = this.getElementsWithoutCasts();
const firstElem = elements[0];
return (
elements.length === 1 &&
(
firstElem instanceof FuncCall &&
firstElem.name === funcName
||
firstElem instanceof Expression &&
firstElem.isThatFuncCall(funcName)
)
);
}
isColumnReference() {
return (
this.elements.length === 1 &&
this.elements[0] instanceof ColumnReference
);
}
isArrayItemOfColumnReference() {
return (
this.elements.length === 2 &&
this.elements[0] instanceof ColumnReference &&
/^\[\s*\d+\s*\]$/.test(this.elements[1].toString().trim())
);
}
isIn() {
return (
this.elements.length === 2 &&
/^\s*in\s*\([^\)]+\)\s*$/.test(this.elements[1].toString())
);
}
isNotExists() {
return (
this.elements.length === 2 &&
this.elements[0].toString() === "not" &&
this.elements[1] instanceof Exists
);
}
isPrimitive() {
const elements = this.getElementsWithoutCasts();
return (
elements.length === 1 &&
elements[0] instanceof UnknownExpressionElement &&
/^(\d+|'.*')$/.test(elements[0].toString())
);
}
hasArraySearchOperator() {
return this.elements.some(element =>
element instanceof Operator &&
element.toString() === "&&"
);
}
isBinary(operator: string) {
const elems = (
operator === "::" ?
this.elements :
this.getElementsWithoutCasts()
);
const isBinaryExpression = (
elems.length === 3 &&
elems[1] instanceof Operator &&
elems[1].toString() === operator
);
return isBinaryExpression;
}
isEqualAny() {
const elems = this.getElementsWithoutCasts();
const isEqualAnyExpression = (
elems.length === 3 &&
elems[1] instanceof Operator &&
elems[1].toString() === "=" &&
/^\s*any\s*\(/i.test(elems[2].toString())
);
return isEqualAnyExpression;
}
getOperands() {
const operands: IExpressionElement[] = [];
for (let i = 0, n = this.elements.length; i < n; i++) {
const elem = this.elements[i];
const nextElem = this.elements[i + 1];
if ( nextElem instanceof Operator && nextElem.toString() === "::" ) {
const castOperator = nextElem;
const castType = this.elements[i + 2];
const elemWithCasting = new Expression([
elem,
castOperator,
castType
]);
operands.push(elemWithCasting);
i += 2;
continue;
}
if ( elem instanceof Operator ) {
continue;
}
operands.push(elem);
}
return operands;
}
private getElementsWithoutCasts() {
const expressionElementsWithoutCasts = this.elements.slice();
for (let i = 0, n = expressionElementsWithoutCasts.length; i < n; i++) {
const elem = expressionElementsWithoutCasts[i];
if ( elem instanceof Operator && elem.toString() === "::" ) {
expressionElementsWithoutCasts.splice(i, 2);
n -= 2;
i--;
}
}
return expressionElementsWithoutCasts;
}
isEmpty(): boolean {
if ( this.elements.length === 0 ) {
return true;
}
if ( this.elements.length === 1 ) {
const firstElem = this.elements[0];
if ( firstElem instanceof Expression ) {
return firstElem.isEmpty();
}
}
return false;
}
replaceFuncCall(replaceFunc: FuncCall, toSql: string): Expression {
const newElements = this.elements.map(elem =>
elem.replaceFuncCall(replaceFunc, toSql)
);
return this.clone(newElements);
}
replaceTable(
replaceTable: TableReference | TableID,
toTable: TableReference
): Expression {
const newElements = this.elements.map(elem =>
elem.replaceTable(replaceTable, toTable)
);
return this.clone(newElements);
}
replaceColumn(replaceColumn: ColumnReference, toSql: IExpressionElement): Expression {
const newElements = this.elements.map(elem =>
elem.replaceColumn(replaceColumn, toSql)
);
return this.clone(newElements);
}
splitBy(operator: string): Expression[] {
const conditions: Expression[] = [];
let currentCondition: any[] = [];
for (const element of this.elements) {
if ( element instanceof Operator ) {
if ( element.toString() === operator ) {
if ( currentCondition.length ) {
const condition = new Expression( currentCondition );
conditions.push(condition);
}
currentCondition = [];
continue;
}
}
currentCondition.push(element);
}
if ( currentCondition.length ) {
const condition = new Expression( currentCondition );
conditions.push(condition);
}
return conditions;
}
extrude(): Expression {
const singleElement = this.elements[0];
if ( this.elements.length === 1 && singleElement instanceof Expression ) {
return singleElement.extrude();
}
return this;
}
clone(newElements?: IExpressionElement[]) {
return new Expression(
newElements || this.elements.map(elem => elem.clone()),
this.brackets
);
}
needWrapToBrackets() {
return (
/^case\s/.test( this.toString().trim() )
);
}
template(spaces: Spaces) {
let lines: string[] = [];
let line = "";
for (let i = 0, n = this.elements.length; i < n; i++) {
const elem = this.elements[i];
if ( elem instanceof Operator && elem.toString() === "::" ) {
const nextElem = this.elements[i + 1];
line += "::" + nextElem.toString();
i++;
continue;
}
const isConditionOperator = (
elem instanceof Operator &&
["and", "or"].includes(elem.toString())
);
if ( isConditionOperator ) {
lines.push(spaces + line.trim());
lines.push(spaces + elem.toString());
line = "";
continue;
}
if ( elem instanceof Expression ) {
if ( line.trim() ) {
lines.push( spaces + line.trim() );
}
line = "";
if ( elem.hasOperator("or") ) {
lines.push(spaces + "(");
lines.push(
...elem.template(
spaces.plusOneLevel()
)
);
lines.push(spaces + ")");
}
else {
lines.push(
...elem.template(spaces)
);
}
continue;
}
else {
line += " " + elem.toSQL(spaces);
}
}
if ( line.trim() ) {
lines.push( spaces + line.trim() );
}
lines = lines.filter(someLine => !!someLine.trim());
if ( this.brackets ) {
const n = lines.length;
lines[0] = spaces + "(" + lines[0].trim();
lines[n - 1] = lines[n - 1] + ")";
}
return lines;
}
private onlyOperators(onlyOperator: string) {
const operators = this.elements.filter(elem =>
elem instanceof Operator
);
return (
operators.length &&
operators.every(operator =>
operator.toString() === onlyOperator
)
);
}
private hasOperator(operator: string) {
return this.elements.some(elem =>
elem instanceof Operator &&
elem.toString() === operator
);
}
}