feat(schema): show table properties in object browser

This commit is contained in:
二丫讲梵 authored and GitHub committed 2026-09-23 14:43:33 +08:00
1 parent 12ed7a49b3
commit 13d2db6191
22 files changed
+284 -12

No files matched your search

@@ -59,6 +59,18 @@ const overviewRows = computed(() => {
{ label: t("structureEditor.comment"), value: props.overviewComment ?? "" },
{ label: t("grid.tableInfoEstimatedRows"), value: formatObjectBrowserCount(props.overviewStats?.estimated_rows), mono: true },
{ label: t("grid.tableInfoTotalSize"), value: formatObjectBrowserBytes(props.overviewStats?.total_bytes), mono: true },
{ label: t("grid.tableInfoDataLength"), value: formatObjectBrowserBytes(props.overviewStats?.data_length), mono: true },
{ label: t("grid.tableInfoEngine"), value: props.overviewStats?.engine ?? "", mono: true },
{ label: t("grid.tableInfoCreatedAt"), value: props.overviewStats?.created_at ?? "", mono: true },
{ label: t("grid.tableInfoUpdatedAt"), value: props.overviewStats?.updated_at ?? "", mono: true },
{ label: t("grid.tableInfoCollation"), value: props.overviewStats?.collation ?? "", mono: true },
{ label: t("grid.tableInfoRowFormat"), value: props.overviewStats?.row_format ?? "", mono: true },
{ label: t("grid.tableInfoAvgRowLength"), value: formatObjectBrowserBytes(props.overviewStats?.avg_row_length), mono: true },
{ label: t("grid.tableInfoMaxDataLength"), value: formatObjectBrowserBytes(props.overviewStats?.max_data_length), mono: true },
{ label: t("grid.tableInfoCheckTime"), value: props.overviewStats?.check_time ?? "", mono: true },
{ label: t("grid.tableInfoIndexLength"), value: formatObjectBrowserBytes(props.overviewStats?.index_length), mono: true },
{ label: t("grid.tableInfoAutoIncrement"), value: props.overviewStats?.auto_increment ?? "", mono: true },
{ label: t("grid.tableInfoDataFree"), value: formatObjectBrowserBytes(props.overviewStats?.data_free), mono: true },
];
const query = props.searchQuery.trim().toLowerCase();
return rows.filter((row) => row.value && (!query || row.label.toLowerCase().includes(query) || row.value.toLowerCase().includes(query)));
@@ -78,6 +78,7 @@ import { isSchemaAware, supportsTableVacuum, supportsTransfer } from "@/lib/data
import { supportsAiAssistantContext, supportsSchemaDiagram, supportsTableImport, supportsTableStructureEditing, supportsTableTruncate } from "@/lib/database/databaseFeatureSupport";
import { codeMirrorSqlDialect, connectionObjectTreeNodeSchema, connectionTableSqlSchema, connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection, objectListSchemaForConnection, tableStructureDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
import { getTableMetadataCapabilities, type TableMetadataCapabilities } from "@/lib/table/tableMetadataCapabilities";
import { findTableStatistics } from "@/lib/dataGrid/tableInfoOverview";
import { constraintsForConstraintsTab } from "@/lib/table/constraintPresentation";
import { buildTableSelectSql } from "@/lib/table/tableSelectSql";
import { PARTITION_TREE_INDENT_PX } from "@/lib/table/pgPartitionPresentation";
@@ -248,7 +249,11 @@ const eventEditorKey = computed(() =>
}),
);
// Table info panel state
const tableInfoTab = ref<TableInfoTab>("ddl");
const tableInfoTab = ref<TableInfoTab>("info");
const tableOverviewStats = ref<ObjectStatistics | null>(null);
const tableOverviewComment = ref<string | null>(null);
const tableOverviewLoading = ref(false);
const tableOverviewLoaded = ref(false);
const tableColumns = ref<ColumnInfo[]>([]);
const tableColumnsLoading = ref(false);
const tableColumnsLoaded = ref(false);
@@ -281,6 +286,7 @@ const tableConstraintsForTab = computed(() => constraintsForConstraintsTab(table
const tableInfoSearchQuery = ref("");
const tableInfoDdlPreRef = ref<HTMLPreElement | null>(null);
const activeTableInfoLoading = computed(() => {
if (tableInfoTab.value === "info") return tableOverviewLoading.value;
if (tableInfoTab.value === "ddl") return tableDdlLoading.value;
if (tableInfoTab.value === "columns") return tableColumnsLoading.value;
if (tableInfoTab.value === "indexes") return tableIndexesLoading.value;
@@ -1063,6 +1069,7 @@ type TableInfoTabItem = { id: TableInfoTab; label: string; icon: Component; coun
const tableInfoTabs = computed<TableInfoTabItem[]>(() => {
const tabs: TableInfoTabItem[] = [];
tabs.push({ id: "info", label: t("grid.tableInfoOverview"), icon: Info });
if (tableMetadataCapabilities.value.ddl) {
tabs.push({ id: "ddl", label: "DDL", icon: Code2 });
}
@@ -1092,6 +1099,31 @@ const tableInfoTabListStyle = computed(() => ({
}));
const filteredTableColumns = computed(() => filterObjectBrowserTableColumns(tableColumns.value, tableInfoSearchQuery.value));
const tableOverviewRows = computed(() => {
const stats = tableOverviewStats.value;
const rows = [
{ label: t("common.table"), value: sidePanelRow.value?.name ?? "" },
{ label: t("common.schema"), value: sidePanelRow.value?.schema || selectedSchema.value || props.database },
{ label: t("common.database"), value: props.database },
{ label: t("structureEditor.comment"), value: tableOverviewComment.value ?? "" },
{ label: t("grid.tableInfoEstimatedRows"), value: formatObjectBrowserCount(stats?.estimated_rows) },
{ label: t("grid.tableInfoTotalSize"), value: formatObjectBrowserBytes(stats?.total_bytes) },
{ label: t("grid.tableInfoDataLength"), value: formatObjectBrowserBytes(stats?.data_length) },
{ label: t("grid.tableInfoEngine"), value: stats?.engine ?? "" },
{ label: t("grid.tableInfoCreatedAt"), value: stats?.created_at ?? "" },
{ label: t("grid.tableInfoUpdatedAt"), value: stats?.updated_at ?? "" },
{ label: t("grid.tableInfoCollation"), value: stats?.collation ?? "" },
{ label: t("grid.tableInfoRowFormat"), value: stats?.row_format ?? "" },
{ label: t("grid.tableInfoAvgRowLength"), value: formatObjectBrowserBytes(stats?.avg_row_length) },
{ label: t("grid.tableInfoMaxDataLength"), value: formatObjectBrowserBytes(stats?.max_data_length) },
{ label: t("grid.tableInfoCheckTime"), value: stats?.check_time ?? "" },
{ label: t("grid.tableInfoIndexLength"), value: formatObjectBrowserBytes(stats?.index_length) },
{ label: t("grid.tableInfoAutoIncrement"), value: stats?.auto_increment ?? "" },
{ label: t("grid.tableInfoDataFree"), value: formatObjectBrowserBytes(stats?.data_free) },
];
const query = tableInfoSearchQuery.value.trim().toLowerCase();
return rows.filter((row) => row.value && (!query || row.label.toLowerCase().includes(query) || row.value.toLowerCase().includes(query)));
});
const filteredTableIndexes = computed(() => {
if (!tableInfoSearchQuery.value) return tableIndexes.value;
@@ -1140,6 +1172,9 @@ async function openTableInfo(row: ObjectBrowserRow, initialTab?: TableInfoTab) {
sidePanelGuard.bump();
// Reset state
tableColumns.value = [];
tableOverviewStats.value = null;
tableOverviewComment.value = null;
tableOverviewLoaded.value = false;
rawTableDdlContent.value = "";
tableIndexes.value = [];
tableForeignKeys.value = [];
@@ -1173,7 +1208,8 @@ async function selectTableInfoTab(tab: TableInfoTab) {
if (!nextTab) return;
tableInfoTab.value = nextTab;
tableInfoSearchQuery.value = "";
if (nextTab === "ddl") await fetchTableDdl();
if (nextTab === "info") await fetchTableOverview();
else if (nextTab === "ddl") await fetchTableDdl();
else if (nextTab === "columns") await fetchTableColumns();
else if (nextTab === "indexes") await fetchTableIndexes();
else if (nextTab === "foreignKeys") await fetchTableForeignKeys();
@@ -1182,6 +1218,23 @@ async function selectTableInfoTab(tab: TableInfoTab) {
else if (nextTab === "partitions") await fetchTablePartitions();
}
async function fetchTableOverview(force = false) {
const row = sidePanelRow.value;
if (!row || (!force && tableOverviewLoaded.value)) return;
const epoch = sidePanelGuard.capture();
const schema = row.schema || selectedSchema.value || props.database;
tableOverviewLoading.value = true;
try {
const [statistics, comment] = await Promise.all([api.listObjectStatistics(props.connection.id, props.database, schema).catch(() => [] as ObjectStatistics[]), api.getTableComment(props.connection.id, props.database, schema, row.name, props.catalog).catch(() => null)]);
if (sidePanelGuard.isStale(epoch)) return;
tableOverviewStats.value = findTableStatistics(statistics, row.name, schema) ?? null;
tableOverviewComment.value = comment;
tableOverviewLoaded.value = true;
} finally {
if (sidePanelGuard.isFresh(epoch)) tableOverviewLoading.value = false;
}
}
function tableMetadataRequest(row: ObjectBrowserRow): ObjectDdlRequest {
return {
connectionId: props.connection.id,
@@ -1392,7 +1445,12 @@ async function refreshActiveTableInfo() {
if (sidePanelMode.value !== "table-info" || !sidePanelRow.value) return;
sidePanelGuard.bump();
if (tableInfoTab.value === "ddl") {
if (tableInfoTab.value === "info") {
tableOverviewStats.value = null;
tableOverviewComment.value = null;
tableOverviewLoaded.value = false;
await fetchTableOverview(true);
} else if (tableInfoTab.value === "ddl") {
rawTableDdlContent.value = "";
tableDdlLoaded.value = false;
await fetchTableDdl(true);
@@ -3893,7 +3951,17 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
<div v-if="tableInfoTab === 'ddl' && effectiveDatabaseType === 'oceanbase-oracle'" class="border-b px-3 py-2">
<DdlStorageToggle :database-type="effectiveDatabaseType" :disabled="tableDdlLoading" />
</div>
<div v-if="tableInfoTab === 'columns'" class="flex-1 min-h-0 overflow-auto">
<div v-if="tableInfoTab === 'info'" class="flex-1 min-h-0 overflow-auto">
<div v-if="tableOverviewLoading" class="h-full flex items-center justify-center"><Loader2 class="w-4 h-4 animate-spin text-muted-foreground" /></div>
<div v-else-if="tableInfoSearchQuery && tableOverviewRows.length === 0" class="p-6 text-center text-xs text-muted-foreground">{{ t("grid.tableInfoNoResults") }}</div>
<div v-else class="divide-y">
<div v-for="row in tableOverviewRows" :key="row.label" class="flex items-baseline gap-3 px-3 py-2 text-xs">
<span class="w-24 shrink-0 text-muted-foreground">{{ row.label }}</span>
<span class="min-w-0 flex-1 select-text break-words font-mono text-[11px]" :title="row.value">{{ row.value }}</span>
</div>
</div>
</div>
<div v-else-if="tableInfoTab === 'columns'" class="flex-1 min-h-0 overflow-auto">
<div v-if="tableColumnsLoading" class="h-full flex items-center justify-center">
<Loader2 class="w-4 h-4 animate-spin text-muted-foreground" />
</div>
+12
View File
@@ -2345,6 +2345,18 @@ export default withEnglishFallback({
tableInfoOverview: "Ümumi baxış",
tableInfoEstimatedRows: "Təxmini sətirlər",
tableInfoTotalSize: "Ümumi həcm",
tableInfoDataLength: "Məlumat uzunluğu",
tableInfoEngine: "Mühərrik",
tableInfoCreatedAt: "Yaradılma tarixi",
tableInfoUpdatedAt: "Dəyişdirilmə tarixi",
tableInfoCollation: "Müqayisə qaydası",
tableInfoRowFormat: "Sətir formatı",
tableInfoAvgRowLength: "Orta sətir uzunluğu",
tableInfoMaxDataLength: "Maksimum məlumat uzunluğu",
tableInfoCheckTime: "Yoxlanma vaxtı",
tableInfoIndexLength: "İndeks uzunluğu",
tableInfoAutoIncrement: "Avtomatik artım",
tableInfoDataFree: "Boş məlumat sahəsi",
pinTableInfo: "Cədvəl məlumatları panelini bərkit",
unpinTableInfo: "Cədvəl məlumatları panelinin bərkidilməsini ləğv et",
tableInfoColumns: "Sütunlar",
+12
View File
@@ -2579,6 +2579,18 @@ export default {
tableInfoOverview: "Overview",
tableInfoEstimatedRows: "Estimated rows",
tableInfoTotalSize: "Total size",
tableInfoDataLength: "Data length",
tableInfoEngine: "Engine",
tableInfoCreatedAt: "Created at",
tableInfoUpdatedAt: "Updated at",
tableInfoCollation: "Collation",
tableInfoRowFormat: "Row format",
tableInfoAvgRowLength: "Average row length",
tableInfoMaxDataLength: "Maximum data length",
tableInfoCheckTime: "Check time",
tableInfoIndexLength: "Index length",
tableInfoAutoIncrement: "Auto increment",
tableInfoDataFree: "Data free",
pinTableInfo: "Pin table info panel",
unpinTableInfo: "Unpin table info panel",
tableInfoColumns: "Columns",
+12
View File
@@ -2420,6 +2420,18 @@ export default withEnglishFallback({
tableInfoOverview: "Resumen",
tableInfoEstimatedRows: "Filas estimadas",
tableInfoTotalSize: "Tamaño total",
tableInfoDataLength: "Longitud de datos",
tableInfoEngine: "Motor",
tableInfoCreatedAt: "Fecha de creación",
tableInfoUpdatedAt: "Fecha de modificación",
tableInfoCollation: "Intercalación",
tableInfoRowFormat: "Formato de fila",
tableInfoAvgRowLength: "Longitud media de fila",
tableInfoMaxDataLength: "Longitud máxima de datos",
tableInfoCheckTime: "Fecha de comprobación",
tableInfoIndexLength: "Longitud de índices",
tableInfoAutoIncrement: "Incremento automático",
tableInfoDataFree: "Espacio libre de datos",
pinTableInfo: "Fijar el panel de información de la tabla",
unpinTableInfo: "Desfijar el panel de información de la tabla",
tableInfoColumns: "Columnas",
+12
View File
@@ -2406,6 +2406,18 @@ export default withEnglishFallback({
tableInfoOverview: "Panoramica",
tableInfoEstimatedRows: "Righe stimate",
tableInfoTotalSize: "Dimensione totale",
tableInfoDataLength: "Lunghezza dati",
tableInfoEngine: "Motore",
tableInfoCreatedAt: "Data di creazione",
tableInfoUpdatedAt: "Data di modifica",
tableInfoCollation: "Regole di confronto",
tableInfoRowFormat: "Formato riga",
tableInfoAvgRowLength: "Lunghezza media riga",
tableInfoMaxDataLength: "Lunghezza massima dati",
tableInfoCheckTime: "Data di controllo",
tableInfoIndexLength: "Lunghezza indici",
tableInfoAutoIncrement: "Incremento automatico",
tableInfoDataFree: "Spazio dati libero",
pinTableInfo: "Blocca il pannello delle informazioni sulla tabella",
unpinTableInfo: "Sblocca il pannello delle informazioni sulla tabella",
tableInfoColumns: "Colonne",
+12
View File
@@ -2417,6 +2417,18 @@ export default withEnglishFallback({
tableInfoOverview: "概要",
tableInfoEstimatedRows: "推定行数",
tableInfoTotalSize: "合計サイズ",
tableInfoDataLength: "データ長",
tableInfoEngine: "エンジン",
tableInfoCreatedAt: "作成日時",
tableInfoUpdatedAt: "更新日時",
tableInfoCollation: "照合順序",
tableInfoRowFormat: "行形式",
tableInfoAvgRowLength: "平均行長",
tableInfoMaxDataLength: "最大データ長",
tableInfoCheckTime: "検査日時",
tableInfoIndexLength: "インデックス長",
tableInfoAutoIncrement: "自動増分",
tableInfoDataFree: "空き領域",
pinTableInfo: "テーブル情報パネルを固定",
unpinTableInfo: "テーブル情報パネルの固定を解除",
tableInfoColumns: "列",
+12
View File
@@ -2400,6 +2400,18 @@ export default withEnglishFallback({
tableInfoOverview: "개요",
tableInfoEstimatedRows: "예상 행 수",
tableInfoTotalSize: "전체 크기",
tableInfoDataLength: "데이터 길이",
tableInfoEngine: "엔진",
tableInfoCreatedAt: "생성 일시",
tableInfoUpdatedAt: "수정 일시",
tableInfoCollation: "정렬 규칙",
tableInfoRowFormat: "행 형식",
tableInfoAvgRowLength: "평균 행 길이",
tableInfoMaxDataLength: "최대 데이터 길이",
tableInfoCheckTime: "검사 일시",
tableInfoIndexLength: "인덱스 길이",
tableInfoAutoIncrement: "자동 증가",
tableInfoDataFree: "사용 가능한 공간",
pinTableInfo: "테이블 정보 패널 고정",
unpinTableInfo: "테이블 정보 패널 고정 해제",
tableInfoColumns: "컬럼",
+12
View File
@@ -2408,6 +2408,18 @@ export default withEnglishFallback({
tableInfoOverview: "Visão geral",
tableInfoEstimatedRows: "Linhas estimadas",
tableInfoTotalSize: "Tamanho total",
tableInfoDataLength: "Tamanho dos dados",
tableInfoEngine: "Mecanismo",
tableInfoCreatedAt: "Data de criação",
tableInfoUpdatedAt: "Data de modificação",
tableInfoCollation: "Ordenação",
tableInfoRowFormat: "Formato da linha",
tableInfoAvgRowLength: "Tamanho médio da linha",
tableInfoMaxDataLength: "Tamanho máximo dos dados",
tableInfoCheckTime: "Data de verificação",
tableInfoIndexLength: "Tamanho dos índices",
tableInfoAutoIncrement: "Incremento automático",
tableInfoDataFree: "Espaço livre de dados",
pinTableInfo: "Fixar painel de informações da tabela",
unpinTableInfo: "Desafixar painel de informações da tabela",
tableInfoColumns: "Colunas",
+12
View File
@@ -2340,6 +2340,18 @@ export default withEnglishFallback({
tableInfoOverview: "Genel bakış",
tableInfoEstimatedRows: "Tahmini satır",
tableInfoTotalSize: "Toplam boyut",
tableInfoDataLength: "Veri uzunluğu",
tableInfoEngine: "Motor",
tableInfoCreatedAt: "Oluşturulma tarihi",
tableInfoUpdatedAt: "Değiştirilme tarihi",
tableInfoCollation: "Karşılaştırma düzeni",
tableInfoRowFormat: "Satır biçimi",
tableInfoAvgRowLength: "Ortalama satır uzunluğu",
tableInfoMaxDataLength: "En fazla veri uzunluğu",
tableInfoCheckTime: "Denetim zamanı",
tableInfoIndexLength: "Dizin uzunluğu",
tableInfoAutoIncrement: "Otomatik artış",
tableInfoDataFree: "Boş veri alanı",
pinTableInfo: "Tablo bilgileri panelini sabitle",
unpinTableInfo: "Tablo bilgileri panelinin sabitlemesini kaldır",
tableInfoColumns: "Sütunlar",
+12
View File
@@ -2492,6 +2492,18 @@ export default withEnglishFallback({
tableInfoOverview: "表信息",
tableInfoEstimatedRows: "估算行数",
tableInfoTotalSize: "总大小",
tableInfoDataLength: "数据长度",
tableInfoEngine: "引擎",
tableInfoCreatedAt: "创建日期",
tableInfoUpdatedAt: "修改日期",
tableInfoCollation: "排序规则",
tableInfoRowFormat: "行格式",
tableInfoAvgRowLength: "平均长度",
tableInfoMaxDataLength: "最大数据长度",
tableInfoCheckTime: "检查时间",
tableInfoIndexLength: "索引长度",
tableInfoAutoIncrement: "自动递增",
tableInfoDataFree: "数据可用空间",
pinTableInfo: "固定表属性面板",
unpinTableInfo: "取消固定表属性面板",
tableInfoColumns: "字段",
+12
View File
@@ -2411,6 +2411,18 @@ export default withEnglishFallback({
tableInfoOverview: "資料表資訊",
tableInfoEstimatedRows: "預估列數",
tableInfoTotalSize: "總大小",
tableInfoDataLength: "資料長度",
tableInfoEngine: "引擎",
tableInfoCreatedAt: "建立日期",
tableInfoUpdatedAt: "修改日期",
tableInfoCollation: "定序規則",
tableInfoRowFormat: "列格式",
tableInfoAvgRowLength: "平均長度",
tableInfoMaxDataLength: "最大資料長度",
tableInfoCheckTime: "檢查時間",
tableInfoIndexLength: "索引長度",
tableInfoAutoIncrement: "自動遞增",
tableInfoDataFree: "資料可用空間",
pinTableInfo: "固定資料表屬性面板",
unpinTableInfo: "取消固定資料表屬性面板",
tableInfoColumns: "欄位",
+12
View File
@@ -857,6 +857,18 @@ export interface ObjectStatistics {
schema?: string | null;
estimated_rows?: number | null;
total_bytes?: number | null;
data_length?: number | null;
engine?: string | null;
created_at?: string | null;
updated_at?: string | null;
collation?: string | null;
row_format?: string | null;
avg_row_length?: number | null;
max_data_length?: number | null;
check_time?: string | null;
index_length?: number | null;
auto_increment?: string | null;
data_free?: number | null;
}
export type ObjectSourceKind = "VIEW" | "MATERIALIZED_VIEW" | "PROCEDURE" | "FUNCTION" | "TRIGGER" | "EVENT" | "SEQUENCE" | "SYNONYM" | "JOB" | "PACKAGE" | "PACKAGE_BODY" | "TYPE" | "TYPE_BODY";