From 84a7ff6b92ea7630a03a62e1cff7e5b47fd6fedc Mon Sep 17 00:00:00 2001
From: t8y2 <1156263951@qq.com>
Date: Mon, 1 Jun 2026 17:18:51 +0800
Subject: [PATCH] fix(tabs): reload missing table data
---
.../src/components/layout/ContentArea.vue | 9 ++++
apps/desktop/src/i18n/locales/en.ts | 1 +
apps/desktop/src/i18n/locales/es.ts | 1 +
apps/desktop/src/i18n/locales/zh-CN.ts | 1 +
apps/desktop/src/stores/queryStore.ts | 11 ++++-
packages/app-tests/queryStore.test.ts | 44 +++++++++++++++++++
6 files changed, 66 insertions(+), 1 deletion(-)
diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue
index d3d8cd5ca0..9470f759e4 100644
--- a/apps/desktop/src/components/layout/ContentArea.vue
+++ b/apps/desktop/src/components/layout/ContentArea.vue
@@ -14,6 +14,7 @@ import {
TableProperties,
ChevronDown,
ChevronUp,
+ RefreshCcw,
} from "lucide-vue-next";
import { Splitpanes, Pane } from "splitpanes";
import "splitpanes/dist/splitpanes.css";
@@ -709,6 +710,14 @@ defineExpose({ focusSearch, refreshData, handleModRTarget });
{{ t("toolbar.stopQuery") }}
+
+
+
{{ t("grid.dataUnavailable") }}
+
+
diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts
index 9764805156..ae0a554cc7 100644
--- a/apps/desktop/src/i18n/locales/en.ts
+++ b/apps/desktop/src/i18n/locales/en.ts
@@ -602,6 +602,7 @@ export default {
tableInfoSearch: "Search...",
tableInfoNoResults: "No results",
queryError: "Query Error",
+ dataUnavailable: "Table data needs to be reloaded.",
refresh: "Refresh",
commit: "Commit",
rollback: "Rollback",
diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts
index 2a188fc3dc..76e4cb04c3 100644
--- a/apps/desktop/src/i18n/locales/es.ts
+++ b/apps/desktop/src/i18n/locales/es.ts
@@ -561,6 +561,7 @@ export default {
tableInfoSearch: "Buscar...",
tableInfoNoResults: "Sin resultados",
queryError: "Error de consulta",
+ dataUnavailable: "Los datos de la tabla deben volver a cargarse.",
refresh: "Actualizar",
commit: "Confirmar",
rollback: "Revertir",
diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts
index 7c984e8afe..c9505cee50 100644
--- a/apps/desktop/src/i18n/locales/zh-CN.ts
+++ b/apps/desktop/src/i18n/locales/zh-CN.ts
@@ -597,6 +597,7 @@ export default {
tableInfoSearch: "搜索...",
tableInfoNoResults: "无匹配结果",
queryError: "查询出错",
+ dataUnavailable: "表数据需要重新加载。",
refresh: "刷新",
commit: "提交",
rollback: "回滚",
diff --git a/apps/desktop/src/stores/queryStore.ts b/apps/desktop/src/stores/queryStore.ts
index a102065eb4..782ddd41e1 100644
--- a/apps/desktop/src/stores/queryStore.ts
+++ b/apps/desktop/src/stores/queryStore.ts
@@ -1126,13 +1126,22 @@ export const useQueryStore = defineStore("query", () => {
async function reloadEvictedTab(id: string) {
const tab = tabs.value.find((t) => t.id === id);
- if (!tab || !tab.resultEvicted) return;
+ const shouldReloadMissingDataTab = tab?.mode === "data" && !tab.result && !tab.isExecuting;
+ if (!tab || (!tab.resultEvicted && !shouldReloadMissingDataTab)) return;
tab.resultEvicted = false;
const sql = tab.lastExecutedSql ?? tab.sql;
if (!sql?.trim()) return;
+ const settingsStore = useSettingsStore();
await executeTabSql(tab.id, sql, {
resultBaseSql: tab.resultBaseSql ?? sql,
resultSortedSql: tab.resultSortedSql,
+ pagination:
+ tab.mode === "data"
+ ? {
+ limit: tab.resultPageLimit ?? settingsStore.editorSettings.pageSize,
+ offset: tab.resultPageOffset ?? 0,
+ }
+ : undefined,
});
}
diff --git a/packages/app-tests/queryStore.test.ts b/packages/app-tests/queryStore.test.ts
index 0bd15e15e4..46c4c9d457 100644
--- a/packages/app-tests/queryStore.test.ts
+++ b/packages/app-tests/queryStore.test.ts
@@ -364,6 +364,50 @@ test("data tab execution preserves pagination offset metadata", async () => {
}
});
+test("reloading an empty data tab fetches table results again", async () => {
+ const restoreStorage = installMemoryStorage();
+ setActivePinia(createPinia());
+ const connectionStore = useConnectionStore();
+ const store = useQueryStore();
+ const originalFetch = globalThis.fetch;
+ let executeBody: any;
+
+ connectionStore.addEphemeralConnection(conn("conn-1"));
+ const tabId = store.createTab("conn-1", "db", "users", "data", "public");
+ const tab = store.tabs.find((item) => item.id === tabId);
+ assert.ok(tab);
+ tab.sql = 'SELECT * FROM "public"."users" LIMIT 50 OFFSET 50;';
+ tab.lastExecutedSql = tab.sql;
+ tab.resultPageLimit = 50;
+ tab.resultPageOffset = 50;
+
+ globalThis.fetch = (async (input, init) => {
+ const url = String(input);
+ if (url === "/api/query/execute-multi") {
+ executeBody = JSON.parse(String(init?.body ?? "{}"));
+ return new Response(JSON.stringify([{ columns: ["id"], rows: [[51]], affected_rows: 0, execution_time_ms: 1 }]), {
+ status: 200,
+ headers: { "Content-Type": "application/json" },
+ });
+ }
+ return new Response("unexpected request", { status: 500 });
+ }) as typeof fetch;
+
+ try {
+ await store.reloadEvictedTab(tabId);
+
+ assert.equal(executeBody.sql, 'SELECT * FROM "public"."users" LIMIT 50 OFFSET 50;');
+ assert.equal(executeBody.maxRows, 50);
+ assert.equal(executeBody.fetchSize, 50);
+ assert.deepEqual(tab.result?.rows, [[51]]);
+ assert.equal(tab.resultPageLimit, 50);
+ assert.equal(tab.resultPageOffset, 50);
+ } finally {
+ globalThis.fetch = originalFetch;
+ restoreStorage();
+ }
+});
+
test("query result export fetches every paginated page", async () => {
const restoreStorage = installMemoryStorage();
setActivePinia(createPinia());