feat(metadata): share table metadata cache
This commit is contained in:
1 parent
81f787e8d1
commit
3da3849953
5 files changed
+269
-39
No files matched your search
@@ -4,19 +4,20 @@ import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useQueryStore } from "@/stores/queryStore";
|
||||
import { buildTableSelectSql, quoteTableDataIdentifier } from "@/lib/table/tableSelectSql";
|
||||
import { tableOpenPageLimit } from "@/lib/table/tableOpenPageLimit";
|
||||
import { editableRowIdentifierColumns, usesSyntheticRowIdKey } from "@/lib/table/tableEditing";
|
||||
import { usesSyntheticRowIdKey } from "@/lib/table/tableEditing";
|
||||
import { tableMetaForDataTab } from "@/lib/table/tableDataTabMeta";
|
||||
import * as api from "@/lib/backend/api";
|
||||
import type { QueryTab } from "@/types/database";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
import { effectiveDatabaseTypeForConnection, metadataSchemaForConnection } from "@/lib/database/jdbcDialect";
|
||||
import { loadTableMetadata, TABLE_METADATA_CACHE_TTL_MS } from "@/lib/metadata/tableMetadataCache";
|
||||
import { applyMongoFindSort } from "@/lib/mongo/mongoShellCommand";
|
||||
import { uuid } from "@/lib/common/utils";
|
||||
import type { DataGridSortMode } from "@/lib/dataGrid/dataGridSort";
|
||||
import type { DataGridReloadIntent } from "@/lib/dataGrid/dataGridToolbar";
|
||||
import { queryResultBaseSql, queryResultExecutionSql } from "@/lib/tabs/tabPresentation";
|
||||
|
||||
const DATA_TAB_METADATA_TTL_MS = 30_000;
|
||||
const DATA_TAB_METADATA_TTL_MS = TABLE_METADATA_CACHE_TTL_MS;
|
||||
|
||||
function visibleQuerySortColumns(columns: string[], hiddenColumnIndexes: number[] | undefined, columnIndex: number): { resultColumns: string[]; columnIndex: number } | undefined {
|
||||
const hiddenIndexes = new Set(hiddenColumnIndexes ?? []);
|
||||
@@ -84,8 +85,19 @@ export function useDataGridActions(activeTab: ComputedRef<QueryTab | undefined>)
|
||||
const config = connectionStore.getConfig(target.connectionId);
|
||||
const querySchema = metadataSchemaForConnection(config, target.database, target.schema);
|
||||
console.info("[DBX][reloadData:metadata:get-columns:start]", { traceId: trace?.traceId, elapsed: trace?.elapsed(), schema: querySchema, table: target.tableName });
|
||||
const columns = await api.getColumns(target.connectionId, target.database, querySchema, target.tableName, target.catalog);
|
||||
const indexes = await api.listIndexes(target.connectionId, target.database, querySchema, target.tableName, target.catalog).catch(() => []);
|
||||
// 复用共享表元数据缓存(30s TTL + in-flight 去重),多个入口对同一张表
|
||||
// 不再各自往返 getColumns/listIndexes
|
||||
const { metadata } = await loadTableMetadata({
|
||||
connectionId: target.connectionId,
|
||||
database: target.database,
|
||||
schema: querySchema,
|
||||
tableName: target.tableName,
|
||||
tableType: target.tableType,
|
||||
databaseType: effectiveDatabaseTypeForConnection(config) ?? config?.db_type ?? "",
|
||||
driverProfile: config?.driver_profile || config?.db_type,
|
||||
catalog: target.catalog,
|
||||
});
|
||||
const columns = metadata.columns;
|
||||
console.info("[DBX][reloadData:metadata:get-columns:done]", { traceId: trace?.traceId, elapsed: trace?.elapsed(), columnCount: columns.length });
|
||||
const current = queryStore.tabs.find((item) => item.id === target.tabId);
|
||||
const currentMeta = current ? tableMetaForDataTab(current) : undefined;
|
||||
@@ -93,7 +105,7 @@ export function useDataGridActions(activeTab: ComputedRef<QueryTab | undefined>)
|
||||
console.info("[DBX][reloadData:metadata:stale-tab]", { traceId: trace?.traceId, elapsed: trace?.elapsed(), table: target.tableName });
|
||||
return;
|
||||
}
|
||||
const primaryKeys = editableRowIdentifierColumns(effectiveDatabaseTypeForConnection(config), columns, indexes, target.tableType);
|
||||
const primaryKeys = metadata.primaryKeys;
|
||||
queryStore.setTableMeta(target.tabId, {
|
||||
catalog: target.catalog,
|
||||
schema: target.schema,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as api from "@/lib/backend/api";
|
||||
import { effectiveDatabaseTypeForConnection, metadataSchemaForConnection } from "@/lib/database/jdbcDialect";
|
||||
import { invalidateTableMetadataCache, loadTableMetadata } from "@/lib/metadata/tableMetadataCache";
|
||||
import { isNoSnapshotErrorResult } from "@/lib/query/queryResultError";
|
||||
import { buildTableSelectSql } from "@/lib/table/tableSelectSql";
|
||||
import { editableRowIdentifierColumns, usesSyntheticRowIdKey } from "@/lib/table/tableEditing";
|
||||
@@ -7,7 +8,7 @@ import { tableOpenPageLimit } from "@/lib/table/tableOpenPageLimit";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useQueryStore } from "@/stores/queryStore";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
import type { TableInfoTab } from "@/types/database";
|
||||
import type { ColumnInfo, TableInfoTab } from "@/types/database";
|
||||
|
||||
export type NavigationTarget = {
|
||||
connectionId: string;
|
||||
@@ -145,9 +146,19 @@ async function openTableTarget(target: NavigationTarget, options: { tableInfoTab
|
||||
await queryStore.executeTabSql(tabId, emptySql, { pagination: { limit: pageLimit, offset: 0 } });
|
||||
}
|
||||
try {
|
||||
const columns = await api.getColumns(target.connectionId, target.database, querySchema, target.tableName, target.catalog);
|
||||
const indexes = await api.listIndexes(target.connectionId, target.database, querySchema, target.tableName, target.catalog).catch(() => []);
|
||||
const primaryKeys = editableRowIdentifierColumns(effectiveDbType, columns, indexes, targetTableType);
|
||||
// 复用共享表元数据缓存(30s TTL + in-flight 去重)
|
||||
const { metadata } = await loadTableMetadata({
|
||||
connectionId: target.connectionId,
|
||||
database: target.database,
|
||||
schema: querySchema,
|
||||
tableName: target.tableName,
|
||||
tableType: targetTableType,
|
||||
databaseType: effectiveDbType ?? config.db_type,
|
||||
driverProfile: config.driver_profile || config.db_type,
|
||||
catalog: target.catalog,
|
||||
});
|
||||
const columns = metadata.columns;
|
||||
const primaryKeys = metadata.primaryKeys;
|
||||
const useRowId = usesSyntheticRowIdKey(effectiveDbType, primaryKeys, targetTableType);
|
||||
queryStore.setTableMeta(tabId, {
|
||||
schema: target.schema,
|
||||
@@ -216,17 +227,40 @@ export function useNavigationTargets(dialogs: { showFieldLineageDialog: { value:
|
||||
} catch {}
|
||||
}
|
||||
queryStore.invalidateTableStructure(context.connectionId, context.database, context.schema, context.tableName);
|
||||
// 结构已变更:无论是否有打开的 data tab 都必须作废共享元数据缓存,否则
|
||||
// 其它 loadTableMetadata 消费者最长 30 秒拿到旧列。不带 schema/catalog
|
||||
// 维度(宁可多废,schema 形态在各消费点可能不同)
|
||||
invalidateTableMetadataCache({ connectionId: context.connectionId, database: context.database, tableName: context.tableName });
|
||||
const matchingDataTabs = queryStore.tabs.filter((tab) => tab.mode === "data" && tab.connectionId === context.connectionId && tab.database === context.database && tab.tableMeta?.tableName === context.tableName && (tab.tableMeta.schema || "") === (context.schema || ""));
|
||||
// 同一 catalog 只强制加载一次,结果分发给全部匹配 tab
|
||||
const loadedByCatalog = new Map<string, { columns: ColumnInfo[]; primaryKeys: string[] }>();
|
||||
for (const tab of matchingDataTabs) {
|
||||
try {
|
||||
const connection = connectionStore.getConfig(tab.connectionId);
|
||||
const metadataSchema = metadataSchemaForConnection(connection, tab.database, tab.tableMeta?.schema);
|
||||
const columns = await api.getColumns(tab.connectionId, tab.database, metadataSchema, tab.tableMeta!.tableName);
|
||||
const indexes = await api.listIndexes(tab.connectionId, tab.database, metadataSchema, tab.tableMeta!.tableName).catch(() => []);
|
||||
// 分组含 tableType:主键计算依赖它,不同 tableType 不能共享加载结果
|
||||
const catalogKey = `${tab.tableMeta?.catalog ?? ""}\u0000${tab.tableMeta?.tableType ?? ""}`;
|
||||
let metadata = loadedByCatalog.get(catalogKey);
|
||||
if (!metadata) {
|
||||
metadata = (
|
||||
await loadTableMetadata({
|
||||
connectionId: tab.connectionId,
|
||||
database: tab.database,
|
||||
schema: metadataSchema,
|
||||
tableName: tab.tableMeta!.tableName,
|
||||
tableType: tab.tableMeta!.tableType,
|
||||
databaseType: effectiveDatabaseTypeForConnection(connection) ?? connection?.db_type ?? "",
|
||||
driverProfile: connection?.driver_profile || connection?.db_type,
|
||||
catalog: tab.tableMeta?.catalog,
|
||||
force: true,
|
||||
})
|
||||
).metadata;
|
||||
loadedByCatalog.set(catalogKey, metadata);
|
||||
}
|
||||
queryStore.setTableMeta(tab.id, {
|
||||
...tab.tableMeta!,
|
||||
columns,
|
||||
primaryKeys: editableRowIdentifierColumns(effectiveDatabaseTypeForConnection(connection), columns, indexes, tab.tableMeta!.tableType),
|
||||
columns: metadata.columns,
|
||||
primaryKeys: metadata.primaryKeys,
|
||||
});
|
||||
if (tab.id === queryStore.activeTabId) await reloadData();
|
||||
} catch (e: any) {
|
||||
|
||||
Reference in new issue
Block a user