Skip to content

Commit 3c58b04

Browse files
committed
refactor explain and query
1 parent 8d633f3 commit 3c58b04

File tree

4 files changed

+112
-84
lines changed

4 files changed

+112
-84
lines changed

src/components/gui/query-explanation.tsx

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import QueryExplanationDiagram from "./query-explanation-diagram";
55

66
interface QueryExplanationProps {
77
data: DatabaseResultSet;
8-
dialect?: SupportedDialect
8+
dialect?: SupportedDialect;
99
}
1010

1111
interface ExplanationRow {
@@ -26,20 +26,22 @@ const queryExplanationRowSchema = z.object({
2626
detail: z.string(),
2727
});
2828

29-
export function isExplainQueryPlan(sql: string) {
29+
export function isExplainQueryPlan(sql: string, dialect: SupportedDialect) {
30+
if (!["sqlite", "mysql"].includes(dialect)) return false;
31+
3032
if (sql.toLowerCase().startsWith("explain query plan")) {
31-
return true
33+
return true;
3234
}
3335

3436
if (sql.toLowerCase().startsWith("explain format=json")) {
35-
return true
37+
return true;
3638
}
3739

3840
if (sql.toLowerCase().startsWith("explain (format json)")) {
39-
return true
41+
return true;
4042
}
4143

42-
return false
44+
return false;
4345
}
4446

4547
function buildQueryExplanationTree(nodes: ExplanationRow[]) {
@@ -64,7 +66,7 @@ function buildQueryExplanationTree(nodes: ExplanationRow[]) {
6466
function mapExplanationRows(props: QueryExplanationProps) {
6567
let isExplanationRows = null;
6668

67-
if (props.dialect === 'sqlite') {
69+
if (props.dialect === "sqlite") {
6870
isExplanationRows = z.array(queryExplanationRowSchema).safeParse(
6971
props.data.rows.map((r) => ({
7072
...r,
@@ -75,17 +77,20 @@ function mapExplanationRows(props: QueryExplanationProps) {
7577
);
7678
}
7779

78-
if (props.dialect === 'mysql') {
79-
const row = (props.data.rows || [])[0]
80-
const explain = String(row.EXPLAIN)
80+
if (props.dialect === "mysql") {
81+
const row = (props.data.rows || [])[0];
82+
const explain = String(row.EXPLAIN);
8183
return {
8284
_tag: "SUCCESS",
83-
value: JSON.parse(explain)
84-
}
85+
value: JSON.parse(explain),
86+
};
8587
}
8688

87-
if (props.dialect === 'postgres') {
88-
return { _tag: "SUCCESS" as const, value: 'Postgres dialect is not supported yet' };
89+
if (props.dialect === "postgres") {
90+
return {
91+
_tag: "SUCCESS" as const,
92+
value: "Postgres dialect is not supported yet",
93+
};
8994
}
9095

9196
if (isExplanationRows?.error) {
@@ -112,18 +117,16 @@ export function QueryExplanation(props: QueryExplanationProps) {
112117
);
113118
}
114119

115-
if (props.dialect !== 'sqlite') {
120+
if (props.dialect !== "sqlite") {
116121
return (
117122
<div className="p-5 font-mono h-full overflow-y-auto">
118-
{
119-
props.dialect === 'mysql' ? (
120-
<QueryExplanationDiagram items={tree.value} />
121-
) : (
122-
<p className="text-destructive">{tree.value}</p>
123-
)
124-
}
123+
{props.dialect === "mysql" ? (
124+
<QueryExplanationDiagram items={tree.value} />
125+
) : (
126+
<p className="text-destructive">{tree.value}</p>
127+
)}
125128
</div>
126-
)
129+
);
127130
}
128131

129132
return (
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { useDatabaseDriver } from "@/context/driver-provider";
2+
import { QueryExplanation } from "../query-explanation";
3+
import { DatabaseResultSet } from "@/drivers/base-driver";
4+
5+
export default function ExplainResultTab({
6+
data,
7+
}: {
8+
data: DatabaseResultSet;
9+
}) {
10+
const { databaseDriver } = useDatabaseDriver();
11+
12+
return (
13+
<div className="flex flex-col h-full w-full border-t">
14+
<div className="grow overflow-hidden">
15+
<QueryExplanation
16+
data={data}
17+
dialect={databaseDriver.getFlags().dialect}
18+
/>
19+
</div>
20+
</div>
21+
);
22+
}

src/components/gui/query-result.tsx renamed to src/components/gui/tabs-result/query-result-tab.tsx

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import { MultipleQueryResult } from "../lib/multiple-query";
2-
import ExportResultButton from "./export/export-result-button";
3-
import ResultTable from "./query-result-table";
4-
import ResultStats from "./result-stat";
1+
import { MultipleQueryResult } from "../../lib/multiple-query";
2+
import ExportResultButton from "../export/export-result-button";
3+
import ResultTable from "../query-result-table";
4+
import ResultStats from "../result-stat";
55
import { useMemo } from "react";
6-
import OptimizeTableState from "./table-optimized/OptimizeTableState";
7-
import { QueryExplanation, isExplainQueryPlan } from "./query-explanation";
6+
import OptimizeTableState from "../table-optimized/OptimizeTableState";
87
import { useDatabaseDriver } from "@/context/driver-provider";
98

109
export default function QueryResult({
@@ -15,40 +14,31 @@ export default function QueryResult({
1514
const { databaseDriver } = useDatabaseDriver();
1615

1716
const data = useMemo(() => {
18-
if (isExplainQueryPlan(result.sql)) {
19-
return { _tag: "EXPLAIN", value: result.result } as const;
20-
}
21-
2217
const state = OptimizeTableState.createFromResult(
2318
databaseDriver,
2419
result.result
2520
);
2621
state.setReadOnlyMode(true);
2722
state.mismatchDetection = databaseDriver.getFlags().mismatchDetection;
28-
return { _tag: "QUERY", value: state } as const;
23+
24+
return state;
2925
}, [result, databaseDriver]);
3026

3127
const stats = result.result.stat;
3228

3329
return (
3430
<div className="flex flex-col h-full w-full border-t">
3531
<div className="grow overflow-hidden">
36-
{data._tag === "QUERY" ? (
37-
<ResultTable data={data.value} />
38-
) : (
39-
<QueryExplanation data={data.value} dialect={databaseDriver.getFlags().dialect} />
40-
)}
32+
<ResultTable data={data} />
4133
</div>
4234
{stats && (
4335
<div className="shrink-0">
4436
<div className="flex p-1 border-t">
4537
<ResultStats stats={stats} />
4638

47-
{data._tag === "QUERY" && (
48-
<div>
49-
<ExportResultButton data={data.value} />
50-
</div>
51-
)}
39+
<div>
40+
<ExportResultButton data={data} />
41+
</div>
5242
</div>
5343
</div>
5444
)}

src/components/gui/tabs/query-tab.tsx

Lines changed: 53 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import {
2121
MultipleQueryResult,
2222
multipleQuery,
2323
} from "@/components/lib/multiple-query";
24-
import WindowTabs, { useTabsContext } from "../windows-tab";
25-
import QueryResult from "../query-result";
24+
import WindowTabs, { useTabsContext, WindowTabItemProps } from "../windows-tab";
25+
import QueryResult from "../tabs-result/query-result-tab";
2626
import { useSchema } from "@/context/schema-provider";
2727
import SaveDocButton from "../save-doc-button";
2828
import {
@@ -49,6 +49,8 @@ import {
4949
DropdownMenuSeparator,
5050
DropdownMenuTrigger,
5151
} from "@/components/ui/dropdown-menu";
52+
import { isExplainQueryPlan } from "../query-explanation";
53+
import ExplainResultTab from "../tabs-result/explain-result-tab";
5254

5355
interface QueryWindowProps {
5456
initialCode?: string;
@@ -115,14 +117,12 @@ export default function QueryWindow({
115117
explained &&
116118
statement.toLowerCase().indexOf("explain query plan") !== 0
117119
) {
118-
if (databaseDriver.getFlags().dialect === 'sqlite') {
120+
if (databaseDriver.getFlags().dialect === "sqlite") {
119121
statement = "explain query plan " + statement;
120-
}
121-
else if (databaseDriver.getFlags().dialect === 'mysql') {
122-
statement = "explain format=json " + statement
123-
}
124-
else if (databaseDriver.getFlags().dialect === 'postgres') {
125-
statement = 'explain (format json) ' + statement
122+
} else if (databaseDriver.getFlags().dialect === "mysql") {
123+
statement = "explain format=json " + statement;
124+
} else if (databaseDriver.getFlags().dialect === "postgres") {
125+
statement = "explain (format json) " + statement;
126126
}
127127
}
128128

@@ -161,7 +161,7 @@ export default function QueryWindow({
161161
} else if (
162162
databaseDriver.getFlags().supportUseStatement &&
163163
log.sql.trim().substring(0, "use ".length).toLowerCase() ===
164-
"use "
164+
"use "
165165
) {
166166
hasAlterSchema = true;
167167
break;
@@ -190,44 +190,57 @@ export default function QueryWindow({
190190
}, [code, name]);
191191

192192
const windowTab = useMemo(() => {
193+
const queryTabs: WindowTabItemProps[] = [];
194+
195+
for (const queryResult of data ?? []) {
196+
if (
197+
isExplainQueryPlan(queryResult.sql, databaseDriver.getFlags().dialect)
198+
) {
199+
queryTabs.push({
200+
component: <ExplainResultTab data={queryResult.result} />,
201+
key: "explain_" + queryResult.order,
202+
identifier: "explain_" + queryResult.order,
203+
title: "Explain (Visual)",
204+
icon: LucideMessageSquareWarning,
205+
});
206+
}
207+
208+
queryTabs.push({
209+
component: <QueryResult result={queryResult} key={queryResult.order} />,
210+
key: "query_" + queryResult.order,
211+
identifier: "query_" + queryResult.order,
212+
title:
213+
`${getSingleTableName(queryResult.sql) ?? "Query " + (queryResult.order + 1)}` +
214+
` (${queryResult.result.rows.length}x${queryResult.result.headers.length})`,
215+
icon: LucideGrid,
216+
});
217+
}
218+
219+
if (progress) {
220+
queryTabs.push({
221+
key: "summary",
222+
identifier: "summary",
223+
title: "Summary",
224+
icon: LucideMessageSquareWarning,
225+
component: (
226+
<div className="w-full h-full overflow-y-auto overflow-x-hidden">
227+
<QueryProgressLog progress={progress} />
228+
</div>
229+
),
230+
});
231+
}
232+
193233
return (
194234
<WindowTabs
195235
key="main-window-tab"
196236
onSelectChange={setQueryTabIndex}
197-
onTabsChange={() => { }}
237+
onTabsChange={() => {}}
198238
hideCloseButton
199239
selected={queryTabIndex}
200-
tabs={[
201-
...(data ?? []).map((queryResult, queryIdx) => ({
202-
component: (
203-
<QueryResult result={queryResult} key={queryResult.order} />
204-
),
205-
key: "query_" + queryResult.order,
206-
identifier: "query_" + queryResult.order,
207-
title:
208-
`${getSingleTableName(queryResult.sql) ?? "Query " + (queryIdx + 1)}` +
209-
` (${queryResult.result.rows.length}x${queryResult.result.headers.length})`,
210-
icon: LucideGrid,
211-
})),
212-
...(progress
213-
? [
214-
{
215-
key: "summary",
216-
identifier: "summary",
217-
title: "Summary",
218-
icon: LucideMessageSquareWarning,
219-
component: (
220-
<div className="w-full h-full overflow-y-auto overflow-x-hidden">
221-
<QueryProgressLog progress={progress} />
222-
</div>
223-
),
224-
},
225-
]
226-
: []),
227-
]}
240+
tabs={queryTabs}
228241
/>
229242
);
230-
}, [progress, queryTabIndex, data]);
243+
}, [progress, queryTabIndex, data, databaseDriver]);
231244

232245
return (
233246
<ResizablePanelGroup direction="vertical">

0 commit comments

Comments
 (0)