diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index 27d52dca8e..7b2132be9f 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -86,6 +86,7 @@ import { tableObjectSourceKind } from "@/lib/table/tableObjectSourceKind"; import { tableColumnDefaultDisplayValue } from "@/lib/table/tableColumnDefaultPresentation"; import { shouldNavigateFromTableInfoColumnClick } from "@/lib/table/tableInfoColumnNavigation"; import { tableInfoTabForDrawerToggle } from "@/lib/table/tableInfoTabPreference"; +import { loadObjectDdl } from "@/lib/metadata/objectDdlCache"; import { loadObjectMetadataFacet } from "@/lib/metadata/objectMetadataCache"; import * as api from "@/lib/backend/api"; import { formatElapsedSeconds } from "@/lib/common/elapsedTime"; @@ -10785,6 +10786,9 @@ function clampCellDetailPanelSize(value: number, layout = cellDetailPanelLayout. const showTableInfo = ref(false); const activeTableInfoTab = ref(settingsStore.editorSettings.tableInfoActiveTab); const ddlContent = ref(""); +const tableInfoColumns = ref(props.tableMeta?.columns ?? []); +const tableInfoColumnsLoading = ref(false); +let tableInfoColumnsRequestGeneration = 0; const ddlPreRef = ref(null); const ddlSearchMatchCount = ref(0); const ddlSearchMatchIndex = ref(0); @@ -10943,6 +10947,14 @@ let constraintsRequestGeneration = 0; // also shown (each constraint appears once; FK navigation stays in that tab). const constraintsForTab = computed(() => constraintsForConstraintsTab(constraints.value, tableMetadataCapabilities.value.foreignKeys)); const searchQuery = ref(""); +const activeTableInfoLoading = computed(() => { + if (activeTableInfoTab.value === "ddl") return ddlLoading.value; + if (activeTableInfoTab.value === "columns") return tableInfoColumnsLoading.value; + if (activeTableInfoTab.value === "indexes") return indexesLoading.value; + if (activeTableInfoTab.value === "foreignKeys") return foreignKeysLoading.value; + if (activeTableInfoTab.value === "constraints") return constraintsLoading.value; + return activeTableInfoTab.value === "triggers" && triggersLoading.value; +}); const cellDetailPanelLayout = computed(() => settingsStore.editorSettings.cellDetailPanelLayout); const cellDetailPanelIsBottom = computed(() => cellDetailPanelLayout.value === "bottom"); @@ -11042,7 +11054,7 @@ const tableInfoTabs = computed(() => { id: "columns", label: t("grid.tableInfoColumns"), icon: ListTree, - count: props.tableMeta?.columns.length, + count: tableInfoColumns.value.length, }); } if (canShowTableIndexes.value) { @@ -11116,13 +11128,24 @@ watch( { immediate: true }, ); -async function fetchDdl() { +async function fetchDdl(force = settingsStore.editorSettings.refreshDdlOnOpen) { if (!props.connectionId || !props.tableMeta) return; showTableInfo.value = true; ddlLoading.value = true; try { // Preserve view identity so the backend loads the stored view source instead of synthesizing table DDL. - ddlContent.value = await api.getTableDisplayDdl(props.connectionId, props.database || "", props.tableMeta.schema || props.database || "", props.tableMeta.tableName, tableObjectSourceKind(props.tableMeta.tableType), props.tableMeta.catalog); + const { ddl } = await loadObjectDdl( + { + connectionId: props.connectionId, + database: props.database || "", + schema: props.tableMeta.schema || props.database || "", + tableName: props.tableMeta.tableName, + objectType: tableObjectSourceKind(props.tableMeta.tableType), + catalog: props.tableMeta.catalog, + }, + { force }, + ); + ddlContent.value = ddl; } catch (e: any) { ddlContent.value = `-- Error: ${e}`; } finally { @@ -11130,6 +11153,53 @@ async function fetchDdl() { } } +async function fetchTableInfoColumns(force = false) { + if (!props.connectionId || !props.tableMeta) return; + const requestGeneration = ++tableInfoColumnsRequestGeneration; + tableInfoColumnsLoading.value = true; + try { + // Route through the shared metadata facet cache so a grid-side refresh + // invalidates/populates the same cache the sidebar and hover views read. + const request = { + connectionId: props.connectionId, + database: props.database || "", + schema: props.tableMeta.schema || props.database || "", + tableName: props.tableMeta.tableName, + objectType: tableObjectSourceKind(props.tableMeta.tableType), + catalog: props.tableMeta.catalog, + }; + const { value: columns } = await loadObjectMetadataFacet(request, "columns", () => api.getColumns(request.connectionId, request.database, request.schema, request.tableName, request.catalog), { force }); + if (requestGeneration !== tableInfoColumnsRequestGeneration) return; + tableInfoColumns.value = columns; + } catch (error) { + if (requestGeneration !== tableInfoColumnsRequestGeneration) return; + toast(translateBackendError(t, error), 5000); + } finally { + if (requestGeneration === tableInfoColumnsRequestGeneration) tableInfoColumnsLoading.value = false; + } +} + +async function refreshActiveTableInfo() { + if (!showTableInfo.value || !props.tableMeta) return; + if (canShowTableOwner.value) void fetchTableOwner(true); + + if (activeTableInfoTab.value === "ddl") await fetchDdl(true); + else if (activeTableInfoTab.value === "columns") await fetchTableInfoColumns(true); + else if (activeTableInfoTab.value === "indexes") { + indexesLoaded.value = false; + await fetchIndexes(); + } else if (activeTableInfoTab.value === "foreignKeys") { + foreignKeysLoaded.value = false; + await fetchForeignKeys(); + } else if (activeTableInfoTab.value === "constraints") { + constraintsLoaded.value = false; + await fetchConstraints(); + } else if (activeTableInfoTab.value === "triggers") { + triggersLoaded.value = false; + await fetchTriggers(); + } +} + async function fetchIndexes() { const requestIdentity = currentIndexTableIdentity.value; if (!props.connectionId || !props.tableMeta || !canShowTableIndexes.value || !requestIdentity || indexesLoaded.value || indexesLoading.value) return; @@ -11318,6 +11388,9 @@ async function fetchConstraints() { watch( () => [props.connectionId, props.database, props.tableMeta?.catalog, props.tableMeta?.schema, props.tableMeta?.tableName], () => { + tableInfoColumns.value = props.tableMeta?.columns ?? []; + tableInfoColumnsLoading.value = false; + tableInfoColumnsRequestGeneration += 1; tableOwner.value = null; tableOwnerLoading.value = false; tableOwnerError.value = ""; @@ -11350,6 +11423,13 @@ watch( }, ); +watch( + () => props.tableMeta?.columns, + (columns) => { + if (!tableInfoColumnsLoading.value) tableInfoColumns.value = columns ?? []; + }, +); + watch([showTableInfo, canShowTableOwner], ([shown, supported]) => { if (shown && supported && !tableOwnerLoading.value && tableOwner.value === null && !tableOwnerError.value) void fetchTableOwner(); }); @@ -11793,7 +11873,7 @@ onUnmounted(() => { stopLoadingElapsedTimer(); }); -const filteredColumns = computed(() => filterObjectBrowserTableColumns(props.tableMeta?.columns ?? [], searchQuery.value)); +const filteredColumns = computed(() => filterObjectBrowserTableColumns(tableInfoColumns.value, searchQuery.value)); const filteredIndexes = computed(() => { if (!searchQuery.value) return indexes.value; @@ -14002,6 +14082,9 @@ function openGridSnapshot() { +