refactor(grid): extract table metadata loaders

This commit is contained in:
t8y2 committed 2026-09-08 22:25:20 +08:00
1 parent ed20ed275d
commit 3bb54625f3
2 files changed
+376 -308

No files matched your search

+63 -308
View File
@@ -84,16 +84,11 @@ import DataGridReadonlyTextSelection from "@/components/grid/DataGridReadonlyTex
import GridSnapshotDialog from "@/components/grid/GridSnapshotDialog.vue";
import type { QueryResult, ColumnInfo, ConstraintInfo, DatabaseType, ForeignKeyInfo, IndexInfo, TriggerInfo, TableInfoTab, QueryResultSourceColumnRef, QueryPageJumpProgress } from "@/types/database";
import { isQueryExecutionErrorResult } from "@/lib/query/queryResultError";
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";
import { sqlFormatDialectForDbType } from "@/lib/sql/sqlFormatter";
import { omitDdlIdentifierQuotes } from "@/lib/sql/ddlDisplay";
import type { SqlInsertMode } from "@/lib/export/sqlInsertMode";
import { dataGridCellDisplayText, dataGridCellEditorText } from "@/lib/dataGrid/dataGridCellCoercion";
import { createColumnDrafts } from "@/lib/table/tableStructureEditorState";
@@ -283,7 +278,7 @@ import {
dataGridSelectedSortMenuValue,
type DataGridColumnSortState,
} from "@/lib/dataGrid/dataGridContextMenu";
import { buildColumnForeignKeyMap, combineForeignKeyConditions, foreignKeyAssociationCells, foreignKeyMetadataRequestCurrent, foreignKeyNavigationTarget, foreignKeySourceColumnName, foreignKeyTableIdentity, type ForeignKeyAssociation } from "@/lib/dataGrid/dataGridForeignKeyNavigation";
import { buildColumnForeignKeyMap, combineForeignKeyConditions, foreignKeyAssociationCells, foreignKeyNavigationTarget, foreignKeySourceColumnName, type ForeignKeyAssociation } from "@/lib/dataGrid/dataGridForeignKeyNavigation";
import {
collectForeignKeyDisplayValues,
createForeignKeyDisplayRequestCoordinator,
@@ -354,13 +349,12 @@ import { constraintsForConstraintsTab } from "@/lib/table/constraintPresentation
import { filterObjectBrowserTableColumns } from "@/lib/table/objectBrowserTableInfo";
import { gaussdbMTypeDisplayName } from "@/lib/table/postgresDataTypeHelp";
import { reserveDataGridHeaderLine } from "@/lib/dataGrid/dataGridHeaderLayout";
import { buildColumnIndexMap, columnIndexColorClass, columnIndexMetadataRequestCurrent, columnIndexNameKey, columnIndexTableIdentity, type ColumnIndexKind } from "@/lib/dataGrid/dataGridColumnIndexIcon";
import { buildColumnIndexMap, columnIndexColorClass, columnIndexNameKey, type ColumnIndexKind } from "@/lib/dataGrid/dataGridColumnIndexIcon";
import { supportsTableStructureEditing } from "@/lib/database/databaseCapabilities";
import { rememberDataGridConditionHistory } from "@/lib/dataGrid/dataGridConditionHistory";
import { buildDataGridLocalFilterOptions, dataGridLocalFilterKey, dataGridLocalFilterLabel, restoreDataGridLocalColumnFilters, rowMatchesDataGridLocalColumnFilters, serializeDataGridLocalColumnFilters, type DataGridLocalFilterOption } from "@/lib/dataGrid/dataGridLocalColumnFilterState";
import { effectiveDatabaseTypeForConnection, gaussdbCountQueryDopHint } from "@/lib/database/jdbcDialect";
import { mongoCollectionSupportsIndexes, supportsMongoIndexMutations } from "@/lib/mongo/mongoCapabilities";
import { refreshLoadedMongoIndexes } from "@/lib/mongo/mongoIndexMetadata";
import { isProtectedMongoIndex, mongoDropAllIndexesPreview, mongoDropIndexFailureCount, mongoDropIndexPreview } from "@/lib/sidebar/mongoCollectionMutation";
import { runMongoMutation } from "@/lib/sidebar/runMongoSidebarMutation";
import { dataGridConditionColumnOptions, dataGridConditionIdentifierQuote, dataGridFilterColumns } from "@/lib/dataGrid/dataGridConditionCompletion";
@@ -370,6 +364,7 @@ import { formatShortcut } from "@/lib/editor/shortcutRegistry";
import { queryTimeoutSecsForConnection } from "@/lib/sql/queryTimeout";
import { SearchableSelect } from "@/components/ui/searchable-select";
import { useDataGridColumnFormatter } from "@/composables/useDataGridColumnFormatter";
import { useDataGridTableMetadataLoaders } from "@/composables/useDataGridTableMetadataLoaders";
const SqlPreviewPanel = defineAsyncComponent(() => import("@/components/editor/SqlPreviewPanel.vue"));
const ImagePreviewDialog = defineAsyncComponent(() => import("@/components/grid/ImagePreviewDialog.vue"));
@@ -401,6 +396,11 @@ const booleanDisplayMode = computed(() => settingsStore.editorSettings.dataGridB
const colorizeDataGridCellTypes = computed(() => settingsStore.editorSettings.colorizeDataGridCellTypes);
const { isDark, themePalette } = useTheme();
const { toast } = useToast();
let tableMetadataLoaderFetchForeignKeys: () => Promise<void> = async () => {};
function fetchForeignKeys() {
return tableMetadataLoaderFetchForeignKeys();
}
// 外键单元格跳转复用导航入口;对话框引用传 stub(同 AiAssistant 模式)
const { openTableTarget } = useNavigationTargets({
showFieldLineageDialog: ref(false),
@@ -10544,42 +10544,16 @@ const tableInfoDrawerPinned = computed(() => settingsStore.editorSettings.tableI
const ddlContent = ref("");
const tableInfoColumns = ref<ColumnInfo[]>(props.tableMeta?.columns ?? []);
const tableInfoColumnsLoading = ref(false);
let tableInfoColumnsRequestGeneration = 0;
const tableInfoColumnsRequestGeneration = ref(0);
const ddlPreRef = ref<HTMLPreElement | null>(null);
const ddlSearchMatchCount = ref(0);
const ddlSearchMatchIndex = ref(0);
const tableOwner = ref<string | null>(null);
const tableOwnerLoading = ref(false);
const tableOwnerError = ref("");
let tableOwnerRequestGeneration = 0;
const tableOwnerRequestGeneration = ref(0);
const canShowTableOwner = computed(() => resolvedDatabaseType.value === "postgres" && !!props.connectionId && !!props.database && !!props.tableMeta?.schema && !!props.tableMeta?.tableName);
async function fetchTableOwner(force = false) {
if (!canShowTableOwner.value || !props.connectionId || !props.database || !props.tableMeta?.schema || !props.tableMeta.tableName) return;
const requestGeneration = ++tableOwnerRequestGeneration;
const request = {
connectionId: props.connectionId,
database: props.database,
schema: props.tableMeta.schema,
tableName: props.tableMeta.tableName,
catalog: props.tableMeta.catalog,
objectType: tableObjectSourceKind(props.tableMeta.tableType),
};
tableOwnerLoading.value = true;
tableOwnerError.value = "";
try {
const result = await loadObjectMetadataFacet(request, "owner", () => api.getTableOwner(request.connectionId, request.database, request.schema, request.tableName), { force });
if (requestGeneration !== tableOwnerRequestGeneration) return;
tableOwner.value = result.value;
} catch (error: any) {
if (requestGeneration !== tableOwnerRequestGeneration) return;
tableOwner.value = null;
tableOwnerError.value = error?.message || String(error);
} finally {
if (requestGeneration === tableOwnerRequestGeneration) tableOwnerLoading.value = false;
}
}
function scrollDdlSearchMatchIntoView(match: HTMLElement) {
const pre = ddlPreRef.value;
if (!pre) return;
@@ -10652,16 +10626,7 @@ let detailResizeStartY = 0;
let detailResizeStartHeight = 0;
let mongoJsonPreviewResizeStartX = 0;
let mongoJsonPreviewResizeStartWidth = 0;
const currentIndexTableIdentity = computed(() =>
columnIndexTableIdentity({
connectionId: props.connectionId,
database: props.database,
catalog: props.tableMeta?.catalog,
schema: props.tableMeta?.schema,
tableName: props.tableMeta?.tableName,
}),
);
let indexesRequestGeneration = 0;
const indexesRequestGeneration = ref(0);
const showDropMongoIndexConfirm = ref(false);
const dropMongoIndexLoading = ref(false);
const pendingDropMongoIndex = ref<IndexInfo | null>(null);
@@ -10671,16 +10636,7 @@ const foreignKeys = ref<ForeignKeyInfo[]>([]);
const foreignKeysLoaded = ref(false);
const foreignKeysLoading = ref(false);
const foreignKeysError = ref("");
const currentForeignKeyTableIdentity = computed(() =>
foreignKeyTableIdentity({
connectionId: props.connectionId,
database: props.database,
catalog: props.tableMeta?.catalog,
schema: props.tableMeta?.schema,
tableName: props.tableMeta?.tableName,
}),
);
let foreignKeysRequestGeneration = 0;
const foreignKeysRequestGeneration = ref(0);
const triggers = ref<TriggerInfo[]>([]);
const triggersLoaded = ref(false);
const triggersLoading = ref(false);
@@ -10689,16 +10645,7 @@ const constraints = ref<ConstraintInfo[]>([]);
const constraintsLoaded = ref(false);
const constraintsLoading = ref(false);
const constraintsError = ref("");
const currentConstraintTableIdentity = computed(() =>
columnIndexTableIdentity({
connectionId: props.connectionId,
database: props.database,
catalog: props.tableMeta?.catalog,
schema: props.tableMeta?.schema,
tableName: props.tableMeta?.tableName,
}),
);
let constraintsRequestGeneration = 0;
const constraintsRequestGeneration = ref(0);
// The Constraints tab hides foreign keys when a dedicated Foreign Keys tab is
// also shown (each constraint appears once; FK navigation stays in that tab).
const constraintsForTab = computed(() => constraintsForConstraintsTab(constraints.value, tableMetadataCapabilities.value.foreignKeys));
@@ -10810,6 +10757,51 @@ const canOpenTableStructureEditor = computed(() => !!props.connectionId && !!pro
const mongoConnectionConfig = resolvedConnectionConfig;
const canManageMongoIndexes = computed(() => resolvedDatabaseType.value === "mongodb" && !!props.connectionId && !!props.database && !!props.tableMeta?.tableName && supportsMongoIndexMutations(mongoConnectionConfig.value, props.tableMeta?.tableType));
const canShowTableIndexes = computed(() => tableMetadataCapabilities.value.indexes && (resolvedDatabaseType.value !== "mongodb" || mongoCollectionSupportsIndexes(props.tableMeta?.tableType)));
const metadataLoaders = useDataGridTableMetadataLoaders({
props,
state: {
ddlContent,
ddlLoading,
tableInfoColumns,
tableInfoColumnsLoading,
tableOwner,
tableOwnerLoading,
tableOwnerError,
indexes,
indexesLoaded,
indexesLoading,
indexesError,
foreignKeys,
foreignKeysLoaded,
foreignKeysLoading,
foreignKeysError,
triggers,
triggersLoaded,
triggersLoading,
triggersError,
constraints,
constraintsLoaded,
constraintsLoading,
constraintsError,
tableInfoColumnsRequestGeneration,
tableOwnerRequestGeneration,
indexesRequestGeneration,
foreignKeysRequestGeneration,
constraintsRequestGeneration,
},
settingsStore,
connectionStore,
resolvedDatabaseType,
canShowTableIndexes,
showTableInfo,
toast,
formatBackendError: (error) => translateBackendError(t, error),
toastMongoIndexRefreshError: (message) => toast(t("contextMenu.mongoIndexRefreshFailed", { message }), 5000),
});
const { fetchDdl, fetchTableInfoColumns, fetchTableOwner, currentIndexTableIdentity, fetchIndexes, refreshMongoIndexMetadataAfterMutation, currentForeignKeyTableIdentity, fetchForeignKeys: fetchForeignKeysMetadata, fetchTriggers, fetchConstraints } = metadataLoaders;
tableMetadataLoaderFetchForeignKeys = fetchForeignKeysMetadata;
const tableInfoTabs = computed(() => {
const tabs: TableInfoTabItem[] = [];
if (tableMetadataCapabilities.value.ddl) {
@@ -10908,58 +10900,6 @@ watch(
{ immediate: true },
);
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.
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 },
);
const formatDialect = sqlFormatDialectForDbType(resolvedDatabaseType.value);
ddlContent.value = settingsStore.editorSettings.generateSqlQuoteIdentifiers ? ddl : omitDdlIdentifierQuotes(ddl, formatDialect);
} catch (e: any) {
ddlContent.value = `-- Error: ${e}`;
} finally {
ddlLoading.value = false;
}
}
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);
@@ -10981,212 +10921,27 @@ async function refreshActiveTableInfo() {
}
}
async function fetchIndexes() {
const requestIdentity = currentIndexTableIdentity.value;
if (!props.connectionId || !props.tableMeta || !canShowTableIndexes.value || !requestIdentity || indexesLoaded.value || indexesLoading.value) return;
const connectionId = props.connectionId;
const database = props.database || "";
const schema = props.tableMeta.schema || props.database || "";
const tableName = props.tableMeta.tableName;
const catalog = props.tableMeta.catalog;
const requestGeneration = ++indexesRequestGeneration;
indexesLoading.value = true;
indexesError.value = "";
try {
const nextIndexes = await api.listIndexes(connectionId, database, schema, tableName, catalog);
if (
!columnIndexMetadataRequestCurrent({
requestGeneration,
currentGeneration: indexesRequestGeneration,
requestIdentity,
currentIdentity: currentIndexTableIdentity.value,
})
)
return;
indexes.value = nextIndexes;
indexesLoaded.value = true;
} catch (e: any) {
if (
!columnIndexMetadataRequestCurrent({
requestGeneration,
currentGeneration: indexesRequestGeneration,
requestIdentity,
currentIdentity: currentIndexTableIdentity.value,
})
)
return;
indexesError.value = String(e?.message || e);
} finally {
if (
columnIndexMetadataRequestCurrent({
requestGeneration,
currentGeneration: indexesRequestGeneration,
requestIdentity,
currentIdentity: currentIndexTableIdentity.value,
})
)
indexesLoading.value = false;
}
}
async function reloadIndexes() {
indexesLoaded.value = false;
await fetchIndexes();
}
async function refreshMongoIndexMetadataAfterMutation() {
await reloadIndexes();
const connectionId = props.connectionId;
const database = props.database;
const collection = props.tableMeta?.tableName;
if (!connectionId || !database || !collection) return;
try {
await refreshLoadedMongoIndexes(connectionStore, {
connectionId,
database,
collection,
});
} catch (e: any) {
toast(
t("contextMenu.mongoIndexRefreshFailed", {
message: String(e?.message || e),
}),
5000,
);
}
}
async function fetchForeignKeys() {
const requestIdentity = currentForeignKeyTableIdentity.value;
if (!props.connectionId || !props.tableMeta || !requestIdentity || foreignKeysLoaded.value || foreignKeysLoading.value) return;
const connectionId = props.connectionId;
const database = props.database || "";
const schema = props.tableMeta.schema || props.database || "";
const tableName = props.tableMeta.tableName;
const catalog = props.tableMeta.catalog;
const requestGeneration = ++foreignKeysRequestGeneration;
foreignKeysLoading.value = true;
foreignKeysError.value = "";
try {
const nextForeignKeys = await api.listForeignKeys(connectionId, database, schema, tableName, catalog);
if (
!foreignKeyMetadataRequestCurrent({
requestGeneration,
currentGeneration: foreignKeysRequestGeneration,
requestIdentity,
currentIdentity: currentForeignKeyTableIdentity.value,
})
)
return;
foreignKeys.value = nextForeignKeys;
foreignKeysLoaded.value = true;
} catch (e: any) {
if (
!foreignKeyMetadataRequestCurrent({
requestGeneration,
currentGeneration: foreignKeysRequestGeneration,
requestIdentity,
currentIdentity: currentForeignKeyTableIdentity.value,
})
)
return;
foreignKeysError.value = String(e?.message || e);
} finally {
if (
foreignKeyMetadataRequestCurrent({
requestGeneration,
currentGeneration: foreignKeysRequestGeneration,
requestIdentity,
currentIdentity: currentForeignKeyTableIdentity.value,
})
)
foreignKeysLoading.value = false;
}
}
async function fetchTriggers() {
if (!props.connectionId || !props.tableMeta || triggersLoaded.value || triggersLoading.value) return;
triggersLoading.value = true;
triggersError.value = "";
try {
triggers.value = await api.listTriggers(props.connectionId, props.database || "", props.tableMeta.schema || props.database || "", props.tableMeta.tableName, props.tableMeta.catalog);
triggersLoaded.value = true;
} catch (e: any) {
triggersError.value = String(e?.message || e);
} finally {
triggersLoading.value = false;
}
}
async function fetchConstraints() {
const requestIdentity = currentConstraintTableIdentity.value;
if (!props.connectionId || !props.tableMeta || !requestIdentity || constraintsLoaded.value || constraintsLoading.value) return;
const connectionId = props.connectionId;
const database = props.database || "";
const schema = props.tableMeta.schema || props.database || "";
const tableName = props.tableMeta.tableName;
const catalog = props.tableMeta.catalog;
const requestGeneration = ++constraintsRequestGeneration;
constraintsLoading.value = true;
constraintsError.value = "";
try {
const nextConstraints = await api.listConstraints(connectionId, database, schema, tableName, catalog);
if (
!columnIndexMetadataRequestCurrent({
requestGeneration,
currentGeneration: constraintsRequestGeneration,
requestIdentity,
currentIdentity: currentConstraintTableIdentity.value,
})
)
return;
constraints.value = nextConstraints;
constraintsLoaded.value = true;
} catch (e: any) {
if (
!columnIndexMetadataRequestCurrent({
requestGeneration,
currentGeneration: constraintsRequestGeneration,
requestIdentity,
currentIdentity: currentConstraintTableIdentity.value,
})
)
return;
constraintsError.value = String(e?.message || e);
} finally {
if (
columnIndexMetadataRequestCurrent({
requestGeneration,
currentGeneration: constraintsRequestGeneration,
requestIdentity,
currentIdentity: currentConstraintTableIdentity.value,
})
)
constraintsLoading.value = false;
}
}
watch(
() => [props.connectionId, props.database, props.tableMeta?.catalog, props.tableMeta?.schema, props.tableMeta?.tableName],
() => {
tableInfoColumns.value = props.tableMeta?.columns ?? [];
tableInfoColumnsLoading.value = false;
tableInfoColumnsRequestGeneration += 1;
tableInfoColumnsRequestGeneration.value += 1;
tableOwner.value = null;
tableOwnerLoading.value = false;
tableOwnerError.value = "";
tableOwnerRequestGeneration += 1;
tableOwnerRequestGeneration.value += 1;
ddlContent.value = "";
indexes.value = [];
indexesLoaded.value = false;
indexesLoading.value = false;
indexesError.value = "";
indexesRequestGeneration += 1;
indexesRequestGeneration.value += 1;
foreignKeys.value = [];
foreignKeysLoaded.value = false;
foreignKeysLoading.value = false;
foreignKeysError.value = "";
foreignKeysRequestGeneration += 1;
foreignKeysRequestGeneration.value += 1;
triggers.value = [];
triggersLoaded.value = false;
triggersError.value = "";
@@ -11194,7 +10949,7 @@ watch(
constraintsLoaded.value = false;
constraintsLoading.value = false;
constraintsError.value = "";
constraintsRequestGeneration += 1;
constraintsRequestGeneration.value += 1;
// 表身份变更后,主动触发索引加载,确保索引指示器在切换表后立即可见
if (showIndexIndicatorsInHeader.value && canShowTableIndexes.value && currentIndexTableIdentity.value) {
void fetchIndexes();
@@ -0,0 +1,313 @@
import { computed, type ComputedRef, type Ref } from "vue";
import * as api from "@/lib/backend/api";
import { omitDdlIdentifierQuotes } from "@/lib/sql/ddlDisplay";
import { sqlFormatDialectForDbType } from "@/lib/sql/sqlFormatter";
import { loadObjectDdl } from "@/lib/metadata/objectDdlCache";
import { loadObjectMetadataFacet } from "@/lib/metadata/objectMetadataCache";
import { tableObjectSourceKind } from "@/lib/table/tableObjectSourceKind";
import { columnIndexMetadataRequestCurrent, columnIndexTableIdentity } from "@/lib/dataGrid/dataGridColumnIndexIcon";
import { foreignKeyMetadataRequestCurrent, foreignKeyTableIdentity } from "@/lib/dataGrid/dataGridForeignKeyNavigation";
import { refreshLoadedMongoIndexes } from "@/lib/mongo/mongoIndexMetadata";
import type { ColumnInfo, ConstraintInfo, DatabaseType, ForeignKeyInfo, IndexInfo, TriggerInfo } from "@/types/database";
import { useConnectionStore } from "@/stores/connectionStore";
type SettingsStore = ReturnType<typeof import("@/stores/settingsStore").useSettingsStore>;
type ConnectionStore = ReturnType<typeof useConnectionStore>;
interface TableMetadataProps {
connectionId?: string;
database?: string;
context?: "results" | "table-data";
tableInfoTab?: string;
autoShowTableInfo?: boolean;
tableMeta?: {
catalog?: string;
database?: string;
schema?: string;
tableName: string;
tableType?: string;
columns: ColumnInfo[];
primaryKeys: string[];
};
}
interface TableMetadataState {
ddlContent: Ref<string>;
ddlLoading: Ref<boolean>;
tableInfoColumns: Ref<ColumnInfo[]>;
tableInfoColumnsLoading: Ref<boolean>;
tableOwner: Ref<string | null>;
tableOwnerLoading: Ref<boolean>;
tableOwnerError: Ref<string>;
indexes: Ref<IndexInfo[]>;
indexesLoaded: Ref<boolean>;
indexesLoading: Ref<boolean>;
indexesError: Ref<string>;
foreignKeys: Ref<ForeignKeyInfo[]>;
foreignKeysLoaded: Ref<boolean>;
foreignKeysLoading: Ref<boolean>;
foreignKeysError: Ref<string>;
triggers: Ref<TriggerInfo[]>;
triggersLoaded: Ref<boolean>;
triggersLoading: Ref<boolean>;
triggersError: Ref<string>;
constraints: Ref<ConstraintInfo[]>;
constraintsLoaded: Ref<boolean>;
constraintsLoading: Ref<boolean>;
constraintsError: Ref<string>;
tableInfoColumnsRequestGeneration: Ref<number>;
tableOwnerRequestGeneration: Ref<number>;
indexesRequestGeneration: Ref<number>;
foreignKeysRequestGeneration: Ref<number>;
constraintsRequestGeneration: Ref<number>;
}
export interface DataGridTableMetadataLoaderOptions {
props: TableMetadataProps;
state: TableMetadataState;
settingsStore: SettingsStore;
connectionStore: ConnectionStore;
resolvedDatabaseType: ComputedRef<DatabaseType | undefined>;
canShowTableIndexes: ComputedRef<boolean>;
showTableInfo: Ref<boolean>;
toast: (message: string, duration?: number) => void;
formatBackendError: (error: unknown) => string;
toastMongoIndexRefreshError: (message: string) => void;
}
export function useDataGridTableMetadataLoaders(options: DataGridTableMetadataLoaderOptions) {
const { props, state } = options;
const currentTableIdentity = (kind: "columns" | "owner" | "indexes" | "foreignKeys" | "constraints") =>
kind === "foreignKeys"
? foreignKeyTableIdentity({
connectionId: props.connectionId,
database: props.database,
catalog: props.tableMeta?.catalog,
schema: props.tableMeta?.schema,
tableName: props.tableMeta?.tableName,
})
: columnIndexTableIdentity({
connectionId: props.connectionId,
database: props.database,
catalog: props.tableMeta?.catalog,
schema: props.tableMeta?.schema,
tableName: props.tableMeta?.tableName,
});
const tableRequest = () => {
if (!props.connectionId || !props.tableMeta) return undefined;
return {
connectionId: props.connectionId,
database: props.database || "",
schema: props.tableMeta.schema || props.database || "",
tableName: props.tableMeta.tableName,
catalog: props.tableMeta.catalog,
objectType: tableObjectSourceKind(props.tableMeta.tableType),
};
};
const currentIndexTableIdentity = computed(() => currentTableIdentity("indexes"));
const currentForeignKeyTableIdentity = computed(() => currentTableIdentity("foreignKeys"));
const currentConstraintTableIdentity = computed(() => currentTableIdentity("constraints"));
async function fetchDdl(force = options.settingsStore.editorSettings.refreshDdlOnOpen) {
const request = tableRequest();
if (!request) return;
options.showTableInfo.value = true;
state.ddlLoading.value = true;
try {
const { ddl } = await loadObjectDdl(request, { force });
const formatDialect = sqlFormatDialectForDbType(options.resolvedDatabaseType.value);
state.ddlContent.value = options.settingsStore.editorSettings.generateSqlQuoteIdentifiers ? ddl : omitDdlIdentifierQuotes(ddl, formatDialect);
} catch (error: any) {
state.ddlContent.value = `-- Error: ${error}`;
} finally {
state.ddlLoading.value = false;
}
}
async function fetchTableInfoColumns(force = false) {
const request = tableRequest();
if (!request) return;
const requestGeneration = ++state.tableInfoColumnsRequestGeneration.value;
state.tableInfoColumnsLoading.value = true;
try {
const { value: columns } = await loadObjectMetadataFacet(request, "columns", () => api.getColumns(request.connectionId, request.database, request.schema, request.tableName, request.catalog), { force });
if (requestGeneration !== state.tableInfoColumnsRequestGeneration.value) return;
state.tableInfoColumns.value = columns;
} catch (error) {
if (requestGeneration !== state.tableInfoColumnsRequestGeneration.value) return;
options.toast(options.formatBackendError(error), 5000);
} finally {
if (requestGeneration === state.tableInfoColumnsRequestGeneration.value) state.tableInfoColumnsLoading.value = false;
}
}
async function fetchTableOwner(force = false) {
if (options.resolvedDatabaseType.value !== "postgres" || !props.connectionId || !props.database || !props.tableMeta?.schema || !props.tableMeta.tableName) return;
const request = tableRequest();
if (!request) return;
const requestGeneration = ++state.tableOwnerRequestGeneration.value;
state.tableOwnerLoading.value = true;
state.tableOwnerError.value = "";
try {
const result = await loadObjectMetadataFacet(request, "owner", () => api.getTableOwner(request.connectionId, request.database, request.schema, request.tableName), { force });
if (requestGeneration !== state.tableOwnerRequestGeneration.value) return;
state.tableOwner.value = result.value;
} catch (error: any) {
if (requestGeneration !== state.tableOwnerRequestGeneration.value) return;
state.tableOwner.value = null;
state.tableOwnerError.value = error?.message || String(error);
} finally {
if (requestGeneration === state.tableOwnerRequestGeneration.value) state.tableOwnerLoading.value = false;
}
}
async function fetchIndexes() {
const request = tableRequest();
const requestIdentity = currentTableIdentity("indexes");
if (!request || !requestIdentity || !options.canShowTableIndexes.value || state.indexesLoaded.value || state.indexesLoading.value) return;
const requestGeneration = ++state.indexesRequestGeneration.value;
await runMetadataRequest({
loading: state.indexesLoading,
error: state.indexesError,
requestGeneration,
currentGeneration: () => state.indexesRequestGeneration.value,
isCurrent: () =>
columnIndexMetadataRequestCurrent({
requestGeneration,
currentGeneration: state.indexesRequestGeneration.value,
requestIdentity,
currentIdentity: currentTableIdentity("indexes"),
}),
load: () => api.listIndexes(request.connectionId, request.database, request.schema, request.tableName, request.catalog),
onSuccess: (value) => {
state.indexes.value = value;
state.indexesLoaded.value = true;
},
});
}
async function reloadIndexes() {
state.indexesLoaded.value = false;
await fetchIndexes();
}
async function refreshMongoIndexMetadataAfterMutation() {
await reloadIndexes();
const connectionId = props.connectionId;
const database = props.database;
const collection = props.tableMeta?.tableName;
if (!connectionId || !database || !collection) return;
try {
await refreshLoadedMongoIndexes(options.connectionStore, { connectionId, database, collection });
} catch (error: any) {
options.toastMongoIndexRefreshError(String(error?.message || error));
}
}
async function fetchForeignKeys() {
const request = tableRequest();
const requestIdentity = currentTableIdentity("foreignKeys");
if (!request || !requestIdentity || state.foreignKeysLoaded.value || state.foreignKeysLoading.value) return;
const requestGeneration = ++state.foreignKeysRequestGeneration.value;
await runMetadataRequest({
loading: state.foreignKeysLoading,
error: state.foreignKeysError,
requestGeneration,
currentGeneration: () => state.foreignKeysRequestGeneration.value,
isCurrent: () =>
foreignKeyMetadataRequestCurrent({
requestGeneration,
currentGeneration: state.foreignKeysRequestGeneration.value,
requestIdentity,
currentIdentity: currentTableIdentity("foreignKeys"),
}),
load: () => api.listForeignKeys(request.connectionId, request.database, request.schema, request.tableName, request.catalog),
onSuccess: (value) => {
state.foreignKeys.value = value;
state.foreignKeysLoaded.value = true;
},
});
}
async function fetchTriggers() {
const request = tableRequest();
if (!request || state.triggersLoaded.value || state.triggersLoading.value) return;
state.triggersLoading.value = true;
state.triggersError.value = "";
try {
state.triggers.value = await api.listTriggers(request.connectionId, request.database, request.schema, request.tableName, request.catalog);
state.triggersLoaded.value = true;
} catch (error: any) {
state.triggersError.value = String(error?.message || error);
} finally {
state.triggersLoading.value = false;
}
}
async function fetchConstraints() {
const request = tableRequest();
const requestIdentity = currentTableIdentity("constraints");
if (!request || !requestIdentity || state.constraintsLoaded.value || state.constraintsLoading.value) return;
const requestGeneration = ++state.constraintsRequestGeneration.value;
await runMetadataRequest({
loading: state.constraintsLoading,
error: state.constraintsError,
requestGeneration,
currentGeneration: () => state.constraintsRequestGeneration.value,
isCurrent: () =>
columnIndexMetadataRequestCurrent({
requestGeneration,
currentGeneration: state.constraintsRequestGeneration.value,
requestIdentity,
currentIdentity: currentTableIdentity("constraints"),
}),
load: () => api.listConstraints(request.connectionId, request.database, request.schema, request.tableName, request.catalog),
onSuccess: (value) => {
state.constraints.value = value;
state.constraintsLoaded.value = true;
},
});
}
return {
currentIndexTableIdentity,
currentForeignKeyTableIdentity,
currentConstraintTableIdentity,
fetchDdl,
fetchTableInfoColumns,
fetchTableOwner,
fetchIndexes,
reloadIndexes,
refreshMongoIndexMetadataAfterMutation,
fetchForeignKeys,
fetchTriggers,
fetchConstraints,
};
}
interface MetadataRequest<T> {
loading: Ref<boolean>;
error: Ref<string>;
requestGeneration: number;
currentGeneration: () => number;
isCurrent: () => boolean;
load: () => Promise<T>;
onSuccess: (value: T) => void;
}
async function runMetadataRequest<T>(request: MetadataRequest<T>) {
request.loading.value = true;
request.error.value = "";
try {
const value = await request.load();
if (!request.isCurrent()) return;
request.onSuccess(value);
} catch (error: any) {
if (request.isCurrent()) request.error.value = String(error?.message || error);
} finally {
if (request.isCurrent() && request.currentGeneration() === request.requestGeneration) request.loading.value = false;
}
}