feat(grid): preserve data-grid viewport and selection across tab switches
This commit is contained in:
1 parent
2b26bd6a1a
commit
41b5e51dcc
21 files changed
+1231
-8
No files matched your search
@@ -0,0 +1,114 @@
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { QueryResult } from "@/types/database";
|
||||
|
||||
function sampleResult(rows: number): QueryResult {
|
||||
return {
|
||||
columns: ["id", "name"],
|
||||
rows: Array.from({ length: rows }, (_, index) => [index, `row-${index}`]),
|
||||
affected_rows: 0,
|
||||
execution_time_ms: 1,
|
||||
};
|
||||
}
|
||||
|
||||
// The first test pays the cold transform of the store module, which can exceed
|
||||
// the default 10s budget on Windows.
|
||||
describe("queryStore resultViewGeneration", { timeout: 30_000 }, () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
vi.unstubAllGlobals();
|
||||
setActivePinia(createPinia());
|
||||
});
|
||||
|
||||
it("assigns a new generation when an error result replaces the dataset", async () => {
|
||||
const { useQueryStore } = await import("@/stores/queryStore");
|
||||
const store = useQueryStore();
|
||||
const tabId = store.createTab("pg-1", "app", "error", "query");
|
||||
const tab = store.tabs.find((item) => item.id === tabId)!;
|
||||
tab.result = sampleResult(2);
|
||||
tab.resultViewGeneration = "gen-old";
|
||||
|
||||
store.setErrorResult(tabId, new Error("boom"));
|
||||
|
||||
expect(tab.resultViewGeneration).toBeTruthy();
|
||||
expect(tab.resultViewGeneration).not.toBe("gen-old");
|
||||
});
|
||||
|
||||
it("assigns a new generation for a local sort, because row order changes", async () => {
|
||||
const { useQueryStore } = await import("@/stores/queryStore");
|
||||
const store = useQueryStore();
|
||||
const tabId = store.createTab("pg-1", "app", "local-sort", "query");
|
||||
const tab = store.tabs.find((item) => item.id === tabId)!;
|
||||
tab.result = {
|
||||
...sampleResult(3),
|
||||
rows: [
|
||||
[3, "c"],
|
||||
[1, "a"],
|
||||
[2, "b"],
|
||||
],
|
||||
};
|
||||
tab.resultViewGeneration = "gen-old";
|
||||
|
||||
store.sortTabResultLocally(tabId, "id", 0, "asc");
|
||||
|
||||
expect(tab.result.rows.map((row) => row[0])).toEqual([1, 2, 3]);
|
||||
expect(tab.resultViewGeneration).toBeTruthy();
|
||||
expect(tab.resultViewGeneration).not.toBe("gen-old");
|
||||
});
|
||||
|
||||
it("inherits the generation when a result is restored from disk", async () => {
|
||||
const cached = sampleResult(2);
|
||||
vi.doMock("@/lib/tabs/tabResultCache", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@/lib/tabs/tabResultCache")>();
|
||||
return {
|
||||
...actual,
|
||||
readTabResultSnapshot: vi.fn(async () => ({ result: cached, resultViewGeneration: "gen-disk" }) as any),
|
||||
};
|
||||
});
|
||||
const { useQueryStore } = await import("@/stores/queryStore");
|
||||
const store = useQueryStore();
|
||||
const tabId = store.createTab("pg-1", "app", "evicted", "query");
|
||||
const tab = store.tabs.find((item) => item.id === tabId)!;
|
||||
tab.resultEvicted = true;
|
||||
tab.resultCacheKey = "tab:cache:key";
|
||||
tab.resultViewGeneration = "gen-disk";
|
||||
|
||||
await store.reloadEvictedTab(tabId);
|
||||
|
||||
expect(tab.result).toBeDefined();
|
||||
expect(tab.resultViewGeneration).toBe("gen-disk");
|
||||
vi.doUnmock("@/lib/tabs/tabResultCache");
|
||||
});
|
||||
|
||||
it("projects the run's generation onto the tab when switching result runs", async () => {
|
||||
const { useQueryStore } = await import("@/stores/queryStore");
|
||||
const store = useQueryStore();
|
||||
const tabId = store.createTab("pg-1", "app", "runs", "query");
|
||||
const tab = store.tabs.find((item) => item.id === tabId)!;
|
||||
const first = { id: "run-1", title: "Run 1", sequence: 1, sql: "select 1", createdAt: 1, result: sampleResult(1), resultGridRevision: "rev-1", resultViewGeneration: "gen-run-1" };
|
||||
const second = { id: "run-2", title: "Run 2", sequence: 2, sql: "select 2", createdAt: 2, result: sampleResult(1), resultGridRevision: "rev-2", resultViewGeneration: "gen-run-2" };
|
||||
tab.resultRuns = [first, second];
|
||||
tab.activeResultRunId = "run-1";
|
||||
tab.resultViewGeneration = "gen-run-1";
|
||||
|
||||
await store.setActiveResultRun(tabId, "run-2", { evictInactive: false });
|
||||
|
||||
expect(tab.resultViewGeneration).toBe("gen-run-2");
|
||||
});
|
||||
|
||||
it("does not let a legacy run without a generation inherit the tab's value", async () => {
|
||||
const { useQueryStore } = await import("@/stores/queryStore");
|
||||
const store = useQueryStore();
|
||||
const tabId = store.createTab("pg-1", "app", "legacy-run", "query");
|
||||
const tab = store.tabs.find((item) => item.id === tabId)!;
|
||||
const legacy = { id: "run-legacy", title: "Legacy", sequence: 1, sql: "select 1", createdAt: 1, result: sampleResult(1) };
|
||||
tab.resultRuns = [legacy];
|
||||
tab.activeResultRunId = "run-legacy";
|
||||
tab.resultViewGeneration = "gen-stale";
|
||||
|
||||
await store.setActiveResultRun(tabId, "run-legacy", { evictInactive: false });
|
||||
|
||||
expect(tab.resultViewGeneration).toBeTruthy();
|
||||
expect(tab.resultViewGeneration).not.toBe("gen-stale");
|
||||
});
|
||||
});
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const queryStoreSource = readFileSync(new URL("../queryStore.ts", import.meta.url), "utf8");
|
||||
|
||||
/** Every direct assignment to the token, with its line number. */
|
||||
function directAssignments(): string[] {
|
||||
return queryStoreSource
|
||||
.split("\n")
|
||||
.map((line, index) => ({ line: line.trim(), number: index + 1 }))
|
||||
.filter((entry) => /^(current|tab|run)\.resultViewGeneration\s*=/.test(entry.line))
|
||||
.map((entry) => `${entry.number}: ${entry.line}`);
|
||||
}
|
||||
|
||||
/** The body of executeTabSql, where every result publication must live. */
|
||||
function executeTabSqlBody(): string {
|
||||
const start = queryStoreSource.indexOf("async function executeTabSql(");
|
||||
const end = queryStoreSource.indexOf("\n }\n", start);
|
||||
expect(start).toBeGreaterThanOrEqual(0);
|
||||
expect(end).toBeGreaterThan(start);
|
||||
return queryStoreSource.slice(start, end);
|
||||
}
|
||||
|
||||
describe("queryStore resultViewGeneration writer", () => {
|
||||
it("assigns the token in exactly the allowed places", () => {
|
||||
const assignments = directAssignments();
|
||||
|
||||
// publishResultGeneration (the writer) plus the two identity-preserving
|
||||
// readers: disk restore inherits, run projection copies the run's value.
|
||||
expect(assignments).toHaveLength(3);
|
||||
expect(assignments.join("\n")).toContain("snapshot.resultViewGeneration");
|
||||
expect(assignments.join("\n")).toContain("run.resultViewGeneration");
|
||||
});
|
||||
|
||||
it("defaults an unclassified publication to a new generation", () => {
|
||||
const start = queryStoreSource.indexOf("function publishResultGeneration(");
|
||||
const end = queryStoreSource.indexOf("function ", start + 10);
|
||||
const body = queryStoreSource.slice(start, end);
|
||||
|
||||
expect(body).toContain('if (origin === "disk-restore") return;');
|
||||
expect(body).toContain('if (origin === "append") {');
|
||||
expect(body).toContain("tab.resultViewGeneration = uuid();");
|
||||
});
|
||||
|
||||
it("publishes a generation for every replacement branch of executeTabSql", () => {
|
||||
const body = executeTabSqlBody();
|
||||
|
||||
// Generic branch: append inherits, everything else replaces.
|
||||
expect(body).toContain('publishResultGeneration(current, shouldAppendResult ? "append" : (options?.publicationOrigin ?? "execute"));');
|
||||
// Redis, MongoDB, and Elasticsearch branches replace the visible result too;
|
||||
// Mongo's append variant must keep the current generation.
|
||||
expect(body).toContain('publishResultGeneration(current, shouldAppendResult ? "append" : "execute");');
|
||||
const publicationCount = body.match(/publishResultGeneration\(current/g)?.length ?? 0;
|
||||
expect(publicationCount).toBeGreaterThanOrEqual(5);
|
||||
});
|
||||
|
||||
it("publishes outside executeTabSql where results are also replaced", () => {
|
||||
expect(queryStoreSource).toContain('publishResultGeneration(tab, "local-sort");');
|
||||
expect(queryStoreSource).toContain('publishResultGeneration(tab, "execute");');
|
||||
expect(queryStoreSource).toContain('publishResultGeneration(current, "execute");');
|
||||
});
|
||||
|
||||
it("reserves the closing tombstone for tab closure, not ordinary execution", () => {
|
||||
// Ordinary execution clears the payload before running; the tombstone there
|
||||
// would block capturing the fresh result's view for five seconds.
|
||||
const clearResultPayloadStart = queryStoreSource.indexOf("function clearResultPayload(");
|
||||
const clearResultPayloadEnd = queryStoreSource.indexOf("\n }\n", clearResultPayloadStart);
|
||||
const clearResultPayloadBody = queryStoreSource.slice(clearResultPayloadStart, clearResultPayloadEnd);
|
||||
expect(clearResultPayloadBody).not.toContain("beginClosingDataGridViewSnapshotsForTab");
|
||||
expect(clearResultPayloadBody).toContain("clearDataGridViewSnapshotsForTab(tab.id);");
|
||||
// Tab closure keeps the tombstone so the teardown capture cannot resurrect.
|
||||
expect(queryStoreSource).toContain("beginClosingDataGridViewSnapshotsForTab(id);");
|
||||
expect(queryStoreSource.match(/beginClosingDataGridViewSnapshotsForTab\(tab\.id\)/g)?.length).toBe(2);
|
||||
});
|
||||
});
|
||||
@@ -60,6 +60,7 @@ import { replaceSqlServerLeadingUseQuery, sqlServerLeadingUseScript, sqlServerUs
|
||||
import { classifySqlRisk } from "@/lib/sql/sqlRisk";
|
||||
import { externalSqlFileDisplayTitles, normalizeExternalSqlPath } from "@/lib/sql/sqlFileOpen";
|
||||
import { clearDataGridPendingSnapshot, clearDataGridPendingSnapshotsForTab } from "@/composables/useDataGridEditor";
|
||||
import { beginClosingDataGridViewSnapshotsForTab, clearDataGridViewSnapshot, clearDataGridViewSnapshotsForTab } from "@/lib/dataGrid/dataGridViewStateCache";
|
||||
import { clearDataGridStructuredFilterStatesForTab } from "@/lib/dataGrid/dataGridFilterBuilderPersistence";
|
||||
import { clearDataGridSearchStatesForTab } from "@/lib/dataGrid/dataGridSearchStatePersistence";
|
||||
import { buildTabResultSnapshot, deleteTabResultSnapshot, pruneTabResultSnapshots, readTabResultSnapshot, tabResultCacheKey, writeTabResultSnapshot } from "@/lib/tabs/tabResultCache";
|
||||
@@ -1475,6 +1476,12 @@ export const useQueryStore = defineStore("query", () => {
|
||||
if (!options.evicted) {
|
||||
if (tab.resultCacheKey && !options.preserveCacheSnapshot) void deleteTabResultSnapshot(tab.resultCacheKey);
|
||||
tab.resultCacheKey = undefined;
|
||||
// Drop the stale view snapshot but do NOT tombstone: ordinary execution
|
||||
// clears the payload before running, and the replacement result must stay
|
||||
// free to capture a fresh snapshot when the user switches away. Tab
|
||||
// closure (closeTab/closeTabsWhere/releaseTabsWhere) uses the tombstone.
|
||||
// An evicted result keeps its snapshot so returning to the tab can replay it.
|
||||
clearDataGridViewSnapshotsForTab(tab.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1554,6 +1561,9 @@ export const useQueryStore = defineStore("query", () => {
|
||||
tab.results = run.results;
|
||||
tab.activeResultIndex = run.activeResultIndex;
|
||||
tab.resultGridRevision = run.resultGridRevision;
|
||||
// A legacy run without the token must not inherit a stale tab value: fail
|
||||
// safe by starting a fresh logical result.
|
||||
tab.resultViewGeneration = run.resultViewGeneration ?? uuid();
|
||||
tab.batchSqlExecution = cloneBatchSqlExecution(run.batchSqlExecution);
|
||||
tab.resultBaseSql = run.resultBaseSql;
|
||||
tab.resultEditorFingerprint = run.resultEditorFingerprint;
|
||||
@@ -1877,6 +1887,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
results: tab.results,
|
||||
activeResultIndex: tab.activeResultIndex,
|
||||
resultGridRevision: tab.resultGridRevision,
|
||||
resultViewGeneration: tab.resultViewGeneration,
|
||||
batchSqlExecution: cloneBatchSqlExecution(tab.batchSqlExecution),
|
||||
resultBaseSql: tab.resultBaseSql,
|
||||
resultEditorFingerprint: tab.resultEditorFingerprint,
|
||||
@@ -1978,6 +1989,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
results: tab.results,
|
||||
activeResultIndex: tab.activeResultIndex,
|
||||
resultGridRevision: tab.resultGridRevision,
|
||||
resultViewGeneration: tab.resultViewGeneration,
|
||||
batchSqlExecution: cloneBatchSqlExecution(tab.batchSqlExecution),
|
||||
resultBaseSql: tab.resultBaseSql,
|
||||
resultEditorFingerprint: tab.resultEditorFingerprint,
|
||||
@@ -2038,6 +2050,27 @@ export const useQueryStore = defineStore("query", () => {
|
||||
}
|
||||
}
|
||||
|
||||
type ResultPublicationOrigin = "execute" | "refresh" | "page" | "sort" | "local-sort" | "append" | "disk-restore";
|
||||
|
||||
/**
|
||||
* Single writer of `resultViewGeneration`, the logical-result identity used by
|
||||
* the tab-switch view snapshot cache (`dataGridViewStateCache.ts`).
|
||||
*
|
||||
* New value by default, so a path that forgets to classify itself still fails
|
||||
* safe (the old view snapshot stops matching) instead of replaying a stale
|
||||
* viewport. `append` extends the current dataset; `disk-restore` inherits, so
|
||||
* an evicted-then-restored payload keeps its captured view. No other code path
|
||||
* may assign the token directly.
|
||||
*/
|
||||
function publishResultGeneration(tab: QueryTab, origin: ResultPublicationOrigin) {
|
||||
if (origin === "disk-restore") return;
|
||||
if (origin === "append") {
|
||||
tab.resultViewGeneration ??= uuid();
|
||||
return;
|
||||
}
|
||||
tab.resultViewGeneration = uuid();
|
||||
}
|
||||
|
||||
function sortTabResultLocally(id: string, column: string, columnIndex: number, direction: DataGridSortDirection | null) {
|
||||
const tab = tabs.value.find((t) => t.id === id);
|
||||
if (!tab?.result) return;
|
||||
@@ -2059,6 +2092,8 @@ export const useQueryStore = defineStore("query", () => {
|
||||
const mongo_copy_documents = originalMongoCopyDocuments ? rowIndexes.map((index) => originalMongoCopyDocuments[index]) : undefined;
|
||||
const large_value_cells = remapLargeValueCells(tab.resultLocalSortOriginalLargeValueCells, rowIndexes);
|
||||
assignDisplayedResult(tab, { ...tab.result, rows, large_value_cells, mongo_documents, mongo_copy_documents });
|
||||
// Reordering rows invalidates source-index view snapshots.
|
||||
publishResultGeneration(tab, "local-sort");
|
||||
|
||||
tab.resultSortColumn = direction ? column : undefined;
|
||||
tab.resultSortColumnIndex = direction ? columnIndex : undefined;
|
||||
@@ -2532,6 +2567,9 @@ export const useQueryStore = defineStore("query", () => {
|
||||
tab.executionId = undefined;
|
||||
tab.executingResultRunId = undefined;
|
||||
tab.queryExecutionStartedAt = undefined;
|
||||
// An externally-supplied result is a brand-new dataset, not the previous
|
||||
// one: publish a fresh generation so the tab can capture a view snapshot.
|
||||
publishResultGeneration(tab, "execute");
|
||||
if (tab.result) touchResult(tab);
|
||||
return id;
|
||||
}
|
||||
@@ -3385,6 +3423,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
persistSavedSqlEditorPosition(tabs.value[idx]);
|
||||
if (tab.mode === "sqlserver-trace") void disposeSqlServerActivityTrace(tab.id);
|
||||
clearDataGridPendingSnapshotsForTab(id);
|
||||
beginClosingDataGridViewSnapshotsForTab(id);
|
||||
clearDataGridStructuredFilterStatesForTab(id);
|
||||
clearDataGridSearchStatesForTab(id);
|
||||
if (tabs.value[idx].txnSessionId) void rollbackTransaction(id);
|
||||
@@ -3746,6 +3785,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
.forEach((tab) => {
|
||||
if (tab.mode === "sqlserver-trace") void disposeSqlServerActivityTrace(tab.id);
|
||||
clearDataGridPendingSnapshotsForTab(tab.id);
|
||||
beginClosingDataGridViewSnapshotsForTab(tab.id);
|
||||
clearDataGridStructuredFilterStatesForTab(tab.id);
|
||||
clearDataGridSearchStatesForTab(tab.id);
|
||||
if (tab.txnSessionId) void rollbackTransaction(tab.id);
|
||||
@@ -3930,6 +3970,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
await executeTabSql(tab.id, sql, {
|
||||
pagination: { limit, offset },
|
||||
preserveResultDuringExecution: true,
|
||||
publicationOrigin: "refresh",
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
@@ -3963,6 +4004,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
.forEach((tab) => {
|
||||
rollbackTabTransaction(tab, { resetAutoCommit: true });
|
||||
clearDataGridPendingSnapshotsForTab(tab.id);
|
||||
beginClosingDataGridViewSnapshotsForTab(tab.id);
|
||||
clearDataGridStructuredFilterStatesForTab(tab.id);
|
||||
clearDataGridSearchStatesForTab(tab.id);
|
||||
if (tab.isExecuting) void cancelTabExecution(tab.id);
|
||||
@@ -4521,6 +4563,8 @@ export const useQueryStore = defineStore("query", () => {
|
||||
tab.result = toErrorResult(e);
|
||||
tab.results = undefined;
|
||||
tab.activeResultIndex = undefined;
|
||||
// An error result is a replacement, not the previous dataset.
|
||||
publishResultGeneration(tab, "execute");
|
||||
tab.resultSessionId = undefined;
|
||||
tab.resultClientSessionId = undefined;
|
||||
tab.isExecuting = false;
|
||||
@@ -4550,6 +4594,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
current.activeResultIndex = undefined;
|
||||
current.resultSessionId = undefined;
|
||||
current.resultClientSessionId = undefined;
|
||||
publishResultGeneration(current, "execute");
|
||||
touchResult(current);
|
||||
}
|
||||
clearLiveBatchSqlExecution(current, executionId);
|
||||
@@ -4584,7 +4629,10 @@ export const useQueryStore = defineStore("query", () => {
|
||||
current.resultGridRevision = uuid();
|
||||
if (current.activeResultRunId) syncActiveResultRunFromDisplayed(current);
|
||||
await nextTick();
|
||||
if (previousGridKey && options?.openInNewResultTab !== true) clearDataGridPendingSnapshot(previousGridKey);
|
||||
if (previousGridKey && options?.openInNewResultTab !== true) {
|
||||
clearDataGridPendingSnapshot(previousGridKey);
|
||||
clearDataGridViewSnapshot(previousGridKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
return producedResult;
|
||||
@@ -5458,6 +5506,8 @@ export const useQueryStore = defineStore("query", () => {
|
||||
};
|
||||
pagination?: { limit: number; offset: number; sessionId?: string; clientSessionId?: string };
|
||||
appendResult?: { maxRows: number };
|
||||
/** Logical-result publication origin for the view-snapshot cache. */
|
||||
publicationOrigin?: ResultPublicationOrigin;
|
||||
mongoSafety?: MongoAggregateSafetyOptions;
|
||||
preserveResultDuringExecution?: boolean;
|
||||
preserveTotalRowCountDuringExecution?: boolean;
|
||||
@@ -5681,6 +5731,8 @@ export const useQueryStore = defineStore("query", () => {
|
||||
current.activeResultIndex = undefined;
|
||||
current.result = allResults[0];
|
||||
}
|
||||
// Redis command batches always replace the visible result.
|
||||
publishResultGeneration(current, "execute");
|
||||
producedResult = current.result !== undefined;
|
||||
touchResult(current);
|
||||
current.queryAnalysis = undefined;
|
||||
@@ -6066,6 +6118,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
current.activeResultIndex = undefined;
|
||||
current.result = allResults[0];
|
||||
}
|
||||
publishResultGeneration(current, shouldAppendResult ? "append" : "execute");
|
||||
producedResult = current.result !== undefined;
|
||||
touchResult(current);
|
||||
current.queryAnalysis = undefined;
|
||||
@@ -6134,6 +6187,8 @@ export const useQueryStore = defineStore("query", () => {
|
||||
current.results = allResults.length > 1 ? allResults : undefined;
|
||||
current.activeResultIndex = allResults.length > 1 ? resultIndex : undefined;
|
||||
current.result = allResults[resultIndex];
|
||||
// Elasticsearch batch requests replace the visible result.
|
||||
publishResultGeneration(current, "execute");
|
||||
producedResult = current.result !== undefined;
|
||||
touchResult(current);
|
||||
current.queryAnalysis = undefined;
|
||||
@@ -6461,6 +6516,9 @@ export const useQueryStore = defineStore("query", () => {
|
||||
current.activeResultIndex = undefined;
|
||||
current.result = results[0];
|
||||
}
|
||||
// Logical-result identity for the view-snapshot cache. An append extends
|
||||
// the same dataset; every other branch above replaces it.
|
||||
publishResultGeneration(current, shouldAppendResult ? "append" : (options?.publicationOrigin ?? "execute"));
|
||||
producedResult = current.result !== undefined;
|
||||
current.resultBaseSql = batchResume ? batchResume.batch.submittedSql : shouldReplaceActiveResultInGroup ? (current.resultBaseSql ?? queryBaseSql) : queryBaseSql;
|
||||
current.resultEditorFingerprint = batchResume ? batchResume.batch.editorFingerprint : shouldReplaceActiveResultInGroup ? (current.resultEditorFingerprint ?? executionEditorFingerprint) : executionEditorFingerprint;
|
||||
@@ -6651,6 +6709,8 @@ export const useQueryStore = defineStore("query", () => {
|
||||
current.resultTotalRowCountLoading = false;
|
||||
touchResult(current);
|
||||
producedResult = true;
|
||||
// An error result replaces the dataset the view snapshot was taken on.
|
||||
publishResultGeneration(current, "execute");
|
||||
// When a pinned result requires a new run, errors must use that same
|
||||
// run instead of being replaced by the retained pinned result below.
|
||||
syncDisplayedResultRun(current, queryBaseSql, captureResultRun);
|
||||
@@ -7274,6 +7334,8 @@ export const useQueryStore = defineStore("query", () => {
|
||||
// 置空让 projectResultRun 按需重算
|
||||
tab.resultRuns = snapshot.resultRuns ? markQueryResultRunsRowsRaw(snapshot.resultRuns).map((run) => ({ ...run, resultEstimatedBytes: undefined })) : tab.resultRuns;
|
||||
tab.activeResultRunId = snapshot.activeResultRunId ?? tab.activeResultRunId;
|
||||
// Disk restore is the same logical result: keep the captured view identity.
|
||||
tab.resultViewGeneration = snapshot.resultViewGeneration ?? tab.resultViewGeneration;
|
||||
if (!tab.result && !tab.results && !tab.resultRuns) return false;
|
||||
|
||||
tab.queryAnalysis = snapshot.queryAnalysis;
|
||||
|
||||
Reference in new issue
Block a user