From 13d2db61912a4fd77bc6d3671170faf048fa564f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E4=B8=AB=E8=AE=B2=E6=A2=B5?= Date: Wed, 23 Sep 2026 14:43:33 +0800 Subject: [PATCH] feat(schema): show table properties in object browser --- .../grid/DataGridTableInfoPanels.vue | 12 +++ .../src/components/objects/ObjectBrowser.vue | 76 ++++++++++++++++++- apps/desktop/src/i18n/locales/az.ts | 12 +++ apps/desktop/src/i18n/locales/en.ts | 12 +++ apps/desktop/src/i18n/locales/es.ts | 12 +++ apps/desktop/src/i18n/locales/it.ts | 12 +++ apps/desktop/src/i18n/locales/ja.ts | 12 +++ apps/desktop/src/i18n/locales/ko.ts | 12 +++ apps/desktop/src/i18n/locales/pt-BR.ts | 12 +++ apps/desktop/src/i18n/locales/tr.ts | 12 +++ apps/desktop/src/i18n/locales/zh-CN.ts | 12 +++ apps/desktop/src/i18n/locales/zh-TW.ts | 12 +++ apps/desktop/src/types/database.ts | 12 +++ crates/dbx-core/src/schema/mod.rs | 6 +- .../dbx-drivers/src/db/clickhouse_driver.rs | 1 + crates/dbx-drivers/src/db/mongo_driver.rs | 1 + crates/dbx-drivers/src/db/mysql.rs | 37 +++++++-- crates/dbx-drivers/src/db/postgres.rs | 1 + crates/dbx-drivers/src/db/sqlserver.rs | 1 + .../src/db/victoriametrics_driver.rs | 1 + crates/dbx-mcp/src/http.rs | 2 +- crates/dbx-types/src/types.rs | 26 ++++++- 22 files changed, 284 insertions(+), 12 deletions(-) diff --git a/apps/desktop/src/components/grid/DataGridTableInfoPanels.vue b/apps/desktop/src/components/grid/DataGridTableInfoPanels.vue index 9347f8510d..2d10e49e47 100644 --- a/apps/desktop/src/components/grid/DataGridTableInfoPanels.vue +++ b/apps/desktop/src/components/grid/DataGridTableInfoPanels.vue @@ -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))); diff --git a/apps/desktop/src/components/objects/ObjectBrowser.vue b/apps/desktop/src/components/objects/ObjectBrowser.vue index 252144bbbc..619d9cfd5a 100644 --- a/apps/desktop/src/components/objects/ObjectBrowser.vue +++ b/apps/desktop/src/components/objects/ObjectBrowser.vue @@ -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("ddl"); +const tableInfoTab = ref("info"); +const tableOverviewStats = ref(null); +const tableOverviewComment = ref(null); +const tableOverviewLoading = ref(false); +const tableOverviewLoaded = ref(false); const tableColumns = ref([]); const tableColumnsLoading = ref(false); const tableColumnsLoaded = ref(false); @@ -281,6 +286,7 @@ const tableConstraintsForTab = computed(() => constraintsForConstraintsTab(table const tableInfoSearchQuery = ref(""); const tableInfoDdlPreRef = ref(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(() => { 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[] {
-
+
+
+
{{ t("grid.tableInfoNoResults") }}
+
+
+ {{ row.label }} + {{ row.value }} +
+
+
+
diff --git a/apps/desktop/src/i18n/locales/az.ts b/apps/desktop/src/i18n/locales/az.ts index 1899190612..5db8f06718 100644 --- a/apps/desktop/src/i18n/locales/az.ts +++ b/apps/desktop/src/i18n/locales/az.ts @@ -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", diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index 150392dd25..e44a8c473b 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -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", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index 0bfe0991b4..afac743e31 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -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", diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index f3cf5954f5..c0c6a8fc1a 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -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", diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index c3a5a5e1ef..949d239461 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -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: "列", diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts index e3b43a171c..26164b54fa 100644 --- a/apps/desktop/src/i18n/locales/ko.ts +++ b/apps/desktop/src/i18n/locales/ko.ts @@ -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: "컬럼", diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index afa81474bc..e8156cfcf2 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -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", diff --git a/apps/desktop/src/i18n/locales/tr.ts b/apps/desktop/src/i18n/locales/tr.ts index 51db87722e..d7cddf6c0b 100644 --- a/apps/desktop/src/i18n/locales/tr.ts +++ b/apps/desktop/src/i18n/locales/tr.ts @@ -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", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index 143a6a1345..e967a5ff9f 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -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: "字段", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index e07fc7e1a4..c4fb75e0b7 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -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: "欄位", diff --git a/apps/desktop/src/types/database.ts b/apps/desktop/src/types/database.ts index b153880ba9..5510399cbe 100644 --- a/apps/desktop/src/types/database.ts +++ b/apps/desktop/src/types/database.ts @@ -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"; diff --git a/crates/dbx-core/src/schema/mod.rs b/crates/dbx-core/src/schema/mod.rs index a7f1104f03..b3b6ef64bd 100644 --- a/crates/dbx-core/src/schema/mod.rs +++ b/crates/dbx-core/src/schema/mod.rs @@ -1873,6 +1873,7 @@ fn oracle_object_statistics_from_query_result(result: db::QueryResult) -> Vec Ok(vec![]), diff --git a/crates/dbx-drivers/src/db/clickhouse_driver.rs b/crates/dbx-drivers/src/db/clickhouse_driver.rs index 3208903353..d605c447b3 100644 --- a/crates/dbx-drivers/src/db/clickhouse_driver.rs +++ b/crates/dbx-drivers/src/db/clickhouse_driver.rs @@ -554,6 +554,7 @@ fn clickhouse_object_statistics_from_row(row: &[serde_json::Value], database: &s schema: Some(database.to_string()), estimated_rows: json_value_as_i64(row.get(1)), total_bytes: json_value_as_i64(row.get(2)), + ..Default::default() }) } diff --git a/crates/dbx-drivers/src/db/mongo_driver.rs b/crates/dbx-drivers/src/db/mongo_driver.rs index 120dc56cce..e096defa63 100644 --- a/crates/dbx-drivers/src/db/mongo_driver.rs +++ b/crates/dbx-drivers/src/db/mongo_driver.rs @@ -321,6 +321,7 @@ pub fn object_statistics_from_collection_stats(name: &str, database: &str, resul (None, None) => None, (data, index) => Some(data.unwrap_or(0).saturating_add(index.unwrap_or(0))), }, + ..Default::default() } } diff --git a/crates/dbx-drivers/src/db/mysql.rs b/crates/dbx-drivers/src/db/mysql.rs index 91e2e03d83..b0c2833a65 100644 --- a/crates/dbx-drivers/src/db/mysql.rs +++ b/crates/dbx-drivers/src/db/mysql.rs @@ -3820,10 +3820,18 @@ pub async fn list_objects_with_logical_tables( Ok(PagedObjectList { objects, paging_applied }) } -pub async fn list_object_statistics(pool: &MySqlPool, database: &str) -> Result, String> { +pub async fn list_object_statistics( + pool: &MySqlPool, + database: &str, + include_mysql_details: bool, +) -> Result, String> { + let columns = if include_mysql_details { + "TABLE_NAME, TABLE_ROWS, DATA_LENGTH, ENGINE, CREATE_TIME, UPDATE_TIME, TABLE_COLLATION, ROW_FORMAT, AVG_ROW_LENGTH, MAX_DATA_LENGTH, CHECK_TIME, INDEX_LENGTH, AUTO_INCREMENT, DATA_FREE, COALESCE(DATA_LENGTH, 0) + COALESCE(INDEX_LENGTH, 0) AS TOTAL_BYTES" + } else { + "TABLE_NAME, TABLE_ROWS, COALESCE(DATA_LENGTH, 0) + COALESCE(INDEX_LENGTH, 0) AS TOTAL_BYTES" + }; let sql = format!( - "SELECT TABLE_NAME, TABLE_ROWS, COALESCE(DATA_LENGTH, 0) + COALESCE(INDEX_LENGTH, 0) AS TOTAL_BYTES \ - FROM information_schema.TABLES \ + "SELECT {columns} FROM information_schema.TABLES \ WHERE TABLE_SCHEMA = {} AND TABLE_TYPE <> 'VIEW' \ ORDER BY TABLE_NAME", quote_value(database), @@ -3838,12 +3846,31 @@ pub async fn list_object_statistics(pool: &MySqlPool, database: &str) -> Result< .iter() .filter_map(|row| { let name = get_str_by_name(row, "TABLE_NAME").trim().to_string(); - (!name.is_empty()).then_some(ObjectStatistics { + if name.is_empty() { + return None; + } + let mut statistics = ObjectStatistics { name, schema: Some(database.to_string()), estimated_rows: get_opt_i64(row, "TABLE_ROWS"), total_bytes: get_opt_i64(row, "TOTAL_BYTES"), - }) + ..Default::default() + }; + if include_mysql_details { + statistics.data_length = get_opt_i64(row, "DATA_LENGTH"); + statistics.engine = get_opt_str(row, "ENGINE"); + statistics.created_at = get_opt_metadata_string(row, "CREATE_TIME"); + statistics.updated_at = get_opt_metadata_string(row, "UPDATE_TIME"); + statistics.collation = get_opt_str(row, "TABLE_COLLATION"); + statistics.row_format = get_opt_str(row, "ROW_FORMAT"); + statistics.avg_row_length = get_opt_i64(row, "AVG_ROW_LENGTH"); + statistics.max_data_length = get_opt_i64(row, "MAX_DATA_LENGTH"); + statistics.check_time = get_opt_metadata_string(row, "CHECK_TIME"); + statistics.index_length = get_opt_i64(row, "INDEX_LENGTH"); + statistics.auto_increment = get_opt_unsigned_metadata_string(row, "AUTO_INCREMENT"); + statistics.data_free = get_opt_i64(row, "DATA_FREE"); + } + Some(statistics) }) .collect()) } diff --git a/crates/dbx-drivers/src/db/postgres.rs b/crates/dbx-drivers/src/db/postgres.rs index a251dfcce7..a672c778d1 100644 --- a/crates/dbx-drivers/src/db/postgres.rs +++ b/crates/dbx-drivers/src/db/postgres.rs @@ -7248,6 +7248,7 @@ pub async fn list_object_statistics(pool: &Pool, schema: &str) -> Result(1).ok(), total_bytes: row.try_get::<_, i64>(2).ok(), + ..Default::default() }) .collect()) } diff --git a/crates/dbx-drivers/src/db/sqlserver.rs b/crates/dbx-drivers/src/db/sqlserver.rs index eacd192c3c..3e573dbc40 100644 --- a/crates/dbx-drivers/src/db/sqlserver.rs +++ b/crates/dbx-drivers/src/db/sqlserver.rs @@ -2836,6 +2836,7 @@ pub async fn list_object_statistics( .ok() .flatten() .or_else(|| row.try_get::(2).ok().flatten().map(i64::from)), + ..Default::default() }) .filter(|stat| !stat.name.is_empty()) .collect()) diff --git a/crates/dbx-drivers/src/db/victoriametrics_driver.rs b/crates/dbx-drivers/src/db/victoriametrics_driver.rs index 876c182037..1c09df7e26 100644 --- a/crates/dbx-drivers/src/db/victoriametrics_driver.rs +++ b/crates/dbx-drivers/src/db/victoriametrics_driver.rs @@ -270,6 +270,7 @@ pub async fn list_object_statistics(client: &VictoriaMetricsClient) -> Result, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct ObjectStatistics { pub name: String, pub schema: Option, pub estimated_rows: Option, pub total_bytes: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub data_length: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub engine: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub created_at: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub updated_at: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub collation: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub row_format: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub avg_row_length: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub max_data_length: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub check_time: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub index_length: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub auto_increment: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub data_free: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]