diff --git a/apps/desktop/src/App.vue b/apps/desktop/src/App.vue
index 6ff4eb920e..c8df98c56d 100644
--- a/apps/desktop/src/App.vue
+++ b/apps/desktop/src/App.vue
@@ -3579,7 +3579,7 @@ onUnmounted(() => {
@view-table-ddl="(_tabId: string, target: SqlObjectNavigationTarget) => onViewTableDdl(target)"
@open-object-source="(_tabId: string, target: SqlObjectNavigationTarget, initialEditing: boolean) => onOpenObjectSource(target, initialEditing)"
@open-object-table="
- (tabId: string, target: { tableName: string; schema?: string; tableType?: string; catalog?: string }) => {
+ (tabId: string, target: { tableName: string; schema?: string; tableType?: string; catalog?: string; comment?: string | null }) => {
const tab = queryStore.tabs.find((candidate) => candidate.id === tabId) ?? activeTab;
if (!tab) return;
openObjectBrowserTableTarget({
@@ -3589,6 +3589,7 @@ onUnmounted(() => {
catalog: target.catalog,
tableName: target.tableName,
tableType: target.tableType,
+ comment: target.comment,
});
}
"
diff --git a/apps/desktop/src/components/layout/EditorGroupTabBar.vue b/apps/desktop/src/components/layout/EditorGroupTabBar.vue
index 61c6b9948a..7faef7ad94 100644
--- a/apps/desktop/src/components/layout/EditorGroupTabBar.vue
+++ b/apps/desktop/src/components/layout/EditorGroupTabBar.vue
@@ -1300,7 +1300,7 @@ watch([() => props.specialPageTabs?.settingsActive, () => props.specialPageTabs?
- {{ line.label }}
+ {{ line.label }}
{{ line.value }}
diff --git a/apps/desktop/src/components/layout/querySurfaces.ts b/apps/desktop/src/components/layout/querySurfaces.ts
index 70b3bf31cb..a1ce13ea2f 100644
--- a/apps/desktop/src/components/layout/querySurfaces.ts
+++ b/apps/desktop/src/components/layout/querySurfaces.ts
@@ -85,7 +85,7 @@ export interface ContentAreaSurfaceEmits {
viewTableDdl: [tabId: string, target: SqlObjectNavigationTarget];
editTableStructure: [tabId: string, target: SqlObjectNavigationTarget];
openObjectSource: [tabId: string, target: SqlObjectNavigationTarget, initialEditing: boolean];
- openObjectTable: [tabId: string, target: { tableName: string; schema?: string; tableType?: string; catalog?: string }];
+ openObjectTable: [tabId: string, target: { tableName: string; schema?: string; tableType?: string; catalog?: string; comment?: string | null }];
objectSchemaChange: [tabId: string, schema: string | undefined];
objectBrowserViewportChange: [tabId: string, viewport: ObjectBrowserViewport];
objectBrowserSearchChange: [tabId: string, query: string];
diff --git a/apps/desktop/src/components/objects/ObjectBrowser.vue b/apps/desktop/src/components/objects/ObjectBrowser.vue
index 5877f08009..7a6121de2e 100644
--- a/apps/desktop/src/components/objects/ObjectBrowser.vue
+++ b/apps/desktop/src/components/objects/ObjectBrowser.vue
@@ -182,7 +182,7 @@ const props = defineProps<{
}>();
const emit = defineEmits<{
- openTable: [target: { tableName: string; schema?: string; tableType?: string; catalog?: string }];
+ openTable: [target: { tableName: string; schema?: string; tableType?: string; catalog?: string; comment?: string | null }];
schemaChange: [schema: string | undefined];
viewportChange: [viewport: ObjectBrowserViewport];
searchChange: [query: string];
@@ -966,7 +966,7 @@ function executeRowAction(row: ObjectBrowserRow, action: ObjectBrowserRowAction)
void openTypeInfo(row);
break;
case "open-table":
- emit("openTable", { tableName: row.name, schema: row.schema, tableType: objectBrowserOpenTableType(row), catalog: props.catalog });
+ emit("openTable", { tableName: row.name, schema: row.schema, tableType: objectBrowserOpenTableType(row), catalog: props.catalog, comment: row.comment });
break;
case "open-source":
void (row.type === "EVENT" ? openEventEditor(row) : openSource(row));
@@ -1736,7 +1736,7 @@ function objectBrowserOpenTableType(row: ObjectBrowserRow): string {
}
function openViewData(row: ObjectBrowserRow) {
- emit("openTable", { tableName: row.name, schema: row.schema, tableType: objectBrowserOpenTableType(row), catalog: props.catalog });
+ emit("openTable", { tableName: row.name, schema: row.schema, tableType: objectBrowserOpenTableType(row), catalog: props.catalog, comment: row.comment });
}
function openStructureEditor(row: ObjectBrowserRow) {
diff --git a/apps/desktop/src/composables/__tests__/useSidebarDataOpenRuntime.spec.ts b/apps/desktop/src/composables/__tests__/useSidebarDataOpenRuntime.spec.ts
index a39f5889cf..81a80475b6 100644
--- a/apps/desktop/src/composables/__tests__/useSidebarDataOpenRuntime.spec.ts
+++ b/apps/desktop/src/composables/__tests__/useSidebarDataOpenRuntime.spec.ts
@@ -202,16 +202,24 @@ describe("useSidebarDataOpenRuntime", () => {
it("reuses a sidebar tab for the same table in same-table mode", async () => {
mocks.openDataTabsNextToActive = true;
- await useSidebarDataOpenRuntime().openData(tableNode);
- await useSidebarDataOpenRuntime().openData(tableNode);
+ await useSidebarDataOpenRuntime().openData({ ...tableNode, comment: "Old comment" });
+ await useSidebarDataOpenRuntime().openData({ ...tableNode, comment: "Updated comment" });
expect(mocks.tabs).toHaveLength(1);
+ expect(mocks.tabs[0]?.tableComment).toBe("Updated comment");
+ });
+
+ it("copies the existing sidebar table comment without another metadata request", async () => {
+ await useSidebarDataOpenRuntime().openData({ ...tableNode, comment: "Application users" });
+
+ expect(mocks.tabs[0]?.tableComment).toBe("Application users");
+ expect(mocks.loadTableMetadata).toHaveBeenCalledTimes(1);
});
it("keeps different sidebar tables independent in same-table mode", async () => {
const ordersNode = { ...tableNode, id: "table-orders", label: "orders" };
- await useSidebarDataOpenRuntime().openData(tableNode);
+ await useSidebarDataOpenRuntime().openData({ ...tableNode, comment: "User table" });
await useSidebarDataOpenRuntime().openData(ordersNode);
expect(mocks.tabs).toHaveLength(2);
@@ -270,6 +278,7 @@ describe("useSidebarDataOpenRuntime", () => {
expect(mocks.tabs).toHaveLength(1);
expect(mocks.tabs[0]?.title).toBe("orders");
+ expect(mocks.tabs[0]?.tableComment).toBeUndefined();
expect(mocks.tabs[0]?.tableMeta?.tableName).toBe("orders");
expect(mocks.tabs[0]?.resultLocalSortOriginalLargeValueCells).toBeUndefined();
});
diff --git a/apps/desktop/src/composables/useNavigationTargets.ts b/apps/desktop/src/composables/useNavigationTargets.ts
index b799394ae2..9098a01967 100644
--- a/apps/desktop/src/composables/useNavigationTargets.ts
+++ b/apps/desktop/src/composables/useNavigationTargets.ts
@@ -22,6 +22,7 @@ export type NavigationTarget = {
schema?: string;
tableName: string;
tableType?: string;
+ comment?: string | null;
columnName?: string;
whereInput?: string;
};
@@ -72,7 +73,10 @@ async function openTableTarget(target: NavigationTarget, options: { tableInfoTab
}
const tabId = queryStore.createTab(target.connectionId, target.database, tabTitle, "data", tableSchema, undefined, undefined, { forceNew: true });
const targetTab = queryStore.tabs.find((tab) => tab.id === tabId);
- if (targetTab) targetTab.tableInfoTab = options.tableInfoTab;
+ if (targetTab) {
+ targetTab.tableInfoTab = options.tableInfoTab;
+ targetTab.tableComment = target.comment;
+ }
// Stamp the new table identity synchronously so SQL rebuilds (refresh,
// filters, row count) never read a stale tableMeta from a reused tab or
// fall back to parsing the schema-qualified tab title (issue #3613).
@@ -339,6 +343,7 @@ export function useNavigationTargets(dialogs: { showFieldLineageDialog: { value:
schema: target.schema,
catalog: target.catalog,
tableType: target.tableType,
+ comment: target.comment,
},
undefined,
"default",
diff --git a/apps/desktop/src/composables/useSidebarDataOpenRuntime.ts b/apps/desktop/src/composables/useSidebarDataOpenRuntime.ts
index 0c588e4bb3..4763a11993 100644
--- a/apps/desktop/src/composables/useSidebarDataOpenRuntime.ts
+++ b/apps/desktop/src/composables/useSidebarDataOpenRuntime.ts
@@ -173,6 +173,7 @@ export function useSidebarDataOpenRuntime() {
};
if (existingSameTableTab && (existingSameTableTab.isExecuting || canActivateExistingDataTableTab(existingSameTableTab, { activateExecuting: false }))) {
+ if (node.comment !== undefined) existingSameTableTab.tableComment = node.comment;
queryStore.switchTab(existingSameTableTab.id);
logPhase("existing-tab-activated", { table: node.label });
// 代次失配视同冷缓存(即使位于 30s TTL 窗口内也要重建):disconnect /
@@ -192,12 +193,16 @@ export function useSidebarDataOpenRuntime() {
if (existingDataTabCandidate) {
queryStore.switchTab(existingDataTabCandidate.tab.id);
resetReusedDataTabState(existingDataTabCandidate.tab);
+ existingDataTabCandidate.tab.tableComment = node.comment;
return existingDataTabCandidate.tab.id;
}
- return queryStore.createTab(node.connectionId, node.database, node.label, "data", tableSchema, undefined, node.catalog, {
+ const createdTabId = queryStore.createTab(node.connectionId, node.database, node.label, "data", tableSchema, undefined, node.catalog, {
forceNew: true,
insertAfterActive: settingsStore.editorSettings.openDataTabsNextToActive,
});
+ const createdTab = queryStore.tabs.find((tab) => tab.id === createdTabId);
+ if (createdTab) createdTab.tableComment = node.comment;
+ return createdTabId;
})();
openDataLog("info", "tab-created", { traceId, tabId, elapsed: elapsed() });
logPhase("tab-created", { tabId });
diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts
index 726d97e9c7..c1a33a02fb 100644
--- a/apps/desktop/src/i18n/locales/en.ts
+++ b/apps/desktop/src/i18n/locales/en.ts
@@ -1456,6 +1456,7 @@ export default {
tooltipGroup: "Group:",
tooltipDatabase: "Database:",
tooltipTable: "Table:",
+ tooltipTableComment: "Table Comment:",
tooltipCollection: "Collection:",
tooltipSchema: "Schema:",
resultN: "Result {n}",
diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts
index a3283e3e69..bc4ed27c07 100644
--- a/apps/desktop/src/i18n/locales/es.ts
+++ b/apps/desktop/src/i18n/locales/es.ts
@@ -1433,6 +1433,7 @@ export default withEnglishFallback({
tooltipGroup: "Grupo:",
tooltipDatabase: "Base de datos:",
tooltipTable: "Tabla:",
+ tooltipTableComment: "Comentario de tabla:",
tooltipCollection: "Colección:",
tooltipSchema: "Esquema:",
resultN: "Resultado {n}",
diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts
index 70156b3336..c4ec1f2bfa 100644
--- a/apps/desktop/src/i18n/locales/it.ts
+++ b/apps/desktop/src/i18n/locales/it.ts
@@ -1431,6 +1431,7 @@ export default withEnglishFallback({
tooltipGroup: "Gruppo:",
tooltipDatabase: "Database:",
tooltipTable: "Tabella:",
+ tooltipTableComment: "Commento tabella:",
tooltipCollection: "Collezione:",
tooltipSchema: "Schema:",
resultN: "Risultato {n}",
diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts
index 164559d3bc..70803f4aef 100644
--- a/apps/desktop/src/i18n/locales/ja.ts
+++ b/apps/desktop/src/i18n/locales/ja.ts
@@ -1450,6 +1450,7 @@ export default withEnglishFallback({
tooltipGroup: "グループ:",
tooltipDatabase: "データベース:",
tooltipTable: "テーブル:",
+ tooltipTableComment: "テーブルコメント:",
tooltipCollection: "コレクション:",
tooltipSchema: "スキーマ:",
resultN: "結果 {n}",
diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts
index 2ae03df8dd..8a336c2004 100644
--- a/apps/desktop/src/i18n/locales/ko.ts
+++ b/apps/desktop/src/i18n/locales/ko.ts
@@ -1337,6 +1337,7 @@ export default withEnglishFallback({
tooltipGroup: "그룹:",
tooltipDatabase: "데이터베이스:",
tooltipTable: "테이블:",
+ tooltipTableComment: "테이블 주석:",
tooltipCollection: "컬렉션:",
tooltipSchema: "스키마:",
resultN: "결과 {n}",
diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts
index 29c94b2713..469b706f98 100644
--- a/apps/desktop/src/i18n/locales/pt-BR.ts
+++ b/apps/desktop/src/i18n/locales/pt-BR.ts
@@ -1433,6 +1433,7 @@ export default withEnglishFallback({
tooltipGroup: "Grupo:",
tooltipDatabase: "Banco de dados:",
tooltipTable: "Tabela:",
+ tooltipTableComment: "Comentário da tabela:",
tooltipCollection: "Coleção:",
tooltipSchema: "Schema:",
resultN: "Resultado {n}",
diff --git a/apps/desktop/src/i18n/locales/tr.ts b/apps/desktop/src/i18n/locales/tr.ts
index 8c305935cf..b129e2788f 100644
--- a/apps/desktop/src/i18n/locales/tr.ts
+++ b/apps/desktop/src/i18n/locales/tr.ts
@@ -1456,6 +1456,7 @@ export default withEnglishFallback({
tooltipGroup: "Grup:",
tooltipDatabase: "Veritabanı:",
tooltipTable: "Tablo:",
+ tooltipTableComment: "Tablo açıklaması:",
tooltipCollection: "Koleksiyon:",
tooltipSchema: "Şema:",
resultN: "Sonuç {n}",
diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts
index 1b3bfd511c..06f7c7c231 100644
--- a/apps/desktop/src/i18n/locales/zh-CN.ts
+++ b/apps/desktop/src/i18n/locales/zh-CN.ts
@@ -1380,6 +1380,7 @@ export default withEnglishFallback({
tooltipGroup: "分组:",
tooltipDatabase: "数据库:",
tooltipTable: "表:",
+ tooltipTableComment: "表注释:",
tooltipCollection: "集合:",
tooltipSchema: "Schema:",
resultN: "结果 {n}",
diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts
index 6e9910904c..d3faa1037f 100644
--- a/apps/desktop/src/i18n/locales/zh-TW.ts
+++ b/apps/desktop/src/i18n/locales/zh-TW.ts
@@ -1432,6 +1432,7 @@ export default withEnglishFallback({
tooltipGroup: "群組:",
tooltipDatabase: "資料庫:",
tooltipTable: "資料表:",
+ tooltipTableComment: "資料表註解:",
tooltipCollection: "集合:",
tooltipSchema: "Schema:",
resultN: "結果 {n}",
diff --git a/apps/desktop/src/lib/__tests__/tabs/tabPresentation.spec.ts b/apps/desktop/src/lib/__tests__/tabs/tabPresentation.spec.ts
index 1f58f3a009..1b1734504d 100644
--- a/apps/desktop/src/lib/__tests__/tabs/tabPresentation.spec.ts
+++ b/apps/desktop/src/lib/__tests__/tabs/tabPresentation.spec.ts
@@ -27,6 +27,8 @@ const translations: Record = {
"tabs.tooltipConnection": "Connection:",
"tabs.tooltipGroup": "Group:",
"tabs.tooltipDatabase": "Database:",
+ "tabs.tooltipTable": "Table:",
+ "tabs.tooltipTableComment": "Table Comment:",
"connectionGroup.ungroupedLabel": "Ungrouped",
"editor.noDatabase": "No database",
};
@@ -209,6 +211,22 @@ describe("tab group presentation", () => {
expect(connectionGroupDisplayName("conn-1", translate)).toBe("Ungrouped");
});
+
+ it("shows a bounded table comment only when it is non-empty", () => {
+ const lines = tabTooltipLines(
+ queryTab({
+ mode: "data",
+ tableComment: ` ${"表".repeat(55)}\narchive `,
+ tableMeta: { schema: "public", tableName: "users", columns: [], primaryKeys: [] },
+ }),
+ translate,
+ );
+ const comment = lines.find((line) => line.label === "Table Comment:")?.value;
+
+ expect(Array.from(comment || "")).toHaveLength(50);
+ expect(comment?.endsWith("…")).toBe(true);
+ expect(tabTooltipLines(queryTab({ mode: "data", tableComment: " ", tableMeta: { schema: "public", tableName: "users", columns: [], primaryKeys: [] } }), translate).some((line) => line.label === "Table Comment:")).toBe(false);
+ });
});
describe("query result source ranges", () => {
diff --git a/apps/desktop/src/lib/app/openTabsPersistence.ts b/apps/desktop/src/lib/app/openTabsPersistence.ts
index c03df61548..710941f1da 100644
--- a/apps/desktop/src/lib/app/openTabsPersistence.ts
+++ b/apps/desktop/src/lib/app/openTabsPersistence.ts
@@ -53,6 +53,7 @@ export interface SavedOpenTab {
objectBrowser?: QueryTab["objectBrowser"];
objectSource?: QueryTab["objectSource"];
sourceView?: boolean;
+ tableComment?: QueryTab["tableComment"];
tableMeta?: QueryTab["tableMeta"];
mongoEditTarget?: QueryTab["mongoEditTarget"];
resultEvicted?: boolean;
@@ -148,6 +149,7 @@ export function serializeOpenTabs(tabs: QueryTab[]): SavedOpenTab[] {
objectBrowser: tab.objectBrowser,
objectSource: tab.objectSource,
...(tab.sourceView ? { sourceView: true } : {}),
+ ...(tab.tableComment !== undefined ? { tableComment: tab.tableComment } : {}),
tableMeta: tab.tableMeta,
...(tab.mongoEditTarget !== undefined ? { mongoEditTarget: tab.mongoEditTarget } : {}),
...(tab.mode !== "data" && tab.resultEvicted ? { resultEvicted: true } : {}),
diff --git a/apps/desktop/src/lib/tabs/tabPresentation.ts b/apps/desktop/src/lib/tabs/tabPresentation.ts
index b4f49b8854..5647000e1f 100644
--- a/apps/desktop/src/lib/tabs/tabPresentation.ts
+++ b/apps/desktop/src/lib/tabs/tabPresentation.ts
@@ -11,6 +11,14 @@ import type { BatchSqlExecution, ConnectionConfig, DatabaseType, QueryResult, Qu
type Translate = (key: string, params?: Record) => string;
export type OutputView = "result" | "summary" | "explain" | "chart";
+const TABLE_COMMENT_TOOLTIP_MAX_LENGTH = 50;
+
+function tableCommentTooltipValue(comment: string | null | undefined): string | undefined {
+ const normalized = comment?.trim().replace(/\s+/g, " ");
+ if (!normalized) return undefined;
+ const characters = Array.from(normalized);
+ return characters.length <= TABLE_COMMENT_TOOLTIP_MAX_LENGTH ? normalized : `${characters.slice(0, TABLE_COMMENT_TOOLTIP_MAX_LENGTH - 1).join("")}…`;
+}
export function connectionDisplayName(connectionId: string): string {
const connectionStore = useConnectionStore();
@@ -170,6 +178,10 @@ export function tabTooltipLines(tab: QueryTab, t: Translate): { label: string; v
}
if (tab.mode === "data" && tab.tableMeta?.tableName) {
lines.push({ label: t("tabs.tooltipTable"), value: tab.tableMeta.tableName });
+ const comment = tableCommentTooltipValue(tab.tableComment);
+ if (comment) {
+ lines.push({ label: t("tabs.tooltipTableComment"), value: comment });
+ }
}
if (tab.mode === "mongo" && tab.sql) {
lines.push({ label: t("tabs.tooltipCollection"), value: tab.sql });
diff --git a/apps/desktop/src/types/database.ts b/apps/desktop/src/types/database.ts
index 364028115d..271d566817 100644
--- a/apps/desktop/src/types/database.ts
+++ b/apps/desktop/src/types/database.ts
@@ -1304,6 +1304,7 @@ export interface QueryTab {
objectType: ObjectSourceKind;
signature?: string;
};
+ tableComment?: string | null;
tableMeta?: {
schema?: string;
tableName: string;
diff --git a/packages/app-tests/openTabsPersistence.test.ts b/packages/app-tests/openTabsPersistence.test.ts
index e71676ba0f..dd0e18d47b 100644
--- a/packages/app-tests/openTabsPersistence.test.ts
+++ b/packages/app-tests/openTabsPersistence.test.ts
@@ -311,6 +311,7 @@ test("restores data and structure tabs with table state", () => {
resultPageOffset: 50,
whereInput: "id > 10",
orderByInput: "id DESC",
+ tableComment: "Application users",
tableMeta: {
schema: "public",
tableName: "users",
@@ -351,6 +352,7 @@ test("restores data and structure tabs with table state", () => {
assert.equal(restored.tabs[0]?.resultPageOffset, 50);
assert.equal(restored.tabs[0]?.whereInput, "id > 10");
assert.equal(restored.tabs[0]?.orderByInput, "id DESC");
+ assert.equal(restored.tabs[0]?.tableComment, "Application users");
assert.equal(restored.tabs[1]?.structureTableName, "users");
});