fix(metadata): bound cache maintenance and pool recovery

This commit is contained in:
miracle authored and GitHub committed 2026-08-19 11:10:12 +08:00
1 parent 21af5cf600
commit 8c2b232243
41 files changed
+2747 -350

No files matched your search

@@ -146,6 +146,7 @@ import { MAX_RESULT_PAGE_SIZE, MIN_RESULT_PAGE_SIZE } from "@/lib/dataGrid/pagin
import { MAX_QUERY_RESULT_MAX_ROWS } from "@/lib/dataGrid/queryResultRowLimit";
import type { PromptTemplate } from "@/types/promptTemplate";
import { GLOBAL_INSTRUCTIONS_MAX, PROMPT_TEMPLATE_CONTENT_MAX, PROMPT_TEMPLATE_NAME_MAX, promptTemplateCharacterCount } from "@/types/promptTemplate";
import { METADATA_CACHE_HARD_MAX_MEMORY_MB, METADATA_CACHE_MIN_MEMORY_MB, normalizeMetadataCacheMemoryMb } from "@/lib/metadata/metadataRuntimeCache";
const { t } = useI18n();
const { toast } = useToast();
@@ -342,6 +343,7 @@ const editQuitOnClose = ref(settingsStore.desktopSettings.quit_on_close);
const desktopCloseBehaviorResetPending = ref(false);
const editIconTheme = ref<DesktopIconTheme>(settingsStore.desktopSettings.icon_theme);
const editDebugLoggingEnabled = ref(settingsStore.desktopSettings.debug_logging_enabled);
const editMetadataCacheMaxMemoryMb = ref(settingsStore.desktopSettings.metadata_cache_max_memory_mb);
const editDuckDbWorkerProcessIsolation = ref(settingsStore.desktopSettings.duckdb_worker_process_isolation);
const editDuckDbWorkerMaxProcesses = ref(settingsStore.desktopSettings.duckdb_worker_max_processes);
const startupDuckDbWorkerProcessIsolation = ref(settingsStore.desktopSettings.duckdb_worker_process_isolation);
@@ -887,6 +889,7 @@ watch(
editQuitOnClose.value = settingsStore.desktopSettings.quit_on_close;
editIconTheme.value = settingsStore.desktopSettings.icon_theme;
editDebugLoggingEnabled.value = settingsStore.desktopSettings.debug_logging_enabled;
editMetadataCacheMaxMemoryMb.value = settingsStore.desktopSettings.metadata_cache_max_memory_mb;
editDuckDbWorkerProcessIsolation.value = settingsStore.desktopSettings.duckdb_worker_process_isolation;
editSidebarTablePageSize.value = settingsStore.desktopSettings.sidebar_table_page_size ?? DEFAULT_SIDEBAR_TABLE_PAGE_SIZE;
}
@@ -960,6 +963,7 @@ function hasChanges(): boolean {
editQuitOnClose.value !== settingsStore.desktopSettings.quit_on_close ||
editIconTheme.value !== settingsStore.desktopSettings.icon_theme ||
editDebugLoggingEnabled.value !== settingsStore.desktopSettings.debug_logging_enabled ||
editMetadataCacheMaxMemoryMb.value !== settingsStore.desktopSettings.metadata_cache_max_memory_mb ||
editDuckDbWorkerProcessIsolation.value !== settingsStore.desktopSettings.duckdb_worker_process_isolation ||
normalizeDuckDbWorkerMaxProcesses(editDuckDbWorkerMaxProcesses.value) !== settingsStore.desktopSettings.duckdb_worker_max_processes ||
editSidebarTablePageSize.value !== (settingsStore.desktopSettings.sidebar_table_page_size ?? DEFAULT_SIDEBAR_TABLE_PAGE_SIZE)
@@ -976,16 +980,19 @@ async function persistSettings() {
await settingsStore.persistEditorSettings();
editEditorSettingsBase.value = editorSettingsDraftFromSettings(settingsStore.editorSettings);
}
const metadataCacheMaxMemoryMb = normalizeMetadataCacheMemoryMb(editMetadataCacheMaxMemoryMb.value);
await settingsStore.updateDesktopSettings({
show_tray_icon: editShowTrayIcon.value,
quit_on_close: editQuitOnClose.value,
close_action_prompted: desktopCloseBehaviorResetPending.value ? false : true,
icon_theme: editIconTheme.value,
debug_logging_enabled: editDebugLoggingEnabled.value,
metadata_cache_max_memory_mb: metadataCacheMaxMemoryMb,
duckdb_worker_process_isolation: editDuckDbWorkerProcessIsolation.value,
duckdb_worker_max_processes: normalizeDuckDbWorkerMaxProcesses(editDuckDbWorkerMaxProcesses.value),
sidebar_table_page_size: editSidebarTablePageSize.value,
});
editMetadataCacheMaxMemoryMb.value = settingsStore.desktopSettings.metadata_cache_max_memory_mb;
desktopCloseBehaviorResetPending.value = false;
if (sidebarObjectDisplayChanged) {
await connectionStore.refreshAllTree();
@@ -1081,6 +1088,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
desktopCloseBehaviorResetPending.value = true;
editIconTheme.value = DEFAULT_DESKTOP_SETTINGS.icon_theme;
editDebugLoggingEnabled.value = DEFAULT_DESKTOP_SETTINGS.debug_logging_enabled;
editMetadataCacheMaxMemoryMb.value = DEFAULT_DESKTOP_SETTINGS.metadata_cache_max_memory_mb;
} else if (tab === "navigation") {
editSidebarTablePageSize.value = DEFAULT_SIDEBAR_TABLE_PAGE_SIZE;
editSidebarActivation.value = DEFAULT_EDITOR_SETTINGS.sidebarActivation;
@@ -1176,6 +1184,7 @@ function resetAllDefaults() {
desktopCloseBehaviorResetPending.value = true;
editIconTheme.value = DEFAULT_DESKTOP_SETTINGS.icon_theme;
editDebugLoggingEnabled.value = DEFAULT_DESKTOP_SETTINGS.debug_logging_enabled;
editMetadataCacheMaxMemoryMb.value = DEFAULT_DESKTOP_SETTINGS.metadata_cache_max_memory_mb;
editDuckDbWorkerProcessIsolation.value = DEFAULT_DESKTOP_SETTINGS.duckdb_worker_process_isolation;
editDuckDbWorkerMaxProcesses.value = DEFAULT_DESKTOP_SETTINGS.duckdb_worker_max_processes;
editSidebarTablePageSize.value = DEFAULT_SIDEBAR_TABLE_PAGE_SIZE;
@@ -2441,6 +2450,7 @@ watch(
editQuitOnClose.value = settingsStore.desktopSettings.quit_on_close;
editIconTheme.value = settingsStore.desktopSettings.icon_theme;
editDebugLoggingEnabled.value = settingsStore.desktopSettings.debug_logging_enabled;
editMetadataCacheMaxMemoryMb.value = settingsStore.desktopSettings.metadata_cache_max_memory_mb;
editDuckDbWorkerProcessIsolation.value = settingsStore.desktopSettings.duckdb_worker_process_isolation;
editDuckDbWorkerMaxProcesses.value = settingsStore.desktopSettings.duckdb_worker_max_processes;
if (!duckDbWorkerStartupCaptured.value) {
@@ -5365,6 +5375,26 @@ onUnmounted(() => {
<Separator />
<div data-settings-search-id="data-performance" :class="['space-y-3', settingsSearchTargetClass('data-performance')]">
<div class="text-sm font-medium text-muted-foreground">
{{ t("settings.performanceSection") }}
</div>
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="min-w-0 space-y-1">
<Label for="metadata-cache-memory-limit">{{ t("settings.metadataCacheMemoryLimit") }}</Label>
<p class="text-xs text-muted-foreground">
{{ t("settings.metadataCacheMemoryLimitDescription") }}
</p>
</div>
<div class="flex shrink-0 items-center gap-2">
<Input id="metadata-cache-memory-limit" v-model.number="editMetadataCacheMaxMemoryMb" type="number" :min="METADATA_CACHE_MIN_MEMORY_MB" :max="METADATA_CACHE_HARD_MAX_MEMORY_MB" :step="16" class="h-8 w-24 text-right" />
<span class="text-xs text-muted-foreground">MB</span>
</div>
</div>
</div>
<Separator />
<div class="space-y-3">
<div class="text-sm font-medium text-muted-foreground">
{{ t("settings.tableStructureSection") }}
@@ -2612,7 +2612,7 @@ async function resolveSqlHoverTooltip(currentView: EditorViewType, pos: number)
try {
const [columnsResult, indexesResult] = await Promise.all([
loadObjectMetadataFacet(objectMetadataRequest, "columns", () => api.getColumns(props.connectionId!, hoverDatabase, hoverSchema, table.name, hoverScope.catalog)),
loadObjectMetadataFacet(objectMetadataRequest, "indexes", () => api.listIndexes(props.connectionId!, hoverDatabase, hoverSchema, table.name, hoverScope.catalog).catch(() => [])),
loadObjectMetadataFacet(objectMetadataRequest, "indexes", () => api.listIndexes(props.connectionId!, hoverDatabase, hoverSchema, table.name, hoverScope.catalog)).catch(() => ({ value: [] as IndexInfo[], cacheStatus: "remote" as const })),
]);
fullColumns = columnsResult.value;
fullIndexes = indexesResult.value;
@@ -129,6 +129,8 @@ import { tableColumnDefaultDisplayValue } from "@/lib/table/tableColumnDefaultPr
import { gaussdbMTypeDisplayName } from "@/lib/table/postgresDataTypeHelp";
import { cacheObjectBrowserRows, createObjectBrowserRowsCacheWriteToken, getCachedObjectBrowserRows, type ObjectBrowserRowsCacheScope, type ObjectBrowserRowsCacheWriteToken } from "@/lib/table/objectBrowserRowsCache";
import { createObjectBrowserRowsLoadGuard, type ObjectBrowserRowsLoadHandle } from "@/lib/table/objectBrowserRowsLoadGuard";
import { loadObjectDdl, type ObjectDdlRequest } from "@/lib/metadata/objectDdlCache";
import { loadObjectMetadataFacet } from "@/lib/metadata/objectMetadataCache";
type ObjectFilter = ObjectBrowserFilter;
type ObjectBrowserColumnKey = "select" | "name" | "type" | "estimatedRows" | "totalBytes" | "created_at" | "updated_at" | "comment";
@@ -183,14 +185,19 @@ const sidePanelMode = ref<"table-info" | "source" | "type-info">("source");
const tableInfoTab = ref<TableInfoTab>("ddl");
const tableColumns = ref<ColumnInfo[]>([]);
const tableColumnsLoading = ref(false);
const tableColumnsLoaded = ref(false);
const tableDdlContent = ref("");
const tableDdlLoading = ref(false);
const tableDdlLoaded = ref(false);
const tableIndexes = ref<IndexInfo[]>([]);
const tableIndexesLoading = ref(false);
const tableIndexesLoaded = ref(false);
const tableForeignKeys = ref<ForeignKeyInfo[]>([]);
const tableForeignKeysLoading = ref(false);
const tableForeignKeysLoaded = ref(false);
const tableTriggers = ref<TriggerInfo[]>([]);
const tableTriggersLoading = ref(false);
const tableTriggersLoaded = ref(false);
const tableInfoSearchQuery = ref("");
const tableInfoDdlPreRef = ref<HTMLPreElement | null>(null);
const SIDE_PANEL_MIN_WIDTH = 280;
@@ -973,6 +980,11 @@ async function openTableInfo(row: ObjectBrowserRow, initialTab?: TableInfoTab) {
tableIndexes.value = [];
tableForeignKeys.value = [];
tableTriggers.value = [];
tableColumnsLoaded.value = false;
tableDdlLoaded.value = false;
tableIndexesLoaded.value = false;
tableForeignKeysLoaded.value = false;
tableTriggersLoaded.value = false;
tableInfoSearchQuery.value = "";
// Determine initial tab: explicit request > previously activated
const firstTab = initialTab ?? tableInfoTab.value;
@@ -991,93 +1003,132 @@ async function selectTableInfoTab(tab: TableInfoTab) {
else if (nextTab === "triggers") await fetchTableTriggers();
}
async function fetchTableDdl() {
function tableMetadataRequest(row: ObjectBrowserRow): ObjectDdlRequest {
return {
connectionId: props.connection.id,
database: props.database || "",
schema: row.schema || selectedSchema.value || props.database,
tableName: row.name,
objectType: tableDdlObjectType(row.type),
catalog: props.catalog,
};
}
async function fetchTableDdl(force = false) {
const row = sidePanelRow.value;
if (!row) return;
if (!row || (tableDdlLoaded.value && !force)) return;
const epoch = sidePanelGuard.capture();
tableDdlLoading.value = true;
let loadedSuccessfully = false;
try {
const schema = row.schema || selectedSchema.value || props.database;
const ddl = await api.getTableDisplayDdl(props.connection.id, props.database || "", schema, row.name, tableDdlObjectType(row.type), props.catalog);
const { ddl } = await loadObjectDdl(tableMetadataRequest(row), { force });
if (sidePanelGuard.isStale(epoch)) return;
tableDdlContent.value = ddl;
loadedSuccessfully = true;
} catch (e: any) {
if (sidePanelGuard.isStale(epoch)) return;
tableDdlContent.value = `-- Error: ${e?.message || e}`;
} finally {
if (sidePanelGuard.isFresh(epoch)) tableDdlLoading.value = false;
if (sidePanelGuard.isFresh(epoch)) {
tableDdlLoaded.value = loadedSuccessfully;
tableDdlLoading.value = false;
}
}
}
async function fetchTableColumns() {
async function fetchTableColumns(force = false) {
const row = sidePanelRow.value;
if (!row || tableColumns.value.length > 0) return;
if (!row || (tableColumnsLoaded.value && !force)) return;
const epoch = sidePanelGuard.capture();
tableColumnsLoading.value = true;
let loadedSuccessfully = false;
try {
const schema = row.schema || selectedSchema.value || props.database;
const columns = await api.getColumns(props.connection.id, props.database || "", schema, row.name, props.catalog);
const request = tableMetadataRequest(row);
const { value: columns } = await loadObjectMetadataFacet(request, "columns", () => api.getColumns(request.connectionId, request.database, request.schema || request.database, request.tableName, request.catalog), { force });
if (sidePanelGuard.isStale(epoch)) return;
tableColumns.value = columns;
} catch {
loadedSuccessfully = true;
} catch (error) {
if (sidePanelGuard.isStale(epoch)) return;
tableColumns.value = [];
toast(translateBackendError(t, error), 5000);
} finally {
if (sidePanelGuard.isFresh(epoch)) tableColumnsLoading.value = false;
if (sidePanelGuard.isFresh(epoch)) {
tableColumnsLoaded.value = loadedSuccessfully;
tableColumnsLoading.value = false;
}
}
}
async function fetchTableIndexes() {
async function fetchTableIndexes(force = false) {
const row = sidePanelRow.value;
if (!row || tableIndexes.value.length > 0) return;
if (!row || (tableIndexesLoaded.value && !force)) return;
const epoch = sidePanelGuard.capture();
tableIndexesLoading.value = true;
let loadedSuccessfully = false;
try {
const schema = row.schema || selectedSchema.value || props.database;
const indexes = await api.listIndexes(props.connection.id, props.database || "", schema, row.name, props.catalog);
const request = tableMetadataRequest(row);
const { value: indexes } = await loadObjectMetadataFacet(request, "indexes", () => api.listIndexes(request.connectionId, request.database, request.schema || request.database, request.tableName, request.catalog), { force });
if (sidePanelGuard.isStale(epoch)) return;
tableIndexes.value = indexes;
} catch {
loadedSuccessfully = true;
} catch (error) {
if (sidePanelGuard.isStale(epoch)) return;
tableIndexes.value = [];
toast(translateBackendError(t, error), 5000);
} finally {
if (sidePanelGuard.isFresh(epoch)) tableIndexesLoading.value = false;
if (sidePanelGuard.isFresh(epoch)) {
tableIndexesLoaded.value = loadedSuccessfully;
tableIndexesLoading.value = false;
}
}
}
async function fetchTableForeignKeys() {
async function fetchTableForeignKeys(force = false) {
const row = sidePanelRow.value;
if (!row || tableForeignKeys.value.length > 0) return;
if (!row || (tableForeignKeysLoaded.value && !force)) return;
const epoch = sidePanelGuard.capture();
tableForeignKeysLoading.value = true;
let loadedSuccessfully = false;
try {
const schema = row.schema || selectedSchema.value || props.database;
const fks = await api.listForeignKeys(props.connection.id, props.database || "", schema, row.name, props.catalog);
const request = tableMetadataRequest(row);
const { value: fks } = await loadObjectMetadataFacet(request, "foreign-keys", () => api.listForeignKeys(request.connectionId, request.database, request.schema || request.database, request.tableName, request.catalog), { force });
if (sidePanelGuard.isStale(epoch)) return;
tableForeignKeys.value = fks;
} catch {
loadedSuccessfully = true;
} catch (error) {
if (sidePanelGuard.isStale(epoch)) return;
tableForeignKeys.value = [];
toast(translateBackendError(t, error), 5000);
} finally {
if (sidePanelGuard.isFresh(epoch)) tableForeignKeysLoading.value = false;
if (sidePanelGuard.isFresh(epoch)) {
tableForeignKeysLoaded.value = loadedSuccessfully;
tableForeignKeysLoading.value = false;
}
}
}
async function fetchTableTriggers() {
async function fetchTableTriggers(force = false) {
const row = sidePanelRow.value;
if (!row || tableTriggers.value.length > 0) return;
if (!row || (tableTriggersLoaded.value && !force)) return;
const epoch = sidePanelGuard.capture();
tableTriggersLoading.value = true;
let loadedSuccessfully = false;
try {
const schema = row.schema || selectedSchema.value || props.database;
const triggers = await api.listTriggers(props.connection.id, props.database || "", schema, row.name, props.catalog);
const request = tableMetadataRequest(row);
const { value: triggers } = await loadObjectMetadataFacet(request, "triggers", () => api.listTriggers(request.connectionId, request.database, request.schema || request.database, request.tableName, request.catalog), { force });
if (sidePanelGuard.isStale(epoch)) return;
tableTriggers.value = triggers;
} catch {
loadedSuccessfully = true;
} catch (error) {
if (sidePanelGuard.isStale(epoch)) return;
tableTriggers.value = [];
toast(translateBackendError(t, error), 5000);
} finally {
if (sidePanelGuard.isFresh(epoch)) tableTriggersLoading.value = false;
if (sidePanelGuard.isFresh(epoch)) {
tableTriggersLoaded.value = loadedSuccessfully;
tableTriggersLoading.value = false;
}
}
}
@@ -1087,19 +1138,24 @@ async function refreshActiveTableInfo() {
if (tableInfoTab.value === "ddl") {
tableDdlContent.value = "";
await fetchTableDdl();
tableDdlLoaded.value = false;
await fetchTableDdl(true);
} else if (tableInfoTab.value === "columns") {
tableColumns.value = [];
await fetchTableColumns();
tableColumnsLoaded.value = false;
await fetchTableColumns(true);
} else if (tableInfoTab.value === "indexes") {
tableIndexes.value = [];
await fetchTableIndexes();
tableIndexesLoaded.value = false;
await fetchTableIndexes(true);
} else if (tableInfoTab.value === "foreignKeys") {
tableForeignKeys.value = [];
await fetchTableForeignKeys();
tableForeignKeysLoaded.value = false;
await fetchTableForeignKeys(true);
} else if (tableInfoTab.value === "triggers") {
tableTriggers.value = [];
await fetchTableTriggers();
tableTriggersLoaded.value = false;
await fetchTableTriggers(true);
}
}
@@ -30,14 +30,33 @@ describe("ObjectBrowser table metadata refresh", () => {
expect(refreshTableInfo).toContain('sidePanelMode.value !== "table-info" || !sidePanelRow.value');
expect(refreshTableInfo).toContain("sidePanelGuard.bump();");
expect(refreshTableInfo).toMatch(/tableInfoTab\.value === "ddl"[\s\S]*?tableDdlContent\.value = "";[\s\S]*?await fetchTableDdl\(\);/);
expect(refreshTableInfo).toMatch(/tableInfoTab\.value === "columns"[\s\S]*?tableColumns\.value = \[\];[\s\S]*?await fetchTableColumns\(\);/);
expect(refreshTableInfo).toMatch(/tableInfoTab\.value === "indexes"[\s\S]*?tableIndexes\.value = \[\];[\s\S]*?await fetchTableIndexes\(\);/);
expect(refreshTableInfo).toMatch(/tableInfoTab\.value === "foreignKeys"[\s\S]*?tableForeignKeys\.value = \[\];[\s\S]*?await fetchTableForeignKeys\(\);/);
expect(refreshTableInfo).toMatch(/tableInfoTab\.value === "triggers"[\s\S]*?tableTriggers\.value = \[\];[\s\S]*?await fetchTableTriggers\(\);/);
expect(refreshTableInfo).toMatch(/tableInfoTab\.value === "ddl"[\s\S]*?tableDdlContent\.value = "";[\s\S]*?await fetchTableDdl\(true\);/);
expect(refreshTableInfo).toMatch(/tableInfoTab\.value === "columns"[\s\S]*?tableColumns\.value = \[\];[\s\S]*?await fetchTableColumns\(true\);/);
expect(refreshTableInfo).toMatch(/tableInfoTab\.value === "indexes"[\s\S]*?tableIndexes\.value = \[\];[\s\S]*?await fetchTableIndexes\(true\);/);
expect(refreshTableInfo).toMatch(/tableInfoTab\.value === "foreignKeys"[\s\S]*?tableForeignKeys\.value = \[\];[\s\S]*?await fetchTableForeignKeys\(true\);/);
expect(refreshTableInfo).toMatch(/tableInfoTab\.value === "triggers"[\s\S]*?tableTriggers\.value = \[\];[\s\S]*?await fetchTableTriggers\(true\);/);
});
it("routes table-info metadata through the object caches", () => {
expect(source).toContain('from "@/lib/metadata/objectDdlCache"');
expect(source).toContain('from "@/lib/metadata/objectMetadataCache"');
expect(functionBody("fetchTableDdl")).toContain("loadObjectDdl(");
for (const name of ["fetchTableColumns", "fetchTableIndexes", "fetchTableForeignKeys", "fetchTableTriggers"]) {
expect(functionBody(name)).toContain("loadObjectMetadataFacet(");
expect(functionBody(name)).not.toContain(".value.length > 0");
}
});
it("keeps automatic object reloads free of extra metadata requests", () => {
expect(functionBody("reload")).not.toContain("refreshActiveTableInfo");
});
it("leaves a failed facet eligible for retry", () => {
for (const name of ["fetchTableDdl", "fetchTableColumns", "fetchTableIndexes", "fetchTableForeignKeys", "fetchTableTriggers"]) {
const body = functionBody(name);
expect(body).toContain("let loadedSuccessfully = false;");
expect(body).toMatch(/loadedSuccessfully = true;/);
expect(body).toMatch(/Loaded\.value = loadedSuccessfully;/);
}
});
});
@@ -1505,7 +1505,7 @@ async function fetchTableCommentValue(connectionId: string, database: string, sc
}
}
function loadCachedTableComment(request: ReturnType<typeof ddlRequest>, force = false): Promise<{ value: string | undefined; cacheStatus: "disk" | "remote" }> {
function loadCachedTableComment(request: ReturnType<typeof ddlRequest>, force = false): Promise<{ value: string | undefined; cacheStatus: "memory" | "disk" | "remote" }> {
return loadObjectMetadataFacet(request, "comment", () => fetchTableCommentValue(request.connectionId, request.database, request.schema, request.tableName, request.catalog), { force });
}
@@ -1547,22 +1547,22 @@ async function loadStructure(
const columnsPromise = scope.columns ? loadObjectMetadataFacet(metadataRequest, "columns", () => api.getColumns(connectionId, database, schema, tableName, catalog), { force: forceMetadata }).then((result) => result.value) : Promise.resolve(undefined);
const indexesPromise = scope.indexes
? tableMetadataCapabilities.value.indexes
? loadObjectMetadataFacet(metadataRequest, "indexes", () => api.listIndexes(connectionId, database, schema, tableName, catalog).catch(() => []), { force: forceMetadata }).then((result) => result.value)
? loadObjectMetadataFacet(metadataRequest, "indexes", () => api.listIndexes(connectionId, database, schema, tableName, catalog), { force: forceMetadata }).then((result) => result.value)
: Promise.resolve([])
: Promise.resolve(undefined);
const foreignKeysPromise = scope.foreignKeys
? tableMetadataCapabilities.value.foreignKeys
? loadObjectMetadataFacet(metadataRequest, "foreign-keys", () => api.listForeignKeys(connectionId, database, schema, tableName, catalog).catch(() => []), { force: forceMetadata }).then((result) => result.value)
? loadObjectMetadataFacet(metadataRequest, "foreign-keys", () => api.listForeignKeys(connectionId, database, schema, tableName, catalog), { force: forceMetadata }).then((result) => result.value)
: Promise.resolve([])
: Promise.resolve(undefined);
const constraintsPromise = scope.constraints
? tableMetadataCapabilities.value.constraints
? loadObjectMetadataFacet(metadataRequest, "constraints", () => api.listConstraints(connectionId, database, schema, tableName, catalog).catch(() => []), { force: forceMetadata }).then((result) => result.value)
? loadObjectMetadataFacet(metadataRequest, "constraints", () => api.listConstraints(connectionId, database, schema, tableName, catalog), { force: forceMetadata }).then((result) => result.value)
: Promise.resolve([])
: Promise.resolve(undefined);
const triggersPromise = scope.triggers
? tableMetadataCapabilities.value.triggers
? loadObjectMetadataFacet(metadataRequest, "triggers", () => api.listTriggers(connectionId, database, schema, tableName, catalog).catch(() => []), { force: forceMetadata }).then((result) => result.value)
? loadObjectMetadataFacet(metadataRequest, "triggers", () => api.listTriggers(connectionId, database, schema, tableName, catalog), { force: forceMetadata }).then((result) => result.value)
: Promise.resolve([])
: Promise.resolve(undefined);
const tableCommentPromise = scope.tableComment && structureCapabilities.value.comment ? loadCachedTableComment(metadataRequest, forceMetadata).then((result) => result.value) : Promise.resolve(undefined);
@@ -1636,6 +1636,7 @@ async function loadStructure(
const secondaryMetadataPromise = applySecondaryMetadata()
.catch((error) => {
console.warn("[DBX][structure-editor:secondary-metadata-failed]", error);
if (showErrors && requestId === structureLoadRequestId) errorMessage.value = error?.message || String(error);
})
.finally(() => {
if (requestId === structureLoadRequestId) setSecondaryMetadataLoading(scope, false);