mirror of
https://github.com/dbgate/dbgate
synced 2024-11-07 20:26:23 +00:00
generated designer query SQL
This commit is contained in:
parent
79759b1665
commit
c10c6afbc5
@ -42,14 +42,14 @@ export function dumpSqlSourceRef(dmp: SqlDumper, source: Source) {
|
|||||||
export function dumpSqlRelation(dmp: SqlDumper, from: Relation) {
|
export function dumpSqlRelation(dmp: SqlDumper, from: Relation) {
|
||||||
dmp.put('&n %k ', from.joinType);
|
dmp.put('&n %k ', from.joinType);
|
||||||
dumpSqlSourceDef(dmp, from);
|
dumpSqlSourceDef(dmp, from);
|
||||||
if (from.conditions) {
|
if (from.conditions && from.conditions.length > 0) {
|
||||||
dmp.put(' ^on ');
|
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) {
|
export function dumpSqlFromDefinition(dmp: SqlDumper, from: FromDefinition) {
|
||||||
dumpSqlSourceDef(dmp, from);
|
dumpSqlSourceDef(dmp, from);
|
||||||
dmp.put(' ');
|
dmp.put(' ');
|
||||||
if (from.relations) from.relations.forEach(rel => dumpSqlRelation(dmp, rel));
|
if (from.relations) from.relations.forEach((rel) => dumpSqlRelation(dmp, rel));
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ export interface Source {
|
|||||||
subQueryString?: string;
|
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 & {
|
export type Relation = Source & {
|
||||||
conditions: Condition[];
|
conditions: Condition[];
|
||||||
|
@ -130,6 +130,7 @@ export default function Designer({ value, onChange }) {
|
|||||||
designerId: uuidv1(),
|
designerId: uuidv1(),
|
||||||
sourceId: source.designerId,
|
sourceId: source.designerId,
|
||||||
targetId: target.designerId,
|
targetId: target.designerId,
|
||||||
|
joinType: 'INNER JOIN',
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
source: source.columnName,
|
source: source.columnName,
|
||||||
|
@ -46,6 +46,7 @@ function ReferenceContextMenu({ remove, setJoinType }) {
|
|||||||
<DropDownMenuItem onClick={() => setJoinType('LEFT JOIN')}>Set LEFT JOIN</DropDownMenuItem>
|
<DropDownMenuItem onClick={() => setJoinType('LEFT JOIN')}>Set LEFT JOIN</DropDownMenuItem>
|
||||||
<DropDownMenuItem onClick={() => setJoinType('RIGHT JOIN')}>Set RIGHT JOIN</DropDownMenuItem>
|
<DropDownMenuItem onClick={() => setJoinType('RIGHT JOIN')}>Set RIGHT JOIN</DropDownMenuItem>
|
||||||
<DropDownMenuItem onClick={() => setJoinType('FULL OUTER JOIN')}>Set FULL OUTER JOIN</DropDownMenuItem>
|
<DropDownMenuItem onClick={() => setJoinType('FULL OUTER JOIN')}>Set FULL OUTER JOIN</DropDownMenuItem>
|
||||||
|
<DropDownMenuItem onClick={() => setJoinType('CROSS JOIN')}>Set CROSS JOIN</DropDownMenuItem>
|
||||||
<DropDownMenuItem onClick={() => setJoinType('WHERE EXISTS')}>Set WHERE EXISTS</DropDownMenuItem>
|
<DropDownMenuItem onClick={() => setJoinType('WHERE EXISTS')}>Set WHERE EXISTS</DropDownMenuItem>
|
||||||
<DropDownMenuItem onClick={() => setJoinType('WHERE NOT EXISTS')}>Set WHERE NOT EXISTS</DropDownMenuItem>
|
<DropDownMenuItem onClick={() => setJoinType('WHERE NOT EXISTS')}>Set WHERE NOT EXISTS</DropDownMenuItem>
|
||||||
</>
|
</>
|
||||||
@ -159,7 +160,7 @@ export default function DesignerReference({
|
|||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
>
|
>
|
||||||
<ReferenceText theme={theme}>
|
<ReferenceText theme={theme}>
|
||||||
{_.snakeCase(joinType || 'INNER JOIN')
|
{_.snakeCase(joinType || 'CROSS JOIN')
|
||||||
.replace('_', '\xa0')
|
.replace('_', '\xa0')
|
||||||
.replace('_', '\xa0')}
|
.replace('_', '\xa0')}
|
||||||
</ReferenceText>
|
</ReferenceText>
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import { TableInfo } from 'dbgate-types';
|
import { DesignerTableInfo } from "./types";
|
||||||
|
|
||||||
type DesignerTableInfo = TableInfo & { designerId: string };
|
|
||||||
|
|
||||||
export default class DomTableRef {
|
export default class DomTableRef {
|
||||||
domTable: Element;
|
domTable: Element;
|
||||||
|
143
packages/web/src/designer/generateDesignedQuery.ts
Normal file
143
packages/web/src/designer/generateDesignedQuery.ts
Normal file
@ -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;
|
||||||
|
}
|
41
packages/web/src/designer/types.ts
Normal file
41
packages/web/src/designer/types.ts
Normal file
@ -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[];
|
||||||
|
// };
|
@ -23,6 +23,8 @@ import LoadingInfo from '../widgets/LoadingInfo';
|
|||||||
import useExtensions from '../utility/useExtensions';
|
import useExtensions from '../utility/useExtensions';
|
||||||
import QueryDesigner from '../designer/QueryDesigner';
|
import QueryDesigner from '../designer/QueryDesigner';
|
||||||
import QueryDesignColumns from '../designer/QueryDesignColumns';
|
import QueryDesignColumns from '../designer/QueryDesignColumns';
|
||||||
|
import { findEngineDriver } from 'dbgate-tools';
|
||||||
|
import generateDesignedQuery from '../designer/generateDesignedQuery';
|
||||||
|
|
||||||
export default function QueryDesignTab({
|
export default function QueryDesignTab({
|
||||||
tabid,
|
tabid,
|
||||||
@ -40,6 +42,9 @@ export default function QueryDesignTab({
|
|||||||
const [busy, setBusy] = React.useState(false);
|
const [busy, setBusy] = React.useState(false);
|
||||||
const saveFileModalState = useModalState();
|
const saveFileModalState = useModalState();
|
||||||
const extensions = useExtensions();
|
const extensions = useExtensions();
|
||||||
|
const connection = useConnectionInfo({ conid });
|
||||||
|
const engine = findEngineDriver(connection, extensions);
|
||||||
|
const [sqlPreview, setSqlPreview] = React.useState('');
|
||||||
const { editorData, setEditorData, isLoading } = useEditorData({
|
const { editorData, setEditorData, isLoading } = useEditorData({
|
||||||
tabid,
|
tabid,
|
||||||
loadFromArgs:
|
loadFromArgs:
|
||||||
@ -54,6 +59,16 @@ export default function QueryDesignTab({
|
|||||||
setBusy(false);
|
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(() => {
|
React.useEffect(() => {
|
||||||
if (sessionId && socket) {
|
if (sessionId && socket) {
|
||||||
socket.on(`session-done-${sessionId}`, handleSessionDone);
|
socket.on(`session-done-${sessionId}`, handleSessionDone);
|
||||||
@ -68,7 +83,6 @@ export default function QueryDesignTab({
|
|||||||
}, [busy]);
|
}, [busy]);
|
||||||
|
|
||||||
useUpdateDatabaseForTab(tabVisible, conid, database);
|
useUpdateDatabaseForTab(tabVisible, conid, database);
|
||||||
const connection = useConnectionInfo({ conid });
|
|
||||||
|
|
||||||
const handleExecute = async () => {
|
const handleExecute = async () => {
|
||||||
if (busy) return;
|
if (busy) return;
|
||||||
@ -135,12 +149,17 @@ export default function QueryDesignTab({
|
|||||||
<TabPage label="Columns" key="columns">
|
<TabPage label="Columns" key="columns">
|
||||||
<QueryDesignColumns value={editorData || {}} onChange={setEditorData} />
|
<QueryDesignColumns value={editorData || {}} onChange={setEditorData} />
|
||||||
</TabPage>
|
</TabPage>
|
||||||
|
<TabPage label="SQL" key="sql">
|
||||||
|
<SqlEditor value={sqlPreview} engine={engine} readOnly />
|
||||||
|
</TabPage>
|
||||||
|
{sessionId && (
|
||||||
<TabPage label="Messages" key="messages">
|
<TabPage label="Messages" key="messages">
|
||||||
<SocketMessagesView
|
<SocketMessagesView
|
||||||
eventName={sessionId ? `session-info-${sessionId}` : null}
|
eventName={sessionId ? `session-info-${sessionId}` : null}
|
||||||
executeNumber={executeNumber}
|
executeNumber={executeNumber}
|
||||||
/>
|
/>
|
||||||
</TabPage>
|
</TabPage>
|
||||||
|
)}
|
||||||
</ResultTabs>
|
</ResultTabs>
|
||||||
</VerticalSplitter>
|
</VerticalSplitter>
|
||||||
{/* {toolbarPortalRef &&
|
{/* {toolbarPortalRef &&
|
||||||
|
Loading…
Reference in New Issue
Block a user