21 files changed
+75
-11
No files matched your search
@@ -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,
|
||||
});
|
||||
}
|
||||
"
|
||||
|
||||
@@ -1300,7 +1300,7 @@ watch([() => props.specialPageTabs?.settingsActive, () => props.specialPageTabs?
|
||||
</TooltipTrigger>
|
||||
<TooltipContent :side="tabTooltipSide" class="text-xs grid grid-cols-[auto_1fr] gap-x-2">
|
||||
<template v-for="line in tabTooltipLines(entry.tab, t)" :key="line.label">
|
||||
<span class="text-muted-foreground">{{ line.label }}</span>
|
||||
<span class="font-medium opacity-70">{{ line.label }}</span>
|
||||
<span>{{ line.value }}</span>
|
||||
</template>
|
||||
</TooltipContent>
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -1456,6 +1456,7 @@ export default {
|
||||
tooltipGroup: "Group:",
|
||||
tooltipDatabase: "Database:",
|
||||
tooltipTable: "Table:",
|
||||
tooltipTableComment: "Table Comment:",
|
||||
tooltipCollection: "Collection:",
|
||||
tooltipSchema: "Schema:",
|
||||
resultN: "Result {n}",
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -1431,6 +1431,7 @@ export default withEnglishFallback({
|
||||
tooltipGroup: "Gruppo:",
|
||||
tooltipDatabase: "Database:",
|
||||
tooltipTable: "Tabella:",
|
||||
tooltipTableComment: "Commento tabella:",
|
||||
tooltipCollection: "Collezione:",
|
||||
tooltipSchema: "Schema:",
|
||||
resultN: "Risultato {n}",
|
||||
|
||||
@@ -1450,6 +1450,7 @@ export default withEnglishFallback({
|
||||
tooltipGroup: "グループ:",
|
||||
tooltipDatabase: "データベース:",
|
||||
tooltipTable: "テーブル:",
|
||||
tooltipTableComment: "テーブルコメント:",
|
||||
tooltipCollection: "コレクション:",
|
||||
tooltipSchema: "スキーマ:",
|
||||
resultN: "結果 {n}",
|
||||
|
||||
@@ -1337,6 +1337,7 @@ export default withEnglishFallback({
|
||||
tooltipGroup: "그룹:",
|
||||
tooltipDatabase: "데이터베이스:",
|
||||
tooltipTable: "테이블:",
|
||||
tooltipTableComment: "테이블 주석:",
|
||||
tooltipCollection: "컬렉션:",
|
||||
tooltipSchema: "스키마:",
|
||||
resultN: "결과 {n}",
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -1456,6 +1456,7 @@ export default withEnglishFallback({
|
||||
tooltipGroup: "Grup:",
|
||||
tooltipDatabase: "Veritabanı:",
|
||||
tooltipTable: "Tablo:",
|
||||
tooltipTableComment: "Tablo açıklaması:",
|
||||
tooltipCollection: "Koleksiyon:",
|
||||
tooltipSchema: "Şema:",
|
||||
resultN: "Sonuç {n}",
|
||||
|
||||
@@ -1380,6 +1380,7 @@ export default withEnglishFallback({
|
||||
tooltipGroup: "分组:",
|
||||
tooltipDatabase: "数据库:",
|
||||
tooltipTable: "表:",
|
||||
tooltipTableComment: "表注释:",
|
||||
tooltipCollection: "集合:",
|
||||
tooltipSchema: "Schema:",
|
||||
resultN: "结果 {n}",
|
||||
|
||||
@@ -1432,6 +1432,7 @@ export default withEnglishFallback({
|
||||
tooltipGroup: "群組:",
|
||||
tooltipDatabase: "資料庫:",
|
||||
tooltipTable: "資料表:",
|
||||
tooltipTableComment: "資料表註解:",
|
||||
tooltipCollection: "集合:",
|
||||
tooltipSchema: "Schema:",
|
||||
resultN: "結果 {n}",
|
||||
|
||||
@@ -27,6 +27,8 @@ const translations: Record<string, string> = {
|
||||
"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", () => {
|
||||
|
||||
@@ -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 } : {}),
|
||||
|
||||
@@ -11,6 +11,14 @@ import type { BatchSqlExecution, ConnectionConfig, DatabaseType, QueryResult, Qu
|
||||
|
||||
type Translate = (key: string, params?: Record<string, unknown>) => 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 });
|
||||
|
||||
@@ -1304,6 +1304,7 @@ export interface QueryTab {
|
||||
objectType: ObjectSourceKind;
|
||||
signature?: string;
|
||||
};
|
||||
tableComment?: string | null;
|
||||
tableMeta?: {
|
||||
schema?: string;
|
||||
tableName: string;
|
||||
|
||||
Reference in new issue
Block a user