Files
dbgate/packages/web/src/designer/DesignerQueryDumper.ts
Jan Prochazka b96576ba6f query designer
2021-03-18 13:57:50 +01:00

216 lines
6.8 KiB
TypeScript

import _ from 'lodash';
import {
dumpSqlSelect,
Select,
JoinType,
Condition,
Relation,
mergeConditions,
Source,
ResultField,
} from 'dbgate-sqltree';
import { EngineDriver } from 'dbgate-types';
import { DesignerInfo, DesignerTableInfo, DesignerReferenceInfo, DesignerJoinType } from './types';
import { DesignerComponent } from './DesignerComponentCreator';
import {
getReferenceConditions,
referenceIsCrossJoin,
referenceIsConnecting,
mergeSelectsFromDesigner,
findQuerySource,
findDesignerFilterType,
} from './designerTools';
import { parseFilter } from 'dbgate-filterparser';
export class DesignerQueryDumper {
constructor(public designer: DesignerInfo, public components: DesignerComponent[]) {}
get topLevelTables(): DesignerTableInfo[] {
return _.flatten(this.components.map(x => x.tables));
}
dumpComponent(component: DesignerComponent) {
const select: Select = {
commandType: 'select',
from: {
name: component.primaryTable,
alias: component.primaryTable.alias,
relations: [],
},
};
for (const [table, ref] of component.nonPrimaryTablesAndReferences) {
select.from.relations.push({
name: table,
alias: table.alias,
joinType: ref.joinType as JoinType,
conditions: getReferenceConditions(ref, this.designer),
});
}
for (const subComponent of component.subComponents) {
const subQuery = this.dumpComponent(subComponent);
subQuery.selectAll = true;
select.where = mergeConditions(select.where, {
conditionType: subComponent.parentReference.joinType == 'WHERE NOT EXISTS' ? 'notExists' : 'exists',
subQuery,
});
}
if (component.parentReference) {
select.where = mergeConditions(select.where, {
conditionType: 'and',
conditions: getReferenceConditions(component.parentReference, this.designer),
});
// cross join conditions in subcomponents
for (const ref of this.designer.references || []) {
if (referenceIsCrossJoin(ref) && referenceIsConnecting(ref, component.tables, component.myAndParentTables)) {
select.where = mergeConditions(select.where, {
conditionType: 'and',
conditions: getReferenceConditions(ref, this.designer),
});
}
}
this.addConditions(select, component.tables);
}
return select;
}
addConditions(select: Select, tables: DesignerTableInfo[]) {
for (const column of this.designer.columns || []) {
if (!column.filter) continue;
const table = (this.designer.tables || []).find(x => x.designerId == column.designerId);
if (!table) continue;
if (!tables.find(x => x.designerId == table.designerId)) continue;
const condition = parseFilter(column.filter, findDesignerFilterType(column, this.designer));
if (condition) {
select.where = mergeConditions(
select.where,
_.cloneDeepWith(condition, expr => {
if (expr.exprType == 'placeholder')
return {
exprType: 'column',
columnName: column.columnName,
source: findQuerySource(this.designer, column.designerId),
};
})
);
}
}
}
addGroupConditions(select: Select, tables: DesignerTableInfo[], selectIsGrouped: boolean) {
for (const column of this.designer.columns || []) {
if (!column.groupFilter) continue;
const table = (this.designer.tables || []).find(x => x.designerId == column.designerId);
if (!table) continue;
if (!tables.find(x => x.designerId == table.designerId)) continue;
const condition = parseFilter(column.groupFilter, findDesignerFilterType(column, this.designer));
if (condition) {
select.having = mergeConditions(
select.having,
_.cloneDeepWith(condition, expr => {
if (expr.exprType == 'placeholder') {
return this.getColumnOutputExpression(column, selectIsGrouped);
}
})
);
}
}
}
getColumnOutputExpression(col, selectIsGrouped): ResultField {
const source = findQuerySource(this.designer, col.designerId);
const { columnName } = col;
let { alias } = col;
if (selectIsGrouped && !col.isGrouped) {
// use aggregate
const aggregate = col.aggregate == null || col.aggregate == '---' ? 'MAX' : col.aggregate;
if (!alias) alias = `${aggregate}(${columnName})`;
return {
exprType: 'call',
func: aggregate == 'COUNT DISTINCT' ? 'COUNT' : aggregate,
argsPrefix: aggregate == 'COUNT DISTINCT' ? 'DISTINCT' : null,
alias,
args: [
{
exprType: 'column',
columnName,
source,
},
],
};
} else {
return {
exprType: 'column',
columnName,
alias,
source,
};
}
}
run() {
let res: Select = null;
for (const component of this.components) {
const select = this.dumpComponent(component);
if (res == null) res = select;
else res = mergeSelectsFromDesigner(res, select);
}
// top level cross join conditions
const topLevelTables = this.topLevelTables;
for (const ref of this.designer.references || []) {
if (referenceIsCrossJoin(ref) && referenceIsConnecting(ref, topLevelTables, topLevelTables)) {
res.where = mergeConditions(res.where, {
conditionType: 'and',
conditions: getReferenceConditions(ref, this.designer),
});
}
}
const topLevelColumns = (this.designer.columns || []).filter(col =>
topLevelTables.find(tbl => tbl.designerId == col.designerId)
);
const selectIsGrouped = !!topLevelColumns.find(x => x.isGrouped || (x.aggregate && x.aggregate != '---'));
const outputColumns = topLevelColumns.filter(x => x.isOutput);
if (outputColumns.length == 0) {
res.selectAll = true;
} else {
res.columns = outputColumns.map(col => this.getColumnOutputExpression(col, selectIsGrouped));
}
const groupedColumns = topLevelColumns.filter(x => x.isGrouped);
if (groupedColumns.length > 0) {
res.groupBy = groupedColumns.map(col => ({
exprType: 'column',
columnName: col.columnName,
source: findQuerySource(this.designer, col.designerId),
}));
}
const orderColumns = _.sortBy(
topLevelColumns.filter(x => x.sortOrder),
x => Math.abs(x.sortOrder)
);
if (orderColumns.length > 0) {
res.orderBy = orderColumns.map(col => ({
exprType: 'column',
direction: col.sortOrder < 0 ? 'DESC' : 'ASC',
columnName: col.columnName,
source: findQuerySource(this.designer, col.designerId),
}));
}
this.addConditions(res, topLevelTables);
this.addGroupConditions(res, topLevelTables, selectIsGrouped);
return res;
}
}