feat(query): pin, rename and label result run tabs

This commit is contained in:
高桥凉介 authored and GitHub committed 2026-09-24 17:18:12 +08:00
1 parent a593a2b200
commit c5eb0a7a4a
26 files changed
+319 -32

No files matched your search

@@ -661,6 +661,7 @@ const debugLogCopied = ref(false);
const debugLogDownloaded = ref(false);
const editShowColumnCommentsInHeader = ref(settingsStore.editorSettings.showColumnCommentsInHeader);
const editShowColumnTypesInHeader = ref(settingsStore.editorSettings.showColumnTypesInHeader);
const editShowResultSourceDatabase = ref(settingsStore.editorSettings.showResultSourceDatabase);
const editDataGridShowTransposeFieldMetadata = ref(settingsStore.editorSettings.dataGridShowTransposeFieldMetadata);
const editColorizeDataGridCellTypes = ref(settingsStore.editorSettings.colorizeDataGridCellTypes);
const editShowIndexIndicatorsInHeader = ref(settingsStore.editorSettings.showIndexIndicatorsInHeader);
@@ -1002,6 +1003,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
tabSortMode: editTabSortMode.value,
showColumnCommentsInHeader: editShowColumnCommentsInHeader.value,
showColumnTypesInHeader: editShowColumnTypesInHeader.value,
showResultSourceDatabase: editShowResultSourceDatabase.value,
dataGridShowTransposeFieldMetadata: editDataGridShowTransposeFieldMetadata.value,
colorizeDataGridCellTypes: editColorizeDataGridCellTypes.value,
dataGridTypeColorSchemes: editDataGridTypeColorSchemes.value,
@@ -1647,6 +1649,7 @@ function syncEditorSettingsDraftFromStore() {
editTabSortMode.value = settingsStore.editorSettings.tabSortMode;
editShowColumnCommentsInHeader.value = settingsStore.editorSettings.showColumnCommentsInHeader;
editShowColumnTypesInHeader.value = settingsStore.editorSettings.showColumnTypesInHeader;
editShowResultSourceDatabase.value = settingsStore.editorSettings.showResultSourceDatabase;
editDataGridShowTransposeFieldMetadata.value = settingsStore.editorSettings.dataGridShowTransposeFieldMetadata;
editColorizeDataGridCellTypes.value = settingsStore.editorSettings.colorizeDataGridCellTypes;
editDataGridTypeColorSchemes.value = cloneDataGridTypeColorSchemes(settingsStore.editorSettings.dataGridTypeColorSchemes);
@@ -1780,6 +1783,7 @@ const editorSettingsDraftRefs: EditorSettingsDraftRefMap = {
tabSortMode: editTabSortMode,
showColumnCommentsInHeader: editShowColumnCommentsInHeader,
showColumnTypesInHeader: editShowColumnTypesInHeader,
showResultSourceDatabase: editShowResultSourceDatabase,
dataGridShowTransposeFieldMetadata: editDataGridShowTransposeFieldMetadata,
colorizeDataGridCellTypes: editColorizeDataGridCellTypes,
dataGridTypeColorSchemes: editDataGridTypeColorSchemes,
@@ -2304,6 +2308,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
historyRetention.reset();
editShowColumnCommentsInHeader.value = DEFAULT_EDITOR_SETTINGS.showColumnCommentsInHeader;
editShowColumnTypesInHeader.value = DEFAULT_EDITOR_SETTINGS.showColumnTypesInHeader;
editShowResultSourceDatabase.value = DEFAULT_EDITOR_SETTINGS.showResultSourceDatabase;
editDataGridShowTransposeFieldMetadata.value = DEFAULT_EDITOR_SETTINGS.dataGridShowTransposeFieldMetadata;
editColorizeDataGridCellTypes.value = DEFAULT_EDITOR_SETTINGS.colorizeDataGridCellTypes;
// Back to the built-in palette, but keep the user's saved schemes available.
@@ -7908,6 +7913,17 @@ onUnmounted(() => {
</div>
<Switch id="show-column-types-in-header" v-model="editShowColumnTypesInHeader" />
</div>
<div class="settings-item flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="space-y-1">
<Label for="show-result-source-database">
{{ t("settings.showResultSourceDatabase") }}
</Label>
<p class="text-xs text-muted-foreground">
{{ t("settings.showResultSourceDatabaseDescription") }}
</p>
</div>
<Switch id="show-result-source-database" v-model="editShowResultSourceDatabase" />
</div>
<div class="settings-item flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="space-y-1">
<Label for="data-grid-show-transpose-field-metadata">
@@ -494,8 +494,10 @@ const activeQueryError = computed(() => {
return String(result.rows[0]?.[0] ?? "");
});
const hasQueryOutput = computed(() => tabHasQueryOutput(props.activeTab));
const visibleResultItems = computed(() => tabularResultItems(props.activeTab.results ?? (props.activeTab.result ? [props.activeTab.result] : undefined)));
const tabularResults = computed(() => tabularResultItems(props.activeTab.results));
// 结果集页签/列表的名称是否带库名,由编辑器设置控制(默认带库名)
const includeResultSourceDatabase = computed(() => settingsStore.editorSettings.showResultSourceDatabase);
const visibleResultItems = computed(() => tabularResultItems(props.activeTab.results ?? (props.activeTab.result ? [props.activeTab.result] : undefined), { includeSourceDatabase: includeResultSourceDatabase.value }));
const tabularResults = computed(() => tabularResultItems(props.activeTab.results, { includeSourceDatabase: includeResultSourceDatabase.value }));
const allResultExportSheets = computed(() =>
tabularResults.value.map((item) => ({
sheetName: item.label || t("tabs.resultN", { n: item.n }),
@@ -503,7 +505,14 @@ const allResultExportSheets = computed(() =>
sql: item.index === props.activeTab.activeResultIndex ? queryResultExecutionSql(props.activeTab) : item.result.sourceStatement,
})),
);
const resultRuns = computed(() => resultRunItems(props.activeTab));
// 结果标签优先显示来源(表名),历史批次缺少来源信息时按批次 SQL 重新解析
const resultRuns = computed(() =>
resultRunItems(props.activeTab, {
includeSourceDatabase: includeResultSourceDatabase.value,
database: props.activeTab.database,
databaseType: activeEffectiveDatabaseType.value,
}),
);
const activeResultGridCacheKey = computed(() => resultGridCacheKey(props.activeTab));
const activeResultGridColumnWidthCacheKey = computed(() => resultGridColumnWidthCacheKey(props.activeTab));
const activeResultGridInstanceKey = computed(() => resultGridInstanceKey(props.activeTab));
@@ -1243,7 +1252,7 @@ async function closeResultRunsToRight(runId: string) {
function openResultRunRename(run: (typeof resultRuns.value)[number]) {
resultRunRenameId.value = run.id;
resultRunRenameTitle.value = run.title || t("tabs.runN", { n: run.sequence });
resultRunRenameTitle.value = run.title || run.sourceLabel || t("tabs.runN", { n: run.sequence });
resultRunRenameOpen.value = true;
nextTick(() => {
const input = document.querySelector<HTMLInputElement>("[data-result-run-name-input]");
@@ -1314,6 +1323,16 @@ async function selectResultRun(runId: string) {
return true;
}
/**
* 点击结果标签:切换显示的同时固定该结果(issue #9975)。
* 未固定的结果会被下一次普通查询复用/覆盖,点击后固定即可保留下来。
*/
async function selectResultRunFromTab(runId: string) {
if (!(await selectResultRun(runId))) return;
const run = resultRuns.value.find((item) => item.id === runId);
if (run && !run.pinned) toggleResultRunPinned(runId);
}
async function focusResultRunByIndex(index: number) {
const run = resultRuns.value[index];
if (!run) return;
@@ -1743,11 +1762,11 @@ defineExpose({
:aria-selected="run.active"
:data-active-result-run="run.active ? 'true' : undefined"
class="flex h-full select-none items-center gap-1 whitespace-nowrap pl-2.5 pr-1 text-xs font-medium outline-none focus-visible:bg-accent focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/50"
@click="selectResultRun(run.id)"
@click="selectResultRunFromTab(run.id)"
@keydown="onResultRunTabKeydown($event, runIndex)"
>
<Pin v-if="run.pinned" class="h-3 w-3 shrink-0 fill-current text-primary" />
{{ run.title || t("tabs.runN", { n: run.sequence }) }}
{{ run.title || run.sourceLabel || t("tabs.runN", { n: run.sequence }) }}
</button>
<button
type="button"
@@ -1767,17 +1786,17 @@ defineExpose({
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button variant="ghost" size="sm" class="h-6 max-w-48 gap-1 px-2 text-xs">
<span class="min-w-0 truncate">{{ activeResultRunItem ? activeResultRunItem.title || t("tabs.runN", { n: activeResultRunItem.sequence }) : t("tabs.resultRuns") }}</span>
<span class="min-w-0 truncate">{{ activeResultRunItem ? activeResultRunItem.title || activeResultRunItem.sourceLabel || t("tabs.runN", { n: activeResultRunItem.sequence }) : t("tabs.resultRuns") }}</span>
<ChevronDown class="h-3.5 w-3.5 shrink-0" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" class="w-48">
<CustomContextMenu v-for="run in resultRuns" :key="run.id" :items="() => resultRunContextMenuItems(run)" v-slot="{ onContextMenu }">
<DropdownMenuItem class="flex items-center gap-2 pr-1" @select="selectResultRun(run.id)" @contextmenu="onContextMenu">
<DropdownMenuItem class="flex items-center gap-2 pr-1" @select="selectResultRunFromTab(run.id)" @contextmenu="onContextMenu">
<Check v-if="run.active" class="h-3.5 w-3.5 shrink-0" />
<span v-else class="h-3.5 w-3.5 shrink-0" />
<Pin v-if="run.pinned" class="h-3 w-3 shrink-0 fill-current text-primary" />
<span class="min-w-0 flex-1 truncate">{{ run.title || t("tabs.runN", { n: run.sequence }) }}</span>
<span class="min-w-0 flex-1 truncate">{{ run.title || run.sourceLabel || t("tabs.runN", { n: run.sequence }) }}</span>
<button
type="button"
class="inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground"
+2
View File
@@ -7280,6 +7280,8 @@ export default withEnglishFallback({
showColumnCommentsInHeaderDescription: "Cədvəl sütunlarının şərhlərini birbaşa sütun adlarının altında göstər.",
showColumnTypesInHeader: "Sütun növlərini adların altında göstər",
showColumnTypesInHeaderDescription: "Hər sütunun verilən növünü birbaşa cədvəldəki sütun adlarının altında göstər.",
showResultSourceDatabase: "Nəticə adlarında verilənlər bazası adını göstər",
showResultSourceDatabaseDescription: "Söndürüldükdə nəticə sekmələri yalnız cədvəl adını göstərir; tam ad (verilənlər bazası adı ilə) ipucunda görünməyə davam edir.",
dataGridShowTransposeFieldMetadata: "Transpozisiya görünüşündə sahə metaverilənlərini göstər",
dataGridShowTransposeFieldMetadataDescription: "Transpozisiya görünüşündə aktiv edilmiş sütun növlərini və şərhləri ayrı sətirlərdə göstər. Bu, sətrin hündürlüyünü artırır.",
colorizeDataGridCellTypes: "Verilənlər cədvəlinin dəyərlərini rənglə fərqləndir",
+2
View File
@@ -7821,6 +7821,8 @@ export default {
showColumnCommentsInHeaderDescription: "Display table column comments directly below grid column names.",
showColumnTypesInHeader: "Show column types under names",
showColumnTypesInHeaderDescription: "Display each column's data type directly below grid column names.",
showResultSourceDatabase: "Include database name in result set names",
showResultSourceDatabaseDescription: "When off, result set tabs show only the table name; the full name stays visible in the tooltip.",
dataGridShowTransposeFieldMetadata: "Show field metadata in transpose view",
dataGridShowTransposeFieldMetadataDescription: "Display enabled column types and comments on separate lines in transpose view. This increases row height.",
colorizeDataGridCellTypes: "Color-code data grid values",
+2
View File
@@ -7211,6 +7211,8 @@ export default withEnglishFallback({
showColumnCommentsInHeaderDescription: "Muestra los comentarios de columnas directamente debajo del nombre de la columna.",
showColumnTypesInHeader: "Mostrar tipos de columna bajo los nombres",
showColumnTypesInHeaderDescription: "Muestra el tipo de dato de cada columna directamente debajo del nombre de la columna.",
showResultSourceDatabase: "Incluir el nombre de la base de datos en los nombres de los resultados",
showResultSourceDatabaseDescription: "Si se desactiva, las pestañas de resultados solo muestran el nombre de la tabla; el nombre completo sigue visible en la información emergente.",
dataGridShowTransposeFieldMetadata: "Mostrar metadatos de campo en la vista transpuesta",
dataGridShowTransposeFieldMetadataDescription: "Muestra los tipos de columna y comentarios habilitados en líneas separadas en la vista transpuesta. Esto aumenta la altura de las filas.",
colorizeDataGridCellTypes: "Colorear valores por tipo de dato",
+2
View File
@@ -7210,6 +7210,8 @@ export default withEnglishFallback({
showColumnCommentsInHeaderDescription: "Visualizza i commenti delle colonne della tabella direttamente sotto i nomi delle colonne nella griglia.",
showColumnTypesInHeader: "Mostra i tipi di colonna sotto i nomi",
showColumnTypesInHeaderDescription: "Visualizza il tipo di dato di ciascuna colonna direttamente sotto i nomi delle colonne nella griglia.",
showResultSourceDatabase: "Includi il nome del database nei nomi dei risultati",
showResultSourceDatabaseDescription: "Se disattivato, le schede dei risultati mostrano solo il nome della tabella; il nome completo resta visibile nel tooltip.",
dataGridShowTransposeFieldMetadata: "Mostra i metadati dei campi nella vista trasposta",
dataGridShowTransposeFieldMetadataDescription: "Visualizza i tipi di colonna e i commenti abilitati su righe separate nella vista trasposta. Questo aumenta l'altezza delle righe.",
colorizeDataGridCellTypes: "Colora i valori in base al tipo di dato",
+2
View File
@@ -7243,6 +7243,8 @@ export default withEnglishFallback({
showColumnCommentsInHeaderDescription: "グリッド列名の直下にテーブル列コメントを表示します。",
showColumnTypesInHeader: "列名の下にデータ型を表示",
showColumnTypesInHeaderDescription: "グリッド列名の直下に各列のデータ型を表示します。",
showResultSourceDatabase: "結果セット名にデータベース名を含める",
showResultSourceDatabaseDescription: "オフにすると結果セットのタブにはテーブル名のみを表示し、完全な名前(データベース名を含む)はツールチップで確認できます。",
dataGridShowTransposeFieldMetadata: "転置ビューにフィールドメタデータを表示",
dataGridShowTransposeFieldMetadataDescription: "有効なデータ型とコメントを転置ビューの別行に表示します。有効にすると行の高さが増えます。",
colorizeDataGridCellTypes: "データ型に応じて値を色分け",
+2
View File
@@ -6978,6 +6978,8 @@ export default withEnglishFallback({
showColumnCommentsInHeaderDescription: "그리드 컬럼 이름 바로 아래에 테이블 컬럼 주석을 표시합니다.",
showColumnTypesInHeader: "이름 아래에 컬럼 타입 표시",
showColumnTypesInHeaderDescription: "그리드 컬럼 이름 바로 아래에 각 컬럼의 데이터 타입을 표시합니다.",
showResultSourceDatabase: "결과 세트 이름에 데이터베이스 이름 포함",
showResultSourceDatabaseDescription: "끄면 결과 세트 탭에 테이블 이름만 표시되며, 전체 이름(데이터베이스 이름 포함)은 툴팁에서 확인할 수 있습니다.",
dataGridShowTransposeFieldMetadata: "전치 보기에 필드 메타데이터 표시",
dataGridShowTransposeFieldMetadataDescription: "활성화된 데이터 타입과 주석을 전치 보기의 별도 줄에 표시합니다. 활성화하면 행 높이가 증가합니다.",
colorizeDataGridCellTypes: "데이터 타입별 값 색상 표시",
+2
View File
@@ -7212,6 +7212,8 @@ export default withEnglishFallback({
showColumnCommentsInHeaderDescription: "Exibir comentários de colunas da tabela diretamente abaixo dos nomes das colunas da grade.",
showColumnTypesInHeader: "Mostrar tipos de coluna sob os nomes",
showColumnTypesInHeaderDescription: "Exibir o tipo de dado de cada coluna diretamente abaixo dos nomes das colunas da grade.",
showResultSourceDatabase: "Incluir o nome do banco de dados nos nomes dos resultados",
showResultSourceDatabaseDescription: "Quando desativado, as abas de resultados mostram apenas o nome da tabela; o nome completo (com o banco de dados) continua na dica de ferramenta.",
dataGridShowTransposeFieldMetadata: "Mostrar metadados de campo na visualização transposta",
dataGridShowTransposeFieldMetadataDescription: "Exibir tipos de coluna e comentários habilitados em linhas separadas na visualização transposta. Isso aumenta a altura das linhas.",
colorizeDataGridCellTypes: "Colorir valores por tipo de dado",
+2
View File
@@ -9363,6 +9363,8 @@ export default withEnglishFallback({
shortcutLowercaseSelection: "Преобразовать выделенное в нижний регистр",
shortcutConvertNamingStyle: "Переключить стиль именования",
shortcutExPasteSqlInCondition: "ExPaste: вставить как условие IN",
showResultSourceDatabase: "Имя набора результатов содержит имя базы данных",
showResultSourceDatabaseDescription: "После отключения вкладки наборов результатов будут отображать только имена таблиц; полное имя (включая имя базы данных) по-прежнему можно посмотреть во всплывающей подсказке.",
},
driverStore: {
jreDirRemoveFailed: "Не удалось удалить старый каталог JRE: {path} (исходная ошибка: {error})",
+2
View File
@@ -7162,6 +7162,8 @@ export default withEnglishFallback({
showColumnCommentsInHeaderDescription: "Tablo sütun açıklamalarını doğrudan ızgara sütun adlarının altında göster.",
showColumnTypesInHeader: "Sütun türlerini adların altında göster",
showColumnTypesInHeaderDescription: "Her sütunun veri türünü doğrudan ızgara sütun adlarının altında göster.",
showResultSourceDatabase: "Sonuç adlarında veritabanı adını göster",
showResultSourceDatabaseDescription: "Kapalıyken sonuç sekmeleri yalnızca tablo adını gösterir; tam ad (veritabanı adı dahil) ipucunda görünmeye devam eder.",
dataGridShowTransposeFieldMetadata: "Devrik görünümde alan meta verisini göster",
dataGridShowTransposeFieldMetadataDescription: "Etkin sütun türlerini ve açıklamalarını devrik görünümde ayrı satırlarda göster. Bu, satır yüksekliğini artırır.",
colorizeDataGridCellTypes: "Veri tablosu değerlerini renklendir",
+2
View File
@@ -7793,6 +7793,8 @@ export default withEnglishFallback({
showColumnCommentsInHeaderDescription: "把表字段注释直接显示在结果表头字段名下方。",
showColumnTypesInHeader: "在字段名下方显示数据类型",
showColumnTypesInHeaderDescription: "把每个字段的数据类型直接显示在结果表头字段名下方。",
showResultSourceDatabase: "结果集名称包含数据库名",
showResultSourceDatabaseDescription: "关闭后结果集页签只显示表名,完整名称(含数据库名)仍可在悬浮提示中查看。",
dataGridShowTransposeFieldMetadata: "在转置视图中显示字段元数据",
dataGridShowTransposeFieldMetadataDescription: "在转置视图中分行显示已启用的数据类型和注释。开启后会增加行高。",
colorizeDataGridCellTypes: "按数据类型为表格值着色",
+2
View File
@@ -6529,6 +6529,8 @@ export default withEnglishFallback({
showColumnCommentsInHeaderDescription: "直接在資料表格欄位名稱下方顯示資料表欄位註解。",
showColumnTypesInHeader: "在欄位名稱下方顯示資料類型",
showColumnTypesInHeaderDescription: "直接在資料表格欄位名稱下方顯示每個欄位的資料類型。",
showResultSourceDatabase: "結果集名稱包含資料庫名稱",
showResultSourceDatabaseDescription: "關閉後結果集頁籤只顯示資料表名稱,完整名稱(含資料庫名稱)仍可在懸浮提示中查看。",
dataGridShowTransposeFieldMetadata: "在轉置檢視中顯示欄位中繼資料",
dataGridShowTransposeFieldMetadataDescription: "在轉置檢視中分行顯示已啟用的資料類型和註解。開啟後會增加列高。",
colorizeDataGridCellTypes: "依資料類型為表格值著色",
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { queryResultNameFromPreamble, queryResultSourceLabel } from "@/lib/sql/queryResultSource";
import { queryResultNameFromPreamble, queryResultSourceLabel, queryResultSourceNameParts } from "@/lib/sql/queryResultSource";
describe("queryResultNameFromPreamble", () => {
it("uses only the immediately preceding line comment", () => {
@@ -65,3 +65,15 @@ describe("queryResultSourceLabel", () => {
expect(queryResultSourceLabel("SELECT 1", { database: "app", databaseType: "mysql" })).toBeUndefined();
});
});
describe("queryResultSourceNameParts", () => {
it("keeps the qualifier separate even when the database name contains dots", () => {
expect(queryResultSourceNameParts("SELECT * FROM data_monitor", { database: "cosimulation2.0", databaseType: "mysql" })).toEqual({ qualifier: "cosimulation2.0", name: "data_monitor" });
});
it("prefers the explicit qualifier and drops an empty one", () => {
expect(queryResultSourceNameParts("SELECT * FROM analytics.events", { database: "app", databaseType: "mysql" })).toEqual({ qualifier: "analytics", name: "events" });
expect(queryResultSourceNameParts("SELECT * FROM events", { databaseType: "mysql" })).toEqual({ name: "events" });
expect(queryResultSourceNameParts("SELECT 1", { database: "app", databaseType: "mysql" })).toBeUndefined();
});
});
+92 -1
View File
@@ -10,6 +10,7 @@ import {
resultGridCacheKey,
resultGridColumnWidthCacheKey,
resultGridInstanceKey,
resultRunItems,
resultSourceRange,
statementExecutionMarkers,
tabColorStyle,
@@ -23,7 +24,7 @@ import {
dirtyTabTitleStyle,
} from "@/lib/tabs/tabPresentation";
import { sqlTextFingerprint } from "@/lib/sql/sqlTextFingerprint";
import type { ConnectionConfig, QueryResult, QueryTab } from "@/types/database";
import type { ConnectionConfig, QueryResult, QueryResultRun, QueryTab } from "@/types/database";
const translations: Record<string, string> = {
"tabs.tooltipConnection": "Connection:",
@@ -176,6 +177,51 @@ describe("query result labels", () => {
expect(item?.label).toBeUndefined();
expect(item?.title).toBe("SELECT 1");
});
it("shows only the object name when result set names exclude the database", () => {
const results = [
{
columns: ["id"],
rows: [[1]],
affected_rows: 0,
execution_time_ms: 1,
sourceLabel: "cosimulation2.0.data_monitor",
sourceQualifier: "cosimulation2.0",
sourceName: "data_monitor",
sourceStatement: "SELECT * FROM data_monitor",
},
];
const [withDatabase] = tabularResultItems(results);
const [withoutDatabase] = tabularResultItems(results, { includeSourceDatabase: false });
expect(withDatabase?.label).toBe("cosimulation2.0.data_monitor");
expect(withoutDatabase?.label).toBe("data_monitor");
expect(withoutDatabase?.displayLabel).toBe("data_monitor");
// 完整名称(含库名)仍保留在悬浮提示中
expect(withoutDatabase?.title).toBe("cosimulation2.0.data_monitor");
});
it("keeps a custom result name even when the database is hidden", () => {
const [item] = tabularResultItems(
[
{
columns: ["id"],
rows: [[1]],
affected_rows: 0,
execution_time_ms: 1,
sourceLabel: "My weekly report",
sourceQualifier: "app",
sourceName: "users",
sourceStatement: "SELECT * FROM users",
},
],
{ includeSourceDatabase: false },
);
expect(item?.label).toBe("My weekly report");
expect(item?.title).toBe("My weekly report");
});
});
describe("query result grid identity", () => {
@@ -192,6 +238,51 @@ describe("query result grid identity", () => {
});
});
describe("result run labels", () => {
// 与 store 真实创建批次的方式保持一致:默认标题是 `Run N`,只有重命名/多库执行才标记 customTitle
const run = (id: string, sequence: number, result?: QueryResult, overrides: Partial<QueryResultRun> = {}): QueryResultRun => ({ id, title: `Run ${sequence}`, sequence, sql: "SELECT * FROM users", createdAt: sequence, result, ...overrides });
const sourceResult = (label: string, qualifier: string, name: string): QueryResult => ({ columns: ["id"], rows: [[1]], affected_rows: 0, execution_time_ms: 1, sourceLabel: label, sourceQualifier: qualifier, sourceName: name }) as QueryResult;
it("names result runs after their source table instead of the run ordinal", () => {
const items = resultRunItems(
queryTab({
resultRuns: [run("run-1", 1, sourceResult("app.users", "app", "users")), run("run-2", 2, sourceResult("app.orders", "app", "orders"))],
}) as QueryTab,
);
expect(items.map((item) => item.sourceLabel)).toEqual(["app.users", "app.orders"]);
});
it("suffixes repeated sources and follows the database-name setting", () => {
const tab = queryTab({
resultRuns: [run("run-1", 1, sourceResult("cosimulation2.0.users", "cosimulation2.0", "users")), run("run-2", 2, sourceResult("cosimulation2.0.users", "cosimulation2.0", "users")), run("run-3", 3)],
}) as QueryTab;
expect(resultRunItems(tab).map((item) => item.sourceLabel)).toEqual(["cosimulation2.0.users", "cosimulation2.0.users (2)", undefined]);
expect(resultRunItems(tab, { includeSourceDatabase: false }).map((item) => item.sourceLabel)).toEqual(["users", "users (2)", undefined]);
});
it("keeps a renamed run title ahead of the source label", () => {
const items = resultRunItems(queryTab({ resultRuns: [run("run-1", 1, sourceResult("app.users", "app", "users"), { title: "报表", customTitle: true })] }) as QueryTab);
expect(items[0]?.title).toBe("报表");
expect(items[0]?.sourceLabel).toBe("app.users");
});
it("replaces the default Run N title with the source label", () => {
const items = resultRunItems(queryTab({ resultRuns: [run("run-1", 1, sourceResult("app.users", "app", "users"))] }) as QueryTab);
expect(items[0]?.title).toBe("");
expect(items[0]?.sourceLabel).toBe("app.users");
});
it("falls back to the run SQL for runs created before source labels were stored", () => {
const items = resultRunItems(queryTab({ resultRuns: [run("run-1", 1)] }) as QueryTab, { database: "cosimulation2.0", databaseType: "mysql" });
expect(items[0]?.sourceLabel).toBe("cosimulation2.0.users");
});
});
describe("tab group presentation", () => {
// 编号由 store 在标签列表变化时分配,渲染函数只读结果;这里把两步串起来,
// 与真实调用顺序保持一致(#9938)。
@@ -11,9 +11,14 @@ export interface SavedQueryResultRun {
sql: string;
createdAt: number;
pinned?: boolean;
/** 标题是否为用户/多库执行显式指定;为假时结果标签改用来源名显示 */
customTitle?: boolean;
activeResultIndex?: number;
resultCacheKey?: string;
resultEvicted?: boolean;
/** 结果来源(库名.表名 / 表名),用于结果标签命名;与结果 payload 分离,回收 payload 后仍可显示 */
sourceLabel?: string;
sourceName?: string;
}
export interface SavedOpenTab {
@@ -219,6 +224,9 @@ export function serializeOpenTabs(tabs: QueryTab[]): SavedOpenTab[] {
sequence: run.sequence,
sql: run.sql,
createdAt: run.createdAt,
...(run.sourceLabel ? { sourceLabel: run.sourceLabel } : {}),
...(run.sourceName ? { sourceName: run.sourceName } : {}),
...(run.customTitle ? { customTitle: true } : {}),
...(run.pinned ? { pinned: true } : {}),
activeResultIndex: run.activeResultIndex,
...(run.resultCacheKey !== undefined ? { resultCacheKey: run.resultCacheKey } : {}),
@@ -46,6 +46,7 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [
"tabSortMode",
"showColumnCommentsInHeader",
"showColumnTypesInHeader",
"showResultSourceDatabase",
"dataGridShowTransposeFieldMetadata",
"colorizeDataGridCellTypes",
"dataGridTypeColorSchemes",
@@ -221,6 +221,7 @@ export const SETTINGS_SEARCH_DEFINITIONS: readonly SettingsSearchDefinition[] =
{ id: "query-result-max-rows", category: "data", titleKey: "settings.queryResultMaxRows", descriptionKey: "settings.queryResultMaxRowsDescription", targetId: "data" },
{ id: "data-grid-header-comments", category: "data", titleKey: "settings.showColumnCommentsInHeader", descriptionKey: "settings.showColumnCommentsInHeaderDescription", targetId: "data" },
{ id: "data-grid-header-types", category: "data", titleKey: "settings.showColumnTypesInHeader", descriptionKey: "settings.showColumnTypesInHeaderDescription", targetId: "data" },
{ id: "result-source-database-name", category: "data", titleKey: "settings.showResultSourceDatabase", descriptionKey: "settings.showResultSourceDatabaseDescription", targetId: "data" },
{ id: "data-grid-transpose-field-metadata", category: "data", titleKey: "settings.dataGridShowTransposeFieldMetadata", descriptionKey: "settings.dataGridShowTransposeFieldMetadataDescription", targetId: "data" },
{ id: "data-grid-cell-type-colors", category: "data", titleKey: "settings.colorizeDataGridCellTypes", descriptionKey: "settings.colorizeDataGridCellTypesDescription", targetId: "data" },
{ id: "data-grid-type-colors", category: "data", titleKey: "settings.dataGridTypeColorScheme", descriptionKey: "settings.dataGridTypeColorSchemeDescription", targetId: "data-grid-type-colors" },
@@ -140,6 +140,7 @@ const SETTINGS_TRANSFER_CATEGORY_KEYS: Record<SettingsTransferCategoryId, readon
data: [
"showColumnCommentsInHeader",
"showColumnTypesInHeader",
"showResultSourceDatabase",
"dataGridShowTransposeFieldMetadata",
"colorizeDataGridCellTypes",
"dataGridTypeColorSchemes",
@@ -268,6 +269,7 @@ const PASS_THROUGH_BOOLEAN_KEYS = [
"confirmUnsavedSqlClose",
"showColumnCommentsInHeader",
"showColumnTypesInHeader",
"showResultSourceDatabase",
"colorizeDataGridCellTypes",
"showIndexIndicatorsInHeader",
"compactColumnHeaderActions",
+19 -2
View File
@@ -29,7 +29,18 @@ function firstSourceOfKind(sources: SqlSemanticRowSource[], kind: SqlSemanticRow
return sources.filter((source) => source.kind === kind).sort((left, right) => left.sourceSpan.start - right.sourceSpan.start)[0];
}
export function queryResultSourceLabel(sql: string, options: QueryResultSourceLabelOptions = {}): string | undefined {
export interface QueryResultSourceNameParts {
/** 库名 / schema 等限定名;SQL 未显式限定时由当前库补齐 */
qualifier?: string;
/** 表名或写入目标对象名 */
name: string;
}
/**
* 解析结果集来源的限定名与对象名。结果集页签需要按设置决定是否展示限定名,
* 而库名本身可能包含点(例如 `cosimulation2.0`),所以不能对拼好的标签做字符串切割。
*/
export function queryResultSourceNameParts(sql: string, options: QueryResultSourceLabelOptions = {}): QueryResultSourceNameParts | undefined {
const statement = sql.trim();
if (!statement) return undefined;
@@ -38,5 +49,11 @@ export function queryResultSourceLabel(sql: string, options: QueryResultSourceLa
if (!source?.name) return undefined;
const qualifier = source.qualifierParts[source.qualifierParts.length - 1]?.trim() || options.database?.trim();
return qualifier ? `${qualifier}.${source.name}` : source.name;
return { qualifier: qualifier || undefined, name: source.name };
}
export function queryResultSourceLabel(sql: string, options: QueryResultSourceLabelOptions = {}): string | undefined {
const parts = queryResultSourceNameParts(sql, options);
if (!parts) return undefined;
return parts.qualifier ? `${parts.qualifier}.${parts.name}` : parts.name;
}
+58 -13
View File
@@ -10,7 +10,8 @@ import type { SqlParameterOptions } from "@/lib/sql/sqlParameters";
import { sqlTextFingerprint } from "@/lib/sql/sqlTextFingerprint";
import { isQueryExecutionErrorResult } from "@/lib/query/queryResultError";
import type { SqlErrorPosition } from "@/lib/backend/errorUtils";
import type { BatchSqlExecution, ConnectionConfig, DatabaseType, QueryResult, QueryTab } from "@/types/database";
import { queryResultSourceNameParts } from "@/lib/sql/queryResultSource";
import type { BatchSqlExecution, ConnectionConfig, DatabaseType, QueryResult, QueryResultRun, QueryTab } from "@/types/database";
type Translate = (key: string, params?: Record<string, unknown>) => string;
export type OutputView = "result" | "summary" | "explain" | "chart";
@@ -440,20 +441,26 @@ export function queryResultExecutionSql(tab: Pick<QueryTab, "result" | "resultBa
return tab.resultSortedSql || resultSqlForGrid(tab);
}
export function tabularResultItems(results: QueryResult[] | undefined): { result: QueryResult; index: number; n: number; label?: string; displayLabel?: string; labelTruncated: boolean; title?: string }[] {
export function tabularResultItems(results: QueryResult[] | undefined, options: { includeSourceDatabase?: boolean } = {}): { result: QueryResult; index: number; n: number; label?: string; displayLabel?: string; labelTruncated: boolean; title?: string }[] {
if (!results) return [];
return results
.map((result, index) => ({ result, index }))
.filter((item) => item.result.columns.length > 0 && item.result.server_message !== true)
.map((item, ordinal) => {
const label = queryResultStatementLabel(item.result);
const displayLabel = label ? middleEllipsis(label) : undefined;
const { sourceName, sourceQualifier } = item.result;
// 只有在标签确实由“库名.对象名”拼成时才启用短名称,避免覆盖 `-- name: xxx` 之类的自定义名称
const qualifiedLabel = sourceName ? (sourceQualifier ? `${sourceQualifier}.${sourceName}` : sourceName) : undefined;
// 关闭“结果集名称包含数据库名”时,页签与结果列表只展示对象名;
// 完整名称(含库名)仍保留在 title,供悬浮提示与搜索使用。
const displaySource = options.includeSourceDatabase === false && sourceName && qualifiedLabel === label ? sourceName : label;
const displayLabel = displaySource ? middleEllipsis(displaySource) : undefined;
return {
...item,
n: ordinal + 1,
label,
label: displaySource,
displayLabel,
labelTruncated: !!label && displayLabel !== label,
labelTruncated: !!displaySource && displayLabel !== displaySource,
title: item.result.sourceLabel || item.result.sourceStatement,
};
});
@@ -463,14 +470,52 @@ export function activeResultRun(tab: Pick<QueryTab, "resultRuns" | "activeResult
return tab.resultRuns?.find((run) => run.id === tab.activeResultRunId);
}
export function resultRunItems(tab: Pick<QueryTab, "resultRuns" | "activeResultRunId">): { id: string; title: string; sequence: number; active: boolean; pinned: boolean }[] {
return (tab.resultRuns ?? []).map((run) => ({
id: run.id,
title: run.title,
sequence: run.sequence,
active: run.id === tab.activeResultRunId,
pinned: run.pinned === true,
}));
/**
* 执行批次默认显示名:取该批次主结果的来源(库名.表名 / 表名)。
* 连表查询同样取 FROM 之后的第一个物理表,与结果集页签的来源解析保持一致。
*/
function resultRunSourceLabel(run: QueryResultRun, includeSourceDatabase: boolean, fallback: { database?: string; databaseType?: DatabaseType }): string | undefined {
// 优先用批次自带的来源:非活动批次的结果 payload 会被回收,payload 里的来源会丢失
const own = includeSourceDatabase ? run.sourceLabel || run.sourceName : run.sourceName || run.sourceLabel;
if (own) return own;
const candidates = run.result ? [run.result, ...(run.results ?? [])] : (run.results ?? []);
for (const result of candidates) {
if (!result) continue;
// 批次级来源缺失时回退到结果本身;关闭“结果集名称包含数据库名”时优先用对象名
const label = includeSourceDatabase ? result.sourceLabel || result.sourceName : result.sourceName || result.sourceLabel;
if (label) return label;
}
// 历史批次(功能上线前创建的)没有来源信息:用批次 SQL 重新解析,连表查询同样取第一个物理表
if (run.sql && fallback.databaseType) {
const parts = queryResultSourceNameParts(run.sql, { database: fallback.database, databaseType: fallback.databaseType });
if (parts) return includeSourceDatabase ? (parts.qualifier ? `${parts.qualifier}.${parts.name}` : parts.name) : parts.name;
}
return undefined;
}
export function resultRunItems(tab: Pick<QueryTab, "resultRuns" | "activeResultRunId">, options: { includeSourceDatabase?: boolean; database?: string; databaseType?: DatabaseType } = {}): { id: string; title: string; sequence: number; active: boolean; pinned: boolean; sourceLabel?: string }[] {
const includeSourceDatabase = options.includeSourceDatabase !== false;
const fallback = { database: options.database, databaseType: options.databaseType };
const seenBySource = new Map<string, number>();
return (tab.resultRuns ?? []).map((run) => {
const source = resultRunSourceLabel(run, includeSourceDatabase, fallback);
let sourceLabel = source;
if (source) {
// 同一张表被查询多次时用序号后缀区分页签(users、users (2)…)
const seen = (seenBySource.get(source) ?? 0) + 1;
seenBySource.set(source, seen);
if (seen > 1) sourceLabel = `${source} (${seen})`;
}
return {
id: run.id,
// 系统默认标题(Run N)由来源名取代;用户重命名/多库按目标命名过的标题优先
title: run.customTitle ? run.title : "",
sequence: run.sequence,
active: run.id === tab.activeResultRunId,
pinned: run.pinned === true,
sourceLabel,
};
});
}
export function resultGridCacheKey(tab: Pick<QueryTab, "id" | "activeResultRunId" | "activeResultIndex">): string {
@@ -74,6 +74,8 @@ interface ColumnarQueryResult {
truncated?: boolean;
has_more?: boolean;
sourceLabel?: string;
sourceQualifier?: string;
sourceName?: string;
sourceStatement?: string;
sourceFrom?: number;
sourceTo?: number;
@@ -363,6 +365,8 @@ function stripSessionIds(result: QueryResult | undefined): QueryResult | undefin
session_id: undefined,
has_more: result.has_more,
sourceLabel: result.sourceLabel,
sourceQualifier: result.sourceQualifier,
sourceName: result.sourceName,
sourceStatement: result.sourceStatement,
sourceFrom: result.sourceFrom,
sourceTo: result.sourceTo,
@@ -419,6 +423,8 @@ function toColumnarResult(result: QueryResult | undefined): ColumnarQueryResult
truncated: result.truncated,
has_more: result.has_more,
sourceLabel: result.sourceLabel,
sourceQualifier: result.sourceQualifier,
sourceName: result.sourceName,
sourceStatement: result.sourceStatement,
sourceFrom: result.sourceFrom,
sourceTo: result.sourceTo,
@@ -449,6 +455,8 @@ function fromColumnarResult(result: ColumnarQueryResult | undefined): QueryResul
session_id: undefined,
has_more: result.has_more,
sourceLabel: result.sourceLabel,
sourceQualifier: result.sourceQualifier,
sourceName: result.sourceName,
sourceStatement: result.sourceStatement,
sourceFrom: result.sourceFrom,
sourceTo: result.sourceTo,
+29 -4
View File
@@ -75,7 +75,7 @@ import { formatDdlForDisplay } from "@/lib/sql/ddlDisplay";
import { buildTableSelectSql, quoteTableDataIdentifier } from "@/lib/table/tableSelectSql";
import { connectionObjectTreeNodeSchema, connectionQueryExecutionSchema, connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection, gaussdbCountQueryDopHint, jdbcConnectionUsesDriverRowOffset, metadataSchemaForConnection } from "@/lib/database/jdbcDialect";
import { frontendQueryTimeoutDelayMs, frontendQueryTimeoutSecsForSql, queryTimeoutSecsForConnection } from "@/lib/sql/queryTimeout";
import { queryResultNameFromPreamble, queryResultSourceLabel } from "@/lib/sql/queryResultSource";
import { queryResultNameFromPreamble, queryResultSourceLabel, queryResultSourceNameParts } from "@/lib/sql/queryResultSource";
import { sqlServerCountUsesLocalTempTable } from "@/lib/query/queryResultCountSession";
import { stripPaginationRowNumber } from "@/lib/query/queryPaginationResult";
import { beginDataGridNativeSelectionBlock, finishDataGridNativeSelectionBlock } from "@/lib/dataGrid/dataGridNativeSelection";
@@ -380,6 +380,8 @@ function releaseResultObjectPayload(result: QueryResult): void {
result.messages = undefined;
result.error = undefined;
result.sourceLabel = undefined;
result.sourceQualifier = undefined;
result.sourceName = undefined;
result.sourceStatement = undefined;
}
@@ -435,7 +437,12 @@ function annotateQueryResultSources(results: QueryResult[], sql: string, databas
const documentStatement = sourceOffset === undefined ? undefined : findSourceDocumentStatement(documentStatements, sourceOffset + statement.from);
const preamble = documentStatement ? sourceDocumentSql!.slice(documentStatement.hitFrom, documentStatement.from) : sql.slice(statement.hitFrom, statement.from);
const customName = queryResultNameFromPreamble(preamble, { databaseType });
if (customName) result.sourceLabel = customName;
if (customName) {
result.sourceLabel = customName;
// 自定义名称(-- name: xxx)优先:清除结构化来源,避免“结果集名称包含数据库名”设置把它替换成表名
result.sourceQualifier = undefined;
result.sourceName = undefined;
}
const successfulUseDatabase = result.execution_error !== true ? useDatabaseFromStatement(statement.sql, databaseType) : undefined;
if (successfulUseDatabase) {
sourceDatabase = successfulUseDatabase;
@@ -650,8 +657,14 @@ function annotateQueryResultSource(result: QueryResult, sourceStatement: string,
result.sourceFrom = sourceRange.from;
result.sourceTo = sourceRange.to;
}
const label = databaseType ? queryResultSourceLabel(sourceStatement, { database, databaseType }) : undefined;
if (label) result.sourceLabel = label;
const parts = databaseType ? queryResultSourceNameParts(sourceStatement, { database, databaseType }) : undefined;
if (parts) {
// 同时保留结构化来源信息:结果集页签可以按设置只展示对象名,避免退化成字符串切割
result.sourceQualifier = parts.qualifier;
result.sourceName = parts.name;
const label = parts.qualifier ? `${parts.qualifier}.${parts.name}` : parts.name;
if (label) result.sourceLabel = label;
}
return result;
}
@@ -1917,6 +1930,7 @@ export const useQueryStore = defineStore("query", () => {
const run = tab?.resultRuns?.find((item) => item.id === runId);
if (!tab || !run) return false;
run.title = trimmed;
run.customTitle = true;
void persistResultRun(tab, run);
return true;
}
@@ -2128,12 +2142,17 @@ export const useQueryStore = defineStore("query", () => {
function captureDisplayedResultRun(tab: QueryTab, sql: string, createdAt = Date.now(), options: ResultRunCaptureOptions = {}) {
if (tab.mode !== "query" || !tab.result) return;
const sequence = nextResultRunSequence(tab);
// 批次级来源:非活动批次的 payload 会被回收,结果标签命名需要独立保存来源
const primaryResult = tab.results?.[0] ?? tab.result;
const run: NonNullable<QueryTab["resultRuns"]>[number] = {
id: uuid(),
title: options.title ?? `Run ${sequence}`,
customTitle: !!options.title,
sequence,
sql,
createdAt,
sourceLabel: primaryResult?.sourceLabel,
sourceName: primaryResult?.sourceName,
result: tab.result,
results: tab.results,
activeResultIndex: tab.activeResultIndex,
@@ -2206,6 +2225,7 @@ export const useQueryStore = defineStore("query", () => {
...workerRun,
id: runId,
title,
customTitle: true,
sequence: nextResultRunSequence(source),
multiDbExecution: execution,
resultCacheKey: undefined,
@@ -2562,6 +2582,9 @@ export const useQueryStore = defineStore("query", () => {
sql: run.sql,
createdAt: run.createdAt,
pinned: run.pinned,
customTitle: run.customTitle,
sourceLabel: run.sourceLabel,
sourceName: run.sourceName,
activeResultIndex: run.activeResultIndex,
resultCacheKey: run.resultCacheKey,
resultEvicted: run.resultEvicted,
@@ -6972,6 +6995,8 @@ export const useQueryStore = defineStore("query", () => {
const annotateMongoResult = (result: QueryResult): QueryResult => {
const annotated = annotateQueryResultSource(result, sourceStatement, undefined, undefined, sourceRange);
if ("collection" in mongoCommand) {
annotated.sourceQualifier = currentDatabase || undefined;
annotated.sourceName = mongoCommand.collection;
annotated.sourceLabel = currentDatabase ? `${currentDatabase}.${mongoCommand.collection}` : mongoCommand.collection;
}
return annotated;
+5
View File
@@ -860,6 +860,8 @@ export interface EditorSettings {
mongoViewMode: "document" | "table";
showColumnCommentsInHeader: boolean;
showColumnTypesInHeader: boolean;
/** 结果集页签/结果列表的名称是否带上库名(关闭后只显示表名,完整名称仍在悬浮提示中)。 */
showResultSourceDatabase: boolean;
dataGridShowTransposeFieldMetadata: boolean;
colorizeDataGridCellTypes: boolean;
dataGridTypeColorSchemes: DataGridTypeColorScheme[];
@@ -1137,6 +1139,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
mongoViewMode: "document",
showColumnCommentsInHeader: true,
showColumnTypesInHeader: true,
showResultSourceDatabase: true,
dataGridShowTransposeFieldMetadata: false,
colorizeDataGridCellTypes: false,
dataGridTypeColorSchemes: [],
@@ -1691,6 +1694,7 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
mongoViewMode: settings.mongoViewMode === "table" ? "table" : DEFAULT_EDITOR_SETTINGS.mongoViewMode,
showColumnCommentsInHeader: settings.showColumnCommentsInHeader ?? DEFAULT_EDITOR_SETTINGS.showColumnCommentsInHeader,
showColumnTypesInHeader: settings.showColumnTypesInHeader ?? DEFAULT_EDITOR_SETTINGS.showColumnTypesInHeader,
showResultSourceDatabase: settings.showResultSourceDatabase ?? DEFAULT_EDITOR_SETTINGS.showResultSourceDatabase,
dataGridShowTransposeFieldMetadata: settings.dataGridShowTransposeFieldMetadata === true,
colorizeDataGridCellTypes: settings.colorizeDataGridCellTypes ?? DEFAULT_EDITOR_SETTINGS.colorizeDataGridCellTypes,
dataGridTypeColorSchemes,
@@ -2511,6 +2515,7 @@ export const useSettingsStore = defineStore("settings", () => {
if (partial.mongoViewMode !== undefined) editorSettings.value.mongoViewMode = partial.mongoViewMode;
if (partial.showColumnCommentsInHeader !== undefined) editorSettings.value.showColumnCommentsInHeader = partial.showColumnCommentsInHeader;
if (partial.showColumnTypesInHeader !== undefined) editorSettings.value.showColumnTypesInHeader = partial.showColumnTypesInHeader;
if (partial.showResultSourceDatabase !== undefined) editorSettings.value.showResultSourceDatabase = partial.showResultSourceDatabase;
if (partial.dataGridShowTransposeFieldMetadata !== undefined) editorSettings.value.dataGridShowTransposeFieldMetadata = partial.dataGridShowTransposeFieldMetadata === true;
if (partial.colorizeDataGridCellTypes !== undefined) editorSettings.value.colorizeDataGridCellTypes = partial.colorizeDataGridCellTypes === true;
if (partial.dataGridTypeColorSchemes !== undefined) {
+15
View File
@@ -1281,6 +1281,10 @@ export interface QueryResult {
* the tabular view and the original JSON. */
elasticsearch_raw_body?: string;
sourceLabel?: string;
/** 结果集来源的库名 / schema(与 sourceLabel 同时写入),供结果集页签按设置决定是否展示。 */
sourceQualifier?: string;
/** 结果集来源的对象名(通常为表名),关闭“结果集名称包含数据库名”时用于展示短名称。 */
sourceName?: string;
sourceStatement?: string;
/** Absolute offsets in the editor document at execution time. */
sourceFrom?: number;
@@ -1346,8 +1350,19 @@ export interface QueryResultRun {
createdAt: number;
/** Keeps this result from being replaced by an ordinary query execution. */
pinned?: boolean;
/**
* 标题是否由用户/多库执行显式指定(重命名或按目标库命名)。
* 为假时 title 只是系统默认的 `Run N`,结果标签应改用来源名显示。
*/
customTitle?: boolean;
/** Distinguishes successive result payloads that reuse the same run slot. */
resultGridRevision?: string;
/**
* 结果来源(库名.表名 / 表名),随批次一起保存。
* 非活动批次的结果 payload 会被回收,因此结果标签命名不能依赖 payload。
*/
sourceLabel?: string;
sourceName?: string;
/**
* Logical-result identity for the tab-switch view snapshot cache. Distinct
* from `resultGridRevision` (the grid remount key): this one changes on every