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) {
|
||||
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));
|
||||
}
|
||||
|
@ -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[];
|
||||
|
@ -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,
|
||||
|
@ -46,6 +46,7 @@ function ReferenceContextMenu({ remove, setJoinType }) {
|
||||
<DropDownMenuItem onClick={() => setJoinType('LEFT JOIN')}>Set LEFT 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('CROSS JOIN')}>Set CROSS JOIN</DropDownMenuItem>
|
||||
<DropDownMenuItem onClick={() => setJoinType('WHERE EXISTS')}>Set WHERE EXISTS</DropDownMenuItem>
|
||||
<DropDownMenuItem onClick={() => setJoinType('WHERE NOT EXISTS')}>Set WHERE NOT EXISTS</DropDownMenuItem>
|
||||
</>
|
||||
@ -159,7 +160,7 @@ export default function DesignerReference({
|
||||
onContextMenu={handleContextMenu}
|
||||
>
|
||||
<ReferenceText theme={theme}>
|
||||
{_.snakeCase(joinType || 'INNER JOIN')
|
||||
{_.snakeCase(joinType || 'CROSS JOIN')
|
||||
.replace('_', '\xa0')
|
||||
.replace('_', '\xa0')}
|
||||
</ReferenceText>
|
||||
|
@ -1,6 +1,4 @@
|
||||
import { TableInfo } from 'dbgate-types';
|
||||
|
||||
type DesignerTableInfo = TableInfo & { designerId: string };
|
||||
import { DesignerTableInfo } from "./types";
|
||||
|
||||
export default class DomTableRef {
|
||||
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 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({
|
||||
<TabPage label="Columns" key="columns">
|
||||
<QueryDesignColumns value={editorData || {}} onChange={setEditorData} />
|
||||
</TabPage>
|
||||
<TabPage label="SQL" key="sql">
|
||||
<SqlEditor value={sqlPreview} engine={engine} readOnly />
|
||||
</TabPage>
|
||||
{sessionId && (
|
||||
<TabPage label="Messages" key="messages">
|
||||
<SocketMessagesView
|
||||
eventName={sessionId ? `session-info-${sessionId}` : null}
|
||||
executeNumber={executeNumber}
|
||||
/>
|
||||
</TabPage>
|
||||
)}
|
||||
</ResultTabs>
|
||||
</VerticalSplitter>
|
||||
{/* {toolbarPortalRef &&
|
||||
|
Loading…
Reference in New Issue
Block a user