From 3c58b0434de2a4edb09c46717fbfdc0f8afd42be Mon Sep 17 00:00:00 2001 From: "Visal .In" Date: Sat, 21 Dec 2024 19:27:55 +0700 Subject: [PATCH] refactor explain and query --- src/components/gui/query-explanation.tsx | 49 +++++----- .../gui/tabs-result/explain-result-tab.tsx | 22 +++++ .../query-result-tab.tsx} | 32 +++---- src/components/gui/tabs/query-tab.tsx | 93 +++++++++++-------- 4 files changed, 112 insertions(+), 84 deletions(-) create mode 100644 src/components/gui/tabs-result/explain-result-tab.tsx rename src/components/gui/{query-result.tsx => tabs-result/query-result-tab.tsx} (50%) diff --git a/src/components/gui/query-explanation.tsx b/src/components/gui/query-explanation.tsx index 1125ff8..8788dbf 100644 --- a/src/components/gui/query-explanation.tsx +++ b/src/components/gui/query-explanation.tsx @@ -5,7 +5,7 @@ import QueryExplanationDiagram from "./query-explanation-diagram"; interface QueryExplanationProps { data: DatabaseResultSet; - dialect?: SupportedDialect + dialect?: SupportedDialect; } interface ExplanationRow { @@ -26,20 +26,22 @@ const queryExplanationRowSchema = z.object({ detail: z.string(), }); -export function isExplainQueryPlan(sql: string) { +export function isExplainQueryPlan(sql: string, dialect: SupportedDialect) { + if (!["sqlite", "mysql"].includes(dialect)) return false; + if (sql.toLowerCase().startsWith("explain query plan")) { - return true + return true; } if (sql.toLowerCase().startsWith("explain format=json")) { - return true + return true; } if (sql.toLowerCase().startsWith("explain (format json)")) { - return true + return true; } - return false + return false; } function buildQueryExplanationTree(nodes: ExplanationRow[]) { @@ -64,7 +66,7 @@ function buildQueryExplanationTree(nodes: ExplanationRow[]) { function mapExplanationRows(props: QueryExplanationProps) { let isExplanationRows = null; - if (props.dialect === 'sqlite') { + if (props.dialect === "sqlite") { isExplanationRows = z.array(queryExplanationRowSchema).safeParse( props.data.rows.map((r) => ({ ...r, @@ -75,17 +77,20 @@ function mapExplanationRows(props: QueryExplanationProps) { ); } - if (props.dialect === 'mysql') { - const row = (props.data.rows || [])[0] - const explain = String(row.EXPLAIN) + if (props.dialect === "mysql") { + const row = (props.data.rows || [])[0]; + const explain = String(row.EXPLAIN); return { _tag: "SUCCESS", - value: JSON.parse(explain) - } + value: JSON.parse(explain), + }; } - if (props.dialect === 'postgres') { - return { _tag: "SUCCESS" as const, value: 'Postgres dialect is not supported yet' }; + if (props.dialect === "postgres") { + return { + _tag: "SUCCESS" as const, + value: "Postgres dialect is not supported yet", + }; } if (isExplanationRows?.error) { @@ -112,18 +117,16 @@ export function QueryExplanation(props: QueryExplanationProps) { ); } - if (props.dialect !== 'sqlite') { + if (props.dialect !== "sqlite") { return (
- { - props.dialect === 'mysql' ? ( - - ) : ( -

{tree.value}

- ) - } + {props.dialect === "mysql" ? ( + + ) : ( +

{tree.value}

+ )}
- ) + ); } return ( diff --git a/src/components/gui/tabs-result/explain-result-tab.tsx b/src/components/gui/tabs-result/explain-result-tab.tsx new file mode 100644 index 0000000..a7dad14 --- /dev/null +++ b/src/components/gui/tabs-result/explain-result-tab.tsx @@ -0,0 +1,22 @@ +import { useDatabaseDriver } from "@/context/driver-provider"; +import { QueryExplanation } from "../query-explanation"; +import { DatabaseResultSet } from "@/drivers/base-driver"; + +export default function ExplainResultTab({ + data, +}: { + data: DatabaseResultSet; +}) { + const { databaseDriver } = useDatabaseDriver(); + + return ( +
+
+ +
+
+ ); +} diff --git a/src/components/gui/query-result.tsx b/src/components/gui/tabs-result/query-result-tab.tsx similarity index 50% rename from src/components/gui/query-result.tsx rename to src/components/gui/tabs-result/query-result-tab.tsx index 02e1fca..d1da3db 100644 --- a/src/components/gui/query-result.tsx +++ b/src/components/gui/tabs-result/query-result-tab.tsx @@ -1,10 +1,9 @@ -import { MultipleQueryResult } from "../lib/multiple-query"; -import ExportResultButton from "./export/export-result-button"; -import ResultTable from "./query-result-table"; -import ResultStats from "./result-stat"; +import { MultipleQueryResult } from "../../lib/multiple-query"; +import ExportResultButton from "../export/export-result-button"; +import ResultTable from "../query-result-table"; +import ResultStats from "../result-stat"; import { useMemo } from "react"; -import OptimizeTableState from "./table-optimized/OptimizeTableState"; -import { QueryExplanation, isExplainQueryPlan } from "./query-explanation"; +import OptimizeTableState from "../table-optimized/OptimizeTableState"; import { useDatabaseDriver } from "@/context/driver-provider"; export default function QueryResult({ @@ -15,17 +14,14 @@ export default function QueryResult({ const { databaseDriver } = useDatabaseDriver(); const data = useMemo(() => { - if (isExplainQueryPlan(result.sql)) { - return { _tag: "EXPLAIN", value: result.result } as const; - } - const state = OptimizeTableState.createFromResult( databaseDriver, result.result ); state.setReadOnlyMode(true); state.mismatchDetection = databaseDriver.getFlags().mismatchDetection; - return { _tag: "QUERY", value: state } as const; + + return state; }, [result, databaseDriver]); const stats = result.result.stat; @@ -33,22 +29,16 @@ export default function QueryResult({ return (
- {data._tag === "QUERY" ? ( - - ) : ( - - )} +
{stats && (
- {data._tag === "QUERY" && ( -
- -
- )} +
+ +
)} diff --git a/src/components/gui/tabs/query-tab.tsx b/src/components/gui/tabs/query-tab.tsx index 780b18c..d1517bf 100644 --- a/src/components/gui/tabs/query-tab.tsx +++ b/src/components/gui/tabs/query-tab.tsx @@ -21,8 +21,8 @@ import { MultipleQueryResult, multipleQuery, } from "@/components/lib/multiple-query"; -import WindowTabs, { useTabsContext } from "../windows-tab"; -import QueryResult from "../query-result"; +import WindowTabs, { useTabsContext, WindowTabItemProps } from "../windows-tab"; +import QueryResult from "../tabs-result/query-result-tab"; import { useSchema } from "@/context/schema-provider"; import SaveDocButton from "../save-doc-button"; import { @@ -49,6 +49,8 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { isExplainQueryPlan } from "../query-explanation"; +import ExplainResultTab from "../tabs-result/explain-result-tab"; interface QueryWindowProps { initialCode?: string; @@ -115,14 +117,12 @@ export default function QueryWindow({ explained && statement.toLowerCase().indexOf("explain query plan") !== 0 ) { - if (databaseDriver.getFlags().dialect === 'sqlite') { + if (databaseDriver.getFlags().dialect === "sqlite") { statement = "explain query plan " + statement; - } - else if (databaseDriver.getFlags().dialect === 'mysql') { - statement = "explain format=json " + statement - } - else if (databaseDriver.getFlags().dialect === 'postgres') { - statement = 'explain (format json) ' + statement + } else if (databaseDriver.getFlags().dialect === "mysql") { + statement = "explain format=json " + statement; + } else if (databaseDriver.getFlags().dialect === "postgres") { + statement = "explain (format json) " + statement; } } @@ -161,7 +161,7 @@ export default function QueryWindow({ } else if ( databaseDriver.getFlags().supportUseStatement && log.sql.trim().substring(0, "use ".length).toLowerCase() === - "use " + "use " ) { hasAlterSchema = true; break; @@ -190,44 +190,57 @@ export default function QueryWindow({ }, [code, name]); const windowTab = useMemo(() => { + const queryTabs: WindowTabItemProps[] = []; + + for (const queryResult of data ?? []) { + if ( + isExplainQueryPlan(queryResult.sql, databaseDriver.getFlags().dialect) + ) { + queryTabs.push({ + component: , + key: "explain_" + queryResult.order, + identifier: "explain_" + queryResult.order, + title: "Explain (Visual)", + icon: LucideMessageSquareWarning, + }); + } + + queryTabs.push({ + component: , + key: "query_" + queryResult.order, + identifier: "query_" + queryResult.order, + title: + `${getSingleTableName(queryResult.sql) ?? "Query " + (queryResult.order + 1)}` + + ` (${queryResult.result.rows.length}x${queryResult.result.headers.length})`, + icon: LucideGrid, + }); + } + + if (progress) { + queryTabs.push({ + key: "summary", + identifier: "summary", + title: "Summary", + icon: LucideMessageSquareWarning, + component: ( +
+ +
+ ), + }); + } + return ( { }} + onTabsChange={() => {}} hideCloseButton selected={queryTabIndex} - tabs={[ - ...(data ?? []).map((queryResult, queryIdx) => ({ - component: ( - - ), - key: "query_" + queryResult.order, - identifier: "query_" + queryResult.order, - title: - `${getSingleTableName(queryResult.sql) ?? "Query " + (queryIdx + 1)}` + - ` (${queryResult.result.rows.length}x${queryResult.result.headers.length})`, - icon: LucideGrid, - })), - ...(progress - ? [ - { - key: "summary", - identifier: "summary", - title: "Summary", - icon: LucideMessageSquareWarning, - component: ( -
- -
- ), - }, - ] - : []), - ]} + tabs={queryTabs} /> ); - }, [progress, queryTabIndex, data]); + }, [progress, queryTabIndex, data, databaseDriver]); return (