UNPKG

ddl-manager

Version:

store postgres procedures and triggers in files

355 lines (291 loc) 11.5 kB
import { AbstractExpressionElement, ColumnReference, Expression, NotExpression, UnknownExpressionElement } from "../../../ast"; import { CacheContext } from "../CacheContext"; import { TableReference } from "../../../database/schema/TableReference"; import { flatMap } from "lodash"; import { buildNoReferenceCondition, buildHasReferenceCondition } from "./buildHasReferenceCondition"; import { replaceOperatorAnyToIndexedOperator } from "./replaceOperatorAnyToIndexedOperator"; import { replaceAmpArrayToAny } from "./replaceAmpArrayToAny"; import { findJoinsMeta } from "../../processor/findJoinsMeta"; import { buildJoinVariables } from "../../processor/buildJoinVariables"; import { Table } from "../../../database/schema/Table"; import { Column } from "../../../database/schema/Column"; import { CoalesceFalseExpression } from "../../../ast/expression/CoalesceFalseExpression"; import { fixArraySearchForDifferentArrayTypesInCondition } from "./fixArraySearchForDifferentArrayTypes"; export type RowType = "new" | "old"; export class ConditionBuilder { private readonly context: CacheContext; constructor(context: CacheContext) { this.context = context; } hasMutableColumns() { return this.getMutableColumns().length > 0; } noReferenceChanges() { const importantColumnsRefs = this.triggerTableColumnsToRefs( this.context.referenceMeta.columns ); for (const filter of this.context.referenceMeta.filters) { const filterColumnsRefs = filter.getColumnReferences(); importantColumnsRefs.push( ...filterColumnsRefs ); } return this.buildNoChanges( importantColumnsRefs ); } noChanges() { const triggerTableColumnsRefs = this.triggerTableColumnsToRefs( this.context.triggerTableColumns ); return this.buildNoChanges( triggerTableColumnsRefs ); } buildNoReference(row: string) { const condition = buildNoReferenceCondition(this.context); const output = this.replaceTriggerTableRefsTo( condition, row ); return output; } hasReferenceWithoutJoins(row: string) { const needUpdate = this.buildHasReferenceWithoutJoins(); const output = this.replaceTriggerTableRefsTo(needUpdate, row); return output; } filtersWithJoins(row: string) { let conditions: Expression[] = this.context.referenceMeta.filters.slice(); const aggFilters = this.matchedAllAggFilters(); if ( aggFilters ) { conditions.push(aggFilters); } conditions = conditions.filter(condition => this.hasJoinsInside(condition) ); conditions = conditions.map(condition => this.replaceTriggerTableRefsTo(condition, row) as Expression ); const output = conditions.length === 1 ? conditions[0] : Expression.and(conditions); if ( !output.isEmpty() ) { return output; } } needUpdateConditionOnUpdate(row: string) { const needUpdate = this.buildNeedUpdateConditionOnUpdate(); const output = this.replaceTriggerTableRefsTo(needUpdate, row); return output; } simpleWhere(row: string) { const simpleWhere = this.buildSimpleWhere(); const output = this.replaceTriggerTableRefsTo(simpleWhere, row); return output; } simpleWhereOnUpdate(row: string) { const simpleWhere = this.buildSimpleWhere(); const output = this.replaceTriggerTableRefsTo(simpleWhere, row); return output; } exitFromDeltaUpdateIf(): Expression | undefined { const conditions: (Expression | NotExpression)[] = this.context.referenceMeta.filters.map(filter => new NotExpression( this.replaceTriggerTableRefsTo(filter, "new")! ) ); const hasNoReference = this.buildNoReference("new"); if ( hasNoReference && !hasNoReference.isEmpty() ) { conditions.unshift( hasNoReference ); } if ( !conditions.length ) { return; } return Expression.or(conditions); } private buildNoChanges(columns: ColumnReference[]) { const mutableColumns = columns.filter(column => column.name !== "id" ); const conditions: string[] = []; for (const columnRef of mutableColumns) { const tableStructure = this.context.database.getTable( columnRef.tableReference.table ) as Table; const column = ( tableStructure && tableStructure.getColumn(columnRef.name) ) as Column; const columnRefExpression = new Expression([ columnRef ]); let oldColumn = this.replaceTriggerTableRefsTo( columnRefExpression, "old" ) as Expression; let newColumn = this.replaceTriggerTableRefsTo( columnRefExpression, "new" ) as Expression; oldColumn = oldColumn.replaceTable( this.context.triggerTable, new TableReference( this.context.triggerTable, "old" ) ); newColumn = newColumn.replaceTable( this.context.triggerTable, new TableReference( this.context.triggerTable, "new" ) ); if ( column && column.type.isArray() ) { conditions.push(`cm_equal_arrays(${newColumn}, ${oldColumn})`); } else { conditions.push(`${ newColumn } is not distinct from ${ oldColumn }`); } } const noChangesCondition = Expression.and(conditions); return noChangesCondition; } private getMutableColumns() { const mutableColumns = this.context.triggerTableColumns .filter(col => col !== "id"); return mutableColumns; } private buildSimpleWhere() { const conditions = this.context.referenceMeta.expressions.map(expression => { // TODO: recursive const orExpressions = expression.extrude().splitBy("or").map(subExpression => { subExpression = subExpression.extrude(); subExpression = replaceOperatorAnyToIndexedOperator( this.context.cache.for, subExpression ); subExpression = replaceAmpArrayToAny( this.context.cache.for, subExpression ); subExpression = fixArraySearchForDifferentArrayTypesInCondition( this.context.cache.for, subExpression ); return subExpression; }); return Expression.or(orExpressions); }); conditions.push( ...this.context.referenceMeta.unknownExpressions ); conditions.push( ...this.context.referenceMeta.cacheTableFilters ); const where = Expression.and(conditions); if ( !where.isEmpty() ) { return where; } } private buildNeedUpdateConditionOnUpdate() { const conditions = [ buildHasReferenceCondition(this.context), Expression.and(this.context.referenceMeta.filters), this.matchedAllAggFilters() ].filter(condition => condition != null && !condition.isEmpty() ) as Expression[]; const needUpdate = Expression.and(conditions); if ( !needUpdate.isEmpty() ) { return needUpdate; } } private buildHasReferenceWithoutJoins() { let conditions: Expression[] = []; const refCondition = buildHasReferenceCondition(this.context); if ( refCondition ) { conditions.push(refCondition); } for (const where of this.context.referenceMeta.filters) { if ( !this.hasJoinsInside(where) ) { conditions.push( where ); } } const aggFilters = this.matchedAllAggFilters(); if ( aggFilters && !this.hasJoinsInside(aggFilters) ) { conditions.push( aggFilters ); } conditions = conditions.filter(condition => condition != null && !condition.isEmpty() ); const needUpdate = Expression.and(conditions); if ( !needUpdate.isEmpty() ) { return needUpdate; } } private matchedAllAggFilters() { const allAggCalls = flatMap( this.context.cache.select.columns, column => column.getAggregations(this.context.database.aggregators) ); const everyAggCallHaveFilter = allAggCalls.every(aggCall => aggCall.where != null); if ( !everyAggCallHaveFilter ) { return; } const filterConditions = allAggCalls.map(aggCall => { const expression = aggCall.where as Expression; return new CoalesceFalseExpression(expression); }); return Expression.or(filterConditions); } replaceTriggerTableRefsTo( expression: AbstractExpressionElement | undefined, row: string ) { if ( !expression ) { return; } let outputExpression = expression as Expression; const refsToTriggerTable = this.context.getTableReferencesToTriggerTable(); const joinsMeta = findJoinsMeta(this.context.cache.select); if ( joinsMeta.length ) { const joins = buildJoinVariables( this.context.database, joinsMeta, row ); joins.forEach((join) => { outputExpression = outputExpression.replaceColumn( join.table.column, UnknownExpressionElement.fromSql(join.variable.name) ); }); } refsToTriggerTable.forEach((triggerTableRef) => { outputExpression = outputExpression.replaceTable( triggerTableRef, new TableReference( this.context.triggerTable, row ) ); }); return outputExpression; } private hasJoinsInside(condition: Expression) { const joinsMeta = findJoinsMeta(this.context.cache.select); if ( !joinsMeta.length ) { return false; } const columnsRefs = condition.getColumnReferences(); const hasJoins = columnsRefs.some(columnRef => !columnRef.tableReference.table.equal(this.context.triggerTable) ); return hasJoins; } private triggerTableColumnsToRefs(columnsNames: string[]) { const triggerTableRef = new TableReference( this.context.triggerTable ); const triggerTableColumnsRefs = columnsNames.map(columnName => new ColumnReference( triggerTableRef, columnName ) ); return triggerTableColumnsRefs; } }