diff --git a/apps/desktop/src/lib/__tests__/table/tableTree.spec.ts b/apps/desktop/src/lib/__tests__/table/tableTree.spec.ts index 8fb947c7c..367883e5c 100644 --- a/apps/desktop/src/lib/__tests__/table/tableTree.spec.ts +++ b/apps/desktop/src/lib/__tests__/table/tableTree.spec.ts @@ -29,6 +29,35 @@ describe("PostgreSQL overloaded routines", () => { }); }); +describe("PostgreSQL table hierarchy", () => { + it("keeps schema pagination visible at the table-group root when a page ends inside nested partitions", () => { + const nodes = buildTableTreeNodes({ + ...context, + schema: "public", + tables: [ + { name: "orders", table_type: "BASE TABLE", comment: null }, + { name: "orders_2026", table_type: "BASE TABLE", comment: null, parent_schema: "public", parent_name: "orders" }, + { name: "orders_2026_01", table_type: "BASE TABLE", comment: null, parent_schema: "public", parent_name: "orders_2026" }, + ], + }); + const loadMore: TreeNode = { + id: "load-more:1000", + label: "tree.loadMore", + type: "load-more", + connectionId: context.connectionId, + database: context.database, + loadMore: { parentId: "connection:db:__tables", offset: 1000, pageSize: 1000 }, + }; + + const withLoadMore = appendTableTreeLoadMoreNode(nodes, loadMore, { schema: "public", name: "orders_2026" }); + + expect(withLoadMore.map((node) => node.label)).toEqual(["orders", "tree.loadMore"]); + const yearPartition = tablePartitionGroups(withLoadMore[0])[0].children?.[0]; + expect(yearPartition?.label).toBe("orders_2026"); + expect(tablePartitionGroups(yearPartition!)[0].children?.map((node) => node.label)).toEqual(["orders_2026_01"]); + }); +}); + describe("TDengine table hierarchy", () => { it("groups child tables under their supertable and keeps ordinary tables flat", () => { const tables: TableInfo[] = [ diff --git a/apps/desktop/src/lib/table/tableTree.ts b/apps/desktop/src/lib/table/tableTree.ts index 38abbe0dc..48822cf5e 100644 --- a/apps/desktop/src/lib/table/tableTree.ts +++ b/apps/desktop/src/lib/table/tableTree.ts @@ -353,7 +353,10 @@ function findTableTreeNode(nodes: readonly TreeNode[], parent: TableTreeLoadMore export function appendTableTreeLoadMoreNode(children: TreeNode[], loadMoreNode: TreeNode, parent?: TableTreeLoadMoreParent): TreeNode[] { const parentNode = parent ? findTableTreeNode(children, parent) : undefined; - const childGroup = parentNode ? tablePartitionGroups(parentNode)[0] : undefined; + // Only TDengine pages are scoped to a STABLE's child-table group. PostgreSQL + // paginates the whole schema, so nesting its global cursor under whichever + // partition happens to end the page can hide all remaining schema objects. + const childGroup = parentNode && isSuperTable(parentNode) ? tablePartitionGroups(parentNode)[0] : undefined; if (!childGroup) return [...children, loadMoreNode]; childGroup.children = [...(childGroup.children ?? []), loadMoreNode]; diff --git a/apps/desktop/src/stores/connectionStore.ts b/apps/desktop/src/stores/connectionStore.ts index fabf4e589..c64624459 100644 --- a/apps/desktop/src/stores/connectionStore.ts +++ b/apps/desktop/src/stores/connectionStore.ts @@ -1045,7 +1045,7 @@ export const useConnectionStore = defineStore("connection", () => { } function objectGroupCacheKey(node: TreeNode): string { - return schemaCacheKey(node.connectionId || "", node.database || "", node.schema || "", node.type, "objects-v3"); + return schemaCacheKey(node.connectionId || "", node.database || "", node.schema || "", node.type, "objects-v4"); } function metadataListDriverProfile(connectionId?: string): string | undefined { @@ -2991,7 +2991,7 @@ export const useConnectionStore = defineStore("connection", () => { await ensureConnected(connectionId); if (useCachedChildren(node, options)) return; const searchFilter = activeTreeLoadSearchFilter(options); - const cacheKey = schemaCacheKey(connectionId, `doris-catalog:${catalog}`, database, "objects-simple-v3"); + const cacheKey = schemaCacheKey(connectionId, `doris-catalog:${catalog}`, database, "objects-simple-v4"); if (!options?.force && !searchFilter) { const cached = await loadPersistedTreeChildren(node, cacheKey); if (cached.hit) { @@ -3064,7 +3064,7 @@ export const useConnectionStore = defineStore("connection", () => { await ensureConnected(connectionId); if (useCachedChildren(node, options)) return; const simpleObjectDisplay = useSettingsStore().editorSettings.sidebarObjectDisplay === "simple"; - const cacheKey = schemaCacheKey(connectionId, database, schema || "", simpleObjectDisplay ? "objects-simple-v3" : "objects-grouped-v3"); + const cacheKey = schemaCacheKey(connectionId, database, schema || "", simpleObjectDisplay ? "objects-simple-v4" : "objects-grouped-v4"); const searchFilter = activeTreeLoadSearchFilter(options); const isSidebarTableSearch = !!options?.sidebarTableSearchParentId; if (!options?.force && !searchFilter) { @@ -3322,7 +3322,7 @@ export const useConnectionStore = defineStore("connection", () => { if (!canApplyTreeMetadataResult(parent)) return; parent.objectCount = mergedChildren.length; setChildren(parent, nextChildren); - await savePersistedTreeChildren(schemaCacheKey(parentConnectionId, parentDatabase, parent.schema || "", "objects-simple-v3"), nextChildren); + await savePersistedTreeChildren(schemaCacheKey(parentConnectionId, parentDatabase, parent.schema || "", "objects-simple-v4"), nextChildren); parent.isExpanded = true; return; }