From a6b26ff1a6597f291557060d81043595eec73009 Mon Sep 17 00:00:00 2001 From: ordinary being Date: Mon, 12 Oct 2026 14:39:38 +0800 Subject: [PATCH] fix(objects): open database schema overview rows under clicked schema --- .../components/objects/DatabaseBrowser.vue | 7 +++-- .../database/objectBrowserTarget.spec.ts | 30 +++++++++++++++++++ apps/desktop/src/lib/database/jdbcDialect.ts | 2 +- .../src/lib/database/objectBrowserTarget.ts | 19 ++++++++++++ 4 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 apps/desktop/src/lib/__tests__/database/objectBrowserTarget.spec.ts create mode 100644 apps/desktop/src/lib/database/objectBrowserTarget.ts diff --git a/apps/desktop/src/components/objects/DatabaseBrowser.vue b/apps/desktop/src/components/objects/DatabaseBrowser.vue index 94c1f0cb2c..d221237796 100644 --- a/apps/desktop/src/components/objects/DatabaseBrowser.vue +++ b/apps/desktop/src/components/objects/DatabaseBrowser.vue @@ -9,6 +9,7 @@ import ToolbarOverflowMenu from "@/components/ui/ToolbarOverflowMenu.vue"; import CustomContextMenu, { type ContextMenuItem } from "@/components/ui/CustomContextMenu.vue"; import { openSidebarDangerDialog } from "@/lib/sidebar/sidebarDangerDialog"; import { useDatabaseBrowserMutation } from "@/lib/database/databaseBrowserActions"; +import { objectBrowserTargetForNamespaceRow } from "@/lib/database/objectBrowserTarget"; import SidebarTreeRuntimeHost from "@/components/sidebar/SidebarTreeRuntimeHost.vue"; import SidebarTreeItemDialogs from "@/components/sidebar/SidebarTreeItemDialogs.vue"; import { useToolbarOverflow } from "@/composables/useToolbarOverflow"; @@ -127,12 +128,14 @@ const visibleRows = computed(() => { }); function openDatabase(database: string) { - const tabId = queryStore.openObjectBrowser(props.connection.id, database); + const target = objectBrowserTargetForNamespaceRow(props.connection, database); + const tabId = queryStore.openObjectBrowser(props.connection.id, target.database, target.schema); void nextTick(() => requestObjectBrowserSearchFocus(tabId)); } function databaseContextMenuItems(row: DatabaseRow): ContextMenuItem[] { - const node: TreeNode = { id: `${props.connection.id}:${row.name}`, label: row.name, type: "database", connectionId: props.connection.id, database: row.name }; + const target = objectBrowserTargetForNamespaceRow(props.connection, row.name); + const node: TreeNode = { id: `${props.connection.id}:${row.name}`, label: row.name, type: "database", connectionId: props.connection.id, database: target.database, schema: target.schema }; const runtimeItems = sidebarRuntimeHost.value?.buildContextMenu(node); if (runtimeItems?.length) return presentMenu(removeUnsupportedDatabaseItems(runtimeItems), "database"); const isDefault = props.connection.database === row.name; diff --git a/apps/desktop/src/lib/__tests__/database/objectBrowserTarget.spec.ts b/apps/desktop/src/lib/__tests__/database/objectBrowserTarget.spec.ts new file mode 100644 index 0000000000..0f23149c29 --- /dev/null +++ b/apps/desktop/src/lib/__tests__/database/objectBrowserTarget.spec.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from "vitest"; +import { objectBrowserTargetForNamespaceRow } from "@/lib/database/objectBrowserTarget"; + +describe("objectBrowserTargetForNamespaceRow", () => { + it("binds connection-root schema engines as both database and schema", () => { + for (const dbType of ["oracle", "dameng", "oceanbase-oracle"] as const) { + expect(objectBrowserTargetForNamespaceRow({ db_type: dbType }, "schema_b")).toEqual({ + database: "schema_b", + schema: "schema_b", + }); + } + }); + + it("resolves inferred JDBC dialects before deciding", () => { + expect(objectBrowserTargetForNamespaceRow({ db_type: "jdbc", jdbc_driver_class: "oracle.jdbc.OracleDriver" }, "APP")).toEqual({ + database: "APP", + schema: "APP", + }); + }); + + it("keeps database rows as databases for engines with a database level", () => { + for (const dbType of ["mysql", "postgres", "sqlserver"] as const) { + expect(objectBrowserTargetForNamespaceRow({ db_type: dbType }, "shop")).toEqual({ database: "shop" }); + } + }); + + it("treats a missing connection as a plain database row", () => { + expect(objectBrowserTargetForNamespaceRow(undefined, "shop")).toEqual({ database: "shop" }); + }); +}); diff --git a/apps/desktop/src/lib/database/jdbcDialect.ts b/apps/desktop/src/lib/database/jdbcDialect.ts index 466190cd21..71cea7a13d 100644 --- a/apps/desktop/src/lib/database/jdbcDialect.ts +++ b/apps/desktop/src/lib/database/jdbcDialect.ts @@ -2,7 +2,7 @@ import type { ConnectionConfig, DatabaseType } from "@/types/database"; import { isSchemaAware, spannerObjectTreeSchema, usesDatabaseObjectTreeMode, usesTreeSchemaMode } from "@/lib/database/databaseFeatureSupport"; import type { CodeMirrorSqlDialectName } from "@/lib/editor/codemirrorSqlDialect"; -type JdbcDialectConnection = Partial>; +export type JdbcDialectConnection = Partial>; export type GaussdbIdentifierQuoteStyle = "auto" | "double" | "backtick"; export type GaussdbConnectionMode = "native" | "m-jdbc"; diff --git a/apps/desktop/src/lib/database/objectBrowserTarget.ts b/apps/desktop/src/lib/database/objectBrowserTarget.ts new file mode 100644 index 0000000000..f7b199ad11 --- /dev/null +++ b/apps/desktop/src/lib/database/objectBrowserTarget.ts @@ -0,0 +1,19 @@ +import { connectionUsesConnectionRootSchemaMode, type JdbcDialectConnection } from "@/lib/database/jdbcDialect"; + +/** + * Object-browser target for a row of the database/schema overview list. + * + * Engines that expose schemas directly below the connection (Oracle, Dameng, + * OceanBase Oracle, and JDBC profiles inferred to them) list schemas in that + * overview, so the clicked row must bind as both the database and the schema — + * the same convention the sidebar uses when it locates a query context (see + * ConnectionTree). Binding the row as a plain database instead opens the tab + * under the connection default or the first schema, while the tab title keeps + * showing the clicked name (#11795). + * + * Engines with a real database level (MySQL, PostgreSQL, ...) keep the row name + * as the database and leave the schema to the browser's default selection. + */ +export function objectBrowserTargetForNamespaceRow(connection: JdbcDialectConnection | undefined, rowName: string): { database: string; schema?: string } { + return connectionUsesConnectionRootSchemaMode(connection) ? { database: rowName, schema: rowName } : { database: rowName }; +}