mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-20 00:17:44 +00:00
@@ -37,7 +37,7 @@ import { formatSidebarTableCopyText } from "@/lib/sidebar/sidebarTableNameCopy";
|
||||
import { pruneTreeSelectionToVisibleNodeIds } from "@/lib/sidebar/sidebarTreeSelection";
|
||||
import { isEditableSidebarTypeSearchTarget, sidebarTypeSearchNextQuery } from "@/lib/sidebar/sidebarTypeSearch";
|
||||
import { isInternalDorisCatalog, usesTreeSchemaMode } from "@/lib/database/databaseFeatureSupport";
|
||||
import { connectionObjectTreeNodeSchema, connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
|
||||
import { connectionObjectTreeNodeSchema, connectionShouldDiscoverJdbcSchemas, connectionUsesConnectionRootSchemaMode, connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
|
||||
import {
|
||||
activeTabSidebarTarget,
|
||||
findSidebarConnectionNode,
|
||||
@@ -1582,8 +1582,10 @@ async function locateTabInSidebar(tab: QueryTab | undefined | null, align: Sideb
|
||||
|
||||
const config = connId ? store.getConfig(connId) : undefined;
|
||||
const cursorCandidate = locatesSavedSql ? null : queryCursorTableCandidate(tab, effectiveDatabaseTypeForConnection(config));
|
||||
const tabTableCandidate = locatesSavedSql || cursorCandidate ? null : tableLocateCandidateFromTarget(tabTarget, config);
|
||||
const locateTableCandidate = cursorCandidate ?? tabTableCandidate;
|
||||
const fallbackTarget = locatesSavedSql ? tabTarget : (queryContextTargetFromCandidate(tab, cursorCandidate) ?? tabTarget);
|
||||
const initialTarget = cursorCandidate ? tableTargetFromCandidate(cursorCandidate) : fallbackTarget;
|
||||
const initialTarget = locateTableCandidate ? tableTargetFromCandidate(locateTableCandidate) : fallbackTarget;
|
||||
if (!initialTarget) return;
|
||||
|
||||
// Ensure the tree is loaded deep enough to contain the preferred target.
|
||||
@@ -1608,7 +1610,7 @@ async function locateTabInSidebar(tab: QueryTab | undefined | null, align: Sideb
|
||||
clearSearchScopeFilter();
|
||||
}
|
||||
|
||||
let target = resolveLoadedLocateTarget(initialTarget, cursorCandidate);
|
||||
let target = resolveLoadedLocateTarget(initialTarget, locateTableCandidate);
|
||||
let nodePath = target ? findNodePathForTarget(target, store.treeNodes) : null;
|
||||
if (!nodePath && !locatesSavedSql) {
|
||||
// The first load may have served a stale schema cache whose async refresh
|
||||
@@ -1616,13 +1618,13 @@ async function locateTabInSidebar(tab: QueryTab | undefined | null, align: Sideb
|
||||
// table isn't in the tree yet. Force a synchronous reload and retry once so
|
||||
// locate reaches the table, not just the database (issue #715).
|
||||
await ensureTreeLoadedForTarget(treeLoadTarget, { force: true });
|
||||
target = resolveLoadedLocateTarget(initialTarget, cursorCandidate);
|
||||
target = resolveLoadedLocateTarget(initialTarget, locateTableCandidate);
|
||||
nodePath = target ? findNodePathForTarget(target, store.treeNodes) : null;
|
||||
}
|
||||
|
||||
if (!nodePath && cursorCandidate) {
|
||||
await store.loadTableForLocate(cursorCandidate);
|
||||
target = resolveLoadedLocateTarget(initialTarget, cursorCandidate);
|
||||
if (!nodePath && locateTableCandidate) {
|
||||
await store.loadTableForLocate(locateTableCandidate);
|
||||
target = resolveLoadedLocateTarget(initialTarget, locateTableCandidate);
|
||||
nodePath = target ? findNodePathForTarget(target, store.treeNodes) : null;
|
||||
}
|
||||
|
||||
@@ -1678,6 +1680,17 @@ function tableTargetFromCandidate(candidate: QueryCursorTableCandidate): ActiveT
|
||||
};
|
||||
}
|
||||
|
||||
function tableLocateCandidateFromTarget(target: ActiveTabSidebarTarget | null, config: ReturnType<typeof store.getConfig>): QueryCursorTableCandidate | null {
|
||||
if (target?.type !== "table") return null;
|
||||
const database = connectionUsesConnectionRootSchemaMode(config) && target.schema ? target.schema : target.database;
|
||||
return {
|
||||
connectionId: target.connectionId,
|
||||
database,
|
||||
schema: target.schema,
|
||||
tableName: target.tableName,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveLoadedLocateTarget(target: ActiveTabSidebarTarget, candidate: QueryCursorTableCandidate | null): ActiveTabSidebarTarget | null {
|
||||
if (!candidate) return target;
|
||||
return findLoadedTableTargetForCandidate(store.treeNodes, candidate);
|
||||
@@ -1741,12 +1754,24 @@ async function ensureTreeLoadedForTarget(target: ActiveTabSidebarTarget, opts?:
|
||||
}
|
||||
|
||||
// Find the database node
|
||||
const targetSchema = "schema" in target ? target.schema : undefined;
|
||||
const effectiveDbType = effectiveDatabaseTypeForConnection(config);
|
||||
if (target.type === "table" && connectionUsesConnectionRootSchemaMode(config)) {
|
||||
const schemaName = targetSchema || target.database;
|
||||
if (!schemaName) return;
|
||||
const schemaNode = findSchemaNode(store.treeNodes, connId, schemaName, schemaName);
|
||||
if (!schemaNode) return;
|
||||
if (force || !schemaNode.children || schemaNode.children.length === 0) {
|
||||
await store.loadTables(connId, schemaNode.database || schemaName, schemaNode.schema ?? schemaName, loadOptions);
|
||||
}
|
||||
await ensureTableObjectGroupsLoaded({ ...target, database: schemaNode.database || schemaName, schema: schemaNode.schema ?? schemaName }, loadOptions);
|
||||
return;
|
||||
}
|
||||
|
||||
const dbNode = findDatabaseNode(store.treeNodes, connId, target.database, targetCatalog, usesExactCatalogScope);
|
||||
if (!dbNode) return;
|
||||
const targetSchema = "schema" in target ? target.schema : undefined;
|
||||
const databaseChildrenLoaded = !!dbNode.children && dbNode.children.length > 0;
|
||||
const effectiveDbType = effectiveDatabaseTypeForConnection(config);
|
||||
const usesSchemaTree = usesTreeSchemaMode(effectiveDbType) && !connectionUsesDatabaseObjectTreeMode(config);
|
||||
const usesSchemaTree = (usesTreeSchemaMode(effectiveDbType) && !connectionUsesDatabaseObjectTreeMode(config)) || connectionShouldDiscoverJdbcSchemas(config);
|
||||
const shouldLoadSchemaTables = target.type === "table" && !!targetSchema && usesSchemaTree;
|
||||
if (!force && databaseChildrenLoaded && !shouldLoadSchemaTables) return;
|
||||
|
||||
|
||||
@@ -214,6 +214,34 @@ describe("useDataGridActions", () => {
|
||||
expect(tab.orderByInput).toBe('"name" DESC');
|
||||
});
|
||||
|
||||
it("preserves a restored data tab's source database when rebuilding stale metadata", async () => {
|
||||
mocks.getConfig.mockReturnValue({ id: "dameng-1", db_type: "dameng" });
|
||||
const tab = tableDataTab({
|
||||
connectionId: "dameng-1",
|
||||
database: "SERVICE_DB",
|
||||
schema: "APP_OWNER",
|
||||
title: "ORDERS",
|
||||
sql: "SELECT * FROM APP_OWNER.ORDERS",
|
||||
tableMetaUpdatedAt: undefined,
|
||||
tableMeta: {
|
||||
database: "APP_OWNER",
|
||||
schema: "APP_OWNER",
|
||||
tableName: "ORDERS",
|
||||
tableType: "TABLE",
|
||||
columns: [{ name: "ID", data_type: "INTEGER", is_nullable: false, column_default: null, is_primary_key: true, extra: null }],
|
||||
primaryKeys: ["ID"],
|
||||
},
|
||||
});
|
||||
mocks.tabs.push(tab);
|
||||
const actions = useDataGridActions(computed(() => tab));
|
||||
|
||||
await actions.onReloadData(tab.id, undefined, undefined, "", "");
|
||||
|
||||
expect(mocks.getColumns).toHaveBeenCalledWith("dameng-1", "APP_OWNER", "APP_OWNER", "ORDERS", undefined);
|
||||
expect(mocks.setTableMeta).toHaveBeenCalledWith("tab-1", expect.objectContaining({ database: "APP_OWNER", schema: "APP_OWNER", tableName: "ORDERS" }));
|
||||
expect(mocks.buildTableSelectSql).toHaveBeenCalledWith(expect.objectContaining({ database: "APP_OWNER", schema: "APP_OWNER", tableName: "ORDERS" }));
|
||||
});
|
||||
|
||||
it("still clears the stored filter when a mounted grid refreshes with an emptied WHERE input", async () => {
|
||||
// 对照:DataGrid 的 currentWhereInput() 在用户清空筛选时返回 undefined,
|
||||
// 所以 onReloadData 里不能无条件回退到 tab.whereInput。
|
||||
|
||||
@@ -120,7 +120,7 @@ export function useDataGridActions(activeTab: ComputedRef<QueryTab | undefined>)
|
||||
const target = {
|
||||
tabId: tab.id,
|
||||
connectionId: tab.connectionId,
|
||||
database: tab.database,
|
||||
database: tableMeta.database ?? tab.database,
|
||||
catalog: tableMeta.catalog,
|
||||
schema: tableMeta.schema,
|
||||
tableName: tableMeta.tableName,
|
||||
@@ -161,13 +161,15 @@ export function useDataGridActions(activeTab: ComputedRef<QueryTab | undefined>)
|
||||
}
|
||||
const current = queryStore.tabs.find((item) => item.id === target.tabId);
|
||||
const currentMeta = current ? tableMetaForDataTab(current) : undefined;
|
||||
if (!current || current.mode !== "data" || current.connectionId !== target.connectionId || current.database !== target.database || currentMeta?.tableName !== target.tableName || (currentMeta.schema ?? "") !== (target.schema ?? "") || (currentMeta.catalog ?? "") !== (target.catalog ?? "")) {
|
||||
const currentSourceDatabase = currentMeta?.database ?? current?.database;
|
||||
if (!current || current.mode !== "data" || current.connectionId !== target.connectionId || currentSourceDatabase !== target.database || currentMeta?.tableName !== target.tableName || (currentMeta.schema ?? "") !== (target.schema ?? "") || (currentMeta.catalog ?? "") !== (target.catalog ?? "")) {
|
||||
console.info("[DBX][reloadData:metadata:stale-tab]", { traceId: trace?.traceId, elapsed: trace?.elapsed(), table: target.tableName });
|
||||
return false;
|
||||
}
|
||||
const primaryKeys = metadata.primaryKeys;
|
||||
queryStore.setTableMeta(target.tabId, {
|
||||
catalog: target.catalog,
|
||||
database: target.database,
|
||||
schema: target.schema,
|
||||
tableName: target.tableName,
|
||||
tableType: target.tableType,
|
||||
|
||||
@@ -27,4 +27,27 @@ describe("sidebar active-tab targets", () => {
|
||||
expect(matchesTarget(icebergDatabase, target!)).toBe(false);
|
||||
expect(matchesTarget(defaultDatabase, target!)).toBe(false);
|
||||
});
|
||||
|
||||
it("uses data-tab table metadata as the locate identity after a reload", () => {
|
||||
const target = activeTabSidebarTarget({
|
||||
mode: "data",
|
||||
connectionId: "dm-1",
|
||||
database: "SERVICE_DB",
|
||||
schema: "APP_OWNER",
|
||||
title: "ORDERS",
|
||||
sql: "SELECT * FROM APP_OWNER.ORDERS",
|
||||
tableMeta: {
|
||||
database: "APP_OWNER",
|
||||
schema: "APP_OWNER",
|
||||
tableName: "ORDERS",
|
||||
tableType: "TABLE",
|
||||
columns: [{ name: "ID", data_type: "INTEGER", is_nullable: false, column_default: null, is_primary_key: true, extra: null }],
|
||||
primaryKeys: ["ID"],
|
||||
},
|
||||
} as QueryTab);
|
||||
|
||||
expect(target).toEqual({ type: "table", connectionId: "dm-1", database: "APP_OWNER", schema: "APP_OWNER", tableName: "ORDERS" });
|
||||
expect(matchesTarget({ id: "dm-1:APP_OWNER:APP_OWNER:tables:ORDERS", label: "ORDERS", type: "table", connectionId: "dm-1", database: "APP_OWNER", schema: "APP_OWNER" } as TreeNode, target!)).toBe(true);
|
||||
expect(matchesTarget({ id: "dm-1:SERVICE_DB", label: "SERVICE_DB", type: "database", connectionId: "dm-1", database: "SERVICE_DB" } as TreeNode, target!)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { SIDEBAR_TREE_ROW_HEIGHT, type FlatTreeNode } from "@/composables/useFlatTree";
|
||||
import { tableMetaForDataTab } from "@/lib/table/tableDataTabMeta";
|
||||
import type { QueryTab, TreeNode } from "@/types/database";
|
||||
|
||||
export type ActiveTabSidebarTarget =
|
||||
@@ -114,13 +115,14 @@ export function activeTabSidebarTarget(tab: QueryTab | undefined | null): Active
|
||||
}
|
||||
|
||||
if (tab.mode === "data") {
|
||||
const tableName = tab.tableMeta?.tableName || tab.title;
|
||||
const tableMeta = tableMetaForDataTab(tab);
|
||||
const tableName = tableMeta?.tableName || tab.title;
|
||||
if (!tableName) return null;
|
||||
return {
|
||||
type: "table",
|
||||
connectionId: tab.connectionId,
|
||||
database: tab.database,
|
||||
schema: tab.tableMeta?.schema ?? tab.schema,
|
||||
database: tableMeta?.database ?? tab.database,
|
||||
schema: tableMeta?.schema ?? tab.schema,
|
||||
tableName,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export function tableMetaForDataTab(tab: QueryTab | undefined): DataTabTableMeta
|
||||
// Keep filters usable when the table identity loaded but its column metadata did not.
|
||||
return {
|
||||
...tab.tableMeta,
|
||||
schema: tab.schema,
|
||||
schema: tab.tableMeta?.schema ?? tab.schema,
|
||||
tableName,
|
||||
columns: (tab.result?.columns ?? []).map(fallbackColumnInfo),
|
||||
primaryKeys: tab.tableMeta?.primaryKeys ?? [],
|
||||
|
||||
@@ -3873,7 +3873,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
const metadataGenerationAtStart = connectionGeneration;
|
||||
const reloadedMetadata = await loadTableMetadata({
|
||||
connectionId: tab.connectionId,
|
||||
database: tab.database,
|
||||
database: tableMeta.database ?? tab.database,
|
||||
schema: tableMeta.schema,
|
||||
tableName: tableMeta.tableName,
|
||||
tableType: tableMeta.tableType,
|
||||
|
||||
Reference in New Issue
Block a user