feat(metadata): optimize agent metadata loading

This commit is contained in:
t8y2 committed 2026-07-05 12:09:24 +08:00
1 parent 8883a6b3b7
commit e74782f651
65 files changed
+6192 -571

No files matched your search

@@ -480,6 +480,9 @@ async function startInsert() {
}
}
perTable.push({ table: r.tableName, total: rowCount, ok, err: rowCount - ok, error: lastError || undefined });
if (ok > 0) {
store.invalidateMetadataCache(cid, db, props.prefillSchema || undefined, r.tableName);
}
}
executeResults.value = perTable;
currentStep.value = "result";
@@ -197,6 +197,7 @@ async function startImport() {
},
);
toast(t("tableImport.success", { count: summary.rowsImported }), 2500);
store.invalidateMetadataCache(props.prefillConnectionId, props.prefillDatabase || "", props.prefillSchema || undefined, props.prefillTable);
open.value = false;
} catch (e: any) {
errorMessage.value = String(e?.message || e);
@@ -77,8 +77,9 @@ import { buildTableDeleteTemplate, buildTableInsertTemplate, buildTableSelectTem
import { connectionFilePath, defaultSqliteBackupFileName, isMemorySqlitePath, sqliteBackupSourcePath } from "@/lib/connection/connectionFile";
import { revealPathInFileManager } from "@/lib/backend/tauri";
import { clearActiveTableReferencePayload, createTableReferencePayload, createTableReferenceDropEvent, setActiveTableReferencePayload, type QueryEditorTableReferencePayload } from "@/lib/editor/queryEditorTableDrop";
import { editableRowIdentifierColumns, usesSyntheticRowIdKey } from "@/lib/table/tableEditing";
import { usesSyntheticRowIdKey } from "@/lib/table/tableEditing";
import { tableOpenPageLimit } from "@/lib/table/tableOpenPageLimit";
import { getCachedTableMetadata, loadTableMetadata, TABLE_METADATA_CACHE_TTL_MS, tableMetadataToDataTabMeta } from "@/lib/metadata/tableMetadataCache";
import { supportsDatabaseCreation, supportsDatabaseSearch, supportsFieldLineage, supportsObjectBrowserTreeNode, supportsSchemaDiagram, supportsSqlFileExecution, supportsTableImport, supportsTableTruncate, supportsTableStructureEditing, usesTreeSchemaMode } from "@/lib/database/databaseCapabilities";
import { copyNameForTreeNode, isDocumentBrowserTreeNode, objectSourceKindForTreeNode, shouldRunTreeNodeRowAction, treeNodeRowAction, treeNodeRowDoubleClickAction } from "@/lib/sidebar/treeNodeClick";
import { isCopySidebarSelectionShortcut, isEditSidebarConnectionShortcut, isPasteSidebarSelectionShortcut } from "@/lib/editor/keyboardShortcuts";
@@ -223,7 +224,7 @@ const useWindowsSidebarCommentFont = isWindows();
type StructureCopyFormat = "tsv" | "markdown";
type DuplicateStructureSource = TreeNode & { connectionId: string; database: string };
const DATA_TAB_METADATA_TTL_MS = 30_000;
const DATA_TAB_METADATA_TTL_MS = TABLE_METADATA_CACHE_TTL_MS;
const { getDatabaseOptions } = useDatabaseOptions();
const showVisibleDatabasesDialog = ref(false);
const showVisibleSchemasDialog = ref(false);
@@ -1121,6 +1122,9 @@ async function openData() {
});
const tableSchema = connectionObjectTreeNodeSchema(config, node.database, node.schema);
const tableType = node.type === "view" ? "VIEW" : node.type === "materialized_view" ? "MATERIALIZED_VIEW" : (node.tableType ?? "TABLE");
const querySchema = config ? connectionObjectTreeQuerySchema(config, node.database, tableSchema) : (tableSchema ?? "");
const effectiveDbType = effectiveDatabaseTypeForConnection(config);
const metadataDatabaseType = effectiveDbType || config?.db_type || "";
const isSameDataTableTab = (tab: (typeof queryStore.tabs)[number]) => tab.mode === "data" && tab.connectionId === node.connectionId && tab.database === node.database && (tab.schema || "") === (tableSchema || "") && (tab.tableMeta?.tableName || tab.title) === node.label;
const existingSameTableTab = queryStore.tabs.find(isSameDataTableTab);
const resetReusedDataTabState = (tab: (typeof queryStore.tabs)[number]) => {
@@ -1186,7 +1190,21 @@ async function openData() {
}
const existingTableMeta = tab?.tableMeta;
const existingTableMetaAgeMs = tab?.tableMetaUpdatedAt ? Date.now() - tab.tableMetaUpdatedAt : Number.POSITIVE_INFINITY;
const cachedTableMeta = existingTableMeta?.tableName === node.label && existingTableMeta.schema === tableSchema && existingTableMeta.tableType === tableType && existingTableMeta.columns.length > 0 && existingTableMetaAgeMs < DATA_TAB_METADATA_TTL_MS ? existingTableMeta : undefined;
const sharedCachedTableMeta = config
? getCachedTableMetadata({
connectionId: node.connectionId,
database: node.database,
schema: querySchema,
tableName: node.label,
tableType,
databaseType: metadataDatabaseType,
driverProfile: config.driver_profile || config.db_type,
})
: undefined;
const tabCachedTableMeta = existingTableMeta?.tableName === node.label && existingTableMeta.schema === tableSchema && existingTableMeta.tableType === tableType && existingTableMeta.columns.length > 0 && existingTableMetaAgeMs < DATA_TAB_METADATA_TTL_MS ? existingTableMeta : undefined;
const cachedTableMeta = sharedCachedTableMeta ? tableMetadataToDataTabMeta(sharedCachedTableMeta.metadata, tableSchema) : tabCachedTableMeta;
const cachedTableMetaAgeMs = sharedCachedTableMeta?.ageMs ?? existingTableMetaAgeMs;
const cachedTableMetaSource = sharedCachedTableMeta ? "shared" : tabCachedTableMeta ? "tab" : undefined;
queryStore.setTableMeta(
tabId,
cachedTableMeta ?? {
@@ -1218,8 +1236,6 @@ async function openData() {
logPhase("ensure-connected", { tabId });
if (!config) throw new Error("Connection config not found");
const querySchema = connectionObjectTreeQuerySchema(config, node.database, tableSchema);
const effectiveDbType = effectiveDatabaseTypeForConnection(config);
const limit = tableOpenPageLimit(settingsStore.editorSettings.pageSize);
const refreshTableMetaInBackground = async () => {
const metadataStartedAt = performance.now();
@@ -1231,30 +1247,34 @@ async function openData() {
elapsed: elapsed(),
});
try {
const nextColumns = await api.getColumns(node.connectionId, node.database, querySchema, node.label);
const indexes = await api.listIndexes(node.connectionId, node.database, querySchema, node.label).catch(() => []);
const loadedMetadata = await loadTableMetadata({
connectionId: node.connectionId,
database: node.database,
schema: querySchema,
tableName: node.label,
tableType,
databaseType: metadataDatabaseType,
driverProfile: config.driver_profile || config.db_type,
traceLogger: (event) => console.debug("[DBX][openData:metadata:trace]", { sourceTraceId: traceId, ...event }),
});
if (!isCurrentDataTab()) {
console.info("[DBX][openData:metadata:stale]", {
traceId,
tabId,
columnCount: nextColumns.length,
columnCount: loadedMetadata.metadata.columns.length,
elapsed: elapsed(),
});
return;
}
const nextPrimaryKeys = editableRowIdentifierColumns(effectiveDbType, nextColumns, indexes, tableType);
queryStore.setTableMeta(tabId, {
schema: tableSchema,
tableName: node.label,
tableType,
columns: nextColumns,
primaryKeys: nextPrimaryKeys,
});
const nextTableMeta = tableMetadataToDataTabMeta(loadedMetadata.metadata, tableSchema);
queryStore.setTableMeta(tabId, nextTableMeta);
console.info("[DBX][openData:metadata:done]", {
traceId,
tabId,
columnCount: nextColumns.length,
primaryKeyCount: nextPrimaryKeys.length,
columnCount: nextTableMeta.columns.length,
primaryKeyCount: nextTableMeta.primaryKeys.length,
cacheStatus: loadedMetadata.cacheStatus,
ageMs: Math.round(loadedMetadata.ageMs),
elapsed: elapsed(),
metadataMs: Math.round(performance.now() - metadataStartedAt),
});
@@ -1269,7 +1289,8 @@ async function openData() {
tabId,
columnCount: cachedTableMeta.columns.length,
primaryKeyCount: cachedTableMeta.primaryKeys.length,
ageMs: Math.round(existingTableMetaAgeMs),
source: cachedTableMetaSource,
ageMs: Math.round(cachedTableMetaAgeMs),
elapsed: elapsed(),
});
} else {