diff --git a/packages/sqltree/src/dumpSqlSource.ts b/packages/sqltree/src/dumpSqlSource.ts
index 46590dc3f..f6b58a53a 100644
--- a/packages/sqltree/src/dumpSqlSource.ts
+++ b/packages/sqltree/src/dumpSqlSource.ts
@@ -42,14 +42,14 @@ export function dumpSqlSourceRef(dmp: SqlDumper, source: Source) {
export function dumpSqlRelation(dmp: SqlDumper, from: Relation) {
dmp.put('&n %k ', from.joinType);
dumpSqlSourceDef(dmp, from);
- if (from.conditions) {
+ if (from.conditions && from.conditions.length > 0) {
dmp.put(' ^on ');
- dmp.putCollection(' ^and ', from.conditions, cond => dumpSqlCondition(dmp, cond));
+ dmp.putCollection(' ^and ', from.conditions, (cond) => dumpSqlCondition(dmp, cond));
}
}
export function dumpSqlFromDefinition(dmp: SqlDumper, from: FromDefinition) {
dumpSqlSourceDef(dmp, from);
dmp.put(' ');
- if (from.relations) from.relations.forEach(rel => dumpSqlRelation(dmp, rel));
+ if (from.relations) from.relations.forEach((rel) => dumpSqlRelation(dmp, rel));
}
diff --git a/packages/sqltree/src/types.ts b/packages/sqltree/src/types.ts
index b6d3da364..8bb29a060 100644
--- a/packages/sqltree/src/types.ts
+++ b/packages/sqltree/src/types.ts
@@ -91,7 +91,7 @@ export interface Source {
subQueryString?: string;
}
-export type JoinType = 'LEFT JOIN' | 'INNER JOIN' | 'RIGHT JOIN';
+export type JoinType = 'LEFT JOIN' | 'INNER JOIN' | 'RIGHT JOIN' | 'CROSS JOIN';
export type Relation = Source & {
conditions: Condition[];
diff --git a/packages/web/src/designer/Designer.js b/packages/web/src/designer/Designer.js
index d016020c9..b345ed28d 100644
--- a/packages/web/src/designer/Designer.js
+++ b/packages/web/src/designer/Designer.js
@@ -130,6 +130,7 @@ export default function Designer({ value, onChange }) {
designerId: uuidv1(),
sourceId: source.designerId,
targetId: target.designerId,
+ joinType: 'INNER JOIN',
columns: [
{
source: source.columnName,
diff --git a/packages/web/src/designer/DesignerReference.js b/packages/web/src/designer/DesignerReference.js
index 7a6ed6a8c..a02ab63d9 100644
--- a/packages/web/src/designer/DesignerReference.js
+++ b/packages/web/src/designer/DesignerReference.js
@@ -46,6 +46,7 @@ function ReferenceContextMenu({ remove, setJoinType }) {
setJoinType('LEFT JOIN')}>Set LEFT JOIN
setJoinType('RIGHT JOIN')}>Set RIGHT JOIN
setJoinType('FULL OUTER JOIN')}>Set FULL OUTER JOIN
+ setJoinType('CROSS JOIN')}>Set CROSS JOIN
setJoinType('WHERE EXISTS')}>Set WHERE EXISTS
setJoinType('WHERE NOT EXISTS')}>Set WHERE NOT EXISTS
>
@@ -159,7 +160,7 @@ export default function DesignerReference({
onContextMenu={handleContextMenu}
>
- {_.snakeCase(joinType || 'INNER JOIN')
+ {_.snakeCase(joinType || 'CROSS JOIN')
.replace('_', '\xa0')
.replace('_', '\xa0')}
diff --git a/packages/web/src/designer/DomTableRef.ts b/packages/web/src/designer/DomTableRef.ts
index b257a262c..ca37a54ba 100644
--- a/packages/web/src/designer/DomTableRef.ts
+++ b/packages/web/src/designer/DomTableRef.ts
@@ -1,6 +1,4 @@
-import { TableInfo } from 'dbgate-types';
-
-type DesignerTableInfo = TableInfo & { designerId: string };
+import { DesignerTableInfo } from "./types";
export default class DomTableRef {
domTable: Element;
diff --git a/packages/web/src/designer/generateDesignedQuery.ts b/packages/web/src/designer/generateDesignedQuery.ts
new file mode 100644
index 000000000..e33673384
--- /dev/null
+++ b/packages/web/src/designer/generateDesignedQuery.ts
@@ -0,0 +1,143 @@
+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;
+}
diff --git a/packages/web/src/designer/types.ts b/packages/web/src/designer/types.ts
new file mode 100644
index 000000000..b62577854
--- /dev/null
+++ b/packages/web/src/designer/types.ts
@@ -0,0 +1,41 @@
+import { JoinType } from 'dbgate-sqltree';
+import { TableInfo } from 'dbgate-types';
+
+export type DesignerTableInfo = TableInfo & {
+ designerId: string;
+ alias?: string;
+ left: number;
+ top: number;
+};
+
+export type DesignerJoinType = JoinType | 'WHERE EXISTS' | 'WHERE NOT EXISTS';
+
+export type DesignerReferenceInfo = {
+ designerId: string;
+ joinType: DesignerJoinType;
+ sourceId: string;
+ targetId: string;
+ columns: {
+ source: string;
+ target: string;
+ }[];
+};
+
+export type DesignerColumnInfo = {
+ designerId: string;
+ columnName: string;
+ alias?: string;
+ isGrouped?: boolean;
+ isOutput?: boolean;
+ filter: string;
+};
+
+export type DesignerInfo = {
+ tables: DesignerTableInfo[];
+ columns: DesignerColumnInfo[];
+ references: DesignerReferenceInfo[];
+};
+
+// export type DesignerComponent = {
+// tables: DesignerTableInfo[];
+// };
diff --git a/packages/web/src/tabs/QueryDesignTab.js b/packages/web/src/tabs/QueryDesignTab.js
index b96abe373..ad17910ea 100644
--- a/packages/web/src/tabs/QueryDesignTab.js
+++ b/packages/web/src/tabs/QueryDesignTab.js
@@ -23,6 +23,8 @@ import LoadingInfo from '../widgets/LoadingInfo';
import useExtensions from '../utility/useExtensions';
import QueryDesigner from '../designer/QueryDesigner';
import QueryDesignColumns from '../designer/QueryDesignColumns';
+import { findEngineDriver } from 'dbgate-tools';
+import generateDesignedQuery from '../designer/generateDesignedQuery';
export default function QueryDesignTab({
tabid,
@@ -40,6 +42,9 @@ export default function QueryDesignTab({
const [busy, setBusy] = React.useState(false);
const saveFileModalState = useModalState();
const extensions = useExtensions();
+ const connection = useConnectionInfo({ conid });
+ const engine = findEngineDriver(connection, extensions);
+ const [sqlPreview, setSqlPreview] = React.useState('');
const { editorData, setEditorData, isLoading } = useEditorData({
tabid,
loadFromArgs:
@@ -54,6 +59,16 @@ export default function QueryDesignTab({
setBusy(false);
}, []);
+ const generatePreview = (value, engine) => {
+ if (!engine || !value) return;
+ const sql = generateDesignedQuery(value, engine);
+ setSqlPreview(sql);
+ };
+
+ React.useEffect(() => {
+ generatePreview(editorData, engine);
+ }, [editorData, engine]);
+
React.useEffect(() => {
if (sessionId && socket) {
socket.on(`session-done-${sessionId}`, handleSessionDone);
@@ -68,7 +83,6 @@ export default function QueryDesignTab({
}, [busy]);
useUpdateDatabaseForTab(tabVisible, conid, database);
- const connection = useConnectionInfo({ conid });
const handleExecute = async () => {
if (busy) return;
@@ -135,12 +149,17 @@ export default function QueryDesignTab({
-
-
+
+
+ {sessionId && (
+
+
+
+ )}
{/* {toolbarPortalRef &&