diff --git a/packages/sqltree/src/dumpSqlCondition.ts b/packages/sqltree/src/dumpSqlCondition.ts index c9da29fef..3da2ab235 100644 --- a/packages/sqltree/src/dumpSqlCondition.ts +++ b/packages/sqltree/src/dumpSqlCondition.ts @@ -2,6 +2,7 @@ import { SqlDumper } from 'dbgate-types'; import { Condition, BinaryCondition } from './types'; import { dumpSqlExpression } from './dumpSqlExpression'; import { link } from 'fs'; +import { dumpSqlSelect } from './dumpSqlCommand'; export function dumpSqlCondition(dmp: SqlDumper, condition: Condition) { switch (condition.conditionType) { @@ -30,7 +31,7 @@ export function dumpSqlCondition(dmp: SqlDumper, condition: Condition) { break; case 'and': case 'or': - dmp.putCollection(` ^${condition.conditionType} `, condition.conditions, cond => { + dmp.putCollection(` ^${condition.conditionType} `, condition.conditions, (cond) => { dmp.putRaw('('); dumpSqlCondition(dmp, cond); dmp.putRaw(')'); @@ -51,5 +52,15 @@ export function dumpSqlCondition(dmp: SqlDumper, condition: Condition) { dumpSqlCondition(dmp, condition.condition); dmp.put(')'); break; + case 'exists': + dmp.put('^exists ('); + dumpSqlSelect(dmp, condition.subQuery); + dmp.put(')'); + break; + case 'notExists': + dmp.put('^not ^exists ('); + dumpSqlSelect(dmp, condition.subQuery); + dmp.put(')'); + break; } } diff --git a/packages/sqltree/src/types.ts b/packages/sqltree/src/types.ts index 8bb29a060..e6679091c 100644 --- a/packages/sqltree/src/types.ts +++ b/packages/sqltree/src/types.ts @@ -82,7 +82,23 @@ export interface CompoudCondition { conditions: Condition[]; } -export type Condition = BinaryCondition | NotCondition | TestCondition | CompoudCondition | LikeCondition; +export interface ExistsCondition { + conditionType: 'exists'; + subQuery: Select; +} +export interface NotExistsCondition { + conditionType: 'notExists'; + subQuery: Select; +} + +export type Condition = + | BinaryCondition + | NotCondition + | TestCondition + | CompoudCondition + | LikeCondition + | ExistsCondition + | NotExistsCondition; export interface Source { name?: NamedObjectInfo; diff --git a/packages/web/src/designer/generateDesignedQuery.ts b/packages/web/src/designer/generateDesignedQuery.ts index e33673384..600a0e8d0 100644 --- a/packages/web/src/designer/generateDesignedQuery.ts +++ b/packages/web/src/designer/generateDesignedQuery.ts @@ -1,5 +1,5 @@ import _ from 'lodash'; -import { dumpSqlSelect, Select, JoinType, Condition } from 'dbgate-sqltree'; +import { dumpSqlSelect, Select, JoinType, Condition, Relation } from 'dbgate-sqltree'; import { EngineDriver } from 'dbgate-types'; import { DesignerInfo, DesignerTableInfo, DesignerReferenceInfo, DesignerJoinType } from './types'; @@ -37,18 +37,51 @@ function findJoinType( table: DesignerTableInfo, dumpedTables: DesignerTableInfo[], references: DesignerReferenceInfo[], - joinTypes: DesignerJoinType[] + joinTypes = ['INNER JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'FULL OUTER JOIN', 'WHERE EXISTS', 'WHERE NOT EXISTS'] ): 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)) + joinTypes.includes(x.joinType) && + ((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 sortTablesByReferences( + dumpedTables: DesignerTableInfo[], + tables: DesignerTableInfo[], + references: DesignerReferenceInfo[], + joinTypes = ['INNER JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'FULL OUTER JOIN', 'WHERE EXISTS', 'WHERE NOT EXISTS'] +) { + const res = []; + const dumpedTableIds = dumpedTables.map((x) => x.designerId); + const toAdd = [...tables]; + while (toAdd.length > 0) { + let found = false; + for (const test of toAdd) { + const reference = references.find( + (x) => + joinTypes.includes(x.joinType) && + ((x.sourceId == test.designerId && dumpedTableIds.includes(x.targetId)) || + (x.targetId == test.designerId && dumpedTableIds.includes(x.sourceId))) + ); + if (reference) { + res.push(test); + _.remove(toAdd, (x) => x == test); + dumpedTableIds.push(test.designerId); + found = true; + break; + } + } + if (!found) break; + } + res.push(...toAdd); + return res; +} + function findConditions( table: DesignerTableInfo, dumpedTables: DesignerTableInfo[], @@ -90,6 +123,24 @@ function findConditions( return res; } +function addRelations( + relations: Relation[], + tables: DesignerTableInfo[], + dumpedTables: DesignerTableInfo[], + designer: DesignerInfo +) { + for (const table of tables) { + if (dumpedTables.includes(table)) continue; + relations.push({ + name: table, + alias: table.alias, + joinType: findJoinType(table, dumpedTables, designer.references) as JoinType, + conditions: findConditions(table, dumpedTables, designer.references, designer.tables), + }); + dumpedTables.push(table); + } +} + export default function generateDesignedQuery(designer: DesignerInfo, engine: EngineDriver) { const { tables, columns, references } = designer; const primaryTable = findPrimaryTable(designer.tables); @@ -111,6 +162,7 @@ export default function generateDesignedQuery(designer: DesignerInfo, engine: En }; const dumpedTables = [primaryTable]; + const conditions: Condition[] = []; for (const component of components) { const subComponents = groupByComponents( component, @@ -119,23 +171,46 @@ export default function generateDesignedQuery(designer: DesignerInfo, engine: En 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), + const sortedSubComponent = sortTablesByReferences(dumpedTables, subComponent, designer.references); + const table0 = sortedSubComponent[0]; + const joinType0 = findJoinType(table0, dumpedTables, designer.references); + if (joinType0 == 'WHERE EXISTS' || joinType0 == 'WHERE NOT EXISTS') { + const subselect: Select = { + commandType: 'select', + from: { + name: table0, + alias: table0.alias, + relations: [], + }, + }; + dumpedTables.push(table0); + addRelations(subselect.from.relations, sortedSubComponent, dumpedTables, designer); + conditions.push({ + conditionType: joinType0 == 'WHERE EXISTS' ? 'exists' : 'notExists', + subQuery: subselect, }); - dumpedTables.push(table); + } else { + addRelations(select.from.relations, sortedSubComponent, dumpedTables, designer); + // for (const table of sortedSubComponent) { + + // if (dumpedTables.includes(table)) continue; + // select.from.relations.push({ + // name: table, + // alias: table.alias, + // joinType: findJoinType(table, dumpedTables, designer.references) as JoinType, + // conditions: findConditions(table, dumpedTables, designer.references, designer.tables), + // }); + // dumpedTables.push(table); + // } } } } + if (conditions.length > 0) { + select.where = { + conditionType: 'and', + conditions, + }; + } const dmp = engine.createDumper(); dumpSqlSelect(dmp, select);