From 120566b1fd79c49ce1ee60789033700ec27e357a Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Thu, 4 Jun 2026 19:40:52 +0800 Subject: [PATCH] fix: improve JDBC dialect handling --- .../src/components/layout/ContentArea.vue | 16 +-- .../src/components/objects/ObjectBrowser.vue | 64 ++++++------ .../src/components/sidebar/ConnectionTree.vue | 3 +- .../src/components/sidebar/TreeItem.vue | 81 ++++++++++------ .../structure/TableStructureEditor.vue | 32 ++++-- .../src/composables/useDataGridActions.ts | 14 +-- .../src/composables/useNavigationTargets.ts | 45 +++++---- .../desktop/src/lib/databaseCapabilitySets.ts | 3 + .../desktop/src/lib/databaseFeatureSupport.ts | 25 +++++ apps/desktop/src/lib/jdbcDialect.ts | 70 +++++++++++++ apps/desktop/src/lib/tableSelectSql.ts | 4 +- apps/desktop/src/stores/connectionStore.ts | 31 ++++-- apps/desktop/src/stores/queryStore.ts | 54 +++++++++-- crates/dbx-core/src/query.rs | 2 +- crates/dbx-core/src/sql_dialect.rs | 24 +++-- .../app-tests/databaseCapabilities.test.ts | 19 ++++ packages/app-tests/jdbcDialect.test.ts | 60 ++++++++++++ packages/app-tests/queryStore.test.ts | 15 +++ packages/app-tests/tableSelectSql.test.ts | 2 +- .../main/java/app/dbx/jdbc/DbxJdbcPlugin.java | 97 +++++++++++++++++-- .../java/app/dbx/jdbc/DbxJdbcPluginTest.java | 13 +++ 21 files changed, 541 insertions(+), 133 deletions(-) create mode 100644 apps/desktop/src/lib/jdbcDialect.ts create mode 100644 packages/app-tests/jdbcDialect.test.ts diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue index 5c9e16974f..3c4c271325 100644 --- a/apps/desktop/src/components/layout/ContentArea.vue +++ b/apps/desktop/src/components/layout/ContentArea.vue @@ -54,6 +54,7 @@ import { canCancelQueryExecution, queryExecutionLabelKey } from "@/lib/queryExec import { databaseDisplayNameForTab } from "@/lib/tabPresentation"; import { isTableDataEditable } from "@/lib/tableEditing"; import { tableMetaForDataTab } from "@/lib/tableDataTabMeta"; +import { effectiveDatabaseTypeForConnection } from "@/lib/jdbcDialect"; import type { QueryTab, ConnectionConfig } from "@/types/database"; import type { SqlFormatDialect } from "@/lib/sqlFormatter"; @@ -148,9 +149,10 @@ const redisKeyBrowserRef = ref(); const objectBrowserRef = ref(); const activeTableMeta = computed(() => props.activeTab.tableMeta); const activeDataTabTableMeta = computed(() => tableMetaForDataTab(props.activeTab)); +const activeEffectiveDatabaseType = computed(() => effectiveDatabaseTypeForConnection(props.activeConnection)); const activeSqlFormatDialect = computed(() => { - switch (props.activeConnection?.db_type) { + switch (activeEffectiveDatabaseType.value) { case "mysql": return "mysql"; case "postgres": @@ -165,8 +167,8 @@ const activeSqlFormatDialect = computed(() => { }); const editorDialect = computed<"mysql" | "postgres" | "sqlserver">(() => { - if (props.activeConnection?.db_type === "postgres") return "postgres"; - if (props.activeConnection?.db_type === "sqlserver") return "sqlserver"; + if (activeEffectiveDatabaseType.value === "postgres") return "postgres"; + if (activeEffectiveDatabaseType.value === "sqlserver") return "sqlserver"; return "mysql"; }); @@ -367,7 +369,7 @@ defineExpose({ focusSearch, refreshData, handleModRTarget }); :connection-id="activeTab.connectionId" :database="activeTab.database" :schema="activeTab.schema" - :database-type="activeConnection?.db_type" + :database-type="activeEffectiveDatabaseType" :dialect="editorDialect" :format-dialect="activeSqlFormatDialect" :format-request-id="formatSqlRequestId" @@ -552,7 +554,7 @@ defineExpose({ focusSearch, refreshData, handleModRTarget }); :editable="!!activeTab.queryAnalysis" :source-columns="activeTab.querySourceColumns" context="results" - :database-type="activeConnection?.db_type" + :database-type="activeEffectiveDatabaseType" :connection-id="activeTab.connectionId" :database="activeTab.database" :schema="activeTab.schema" @@ -799,10 +801,10 @@ defineExpose({ focusSearch, refreshData, handleModRTarget }); :initial-order-by-input="activeTab.orderByInput" :sql="activeTab.sql" :loading="activeTab.isExecuting" - :editable="isTableDataEditable(activeConnection?.db_type, activeTableMeta?.primaryKeys ?? [])" + :editable="isTableDataEditable(activeEffectiveDatabaseType, activeTableMeta?.primaryKeys ?? [])" context="table-data" :initial-where-input="activeTab.whereInput" - :database-type="activeConnection?.db_type" + :database-type="activeEffectiveDatabaseType" :connection-id="activeTab.connectionId" :database="activeTab.database" :table-meta="activeDataTabTableMeta" diff --git a/apps/desktop/src/components/objects/ObjectBrowser.vue b/apps/desktop/src/components/objects/ObjectBrowser.vue index 8827db0512..c5b6165d6c 100644 --- a/apps/desktop/src/components/objects/ObjectBrowser.vue +++ b/apps/desktop/src/components/objects/ObjectBrowser.vue @@ -51,6 +51,7 @@ import { supportsTableStructureEditing, supportsTableTruncate, } from "@/lib/databaseFeatureSupport"; +import { connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection } from "@/lib/jdbcDialect"; import { buildTableSelectSql } from "@/lib/tableSelectSql"; import { buildDropObjectSql, @@ -128,6 +129,9 @@ const sourceContent = ref(""); const sourceError = ref(""); const sourceRow = ref(null); const sourceEditing = ref(false); +const effectiveDatabaseType = computed( + () => effectiveDatabaseTypeForConnection(props.connection) ?? props.connection.db_type, +); const sourceDraft = ref(""); const sourceSaving = ref(false); const sourceSaveError = ref(""); @@ -159,7 +163,9 @@ let loadId = 0; // Export via background tracker const { addTask: addExportTask } = useExportTracker(); -const needsSchema = computed(() => isSchemaAware(props.connection.db_type)); +const needsSchema = computed( + () => isSchemaAware(props.connection.db_type) && !connectionUsesDatabaseObjectTreeMode(props.connection), +); const tableCount = computed(() => rows.value.filter((row) => row.type === "TABLE").length); const viewCount = computed(() => rows.value.filter((row) => row.type === "VIEW").length); const procedureCount = computed(() => rows.value.filter((row) => row.type === "PROCEDURE").length); @@ -167,27 +173,27 @@ const functionCount = computed(() => rows.value.filter((row) => row.type === "FU const packageCount = computed( () => rows.value.filter((row) => row.type === "PACKAGE" || row.type === "PACKAGE_BODY").length, ); -const canOpenStructureEditor = computed(() => supportsTableStructureEditing(props.connection.db_type)); -const canOpenDiagram = computed(() => !!props.database && supportsSchemaDiagram(props.connection.db_type)); -const canOpenTableImport = computed(() => !!props.database && supportsTableImport(props.connection.db_type)); -const supportsTruncateTable = computed(() => supportsTableTruncate(props.connection.db_type)); +const canOpenStructureEditor = computed(() => supportsTableStructureEditing(effectiveDatabaseType.value)); +const canOpenDiagram = computed(() => !!props.database && supportsSchemaDiagram(effectiveDatabaseType.value)); +const canOpenTableImport = computed(() => !!props.database && supportsTableImport(effectiveDatabaseType.value)); +const supportsTruncateTable = computed(() => supportsTableTruncate(effectiveDatabaseType.value)); const sourceDialect = computed<"mysql" | "postgres" | "sqlserver">(() => { if ( - props.connection.db_type === "postgres" || - props.connection.db_type === "gaussdb" || - props.connection.db_type === "opengauss" + effectiveDatabaseType.value === "postgres" || + effectiveDatabaseType.value === "gaussdb" || + effectiveDatabaseType.value === "opengauss" ) return "postgres"; - if (props.connection.db_type === "sqlserver") return "sqlserver"; + if (effectiveDatabaseType.value === "sqlserver") return "sqlserver"; return "mysql"; }); const sourceFormatDialect = computed(() => { - switch (props.connection.db_type) { + switch (effectiveDatabaseType.value) { case "mysql": case "postgres": case "sqlite": case "sqlserver": - return props.connection.db_type; + return effectiveDatabaseType.value; case "gaussdb": case "opengauss": return "postgres"; @@ -348,8 +354,8 @@ function canOpenSource(row: ObjectBrowserRow) { function canRename(row: ObjectBrowserRow) { return ( - supportsObjectRename(props.connection.db_type, row.type) || - supportsSourceBackedRoutineRename(props.connection.db_type, row.type as ObjectSourceKind) + supportsObjectRename(effectiveDatabaseType.value, row.type) || + supportsSourceBackedRoutineRename(effectiveDatabaseType.value, row.type as ObjectSourceKind) ); } @@ -414,7 +420,7 @@ async function openViewDdl(row: ObjectBrowserRow) { "VIEW", ); const ddl = await buildViewDdl({ - databaseType: props.connection.db_type, + databaseType: effectiveDatabaseType.value, schema: row.schema || selectedSchema.value || props.database, name: row.name, source: result.source, @@ -431,7 +437,7 @@ async function openNewQuery(row: ObjectBrowserRow) { queryStore.updateSql( tabId, await buildTableSelectSql({ - databaseType: props.connection.db_type, + databaseType: effectiveDatabaseType.value, schema: row.schema || selectedSchema.value, tableName: row.name, limit: 100, @@ -485,13 +491,13 @@ async function refreshRenamePreviewSql() { renamePreviewSqlText.value = ""; return; } - if (supportsSourceBackedRoutineRename(props.connection.db_type, row.type as ObjectSourceKind)) { + if (supportsSourceBackedRoutineRename(effectiveDatabaseType.value, row.type as ObjectSourceKind)) { renamePreviewSqlText.value = `-- Recreate ${row.type} from source, then drop the original object.`; return; } try { const sql = await buildRenameObjectSql({ - databaseType: props.connection.db_type, + databaseType: effectiveDatabaseType.value, objectType: row.type, schema: row.schema || selectedSchema.value, oldName: row.name, @@ -514,7 +520,7 @@ async function confirmRename() { renameError.value = ""; try { const schema = row.schema || selectedSchema.value || props.database; - if (supportsSourceBackedRoutineRename(props.connection.db_type, row.type as ObjectSourceKind)) { + if (supportsSourceBackedRoutineRename(effectiveDatabaseType.value, row.type as ObjectSourceKind)) { const source = await api.getObjectSource( props.connection.id, props.database, @@ -523,7 +529,7 @@ async function confirmRename() { row.type as ObjectSourceKind, ); const statements = await buildRoutineRenameObjectSourceStatements({ - databaseType: props.connection.db_type, + databaseType: effectiveDatabaseType.value, objectType: row.type as ObjectSourceKind, schema, name: row.name, @@ -535,7 +541,7 @@ async function confirmRename() { } } else { const sql = await buildRenameObjectSql({ - databaseType: props.connection.db_type, + databaseType: effectiveDatabaseType.value, objectType: row.type, schema, oldName: row.name, @@ -562,7 +568,7 @@ async function confirmDrop() { const row = dropTarget.value; try { const sql = await buildDropObjectSql({ - databaseType: props.connection.db_type, + databaseType: effectiveDatabaseType.value, objectType: row.type, schema: row.schema || selectedSchema.value, name: row.name, @@ -727,7 +733,7 @@ async function refreshBatchDropPreviewSql() { const statements: string[] = []; for (const row of selectedTableRows.value) { const sql = await buildDropObjectSql({ - databaseType: props.connection.db_type, + databaseType: effectiveDatabaseType.value, objectType: "TABLE", schema: row.schema || selectedSchema.value, name: row.name, @@ -750,7 +756,7 @@ async function confirmBatchDropTables() { try { for (const row of targets) { const sql = await buildDropObjectSql({ - databaseType: props.connection.db_type, + databaseType: effectiveDatabaseType.value, objectType: "TABLE", schema: row.schema || selectedSchema.value, name: row.name, @@ -786,7 +792,7 @@ async function exportDataLegacy(row: ObjectBrowserRow, format: "json" | "sql") { ) : undefined; const result = await fetchTableDataForExport({ - databaseType: props.connection.db_type, + databaseType: effectiveDatabaseType.value, schema, tableName: row.name, columns: queryColumns, @@ -810,7 +816,7 @@ async function exportDataLegacy(row: ObjectBrowserRow, format: "json" | "sql") { } const content = await formatSqlInsert({ - databaseType: props.connection.db_type, + databaseType: effectiveDatabaseType.value, schema, tableName: row.name, columns: result.columns, @@ -916,7 +922,7 @@ async function confirmDuplicateStructure() { try { const schema = row.schema || selectedSchema.value; const sql = await buildDuplicateTableStructureSql({ - databaseType: props.connection.db_type, + databaseType: effectiveDatabaseType.value, schema, sourceName: row.name, targetName: newName, @@ -932,7 +938,7 @@ async function confirmDuplicateStructure() { function tableAdminSqlOptions(row: ObjectBrowserRow): TableAdminSqlOptions { return { - databaseType: props.connection.db_type, + databaseType: effectiveDatabaseType.value, schema: row.schema || selectedSchema.value, tableName: row.name, }; @@ -1026,14 +1032,14 @@ async function saveSource() { sourceSaveError.value = ""; try { const statements = await buildExecutableObjectSourceStatements({ - databaseType: props.connection.db_type, + databaseType: effectiveDatabaseType.value, objectType: row.type as ObjectSourceKind, schema, name: row.name, source: sourceDraft.value, }); for (const sql of statements) { - if (objectSourceSaveExecutionMode(props.connection.db_type) === "single") { + if (objectSourceSaveExecutionMode(effectiveDatabaseType.value) === "single") { await api.executeQuery(props.connection.id, props.database, sql, schema); } else { await api.executeScript(props.connection.id, props.database, sql, schema); diff --git a/apps/desktop/src/components/sidebar/ConnectionTree.vue b/apps/desktop/src/components/sidebar/ConnectionTree.vue index d5f7ecc74f..abd3b5922b 100644 --- a/apps/desktop/src/components/sidebar/ConnectionTree.vue +++ b/apps/desktop/src/components/sidebar/ConnectionTree.vue @@ -9,6 +9,7 @@ import type { QueryTab, TreeNode, TreeNodeType } from "@/types/database"; import { filterSidebarSearchRootsByConnectionState, filterSidebarTree } from "@/lib/sidebarSearchTree"; import { isCancelSearchShortcut } from "@/lib/keyboardShortcuts"; import { usesTreeSchemaMode } from "@/lib/databaseFeatureSupport"; +import { connectionUsesDatabaseObjectTreeMode } from "@/lib/jdbcDialect"; import { findSidebarNodeForActiveTab, findNodePathForActiveTab, @@ -277,7 +278,7 @@ async function ensureTreeLoadedForTab(tab: QueryTab) { try { if (config.db_type === "sqlserver") { await store.loadSqlServerDatabaseObjects(connId, tab.database); - } else if (usesTreeSchemaMode(config.db_type)) { + } else if (usesTreeSchemaMode(config.db_type) && !connectionUsesDatabaseObjectTreeMode(config)) { await store.loadSchemas(connId, tab.database); // If we have a schema, also load tables under that schema if (tab.schema) { diff --git a/apps/desktop/src/components/sidebar/TreeItem.vue b/apps/desktop/src/components/sidebar/TreeItem.vue index 05d7725ca9..a4ef7568e1 100644 --- a/apps/desktop/src/components/sidebar/TreeItem.vue +++ b/apps/desktop/src/components/sidebar/TreeItem.vue @@ -116,6 +116,11 @@ import { buildRenameObjectSql, supportsObjectRename, type RenameableObjectType } import { buildRoutineRenameObjectSourceStatements, supportsSourceBackedRoutineRename } from "@/lib/objectSourceEditor"; import { buildViewDdl } from "@/lib/viewDdl"; import { getTableStructureCapabilities } from "@/lib/tableStructureCapabilities"; +import { + connectionObjectTreeQuerySchema, + connectionUsesDatabaseObjectTreeMode, + effectiveDatabaseTypeForConnection, +} from "@/lib/jdbcDialect"; import { hexToRgba } from "@/lib/color"; import { focusSidebarRenameInput } from "@/lib/sidebarRenameFocus"; import { hasTreeNodeDatabaseContext } from "@/lib/treeNodeContext"; @@ -187,9 +192,21 @@ const labelWidthClass = computed(() => ); function currentDatabaseType(): DatabaseType | undefined { + return props.node.connectionId + ? effectiveDatabaseTypeForConnection(connectionStore.getConfig(props.node.connectionId)) + : undefined; +} + +function rawDatabaseType(): DatabaseType | undefined { return props.node.connectionId ? connectionStore.getConfig(props.node.connectionId)?.db_type : undefined; } +function databaseTypeForNode(node: TreeNode): DatabaseType | undefined { + return node.connectionId + ? effectiveDatabaseTypeForConnection(connectionStore.getConfig(node.connectionId)) + : undefined; +} + function hasNodeDatabaseContext(node: TreeNode): node is TreeNode & { connectionId: string; database: string } { return !!node.connectionId && hasTreeNodeDatabaseContext(node); } @@ -370,7 +387,7 @@ async function toggle() { const config = connectionStore.getConfig(node.connectionId); if (config?.db_type === "sqlserver") { await connectionStore.loadSqlServerDatabaseObjects(node.connectionId, node.database); - } else if (usesTreeSchemaMode(config?.db_type)) { + } else if (usesTreeSchemaMode(config?.db_type) && !connectionUsesDatabaseObjectTreeMode(config)) { await connectionStore.loadSchemas(node.connectionId, node.database); } else { await connectionStore.loadTables(node.connectionId, node.database); @@ -723,6 +740,7 @@ async function openData() { table: node.label, dbType: config?.db_type, }); + const tableSchema = connectionUsesDatabaseObjectTreeMode(config) ? undefined : node.schema; const tabId = (() => { if (settingsStore.editorSettings.reuseDataTab) { const existing = queryStore.tabs.find( @@ -730,12 +748,12 @@ async function openData() { ); if (existing) { existing.title = node.label; - existing.schema = node.schema; + existing.schema = tableSchema; queryStore.activeTabId = existing.id; return existing.id; } } - return queryStore.createTab(node.connectionId, node.database, node.label, "data", node.schema); + return queryStore.createTab(node.connectionId, node.database, node.label, "data", tableSchema); })(); console.info("[DBX][openData:tab-created]", { traceId, tabId, elapsed: elapsed() }); queryStore.setExecuting(tabId, true); @@ -746,11 +764,12 @@ async function openData() { console.info("[DBX][openData:ensure-connected:done]", { traceId, elapsed: elapsed() }); if (!config) throw new Error("Connection config not found"); - const querySchema = node.schema || node.database; + const querySchema = connectionObjectTreeQuerySchema(config, node.database, tableSchema); + const effectiveDbType = effectiveDatabaseTypeForConnection(config); const limit = settingsStore.editorSettings.pageSize; const sql = await buildTableSelectSql({ - databaseType: config.db_type, - schema: node.schema, + databaseType: effectiveDbType, + schema: tableSchema, tableName: node.label, columns: [], primaryKeys: [], @@ -782,9 +801,9 @@ async function openData() { primaryKeys: columns.filter((column) => column.is_primary_key).map((column) => column.name), elapsed: elapsed(), }); - const pks = editablePrimaryKeys(config.db_type, columns); + const pks = editablePrimaryKeys(effectiveDbType, columns); queryStore.setTableMeta(tabId, { - schema: node.schema, + schema: tableSchema, tableName: node.label, columns, primaryKeys: pks, @@ -986,7 +1005,7 @@ function dropObjectSqlOptions(): DropObjectSqlOptions | null { function dropObjectSqlOptionsForNode(node: TreeNode): DropObjectSqlOptions | null { if (node.type !== "view" && node.type !== "procedure" && node.type !== "function") return null; return { - databaseType: node.connectionId ? connectionStore.getConfig(node.connectionId)?.db_type : undefined, + databaseType: databaseTypeForNode(node), objectType: node.type === "view" ? "VIEW" : node.type === "procedure" ? "PROCEDURE" : "FUNCTION", schema: node.schema, name: node.label, @@ -1022,7 +1041,7 @@ function dropTableChildObjectSqlOptionsForNode(node: TreeNode): DropTableChildOb const name = tableChildDropObjectName(node).trim(); if (!name) return null; return { - databaseType: node.connectionId ? connectionStore.getConfig(node.connectionId)?.db_type : undefined, + databaseType: databaseTypeForNode(node), objectType, schema: node.schema, tableName: node.tableName, @@ -1138,7 +1157,7 @@ function viewObjectDdl() { .then(async (result) => { const connection = connectionStore.getConfig(node.connectionId!); const ddl = await buildViewDdl({ - databaseType: connection?.db_type, + databaseType: effectiveDatabaseTypeForConnection(connection), schema, name: node.label, source: result.source, @@ -1221,7 +1240,7 @@ function batchDropConfirmMessage(): string { async function dropSqlForTreeNode(node: TreeNode): Promise { if (node.type === "table" && node.connectionId && node.database) { return buildDropTableSql({ - databaseType: node.connectionId ? connectionStore.getConfig(node.connectionId)?.db_type : undefined, + databaseType: databaseTypeForNode(node), schema: node.schema, tableName: node.label, }); @@ -1454,7 +1473,7 @@ const canCreateTable = computed(() => { return ( (props.node.type === "database" || props.node.type === "schema" || props.node.type === "group-tables") && !!props.node.database && - supportsTableStructureEditing(config?.db_type) + supportsTableStructureEditing(effectiveDatabaseTypeForConnection(config)) ); }); @@ -1482,12 +1501,18 @@ const canDropDatabase = computed(() => { const canCreateSchema = computed(() => { const config = props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined; - return props.node.type === "database" && usesTreeSchemaMode(config?.db_type); + return ( + props.node.type === "database" && + usesTreeSchemaMode(config?.db_type) && + !connectionUsesDatabaseObjectTreeMode(config) + ); }); const canDropSchema = computed(() => { const config = props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined; - return props.node.type === "schema" && usesTreeSchemaMode(config?.db_type); + return ( + props.node.type === "schema" && usesTreeSchemaMode(config?.db_type) && !connectionUsesDatabaseObjectTreeMode(config) + ); }); function tableAdminSqlOptions(): TableAdminSqlOptions { @@ -1793,7 +1818,7 @@ async function confirmDuplicateStructure() { showDuplicateDialog.value = false; try { await connectionStore.ensureConnected(node.connectionId); - const databaseType = connectionStore.getConfig(node.connectionId)?.db_type; + const databaseType = databaseTypeForNode(node); const sql = await buildDuplicateTableStructureSql({ databaseType, schema: node.schema, @@ -2065,8 +2090,9 @@ async function exportDataLegacy(format: "csv" | "json" | "sql") { (column) => column.name, ) : undefined; + const effectiveDbType = effectiveDatabaseTypeForConnection(config); const result = await fetchTableDataForExport({ - databaseType: config.db_type, + databaseType: effectiveDbType, schema: node.schema, tableName: node.label, columns: queryColumns, @@ -2106,7 +2132,7 @@ async function exportDataLegacy(format: "csv" | "json" | "sql") { } const content = await formatSqlInsert({ - databaseType: config.db_type, + databaseType: effectiveDbType, schema: node.schema, tableName: node.label, columns: result.columns, @@ -2335,36 +2361,31 @@ const canExpand = computed(() => childCount: props.node.children?.length ?? 0, }), ); -const nodeConfig = computed(() => - props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined, -); const canPin = computed(() => pinnableTypes.has(props.node.type)); const canOpenSqlFileExecution = computed(() => { - return supportsSqlFileExecution(nodeConfig.value?.db_type); + return supportsSqlFileExecution(rawDatabaseType()); }); const canOpenDiagram = computed(() => { - return !!props.node.database && supportsSchemaDiagram(nodeConfig.value?.db_type); + return !!props.node.database && supportsSchemaDiagram(currentDatabaseType()); }); const canOpenDatabaseSearch = computed(() => { - return !!props.node.database && supportsDatabaseSearch(nodeConfig.value?.db_type); + return !!props.node.database && supportsDatabaseSearch(currentDatabaseType()); }); const canOpenObjectBrowser = computed(() => { - return supportsObjectBrowserTreeNode(nodeConfig.value?.db_type, props.node.type); + return supportsObjectBrowserTreeNode(rawDatabaseType(), props.node.type); }); const canOpenTableImport = computed(() => { - return props.node.type === "table" && !!props.node.database && supportsTableImport(nodeConfig.value?.db_type); + return props.node.type === "table" && !!props.node.database && supportsTableImport(currentDatabaseType()); }); const canOpenStructureEditor = computed(() => { - return ( - props.node.type === "table" && !!props.node.database && supportsTableStructureEditing(nodeConfig.value?.db_type) - ); + return props.node.type === "table" && !!props.node.database && supportsTableStructureEditing(currentDatabaseType()); }); const canOpenFieldLineage = computed(() => { return ( props.node.type === "column" && !!props.node.database && !!props.node.tableName && - supportsFieldLineage(nodeConfig.value?.db_type) + supportsFieldLineage(currentDatabaseType()) ); }); const isPinned = computed(() => props.node.pinned || connectionStore.isTreeNodePinned(props.node.id)); diff --git a/apps/desktop/src/components/structure/TableStructureEditor.vue b/apps/desktop/src/components/structure/TableStructureEditor.vue index 415588eb28..d75dd5343c 100644 --- a/apps/desktop/src/components/structure/TableStructureEditor.vue +++ b/apps/desktop/src/components/structure/TableStructureEditor.vue @@ -35,6 +35,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip import { SearchableSelect } from "@/components/ui/searchable-select"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { useConnectionStore } from "@/stores/connectionStore"; +import { useQueryStore } from "@/stores/queryStore"; import { useHistoryStore } from "@/stores/historyStore"; import { useSettingsStore, type StructureEditorDensity } from "@/stores/settingsStore"; import { useTheme } from "@/composables/useTheme"; @@ -44,6 +45,7 @@ import { copyToClipboard } from "@/lib/clipboard"; import { queryTimeoutSecsForConnection } from "@/lib/queryTimeout"; import { type EditableStructureColumn, type EditableStructureIndex } from "@/lib/tableStructureEditorSql"; import { getTableStructureCapabilities } from "@/lib/tableStructureCapabilities"; +import { connectionObjectTreeQuerySchema, effectiveDatabaseTypeForConnection } from "@/lib/jdbcDialect"; import { buildStructureTargetLabel, combineDataTypeForDatabase, @@ -60,6 +62,7 @@ import * as api from "@/lib/api"; const { t } = useI18n(); const { isDark } = useTheme(); const store = useConnectionStore(); +const queryStore = useQueryStore(); const historyStore = useHistoryStore(); const settingsStore = useSettingsStore(); const { toast } = useToast(); @@ -283,7 +286,7 @@ function onIndexColResize(e: MouseEvent, col: number) { } const connection = computed(() => (props.connectionId ? store.getConfig(props.connectionId) : undefined)); -const databaseType = computed(() => connection.value?.db_type); +const databaseType = computed(() => effectiveDatabaseTypeForConnection(connection.value)); const structureCapabilities = computed(() => getTableStructureCapabilities(databaseType.value)); const structureDialect = computed(() => structureCapabilities.value.dialect); const isTableCommentDisabled = computed(() => !structureCapabilities.value.comment); @@ -355,7 +358,12 @@ const indexColLabels = computed(() => [ t("structureEditor.comment"), t("structureEditor.actions"), ]); -const targetSchema = computed(() => props.schema || props.database || ""); +const metadataSchema = computed(() => connectionObjectTreeQuerySchema(connection.value, props.database, props.schema)); +const refreshVersion = computed(() => + props.connectionId && props.tableName + ? queryStore.tableStructureRefreshVersion(props.connectionId, props.database, props.schema, props.tableName) + : 0, +); const isCreateMode = computed(() => !props.tableName); const newTableName = ref(""); const tableComment = ref(""); @@ -434,18 +442,18 @@ async function loadStructure(silent = false) { errorMessage.value = ""; try { await store.ensureConnected(props.connectionId); - const nextColumns = await api.getColumns(props.connectionId, props.database, targetSchema.value, props.tableName); + const nextColumns = await api.getColumns(props.connectionId, props.database, metadataSchema.value, props.tableName); const [nextIndexes, nextForeignKeys, nextTriggers] = await Promise.all([ - api.listIndexes(props.connectionId, props.database, targetSchema.value, props.tableName).catch(() => []), - api.listForeignKeys(props.connectionId, props.database, targetSchema.value, props.tableName).catch(() => []), - api.listTriggers(props.connectionId, props.database, targetSchema.value, props.tableName).catch(() => []), + api.listIndexes(props.connectionId, props.database, metadataSchema.value, props.tableName).catch(() => []), + api.listForeignKeys(props.connectionId, props.database, metadataSchema.value, props.tableName).catch(() => []), + api.listTriggers(props.connectionId, props.database, metadataSchema.value, props.tableName).catch(() => []), ]); columns.value = createColumnDrafts(nextColumns, databaseType.value); indexes.value = createIndexDrafts(nextIndexes); foreignKeys.value = nextForeignKeys; triggers.value = nextTriggers; try { - const tables = await api.listTables(props.connectionId, props.database, targetSchema.value); + const tables = await api.listTables(props.connectionId, props.database, metadataSchema.value); const table = tables.find( (t) => t.name.toLowerCase() === props.tableName!.toLowerCase() && t.table_type !== "VIEW", ); @@ -746,7 +754,10 @@ onMounted(() => { void loadStructure(); }); -onActivated(registerStructureEditorShortcuts); +onActivated(() => { + registerStructureEditorShortcuts(); + if (!isCreateMode.value) void loadStructure(true); +}); onDeactivated(unregisterStructureEditorShortcuts); onBeforeUnmount(() => { unregisterStructureEditorShortcuts(); @@ -759,6 +770,11 @@ watch( }, { deep: true, immediate: true }, ); + +watch(refreshVersion, (version, previous) => { + if (version === previous || !version || isCreateMode.value) return; + void loadStructure(true); +});