Skip to content

Commit

Permalink
refactor explain and query
Browse files Browse the repository at this point in the history
  • Loading branch information
invisal committed Dec 21, 2024
1 parent 8d633f3 commit 3c58b04
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 84 deletions.
49 changes: 26 additions & 23 deletions src/components/gui/query-explanation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import QueryExplanationDiagram from "./query-explanation-diagram";

interface QueryExplanationProps {
data: DatabaseResultSet;
dialect?: SupportedDialect
dialect?: SupportedDialect;
}

interface ExplanationRow {
Expand All @@ -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[]) {
Expand All @@ -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,
Expand All @@ -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) {
Expand All @@ -112,18 +117,16 @@ export function QueryExplanation(props: QueryExplanationProps) {
);
}

if (props.dialect !== 'sqlite') {
if (props.dialect !== "sqlite") {
return (
<div className="p-5 font-mono h-full overflow-y-auto">
{
props.dialect === 'mysql' ? (
<QueryExplanationDiagram items={tree.value} />
) : (
<p className="text-destructive">{tree.value}</p>
)
}
{props.dialect === "mysql" ? (
<QueryExplanationDiagram items={tree.value} />
) : (
<p className="text-destructive">{tree.value}</p>
)}
</div>
)
);
}

return (
Expand Down
22 changes: 22 additions & 0 deletions src/components/gui/tabs-result/explain-result-tab.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex flex-col h-full w-full border-t">
<div className="grow overflow-hidden">
<QueryExplanation
data={data}
dialect={databaseDriver.getFlags().dialect}
/>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -15,40 +14,31 @@ 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;

return (
<div className="flex flex-col h-full w-full border-t">
<div className="grow overflow-hidden">
{data._tag === "QUERY" ? (
<ResultTable data={data.value} />
) : (
<QueryExplanation data={data.value} dialect={databaseDriver.getFlags().dialect} />
)}
<ResultTable data={data} />
</div>
{stats && (
<div className="shrink-0">
<div className="flex p-1 border-t">
<ResultStats stats={stats} />

{data._tag === "QUERY" && (
<div>
<ExportResultButton data={data.value} />
</div>
)}
<div>
<ExportResultButton data={data} />
</div>
</div>
</div>
)}
Expand Down
93 changes: 53 additions & 40 deletions src/components/gui/tabs/query-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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: <ExplainResultTab data={queryResult.result} />,
key: "explain_" + queryResult.order,
identifier: "explain_" + queryResult.order,
title: "Explain (Visual)",
icon: LucideMessageSquareWarning,
});
}

queryTabs.push({
component: <QueryResult result={queryResult} key={queryResult.order} />,
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: (
<div className="w-full h-full overflow-y-auto overflow-x-hidden">
<QueryProgressLog progress={progress} />
</div>
),
});
}

return (
<WindowTabs
key="main-window-tab"
onSelectChange={setQueryTabIndex}
onTabsChange={() => { }}
onTabsChange={() => {}}
hideCloseButton
selected={queryTabIndex}
tabs={[
...(data ?? []).map((queryResult, queryIdx) => ({
component: (
<QueryResult result={queryResult} key={queryResult.order} />
),
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: (
<div className="w-full h-full overflow-y-auto overflow-x-hidden">
<QueryProgressLog progress={progress} />
</div>
),
},
]
: []),
]}
tabs={queryTabs}
/>
);
}, [progress, queryTabIndex, data]);
}, [progress, queryTabIndex, data, databaseDriver]);

return (
<ResizablePanelGroup direction="vertical">
Expand Down

0 comments on commit 3c58b04

Please sign in to comment.