From ff810f3058ea9a2535ea8db69a85ca55a3b3cbdd Mon Sep 17 00:00:00 2001 From: kkk000111999 <254467556@qq.com> Date: Sat, 27 Jun 2026 12:36:13 +0800 Subject: [PATCH] fix(sql-format): format source and DDL with proper dialects --- .../src/components/layout/ContentArea.vue | 20 +---- .../src/components/objects/DdlViewDialog.vue | 9 ++- .../src/components/objects/ObjectBrowser.vue | 37 +++------- .../src/components/sidebar/TreeItem.vue | 16 +++- .../structure/TableStructureEditor.vue | 6 +- .../src/lib/__tests__/sqlFormatter.spec.ts | 30 ++++++++ apps/desktop/src/lib/sqlFormatter.ts | 74 ++++++++++++++++++- 7 files changed, 136 insertions(+), 56 deletions(-) create mode 100644 apps/desktop/src/lib/__tests__/sqlFormatter.spec.ts diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue index e20e9538f..34b08eb4d 100644 --- a/apps/desktop/src/components/layout/ContentArea.vue +++ b/apps/desktop/src/components/layout/ContentArea.vue @@ -63,7 +63,7 @@ import type { SqlExecutionOverride } from "@/lib/sqlExecutionTarget"; import type { DataGridSortMode } from "@/lib/dataGridSort"; import { useTabScroll } from "@/composables/useTabScroll"; import type { QueryTab, ConnectionConfig, TableInfoTab, TreeNode, VectorCollectionMeta } from "@/types/database"; -import type { SqlFormatDialect } from "@/lib/sqlFormatter"; +import { sqlFormatDialectForDbType, type SqlFormatDialect } from "@/lib/sqlFormatter"; type DataGridHandle = { onToolbarRefresh: () => Promise | void; @@ -196,23 +196,7 @@ const activeTabDimension = computed(() => { return meta && "dimension" in meta ? (meta as VectorCollectionMeta).dimension : undefined; }); -const activeSqlFormatDialect = computed(() => { - switch (activeEffectiveDatabaseType.value) { - case "mysql": - return "mysql"; - case "postgres": - case "kwdb": - return "postgres"; - case "sqlite": - case "rqlite": - case "turso": - return "sqlite"; - case "sqlserver": - return "sqlserver"; - default: - return "generic"; - } -}); +const activeSqlFormatDialect = computed(() => sqlFormatDialectForDbType(activeEffectiveDatabaseType.value)); const editorDialect = computed<"mysql" | "postgres" | "sqlserver">(() => { if (activeEffectiveDatabaseType.value === "postgres" || activeEffectiveDatabaseType.value === "kwdb") return "postgres"; diff --git a/apps/desktop/src/components/objects/DdlViewDialog.vue b/apps/desktop/src/components/objects/DdlViewDialog.vue index b1427ca94..d4fdc99e4 100644 --- a/apps/desktop/src/components/objects/DdlViewDialog.vue +++ b/apps/desktop/src/components/objects/DdlViewDialog.vue @@ -8,6 +8,7 @@ import { useSettingsStore } from "@/stores/settingsStore"; import { loadEditorTheme, editorFontTheme } from "@/lib/editorThemes"; import { createDbxCodeMirrorSqlDialect } from "@/lib/codemirrorSqlDialect"; import { copyToClipboard } from "@/lib/clipboard"; +import { formatSqlForDisplay, type SqlFormatDialect } from "@/lib/sqlFormatter"; import * as api from "@/lib/api"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; @@ -23,6 +24,8 @@ const props = withDefaults( tableName: string; /** SQL dialect for syntax highlighting. Non-PG/non-MSSQL databases fall back to MySQL (same as QueryEditor's source viewer). */ dialect: "mysql" | "postgres" | "sqlserver"; + /** SQL formatter dialect. Kept separate from the syntax-highlighting dialect because several PG-compatible DBs highlight as MySQL. */ + formatDialect?: SqlFormatDialect; }>(), {}, ); @@ -54,7 +57,7 @@ watch( try { const schema = props.schema || props.database; const ddl = await api.getTableDdl(props.connectionId, props.database, schema, props.tableName); - ddlContent.value = ddl; + ddlContent.value = await formatSqlForDisplay(ddl, props.formatDialect ?? props.dialect, settingsStore.editorSettings.sqlFormatter); } catch (e: any) { ddlError.value = e?.message || String(e); } finally { @@ -160,8 +163,8 @@ function retry() { const schema = props.schema || props.database; api .getTableDdl(props.connectionId, props.database, schema, props.tableName) - .then((ddl) => { - ddlContent.value = ddl; + .then(async (ddl) => { + ddlContent.value = await formatSqlForDisplay(ddl, props.formatDialect ?? props.dialect, settingsStore.editorSettings.sqlFormatter); }) .catch((e: any) => { ddlError.value = e?.message || String(e); diff --git a/apps/desktop/src/components/objects/ObjectBrowser.vue b/apps/desktop/src/components/objects/ObjectBrowser.vue index 01b46b213..82fdb7fdd 100644 --- a/apps/desktop/src/components/objects/ObjectBrowser.vue +++ b/apps/desktop/src/components/objects/ObjectBrowser.vue @@ -67,7 +67,7 @@ import { useSettingsStore } from "@/stores/settingsStore"; import { useQueryStore } from "@/stores/queryStore"; import QueryEditor from "@/components/editor/QueryEditor.vue"; import DdlViewDialog from "./DdlViewDialog.vue"; -import type { SqlFormatDialect } from "@/lib/sqlFormatter"; +import { formatSqlForDisplay, sqlFormatDialectForDbType, type SqlFormatDialect } from "@/lib/sqlFormatter"; import { isCancelSearchShortcut } from "@/lib/keyboardShortcuts"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { @@ -122,6 +122,7 @@ const sourceRow = ref(null); const sourceEditing = ref(false); const effectiveDatabaseType = computed(() => effectiveDatabaseTypeForConnection(props.connection) ?? props.connection.db_type); const tableStructureDatabaseType = computed(() => tableStructureDatabaseTypeForConnection(props.connection) ?? props.connection.db_type); +const sourceEditableText = ref(""); const sourceDraft = ref(""); const sourceSaving = ref(false); const sourceSaveError = ref(""); @@ -178,25 +179,7 @@ const canOpenDiagram = computed(() => !!props.database && supportsSchemaDiagram( const canOpenTableImport = computed(() => !!props.database && supportsTableImport(effectiveDatabaseType.value)); const supportsTruncateTable = computed(() => supportsTableTruncate(effectiveDatabaseType.value)); const sourceDialect = computed(() => codeMirrorSqlDialect(effectiveDatabaseType.value)); -const sourceFormatDialect = computed(() => { - switch (effectiveDatabaseType.value) { - case "mysql": - case "postgres": - case "sqlite": - case "sqlserver": - return effectiveDatabaseType.value; - case "rqlite": - case "turso": - return "sqlite"; - case "gaussdb": - case "kwdb": - case "opengauss": - case "questdb": - return "postgres"; - default: - return "generic"; - } -}); +const sourceFormatDialect = computed(() => sqlFormatDialectForDbType(effectiveDatabaseType.value)); const objectFilters = computed(() => ( [ @@ -425,6 +408,7 @@ async function openSource(row: ObjectBrowserRow) { sourceContent.value = ""; sourceError.value = ""; sourceEditing.value = false; + sourceEditableText.value = ""; sourceDraft.value = ""; sourceSaveError.value = ""; sourceLoading.value = true; @@ -437,7 +421,8 @@ async function openSource(row: ObjectBrowserRow) { name: row.name, source: result.source, }); - sourceContent.value = editable; + sourceEditableText.value = editable; + sourceContent.value = await formatSqlForDisplay(editable, sourceFormatDialect.value, settingsStore.editorSettings.sqlFormatter); sourceDraft.value = editable; sourceEditing.value = row.type !== "SEQUENCE"; } catch (e: any) { @@ -457,8 +442,9 @@ async function openViewDdl(row: ObjectBrowserRow) { name: row.name, source: result.source, }); + const formatted = await formatSqlForDisplay(ddl, sourceFormatDialect.value, settingsStore.editorSettings.sqlFormatter); const tabId = queryStore.createTab(props.connection.id, props.database, `DDL - ${row.name}`); - queryStore.updateSql(tabId, ddl); + queryStore.updateSql(tabId, formatted); } catch (e: any) { toast(e?.message || String(e), 5000); } @@ -630,6 +616,7 @@ function closeSource() { sourceContent.value = ""; sourceError.value = ""; sourceEditing.value = false; + sourceEditableText.value = ""; sourceDraft.value = ""; sourceSaveError.value = ""; } @@ -1040,8 +1027,8 @@ async function copySource() { } function editSource() { - if (!sourceRow.value || !sourceContent.value) return; - sourceDraft.value = sourceContent.value; + if (!sourceRow.value || !sourceEditableText.value) return; + sourceDraft.value = sourceEditableText.value; sourceSaveError.value = ""; sourceEditing.value = true; } @@ -1684,7 +1671,7 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] { - +