Files
dbgate/packages/web/src/designer/generateDesignedQuery.ts
2020-12-28 13:59:36 +01:00

144 lines
4.5 KiB
TypeScript

import _ from 'lodash';
import { dumpSqlSelect, Select, JoinType, Condition } from 'dbgate-sqltree';
import { EngineDriver } from 'dbgate-types';
import { DesignerInfo, DesignerTableInfo, DesignerReferenceInfo, DesignerJoinType } from './types';
function groupByComponents(
tables: DesignerTableInfo[],
references: DesignerReferenceInfo[],
joinTypes: string[],
primaryTable: DesignerTableInfo
) {
let components = tables.map((table) => [table]);
for (const ref of references) {
if (joinTypes.includes(ref.joinType)) {
const comp1 = components.find((comp) => comp.find((t) => t.designerId == ref.sourceId));
const comp2 = components.find((comp) => comp.find((t) => t.designerId == ref.targetId));
if (comp1 && comp2 && comp1 != comp2) {
// join components
components = [...components.filter((x) => x != comp1 && x != comp2), [...comp1, ...comp2]];
}
}
}
if (primaryTable) {
const primaryComponent = components.find((comp) => comp.find((t) => t == primaryTable));
if (primaryComponent) {
components = [primaryComponent, ...components.filter((x) => x != primaryComponent)];
}
}
return components;
}
function findPrimaryTable(tables: DesignerTableInfo[]) {
return _.minBy(tables, (x) => x.left + x.top);
}
function findJoinType(
table: DesignerTableInfo,
dumpedTables: DesignerTableInfo[],
references: DesignerReferenceInfo[],
joinTypes: DesignerJoinType[]
): DesignerJoinType {
const dumpedTableIds = dumpedTables.map((x) => x.designerId);
const reference = references.find(
(x) =>
(x.sourceId == table.designerId && dumpedTableIds.includes(x.targetId)) ||
(x.targetId == table.designerId && dumpedTableIds.includes(x.sourceId))
);
if (reference) return reference.joinType || 'CROSS JOIN';
return 'CROSS JOIN';
}
function findConditions(
table: DesignerTableInfo,
dumpedTables: DesignerTableInfo[],
references: DesignerReferenceInfo[],
tables: DesignerTableInfo[]
): Condition[] {
const dumpedTableIds = dumpedTables.map((x) => x.designerId);
const res = [];
for (const reference of references.filter(
(x) =>
(x.sourceId == table.designerId && dumpedTableIds.includes(x.targetId)) ||
(x.targetId == table.designerId && dumpedTableIds.includes(x.sourceId))
)) {
const sourceTable = tables.find((x) => x.designerId == reference.sourceId);
const targetTable = tables.find((x) => x.designerId == reference.targetId);
res.push(
...reference.columns.map((col) => ({
conditionType: 'binary',
operator: '=',
left: {
exprType: 'column',
columnName: col.source,
source: {
name: sourceTable,
alias: sourceTable.alias,
},
},
right: {
exprType: 'column',
columnName: col.target,
source: {
name: targetTable,
alias: targetTable.alias,
},
},
}))
);
}
return res;
}
export default function generateDesignedQuery(designer: DesignerInfo, engine: EngineDriver) {
const { tables, columns, references } = designer;
const primaryTable = findPrimaryTable(designer.tables);
if (!primaryTable) return '';
const components = groupByComponents(
designer.tables,
designer.references,
['INNER JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'FULL OUTER JOIN', 'WHERE EXISTS', 'WHERE NOT EXISTS'],
primaryTable
);
const select: Select = {
commandType: 'select',
from: {
name: primaryTable,
alias: primaryTable.alias,
relations: [],
},
};
const dumpedTables = [primaryTable];
for (const component of components) {
const subComponents = groupByComponents(
component,
designer.references,
['INNER JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'FULL OUTER JOIN'],
primaryTable
);
for (const subComponent of subComponents) {
for (const table of subComponent) {
if (dumpedTables.includes(table)) continue;
select.from.relations.push({
name: table,
alias: table.alias,
joinType: findJoinType(table, dumpedTables, designer.references, [
'INNER JOIN',
'LEFT JOIN',
'RIGHT JOIN',
'FULL OUTER JOIN',
]) as JoinType,
conditions: findConditions(table, dumpedTables, designer.references, designer.tables),
});
dumpedTables.push(table);
}
}
}
const dmp = engine.createDumper();
dumpSqlSelect(dmp, select);
return dmp.s;
}