From 1533064f1fbdf21cd17f275f7c51ee2cb66411ac Mon Sep 17 00:00:00 2001 From: New2Niu Date: Mon, 14 Sep 2026 14:52:13 +0800 Subject: [PATCH] fix(gbase8s): omit owner qualifier in table admin SQL --- .../src/components/objects/ObjectBrowser.vue | 4 ++-- .../sidebar/SidebarTreeRuntimeHost.vue | 6 ++++-- ...eSidebarTableMutationRuntime.saphana.spec.ts | 17 +++++++++++++---- .../useSidebarTableMutationRuntime.ts | 7 +++++-- .../lib/__tests__/database/jdbcDialect.spec.ts | 7 +++++++ apps/desktop/src/lib/database/jdbcDialect.ts | 6 ++++++ 6 files changed, 37 insertions(+), 10 deletions(-) diff --git a/apps/desktop/src/components/objects/ObjectBrowser.vue b/apps/desktop/src/components/objects/ObjectBrowser.vue index e848819d70..de57e3f988 100644 --- a/apps/desktop/src/components/objects/ObjectBrowser.vue +++ b/apps/desktop/src/components/objects/ObjectBrowser.vue @@ -72,7 +72,7 @@ import type { ColumnInfo, ConnectionConfig, ConstraintInfo, ForeignKeyInfo, Inde import { sortTablesByFkDependency, type TableWithFk } from "@/lib/table/tableDependencySort"; import { isSchemaAware, supportsTableVacuum, supportsTransfer } from "@/lib/database/databaseCapabilities"; import { supportsAiAssistantContext, supportsSchemaDiagram, supportsTableImport, supportsTableStructureEditing, supportsTableTruncate } from "@/lib/database/databaseFeatureSupport"; -import { codeMirrorSqlDialect, connectionObjectTreeNodeSchema, connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection, objectListSchemaForConnection, tableStructureDatabaseTypeForConnection } from "@/lib/database/jdbcDialect"; +import { codeMirrorSqlDialect, connectionObjectTreeNodeSchema, connectionTableSqlSchema, connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection, objectListSchemaForConnection, tableStructureDatabaseTypeForConnection } from "@/lib/database/jdbcDialect"; import { getTableMetadataCapabilities, type TableMetadataCapabilities } from "@/lib/table/tableMetadataCapabilities"; import { constraintsForConstraintsTab } from "@/lib/table/constraintPresentation"; import { buildTableSelectSql } from "@/lib/table/tableSelectSql"; @@ -2531,7 +2531,7 @@ async function confirmPasteTable() { function tableAdminSqlOptions(row: ObjectBrowserRow, options?: { cascade?: boolean }): TableAdminSqlOptions { const result: TableAdminSqlOptions = { databaseType: effectiveDatabaseType.value, - schema: row.schema || selectedSchema.value, + schema: connectionTableSqlSchema(props.connection, row.schema || selectedSchema.value), tableName: row.name, // Cloud Spanner's dialect decides the quote; the static per-type mapping cannot. identifierQuote: connectionStore.connectionIdentifierQuote?.(props.connection.id), diff --git a/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue b/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue index 81c0973356..86d8a50fa2 100644 --- a/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue +++ b/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue @@ -162,7 +162,7 @@ import { buildViewDdl } from "@/lib/table/viewDdl"; import { formatSqlForDisplay, sqlFormatDialectForDbType } from "@/lib/sql/sqlFormatter"; import { omitDdlIdentifierQuotes } from "@/lib/sql/ddlDisplay"; import { getTableStructureCapabilities } from "@/lib/table/tableStructureCapabilities"; -import { connectionObjectTreeNodeSchema, connectionObjectTreeQuerySchema, connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection, tableStructureDatabaseTypeForConnection } from "@/lib/database/jdbcDialect"; +import { connectionObjectTreeNodeSchema, connectionObjectTreeQuerySchema, connectionTableSqlSchema, connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection, tableStructureDatabaseTypeForConnection } from "@/lib/database/jdbcDialect"; import { hasTreeNodeDatabaseContext } from "@/lib/sidebar/treeNodeContext"; import { defaultPasteTableMode, @@ -2798,11 +2798,13 @@ function batchTruncateConfirmMessage(): string { async function dropSqlForTreeNode(node: TreeNode, options?: { cascade?: boolean }): Promise { if (node.type === "table" && node.connectionId && node.database) { + const config = connectionStore.getConfig(node.connectionId); return buildDropTableSql({ databaseType: databaseTypeForNode(node), - schema: node.schema, + schema: connectionTableSqlSchema(config, node.schema), tableName: node.label, cascade: options?.cascade && supportsDropTableCascade(databaseTypeForNode(node)), + identifierQuote: connectionStore.connectionIdentifierQuote?.(node.connectionId), }); } const objectOptions = dropObjectSqlOptionsForNode(node); diff --git a/apps/desktop/src/composables/__tests__/useSidebarTableMutationRuntime.saphana.spec.ts b/apps/desktop/src/composables/__tests__/useSidebarTableMutationRuntime.saphana.spec.ts index 22cb947110..bbc7a67840 100644 --- a/apps/desktop/src/composables/__tests__/useSidebarTableMutationRuntime.saphana.spec.ts +++ b/apps/desktop/src/composables/__tests__/useSidebarTableMutationRuntime.saphana.spec.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { shallowRef } from "vue"; -import type { TreeNode } from "@/types/database"; +import type { ConnectionConfig, DatabaseType, TreeNode } from "@/types/database"; import { dropTableCascade, dropTablePreviewSql, emptyTablePreviewSql, sidebarDangerRunningCancel, sidebarDangerRunningExecutionId, sidebarDangerTarget, truncateTableCascade, truncateTablePreviewSql } from "@/components/sidebar/sidebarTreeDialogState"; const mocks = vi.hoisted(() => ({ @@ -49,19 +49,20 @@ function tableNode(database: string | null | undefined): TreeNode { } as TreeNode; } -function runtime(database: string | null | undefined) { +function runtime(database: string | null | undefined, config: Pick & Partial> = { db_type: "saphana" }, databaseType: DatabaseType = "saphana") { const node = tableNode(database); const activeNode = shallowRef(node); const connectionStore = { ensureConnected: mocks.ensureConnected, + getConfig: () => config, removeTreeNode: mocks.removeTreeNode, } as any; const feature = useSidebarTableMutationRuntime({ activeNode, releaseActiveNodeReference: mocks.releaseActiveNodeReference, connectionStore, - currentDatabaseType: () => "saphana", - databaseTypeForNode: () => "saphana", + currentDatabaseType: () => databaseType, + databaseTypeForNode: () => databaseType, executeWithProductionGuard: mocks.executeWithProductionGuard, closeDroppedTableObjectTabsForNode: mocks.closeDroppedTableObjectTabsForNode, refreshMutatedTableDataTabsForNode: mocks.refreshMutatedTableDataTabsForNode, @@ -121,6 +122,14 @@ describe("useSidebarTableMutationRuntime SAP HANA schema-scoped actions", () => expect(mocks.releaseActiveNodeReference).toHaveBeenCalledWith([node.id]); }); + it("omits the GBase 8s metadata owner from generated table SQL", async () => { + const { feature } = runtime("tenant", { db_type: "gbase", driver_profile: "gbase8s" }, "informix"); + + await feature.confirmDropTable(); + + expect(mocks.buildDropTableSql).toHaveBeenCalledWith(expect.objectContaining({ databaseType: "informix", schema: undefined, tableName: "ORDERS" })); + }); + it.each(["confirmEmptyTable", "confirmTruncateTable"] as const)("refreshes data tabs after %s", async (action) => { const { feature, node } = runtime(""); diff --git a/apps/desktop/src/composables/useSidebarTableMutationRuntime.ts b/apps/desktop/src/composables/useSidebarTableMutationRuntime.ts index 33902d10f2..1b56d81a75 100644 --- a/apps/desktop/src/composables/useSidebarTableMutationRuntime.ts +++ b/apps/desktop/src/composables/useSidebarTableMutationRuntime.ts @@ -19,6 +19,7 @@ import { type VacuumTableSqlOptions, } from "@/lib/database/dbAdminSql"; import { isSqlServerLinkedNode } from "@/lib/database/sqlServerLinkedServers"; +import { connectionTableSqlSchema } from "@/lib/database/jdbcDialect"; import { isQueryTimeoutErrorMessage } from "@/lib/sql/queryError"; import { uuid } from "@/lib/common/utils"; import * as api from "@/lib/backend/api"; @@ -74,9 +75,10 @@ export function useSidebarTableMutationRuntime(options: SidebarTableMutationRunt const supportsMysqlAutoIncrement = computed(() => activeNode.value.type === "table" && supportsNativeMysqlAutoIncrement(activeNode.value.connectionId ? connectionStore.getConfig(activeNode.value.connectionId) : undefined)); function tableAdminSqlOptions(optionsOverride?: { cascade?: boolean }): TableAdminSqlOptions { + const config = activeNode.value.connectionId ? connectionStore.getConfig(activeNode.value.connectionId) : undefined; const result: TableAdminSqlOptions = { databaseType: currentDatabaseType(), - schema: activeNode.value.schema, + schema: connectionTableSqlSchema(config, activeNode.value.schema), tableName: activeNode.value.label, // Cloud Spanner's two dialects quote differently, so admin SQL needs the quote the connected // agent reported rather than the static per-type mapping. @@ -87,9 +89,10 @@ export function useSidebarTableMutationRuntime(options: SidebarTableMutationRunt } function tableAdminSqlOptionsForNode(node: TreeNode, optionsOverride?: { cascade?: boolean }): TableAdminSqlOptions { + const config = node.connectionId ? connectionStore.getConfig(node.connectionId) : undefined; const result: TableAdminSqlOptions = { databaseType: databaseTypeForNode(node), - schema: node.schema, + schema: connectionTableSqlSchema(config, node.schema), tableName: node.label, identifierQuote: connectionStore.connectionIdentifierQuote?.(node.connectionId), }; diff --git a/apps/desktop/src/lib/__tests__/database/jdbcDialect.spec.ts b/apps/desktop/src/lib/__tests__/database/jdbcDialect.spec.ts index 3359eeeeb0..435a3720fa 100644 --- a/apps/desktop/src/lib/__tests__/database/jdbcDialect.spec.ts +++ b/apps/desktop/src/lib/__tests__/database/jdbcDialect.spec.ts @@ -9,6 +9,7 @@ import { connectionQueryExecutionSchema, connectionShouldDiscoverJdbcSchemas, connectionShouldLoadIdentifierQuote, + connectionTableSqlSchema, connectionUsesConnectionRootSchemaMode, connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection, @@ -176,6 +177,12 @@ describe("jdbc dialect inference", () => { expect(connectionShouldDiscoverJdbcSchemas({ db_type: "gbase", driver_profile: "gbase8a" })).toBe(false); }); + it("omits the metadata owner from GBase 8s table SQL", () => { + expect(connectionTableSqlSchema({ db_type: "gbase", driver_profile: "gbase8s" }, "gbasedbt")).toBeUndefined(); + expect(connectionTableSqlSchema({ db_type: "gbase", driver_profile: "gbase8a" }, "analytics")).toBe("analytics"); + expect(connectionTableSqlSchema({ db_type: "informix", driver_profile: "informix" }, "informix")).toBe("informix"); + }); + it("recognizes GaussDB reached through PostgreSQL-compatible JDBC drivers", () => { const connection = { db_type: "jdbc" as const, diff --git a/apps/desktop/src/lib/database/jdbcDialect.ts b/apps/desktop/src/lib/database/jdbcDialect.ts index a2fe509a6f..54924d6b23 100644 --- a/apps/desktop/src/lib/database/jdbcDialect.ts +++ b/apps/desktop/src/lib/database/jdbcDialect.ts @@ -312,6 +312,12 @@ export function connectionObjectTreeNodeSchema(connection: JdbcDialectConnection return isSchemaAware(type) ? schema || database : undefined; } +/** GBase 8s reports the table owner as a schema, but does not accept it in table DML/DDL names. */ +export function connectionTableSqlSchema(connection: JdbcDialectConnection | undefined, schema?: string): string | undefined { + if (connection?.db_type === "gbase" && isGbase8sProfile(connection.driver_profile)) return undefined; + return schema; +} + /** Maps a database type to the corresponding CodeMirror SQL dialect name used by QueryEditor and DdlViewDialog. */ export function codeMirrorSqlDialect(dbType: DatabaseType | undefined): "mysql" | "postgres" | "sqlserver" { if (dbType === "postgres" || dbType === "gaussdb" || dbType === "kwdb" || dbType === "opengauss") return "postgres";