From 5ba0d435f00e0856ad439ec3f64385227b183b25 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Sun, 27 Sep 2026 09:35:28 +0000 Subject: [PATCH] feat(sidebar): auto-load all tables per connection when expanded Closes #9834 --- .../connection/ConnectionDialog.vue | 17 ++- .../ConnectionDialog.deepLinkEdit.spec.ts | 3 +- .../sidebar/SidebarTreeRuntimeHost.vue | 16 ++- .../SidebarTreeRuntimeHost.expansion.spec.ts | 36 +++++ apps/desktop/src/i18n/locales/en.ts | 3 + apps/desktop/src/i18n/locales/zh-CN.ts | 3 + .../__tests__/connectionDatabaseInfo.spec.ts | 3 +- .../connectionConfigTransfer.spec.ts | 10 ++ .../lib/connection/connectionDatabaseInfo.ts | 2 +- .../connectionStore.metadataLoading.spec.ts | 124 ++++++++++++++++++ apps/desktop/src/stores/connectionStore.ts | 31 ++++- apps/desktop/src/types/database.ts | 2 + .../examples/table_import_live_bench.rs | 1 + crates/dbx-core/src/admin/mq/config.rs | 1 + crates/dbx-core/src/admin/mq/service.rs | 1 + crates/dbx-core/src/admin/nacos/config.rs | 1 + crates/dbx-core/src/admin/nacos/service.rs | 2 + crates/dbx-core/src/ai/agent_tools.rs | 1 + .../src/connection/connection_secrets.rs | 1 + crates/dbx-core/src/connection/mod.rs | 32 +++-- crates/dbx-core/src/data/transfer.rs | 2 + crates/dbx-core/src/persistence/cloud_sync.rs | 3 + crates/dbx-core/src/persistence/storage.rs | 2 + crates/dbx-core/src/query/mod.rs | 2 + .../dbx-core/src/safety/production_safety.rs | 1 + crates/dbx-core/src/schema/mod.rs | 1 + crates/dbx-core/src/schema/normalization.rs | 1 + .../tests/connection_url_compatibility.rs | 1 + crates/dbx-core/tests/dump_docs_fixture.rs | 1 + .../tests/live_postgres_docs_annotations.rs | 1 + .../tests/live_postgres_docs_snapshot.rs | 1 + .../tests/live_postgres_error_position.rs | 1 + .../live_postgres_query_result_export.rs | 1 + .../dbx-core/tests/live_postgres_transfer.rs | 1 + .../tests/live_sqlserver_completion.rs | 1 + .../live_sqlserver_query_result_export.rs | 1 + .../dbx-core/tests/live_sqlserver_transfer.rs | 1 + crates/dbx-core/tests/support/mod.rs | 1 + .../dbx-driver-agent/src/agent_connection.rs | 1 + crates/dbx-driver-redis/src/lib.rs | 1 + crates/dbx-types/src/models/connection.rs | 34 +++++ crates/dbx-web/src/routes/connection.rs | 1 + crates/dbx-web/src/routes/transfer.rs | 1 + src-tauri/src/commands/connection.rs | 1 + 44 files changed, 332 insertions(+), 20 deletions(-) diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index d3a5c68764..8c7ed5872d 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -148,7 +148,7 @@ import { } from "@lucide/vue"; import { buildDraftVisibleDatabasesConnectionId, connectionCanChooseVisibleDatabases, initialVisibleDatabaseSelection, visibleObjectFiltersNeedReset } from "@/lib/connection/connectionVisibleDatabases"; import { canSaveVisibleDatabaseSelection, connectionUsesVisibleSchemaFilter, filterDatabaseNamesForVisiblePicker, filterSchemaNamesForVisiblePicker, normalizeVisibleDatabaseSelection, buildDraftVisibleSchemasConnectionId, normalizeVisibleSchemaSelection } from "@/lib/database/visibleDatabases"; -import { isSchemaAware, isSingleDatabase } from "@/lib/database/databaseFeatureSupport"; +import { isSchemaAware, isSingleDatabase, supportsDataDictionary } from "@/lib/database/databaseFeatureSupport"; import { normalizeConnectionScope, normalizeConnectionTimeouts } from "@/lib/connection/connectionSubmitNormalization"; import { databaseConnectionFormKind } from "@/lib/database/databaseDriverManifest"; import VisibleSchemasDialog from "@/components/sidebar/VisibleSchemasDialog.vue"; @@ -231,6 +231,7 @@ const DREMIO_ARROW_FLIGHT_SQL_JDBC_DRIVER_CLASS = "org.apache.arrow.driver.jdbc. const DREMIO_LEGACY_JDBC_URL = "jdbc:dremio:direct=127.0.0.1:31010"; const DREMIO_LEGACY_JDBC_DRIVER_CLASS = "com.dremio.jdbc.Driver"; const DEFAULT_SSH_USER = "root"; +const DIRECT_SIDEBAR_OBJECT_TYPES = new Set(["redis", "etcd", "zookeeper", "consul", "mongodb", "dynamodb", "elasticsearch", "easysearch", "meilisearch", "solr", "milvus", "qdrant", "weaviate", "chromadb", "mq", "mqtt", "nacos", "plugin"]); const ETCD_GRPC_MAX_INBOUND_DEFAULT_MIB = 32; const ETCD_GRPC_MAX_INBOUND_MIN_MIB = 1; const ETCD_GRPC_MAX_INBOUND_MAX_MIB = 256; @@ -441,6 +442,7 @@ const defaultForm = (): ConnectionForm => ({ docs_notes_path: undefined, read_only: false, show_system_schemas: false, + sidebar_auto_load_all_tables: false, is_production: false, production_databases: [], visible_databases: undefined, @@ -632,6 +634,7 @@ function sshLayersForConfig(config: LegacyConnectionConfig): SshTunnelConfig[] { } const form = ref(defaultForm()); +const supportsAutomaticTableLoading = computed(() => supportsDataDictionary(form.value.db_type) && !DIRECT_SIDEBAR_OBJECT_TYPES.has(form.value.db_type)); const redisKeyTemplatesText = ref(""); const noteTextareaRef = ref(null); const showGaussdbConnectionMode = computed(() => form.value.db_type === "gaussdb"); @@ -3110,6 +3113,7 @@ watch( docs_notes_path: config.docs_notes_path, read_only: config.read_only || false, show_system_schemas: config.show_system_schemas || false, + sidebar_auto_load_all_tables: config.sidebar_auto_load_all_tables === true, is_production: config.is_production || false, production_databases: config.production_databases || [], visible_databases: config.visible_databases, @@ -5141,6 +5145,7 @@ function connectionConfigForSubmit(id: string, generatedName = "", validatePlugi config.visible_databases = Array.isArray(config.visible_databases) && config.visible_databases.length > 0 ? config.visible_databases : undefined; } if (!config.show_system_schemas) config.show_system_schemas = undefined; + if (!config.sidebar_auto_load_all_tables) config.sidebar_auto_load_all_tables = undefined; if (config.visible_schemas && Object.keys(config.visible_schemas).length === 0) config.visible_schemas = undefined; if (config.agent_java_options && config.agent_java_options.length === 0) config.agent_java_options = undefined; // Pasted credentials may carry invisible characters that trim() keeps (#9043). @@ -9878,6 +9883,16 @@ function openExternalUrl(url: string) { {{ t("connection.showSystemSchemasHint") }} +
+ +
+ +

{{ t("connection.autoLoadAllTablesHint") }}

+
+
diff --git a/apps/desktop/src/components/connection/__tests__/ConnectionDialog.deepLinkEdit.spec.ts b/apps/desktop/src/components/connection/__tests__/ConnectionDialog.deepLinkEdit.spec.ts index 3b77a65f8b..28153a5f18 100644 --- a/apps/desktop/src/components/connection/__tests__/ConnectionDialog.deepLinkEdit.spec.ts +++ b/apps/desktop/src/components/connection/__tests__/ConnectionDialog.deepLinkEdit.spec.ts @@ -234,7 +234,7 @@ describe("ConnectionDialog deep-link edit confirmation", () => { }); it("updates the existing ID only after Save and preserves omitted fields", async () => { - await mountDialog(savedConnection(), "dbx://connection/new?id=saved-production&password=new-password"); + await mountDialog(savedConnection({ sidebar_auto_load_all_tables: true }), "dbx://connection/new?id=saved-production&password=new-password"); const save = [...document.querySelectorAll("button")].find((button) => button.textContent?.trim() === "Save"); expect(save).toBeTruthy(); save!.click(); @@ -255,6 +255,7 @@ describe("ConnectionDialog deep-link edit confirmation", () => { is_production: true, production_databases: ["orders"], visible_databases: ["orders"], + sidebar_auto_load_all_tables: true, url_params: "connectTimeout=25", }), ); diff --git a/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue b/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue index bfbd0c4945..428c002573 100644 --- a/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue +++ b/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue @@ -87,7 +87,18 @@ import { canTreeNodePin, canTreeNodeShowExpander } from "@/lib/sidebar/sidebarTr import { sidebarConnectionVisibleFilterMenu } from "@/lib/sidebar/sidebarVisibleFilterMenu"; import { supportsSidebarObjectNameFilter } from "@/lib/sidebar/sidebarObjectNameFilter"; import { connectionGroupDestinationRows } from "@/lib/sidebar/sidebarLayout"; -import { hasTableVGroupEntries, isTableVGroupContainerNode, isTableVGroupGroupableRowType, resolveTableVGroupScopeFromNode, selectedTableVGroupMoveTargets, tableVGroupDestinationRows, tableVGroupPathForTable, tableVGroupScopeKey, tableVGroupsEnabled } from "@/lib/table/tableVGroup"; +import { + hasTableTreeLoadMore, + hasTableVGroupEntries, + isTableVGroupContainerNode, + isTableVGroupGroupableRowType, + resolveTableVGroupScopeFromNode, + selectedTableVGroupMoveTargets, + tableVGroupDestinationRows, + tableVGroupPathForTable, + tableVGroupScopeKey, + tableVGroupsEnabled, +} from "@/lib/table/tableVGroup"; import { objectTypesForGroupNode } from "@/lib/table/tableTree"; import { loadSidebarObjectGroup } from "@/lib/sidebar/sidebarObjectGroupRouting"; import { requestObjectBrowserSearchFocus } from "@/lib/tabs/objectBrowserSearchFocus"; @@ -883,7 +894,8 @@ async function toggle(requestId = beginNavigationRequest()) { // particular, schema-level trigger/type groups have no tableName, so they // must use the generic object loader rather than the table-trigger loader. const databaseObjectGroup = !!objectTypesForGroupNode(node.type); - if (databaseObjectGroup && connectionStore.canUseLoadedTreeNodeToggle(node)) { + const needsConfiguredTablePageDrain = node.type === "group-tables" && connectionStore.getConfig(node.connectionId || "")?.sidebar_auto_load_all_tables === true && hasTableTreeLoadMore(node.children ?? []); + if (databaseObjectGroup && connectionStore.canUseLoadedTreeNodeToggle(node) && !needsConfiguredTablePageDrain) { node.isExpanded = !node.isExpanded; if (wasExpanded && shouldReleaseCollapsedTreeNodeChildren()) connectionStore.releaseCollapsedTreeNodeChildren(node.id); emitNodeToggled(node, wasExpanded); diff --git a/apps/desktop/src/components/sidebar/__tests__/SidebarTreeRuntimeHost.expansion.spec.ts b/apps/desktop/src/components/sidebar/__tests__/SidebarTreeRuntimeHost.expansion.spec.ts index fde905d353..85b620135a 100644 --- a/apps/desktop/src/components/sidebar/__tests__/SidebarTreeRuntimeHost.expansion.spec.ts +++ b/apps/desktop/src/components/sidebar/__tests__/SidebarTreeRuntimeHost.expansion.spec.ts @@ -259,6 +259,42 @@ describe("SidebarTreeRuntimeHost expansion", () => { expect(connectionStore.loadObjectGroupChildren).not.toHaveBeenCalled(); }); + it("routes an opted-in partial table group back through the loader", async () => { + const group: TreeNode = { + id: "mysql:basic:__tables", + label: "tree.tables", + type: "group-tables", + connectionId: "mysql", + database: "basic", + isExpanded: false, + children: [ + { id: "mysql:basic:orders", label: "orders", type: "table", connectionId: "mysql", database: "basic" }, + { + id: "mysql:basic:__tables:__load_more:1000", + label: "tree.loadMore", + type: "load-more", + connectionId: "mysql", + database: "basic", + loadMore: { parentId: "mysql:basic:__tables", offset: 1000, pageSize: 1000 }, + }, + ], + }; + connectionStore.getConfig.mockReturnValue({ db_type: "mysql", name: "connection", sidebar_auto_load_all_tables: true }); + + const host = ref | null>(null); + const app = createApp(defineComponent({ setup: () => () => h(SidebarTreeRuntimeHost, { ref: host, node: group, depth: 0 }) })); + mountedApps.push(app); + const container = document.createElement("div"); + document.body.append(container); + app.use(i18n); + app.mount(container); + + host.value?.toggleNode(group); + + await vi.waitFor(() => expect(connectionStore.loadObjectGroupChildren).toHaveBeenCalledWith(group, undefined)); + expect(group.isExpanded).toBe(true); + }); + it("publishes a rendered group collapse and synchronizes the live tree", async () => { const liveGroup: TreeNode = { id: "connection:database:__tables", diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index c3f9b6c0c8..d8af162d21 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -1615,6 +1615,9 @@ export default { doltShowSystemTablesHint: "Show Dolt's built-in version-control tables in table lists for this connection.", showSystemSchemas: "Show System Schemas", showSystemSchemasHint: "Show built-in and metadata schemas in the sidebar and schema pickers for this connection.", + tableLoading: "Table loading", + autoLoadAllTables: "Load all tables automatically", + autoLoadAllTablesHint: "In Advanced sidebar view, opening a Tables group loads every page for this connection so table-name filtering covers all tables. Large schemas may take longer.", docsNotesPath: "Notes file", docsNotesPathPlaceholder: "docs/dbx-docs.json", docsNotesPathHint: "Where documentation notes are stored. Leave empty to keep them in the app data directory, or point this at a file in your repository to review schema documentation in pull requests.", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index 00e6392512..e39edb6afb 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -1532,6 +1532,9 @@ export default withEnglishFallback({ doltShowSystemTablesHint: "在当前连接的表列表中显示 Dolt 内置的版本控制表。", showSystemSchemas: "显示系统 Schema", showSystemSchemasHint: "为当前连接在侧边栏和 Schema 选择器中显示内置/元数据 Schema。", + tableLoading: "表加载策略", + autoLoadAllTables: "自动加载全部表", + autoLoadAllTablesHint: "使用侧边栏高级视图时,展开“表”分组会自动加载该连接的全部分页,使表名筛选覆盖所有表。表很多时可能需要更长时间。", docsNotesPath: "笔记文件", docsNotesPathPlaceholder: "docs/dbx-docs.json", docsNotesPathHint: "文档笔记的存储位置。留空则保存在应用数据目录中;指向仓库中的文件后,即可在 Pull Request 中审阅 Schema 文档。", diff --git a/apps/desktop/src/lib/connection/__tests__/connectionDatabaseInfo.spec.ts b/apps/desktop/src/lib/connection/__tests__/connectionDatabaseInfo.spec.ts index 117e72d0b1..0e4c373d35 100644 --- a/apps/desktop/src/lib/connection/__tests__/connectionDatabaseInfo.spec.ts +++ b/apps/desktop/src/lib/connection/__tests__/connectionDatabaseInfo.spec.ts @@ -65,7 +65,7 @@ describe("connectionDatabaseInfo", () => { }); }); - it("fingerprints the complete submitted config without depending on object key order", () => { + it("fingerprints connection-affecting config without depending on object key order", () => { const original = config({ transport_layers: [{ id: "ssh", type: "ssh", host: "jump", port: 22, user: "root", password: "hop-secret" }] }); const reordered = Object.fromEntries(Object.entries(original).reverse()) as unknown as ConnectionConfig; expect(connectionConfigFingerprint(reordered)).toBe(connectionConfigFingerprint(original)); @@ -76,6 +76,7 @@ describe("connectionDatabaseInfo", () => { expect(connectionConfigFingerprint({ ...original, database_info: { productName: "MySQL", productVersion: "8.4.0" } })).toBe(connectionConfigFingerprint(original)); expect(connectionConfigFingerprint({ ...original, note: "Production reporting" })).toBe(connectionConfigFingerprint(original)); expect(connectionConfigFingerprint({ ...original, default_schema: "archive" })).toBe(connectionConfigFingerprint(original)); + expect(connectionConfigFingerprint({ ...original, sidebar_auto_load_all_tables: true })).toBe(connectionConfigFingerprint(original)); }); it("formats only database metadata for rows and copied text", () => { diff --git a/apps/desktop/src/lib/connection/connectionConfigTransfer.spec.ts b/apps/desktop/src/lib/connection/connectionConfigTransfer.spec.ts index 92bf6f4ddf..8f2f2ae2a7 100644 --- a/apps/desktop/src/lib/connection/connectionConfigTransfer.spec.ts +++ b/apps/desktop/src/lib/connection/connectionConfigTransfer.spec.ts @@ -83,6 +83,16 @@ describe("connectionConfigTransfer", () => { }); }); + it("preserves the per-connection automatic table loading opt-in", () => { + const source = conn("large-schema", "Large schema", { sidebar_auto_load_all_tables: true }); + const serialized = JSON.parse(JSON.stringify(buildConnectionConfigBundle([source], null, []))); + const parsed = parseConnectionConfigObject(serialized); + const imported = prepareConnectionConfigImport(parsed, [], [], () => "imported-id"); + + expect(parsed.connections[0].sidebar_auto_load_all_tables).toBe(true); + expect(imported.connections[0]).toMatchObject({ id: "imported-id", sidebar_auto_load_all_tables: true }); + }); + it("keeps stable ids and removes secrets from plaintext exports", () => { const exported = scrubConnectionForPlaintextExport( conn("stable", "A", { diff --git a/apps/desktop/src/lib/connection/connectionDatabaseInfo.ts b/apps/desktop/src/lib/connection/connectionDatabaseInfo.ts index f1eec49276..7718347811 100644 --- a/apps/desktop/src/lib/connection/connectionDatabaseInfo.ts +++ b/apps/desktop/src/lib/connection/connectionDatabaseInfo.ts @@ -94,7 +94,7 @@ function fnv1a(value: string, seed: number): number { } export function connectionConfigFingerprint(config: ConnectionConfig, sourceName = config.name): string { - const { database_info: _databaseInfo, default_schema: _defaultSchema, note: _note, ...submittedConfig } = config; + const { database_info: _databaseInfo, default_schema: _defaultSchema, note: _note, sidebar_auto_load_all_tables: _sidebarAutoLoadAllTables, ...submittedConfig } = config; const serialized = JSON.stringify(stableValue({ config: submittedConfig, sourceName })); const first = fnv1a(serialized, 0x811c9dc5).toString(16).padStart(8, "0"); const second = fnv1a(serialized, 0x9e3779b9).toString(16).padStart(8, "0"); diff --git a/apps/desktop/src/stores/__tests__/connectionStore.metadataLoading.spec.ts b/apps/desktop/src/stores/__tests__/connectionStore.metadataLoading.spec.ts index c6c8e4e06b..439f3f678d 100644 --- a/apps/desktop/src/stores/__tests__/connectionStore.metadataLoading.spec.ts +++ b/apps/desktop/src/stores/__tests__/connectionStore.metadataLoading.spec.ts @@ -1,6 +1,7 @@ import { createPinia, setActivePinia } from "pinia"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { canTreeNodeShowExpander } from "@/lib/sidebar/sidebarTreeItemLayout"; +import { filterLocallySearchedTables } from "@/lib/sidebar/sidebarSearchTree"; import type { ConnectionConfig, ObjectInfo, TableInfo, TreeNode } from "@/types/database"; function installLocalStorage() { @@ -3170,6 +3171,129 @@ describe("connectionStore metadata loading", () => { expect(tablesGroup.children?.some((child) => child.label?.startsWith("fresh_"))).toBe(true); }); + it("automatically drains only opted-in table groups", async () => { + const rows = Array.from({ length: 5 }, (_, index) => ({ + name: `object_${index + 1}`, + table_type: "TABLE" as const, + comment: null, + })); + const listTables = vi.fn((_connectionId: string, _database: string, _schema: string, searchFilter?: string, limit?: number, offset?: number, objectTypes?: string[]) => { + const tableType = objectTypes?.includes("VIEW") ? "VIEW" : "TABLE"; + const matches = searchFilter ? rows.filter((row) => row.name.includes(searchFilter)) : rows; + return Promise.resolve(matches.slice(offset ?? 0, (offset ?? 0) + (limit ?? matches.length)).map((row) => ({ ...row, table_type: tableType }))); + }); + + vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false })); + vi.doMock("@/lib/backend/api", () => ({ + checkConnectionHealth: vi.fn().mockResolvedValue(undefined), + deleteSchemaCachePrefix: vi.fn().mockResolvedValue(undefined), + listTables, + loadSchemaCache: vi.fn().mockResolvedValue(null), + saveSchemaCache: vi.fn().mockResolvedValue(undefined), + saveConnections: vi.fn().mockResolvedValue(undefined), + saveSidebarLayout: vi.fn().mockResolvedValue(undefined), + })); + + const { useConnectionStore } = await import("@/stores/connectionStore"); + const { useSettingsStore } = await import("@/stores/settingsStore"); + const store = useConnectionStore(); + const settingsStore = useSettingsStore(); + settingsStore.editorSettings.sidebarObjectDisplay = "grouped"; + settingsStore.desktopSettings.sidebar_table_page_size = 2; + + const connection = { ...mysqlConnection(), sidebar_auto_load_all_tables: true }; + const tablesGroup: TreeNode = { + id: `${connection.id}:app:__tables`, + label: "tree.tables", + type: "group-tables", + connectionId: connection.id, + database: "app", + isExpanded: false, + children: [], + }; + const viewsGroup: TreeNode = { + ...tablesGroup, + id: `${connection.id}:app:__views`, + label: "tree.views", + type: "group-views", + }; + store.connections = [connection]; + store.connectedIds.add(connection.id); + store.treeNodes = [{ id: connection.id, label: connection.name, type: "connection", connectionId: connection.id, children: [tablesGroup, viewsGroup] }]; + + await store.loadObjectGroupChildren(tablesGroup); + + expect(listTables.mock.calls.map((call) => call[5])).toEqual([0, 2, 4]); + expect(tablesGroup.children?.map((child) => child.label)).toEqual(rows.map((row) => row.name)); + expect(tablesGroup.children?.some((child) => child.type === "load-more")).toBe(false); + const filteredTree = filterLocallySearchedTables(store.treeNodes, { + enabled: true, + queries: { [tablesGroup.id]: "object_5" }, + indexedResults: {}, + }); + expect(filteredTree[0].children?.[0]?.children?.map((child) => child.label)).toEqual(["object_5"]); + + await store.loadObjectGroupChildren(viewsGroup); + + expect(listTables).toHaveBeenCalledTimes(4); + expect(viewsGroup.children?.map((child) => child.label)).toEqual(["object_1", "object_2", "tree.loadMore"]); + + const searchedTablesGroup: TreeNode = { ...tablesGroup, id: `${connection.id}:search:__tables`, database: "search", children: [] }; + store.treeNodes[0].children!.push(searchedTablesGroup); + store.sidebarSearchQuery = "object_5"; + await store.loadObjectGroupChildren(searchedTablesGroup, { force: true }); + + expect(listTables).toHaveBeenCalledTimes(5); + expect(listTables.mock.calls.at(-1)?.slice(3, 6)).toEqual(["object_5", SIDEBAR_SEARCH_RESULT_BUDGET, undefined]); + expect(searchedTablesGroup.children?.map((child) => child.label)).toEqual(["object_5"]); + }); + + it("keeps legacy connections on the first table page", async () => { + const rows = Array.from({ length: 3 }, (_, index) => ({ + name: `table_${index + 1}`, + table_type: "TABLE" as const, + comment: null, + })); + const listTables = vi.fn((_connectionId: string, _database: string, _schema: string, _searchFilter?: string, limit?: number, offset?: number) => Promise.resolve(rows.slice(offset ?? 0, (offset ?? 0) + (limit ?? rows.length)))); + + vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false })); + vi.doMock("@/lib/backend/api", () => ({ + checkConnectionHealth: vi.fn().mockResolvedValue(undefined), + deleteSchemaCachePrefix: vi.fn().mockResolvedValue(undefined), + listTables, + loadSchemaCache: vi.fn().mockResolvedValue(null), + saveSchemaCache: vi.fn().mockResolvedValue(undefined), + saveConnections: vi.fn().mockResolvedValue(undefined), + saveSidebarLayout: vi.fn().mockResolvedValue(undefined), + })); + + const { useConnectionStore } = await import("@/stores/connectionStore"); + const { useSettingsStore } = await import("@/stores/settingsStore"); + const store = useConnectionStore(); + const settingsStore = useSettingsStore(); + settingsStore.editorSettings.sidebarObjectDisplay = "grouped"; + settingsStore.desktopSettings.sidebar_table_page_size = 2; + + const connection = mysqlConnection(); + const tablesGroup: TreeNode = { + id: `${connection.id}:app:__tables`, + label: "tree.tables", + type: "group-tables", + connectionId: connection.id, + database: "app", + isExpanded: false, + children: [], + }; + store.connections = [connection]; + store.connectedIds.add(connection.id); + store.treeNodes = [{ id: connection.id, label: connection.name, type: "connection", connectionId: connection.id, children: [tablesGroup] }]; + + await store.loadObjectGroupChildren(tablesGroup); + + expect(listTables).toHaveBeenCalledTimes(1); + expect(tablesGroup.children?.map((child) => child.label)).toEqual(["table_1", "table_2", "tree.loadMore"]); + }); + it("restores and drains unfiltered pages when expand-all races a search clear reload", async () => { const tables = Array.from({ length: 5 }, (_, index) => ({ name: `t_${String(index + 1).padStart(4, "0")}`, diff --git a/apps/desktop/src/stores/connectionStore.ts b/apps/desktop/src/stores/connectionStore.ts index ad619c9332..4df558c1a8 100644 --- a/apps/desktop/src/stores/connectionStore.ts +++ b/apps/desktop/src/stores/connectionStore.ts @@ -1721,6 +1721,7 @@ export const useConnectionStore = defineStore("connection", () => { docs_notes_path: config.docs_notes_path?.trim() ? config.docs_notes_path.trim() : undefined, transport_layers: Array.isArray(config.transport_layers) ? config.transport_layers : [], show_system_schemas: config.show_system_schemas === true, + sidebar_auto_load_all_tables: config.sidebar_auto_load_all_tables === true, connect_timeout_secs: connectTimeoutInherit ? settingsStore.editorSettings.globalConnectTimeoutSecs : config.connect_timeout_secs || 10, connect_timeout_inherit: connectTimeoutInherit, query_timeout_secs: queryTimeoutInherit ? settingsStore.editorSettings.globalQueryTimeoutSecs : (config.query_timeout_secs ?? DEFAULT_QUERY_TIMEOUT_SECS), @@ -6471,9 +6472,9 @@ export const useConnectionStore = defineStore("connection", () => { ); } - async function loadObjectGroupChildren(node: TreeNode, options?: LoadTreeOptions): Promise { + async function loadObjectGroupFirstPage(node: TreeNode, options?: LoadTreeOptions): Promise { if (options?.sidebarSearch) { - return withSidebarSearchLoad(node.connectionId, () => loadObjectGroupChildren(node, { ...options, sidebarSearch: false })); + return withSidebarSearchLoad(node.connectionId, () => loadObjectGroupFirstPage(node, { ...options, sidebarSearch: false })); } // Queued search/refresh tasks can outlive disconnect, which removes their nodes. if (!treeNodeInSidebarTree(node)) return; @@ -6501,7 +6502,7 @@ export const useConnectionStore = defineStore("connection", () => { if (!options?.force && !searchFilter && !options?.sidebarTableSearchParentId && !tableNameFilterForScope) { if (await hydrateTreeNodeFromCache(node, objectGroupCacheKey(node))) { forgetFilteredObjectGroupChildren(node.id); - void loadObjectGroupChildren(node, { ...options, force: true }).catch(() => undefined); + void loadObjectGroupFirstPage(node, { ...options, force: true }).catch(() => undefined); return; } } @@ -6624,6 +6625,23 @@ export const useConnectionStore = defineStore("connection", () => { ); } + function shouldAutoLoadAllTableGroupChildren(node: TreeNode, options?: LoadTreeOptions): boolean { + if (node.type !== "group-tables" || !node.connectionId) return false; + if (getConfig(node.connectionId)?.sidebar_auto_load_all_tables !== true) return false; + // Search projections have their own bounded/paged lifecycle. Automatically + // draining those pages would turn a filter operation into an unbounded load + // and could replace the unfiltered snapshot that clearing search restores. + return !options?.sidebarSearch && !options?.sidebarTableSearchParentId && !activeTreeLoadSearchFilter(options); + } + + async function loadObjectGroupChildren(node: TreeNode, options?: LoadTreeOptions): Promise { + if (shouldAutoLoadAllTableGroupChildren(node, options)) { + await loadAllObjectGroupChildren(node, options); + return; + } + await loadObjectGroupFirstPage(node, options); + } + async function loadCustomTypeChildren(node: TreeNode, options?: LoadTreeOptions) { if (node.type !== "type" || !node.connectionId || !hasTreeNodeDatabaseContext(node)) return; let load = beginTreeNodeLoad(node); @@ -6971,14 +6989,15 @@ export const useConnectionStore = defineStore("connection", () => { ); } - async function loadAllObjectGroupChildren(parent: TreeNode) { + async function loadAllObjectGroupChildren(parent: TreeNode, initialLoadOptions?: LoadTreeOptions) { if (!parent.connectionId || !hasTreeNodeDatabaseContext(parent)) return; if (!objectTypesForGroupNode(parent.type)) return; const liveParent = treeNodeInSidebarTree(parent); if (!liveParent) return; - if (!isTreeNodeChildrenLoaded(liveParent.id) || filteredObjectGroupChildrenIds.has(liveParent.id)) { + if (initialLoadOptions?.force || !isTreeNodeChildrenLoaded(liveParent.id) || filteredObjectGroupChildrenIds.has(liveParent.id)) { let isPreparedPageCurrent: (() => boolean) | undefined; - await loadObjectGroupChildren(liveParent, { + await loadObjectGroupFirstPage(liveParent, { + ...initialLoadOptions, force: true, searchFilter: "", allowGlobalSearchMismatch: true, diff --git a/apps/desktop/src/types/database.ts b/apps/desktop/src/types/database.ts index ec37b5f249..487c064c51 100644 --- a/apps/desktop/src/types/database.ts +++ b/apps/desktop/src/types/database.ts @@ -105,6 +105,8 @@ export interface ConnectionConfig { visible_database_patterns?: string[]; visible_schemas?: Record; show_system_schemas?: boolean; + /** Load every page when the sidebar's Tables group is opened for this connection. */ + sidebar_auto_load_all_tables?: boolean; attached_databases?: AttachedDatabaseConfig[]; init_script?: string; color?: string; diff --git a/crates/dbx-core/examples/table_import_live_bench.rs b/crates/dbx-core/examples/table_import_live_bench.rs index 1619dfc91c..d84be9e0a9 100644 --- a/crates/dbx-core/examples/table_import_live_bench.rs +++ b/crates/dbx-core/examples/table_import_live_bench.rs @@ -173,6 +173,7 @@ fn connection_config(id: &str, database: BenchDatabase) -> Result bool { if a == b { return true; } + let mut a = a.clone(); + let mut b = b.clone(); + // Sidebar paging is a presentation preference and cannot change an + // established database session. + a.sidebar_auto_load_all_tables = false; + b.sidebar_auto_load_all_tables = false; if !a.save_password && !b.save_password { - let mut a = a.clone(); a.password.clear(); - let mut b = b.clone(); b.password.clear(); - return a == b; } - false + a == b } /// Whether transient credentials can safely survive a persisted config update. @@ -6393,6 +6397,7 @@ pub fn connection_configs_session_credentials_compatible(a: &ConnectionConfig, b config.visible_databases = None; config.visible_schemas = None; config.show_system_schemas = false; + config.sidebar_auto_load_all_tables = false; config.color = None; config.docs_notes_path = None; config.connect_timeout_secs = 0; @@ -6953,6 +6958,7 @@ mod tests { visible_database_patterns: None, visible_schemas: None, show_system_schemas: false, + sidebar_auto_load_all_tables: false, attached_databases: Vec::new(), init_script: None, color: None, @@ -7048,6 +7054,16 @@ mod tests { assert!(connection_configs_pool_equivalent(&a, &c)); } + #[test] + fn connection_configs_pool_equivalent_ignores_sidebar_table_loading_preference() { + let a = mysql_config(None); + let mut b = a.clone(); + b.sidebar_auto_load_all_tables = true; + + assert!(connection_configs_pool_equivalent(&a, &b)); + assert!(connection_configs_pool_equivalent(&b, &a)); + } + #[test] fn connection_configs_pool_equivalent_detects_saved_password_change() { let mut a = mysql_config(None); diff --git a/crates/dbx-core/src/data/transfer.rs b/crates/dbx-core/src/data/transfer.rs index f82a289da5..c22fdb54fd 100644 --- a/crates/dbx-core/src/data/transfer.rs +++ b/crates/dbx-core/src/data/transfer.rs @@ -10855,6 +10855,7 @@ mod tests { visible_database_patterns: None, visible_schemas: None, show_system_schemas: false, + sidebar_auto_load_all_tables: false, attached_databases: Vec::new(), init_script: None, color: None, @@ -16854,6 +16855,7 @@ SELECT 1 FROM dual"# visible_database_patterns: None, visible_schemas: None, show_system_schemas: false, + sidebar_auto_load_all_tables: false, attached_databases: Vec::new(), init_script: None, color: None, diff --git a/crates/dbx-core/src/persistence/cloud_sync.rs b/crates/dbx-core/src/persistence/cloud_sync.rs index ab2c1e4b76..b3e3ea54fa 100644 --- a/crates/dbx-core/src/persistence/cloud_sync.rs +++ b/crates/dbx-core/src/persistence/cloud_sync.rs @@ -2480,6 +2480,7 @@ mod tests { visible_database_patterns: None, visible_schemas: None, show_system_schemas: false, + sidebar_auto_load_all_tables: false, attached_databases: Vec::new(), init_script: None, color: None, @@ -2563,6 +2564,7 @@ mod tests { visible_database_patterns: None, visible_schemas: None, show_system_schemas: false, + sidebar_auto_load_all_tables: false, attached_databases: Vec::new(), init_script: None, color: None, @@ -2801,6 +2803,7 @@ mod tests { visible_database_patterns: None, visible_schemas: None, show_system_schemas: false, + sidebar_auto_load_all_tables: false, attached_databases: Vec::new(), init_script: Some("CREATE SECRET (TYPE quack, TOKEN 'token-value');".to_string()), color: None, diff --git a/crates/dbx-core/src/persistence/storage.rs b/crates/dbx-core/src/persistence/storage.rs index 98e4ddc660..53b8d30679 100644 --- a/crates/dbx-core/src/persistence/storage.rs +++ b/crates/dbx-core/src/persistence/storage.rs @@ -10258,6 +10258,7 @@ mod tests { visible_database_patterns: None, visible_schemas: None, show_system_schemas: false, + sidebar_auto_load_all_tables: false, attached_databases: Vec::new(), init_script: None, color: None, @@ -10332,6 +10333,7 @@ mod tests { visible_database_patterns: None, visible_schemas: None, show_system_schemas: false, + sidebar_auto_load_all_tables: false, attached_databases: Vec::new(), init_script: None, color: None, diff --git a/crates/dbx-core/src/query/mod.rs b/crates/dbx-core/src/query/mod.rs index 0ec1c5efda..84e5209e6b 100644 --- a/crates/dbx-core/src/query/mod.rs +++ b/crates/dbx-core/src/query/mod.rs @@ -8060,6 +8060,7 @@ for line in sys.stdin: visible_database_patterns: None, visible_schemas: None, show_system_schemas: false, + sidebar_auto_load_all_tables: false, attached_databases: Vec::new(), init_script: None, color: None, @@ -10901,6 +10902,7 @@ for line in sys.stdin: visible_database_patterns: None, visible_schemas: None, show_system_schemas: false, + sidebar_auto_load_all_tables: false, attached_databases: Vec::new(), init_script: None, color: None, diff --git a/crates/dbx-core/src/safety/production_safety.rs b/crates/dbx-core/src/safety/production_safety.rs index 9d18b3e021..22bba50c08 100644 --- a/crates/dbx-core/src/safety/production_safety.rs +++ b/crates/dbx-core/src/safety/production_safety.rs @@ -777,6 +777,7 @@ mod tests { visible_database_patterns: None, visible_schemas: None, show_system_schemas: false, + sidebar_auto_load_all_tables: false, attached_databases: vec![], init_script: None, color: None, diff --git a/crates/dbx-core/src/schema/mod.rs b/crates/dbx-core/src/schema/mod.rs index 38bcf787d9..05d1ed29ba 100644 --- a/crates/dbx-core/src/schema/mod.rs +++ b/crates/dbx-core/src/schema/mod.rs @@ -3349,6 +3349,7 @@ mod tests { visible_database_patterns: None, visible_schemas: None, show_system_schemas: false, + sidebar_auto_load_all_tables: false, attached_databases: Vec::new(), init_script: None, color: None, diff --git a/crates/dbx-core/src/schema/normalization.rs b/crates/dbx-core/src/schema/normalization.rs index 0834f64f0a..2b5dc32a35 100644 --- a/crates/dbx-core/src/schema/normalization.rs +++ b/crates/dbx-core/src/schema/normalization.rs @@ -162,6 +162,7 @@ mod tests { visible_database_patterns: None, visible_schemas: None, show_system_schemas: false, + sidebar_auto_load_all_tables: false, attached_databases: Vec::new(), init_script: None, color: None, diff --git a/crates/dbx-core/tests/connection_url_compatibility.rs b/crates/dbx-core/tests/connection_url_compatibility.rs index 35900a96fd..a3102fc0e7 100644 --- a/crates/dbx-core/tests/connection_url_compatibility.rs +++ b/crates/dbx-core/tests/connection_url_compatibility.rs @@ -25,6 +25,7 @@ fn mysql_config(username: &str, password: &str, database: Option<&str>) -> Conne visible_database_patterns: None, visible_schemas: None, show_system_schemas: false, + sidebar_auto_load_all_tables: false, attached_databases: Vec::new(), init_script: None, color: None, diff --git a/crates/dbx-core/tests/dump_docs_fixture.rs b/crates/dbx-core/tests/dump_docs_fixture.rs index 3ec689c11e..503edfe4af 100644 --- a/crates/dbx-core/tests/dump_docs_fixture.rs +++ b/crates/dbx-core/tests/dump_docs_fixture.rs @@ -82,6 +82,7 @@ fn live_postgres_config( is_production: false, production_databases: vec![], show_system_schemas: false, + sidebar_auto_load_all_tables: false, database_info: None, } } diff --git a/crates/dbx-core/tests/live_postgres_docs_annotations.rs b/crates/dbx-core/tests/live_postgres_docs_annotations.rs index 56c4008aeb..7d67249f80 100644 --- a/crates/dbx-core/tests/live_postgres_docs_annotations.rs +++ b/crates/dbx-core/tests/live_postgres_docs_annotations.rs @@ -78,6 +78,7 @@ fn live_postgres_config( is_production: false, production_databases: vec![], show_system_schemas: false, + sidebar_auto_load_all_tables: false, database_info: None, } } diff --git a/crates/dbx-core/tests/live_postgres_docs_snapshot.rs b/crates/dbx-core/tests/live_postgres_docs_snapshot.rs index ed6c841a72..f2076dab42 100644 --- a/crates/dbx-core/tests/live_postgres_docs_snapshot.rs +++ b/crates/dbx-core/tests/live_postgres_docs_snapshot.rs @@ -73,6 +73,7 @@ fn live_postgres_config( is_production: false, production_databases: vec![], show_system_schemas: false, + sidebar_auto_load_all_tables: false, database_info: None, } } diff --git a/crates/dbx-core/tests/live_postgres_error_position.rs b/crates/dbx-core/tests/live_postgres_error_position.rs index 627018b43a..0720890635 100644 --- a/crates/dbx-core/tests/live_postgres_error_position.rs +++ b/crates/dbx-core/tests/live_postgres_error_position.rs @@ -227,6 +227,7 @@ fn live_connection_config( is_production: false, production_databases: vec![], show_system_schemas: false, + sidebar_auto_load_all_tables: false, database_info: None, } } diff --git a/crates/dbx-core/tests/live_postgres_query_result_export.rs b/crates/dbx-core/tests/live_postgres_query_result_export.rs index 24f0440a6b..1c1da7e56b 100644 --- a/crates/dbx-core/tests/live_postgres_query_result_export.rs +++ b/crates/dbx-core/tests/live_postgres_query_result_export.rs @@ -78,6 +78,7 @@ fn live_postgres_config( is_production: false, production_databases: vec![], show_system_schemas: false, + sidebar_auto_load_all_tables: false, database_info: None, } } diff --git a/crates/dbx-core/tests/live_postgres_transfer.rs b/crates/dbx-core/tests/live_postgres_transfer.rs index 4cfa24168f..9e8ba5f570 100644 --- a/crates/dbx-core/tests/live_postgres_transfer.rs +++ b/crates/dbx-core/tests/live_postgres_transfer.rs @@ -72,6 +72,7 @@ fn postgres_test_config(id: &str, database: &str) -> ConnectionConfig { is_production: false, production_databases: vec![], show_system_schemas: false, + sidebar_auto_load_all_tables: false, database_info: None, } } diff --git a/crates/dbx-core/tests/live_sqlserver_completion.rs b/crates/dbx-core/tests/live_sqlserver_completion.rs index 18c3137e35..789a865582 100644 --- a/crates/dbx-core/tests/live_sqlserver_completion.rs +++ b/crates/dbx-core/tests/live_sqlserver_completion.rs @@ -79,6 +79,7 @@ fn live_sqlserver_config(id: &str, database: &str) -> dbx_core::models::connecti is_production: false, production_databases: vec![], show_system_schemas: false, + sidebar_auto_load_all_tables: false, database_info: None, } } diff --git a/crates/dbx-core/tests/live_sqlserver_query_result_export.rs b/crates/dbx-core/tests/live_sqlserver_query_result_export.rs index 5671718855..4888b34835 100644 --- a/crates/dbx-core/tests/live_sqlserver_query_result_export.rs +++ b/crates/dbx-core/tests/live_sqlserver_query_result_export.rs @@ -68,6 +68,7 @@ fn live_sqlserver_config(id: &str, database: &str) -> dbx_core::models::connecti is_production: false, production_databases: vec![], show_system_schemas: false, + sidebar_auto_load_all_tables: false, database_info: None, } } diff --git a/crates/dbx-core/tests/live_sqlserver_transfer.rs b/crates/dbx-core/tests/live_sqlserver_transfer.rs index 3eee1ca370..e831e38507 100644 --- a/crates/dbx-core/tests/live_sqlserver_transfer.rs +++ b/crates/dbx-core/tests/live_sqlserver_transfer.rs @@ -71,6 +71,7 @@ fn live_sqlserver_config(id: &str, database: &str) -> ConnectionConfig { is_production: false, production_databases: vec![], show_system_schemas: false, + sidebar_auto_load_all_tables: false, database_info: None, } } diff --git a/crates/dbx-core/tests/support/mod.rs b/crates/dbx-core/tests/support/mod.rs index 8e576f3786..348f4a0896 100644 --- a/crates/dbx-core/tests/support/mod.rs +++ b/crates/dbx-core/tests/support/mod.rs @@ -193,6 +193,7 @@ pub fn postgres_test_config(id: &str, port: u16) -> ConnectionConfig { is_production: false, production_databases: vec![], show_system_schemas: false, + sidebar_auto_load_all_tables: false, database_info: None, } } diff --git a/crates/dbx-driver-agent/src/agent_connection.rs b/crates/dbx-driver-agent/src/agent_connection.rs index e360fa392f..a325b04165 100644 --- a/crates/dbx-driver-agent/src/agent_connection.rs +++ b/crates/dbx-driver-agent/src/agent_connection.rs @@ -752,6 +752,7 @@ mod tests { visible_database_patterns: None, visible_schemas: None, show_system_schemas: false, + sidebar_auto_load_all_tables: false, attached_databases: Vec::new(), init_script: None, color: None, diff --git a/crates/dbx-driver-redis/src/lib.rs b/crates/dbx-driver-redis/src/lib.rs index 1d96258d78..df84fd99f4 100644 --- a/crates/dbx-driver-redis/src/lib.rs +++ b/crates/dbx-driver-redis/src/lib.rs @@ -7486,6 +7486,7 @@ mod tests { visible_database_patterns: None, visible_schemas: None, show_system_schemas: false, + sidebar_auto_load_all_tables: false, attached_databases: Vec::new(), init_script: None, color: None, diff --git a/crates/dbx-types/src/models/connection.rs b/crates/dbx-types/src/models/connection.rs index d5308a5ecb..72bcec9c7e 100644 --- a/crates/dbx-types/src/models/connection.rs +++ b/crates/dbx-types/src/models/connection.rs @@ -123,6 +123,9 @@ pub struct ConnectionConfig { pub visible_schemas: Option>>, #[serde(default, skip_serializing_if = "is_false")] pub show_system_schemas: bool, + /// Frontend navigation preference: exhaust the paginated Tables group when opened. + #[serde(default, skip_serializing_if = "is_false")] + pub sidebar_auto_load_all_tables: bool, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub attached_databases: Vec, /// SQL statements executed right after the connection is established @@ -581,6 +584,8 @@ struct ConnectionConfigData { #[serde(default)] pub show_system_schemas: bool, #[serde(default)] + pub sidebar_auto_load_all_tables: bool, + #[serde(default)] pub attached_databases: Vec, #[serde(default)] pub init_script: Option, @@ -691,6 +696,7 @@ impl From for ConnectionConfig { visible_database_patterns: data.visible_database_patterns, visible_schemas: data.visible_schemas, show_system_schemas: data.show_system_schemas, + sidebar_auto_load_all_tables: data.sidebar_auto_load_all_tables, attached_databases: data.attached_databases, init_script: data.init_script, color: data.color, @@ -2758,6 +2764,33 @@ mod tests { assert_eq!(serde_json::to_value(parsed).unwrap()["default_schema"], "archive"); } + #[test] + fn sidebar_auto_load_all_tables_defaults_off_and_round_trips() { + let mut value = serde_json::json!({ + "id": "id", + "name": "MariaDB", + "db_type": "mysql", + "host": "localhost", + "port": 3306, + "username": "root", + "password": "", + "database": "app" + }); + let legacy: ConnectionConfig = serde_json::from_value(value.clone()).unwrap(); + assert!(!legacy.sidebar_auto_load_all_tables); + let serialized_legacy = serde_json::to_value(legacy).unwrap(); + assert!(serialized_legacy.get("sidebar_auto_load_all_tables").is_none()); + + value["sidebar_auto_load_all_tables"] = serde_json::json!(true); + let configured: ConnectionConfig = serde_json::from_value(value).unwrap(); + assert!(configured.sidebar_auto_load_all_tables); + assert!( + serde_json::to_value(configured).unwrap()["sidebar_auto_load_all_tables"] + .as_bool() + .unwrap() + ); + } + fn mysql_config(username: &str, password: &str, database: Option<&str>) -> ConnectionConfig { ConnectionConfig { docs_notes_path: None, @@ -2779,6 +2812,7 @@ mod tests { visible_database_patterns: None, visible_schemas: None, show_system_schemas: false, + sidebar_auto_load_all_tables: false, attached_databases: Vec::new(), init_script: None, color: None, diff --git a/crates/dbx-web/src/routes/connection.rs b/crates/dbx-web/src/routes/connection.rs index 283ac34d6e..e97e6dcdbc 100644 --- a/crates/dbx-web/src/routes/connection.rs +++ b/crates/dbx-web/src/routes/connection.rs @@ -1010,6 +1010,7 @@ mod tests { visible_database_patterns: None, visible_schemas: None, show_system_schemas: false, + sidebar_auto_load_all_tables: false, attached_databases: Vec::new(), init_script: None, color: None, diff --git a/crates/dbx-web/src/routes/transfer.rs b/crates/dbx-web/src/routes/transfer.rs index 45659d837e..ecf22d174c 100644 --- a/crates/dbx-web/src/routes/transfer.rs +++ b/crates/dbx-web/src/routes/transfer.rs @@ -692,6 +692,7 @@ mod tests { visible_database_patterns: None, visible_schemas: None, show_system_schemas: false, + sidebar_auto_load_all_tables: false, attached_databases: Vec::new(), init_script: None, color: None, diff --git a/src-tauri/src/commands/connection.rs b/src-tauri/src/commands/connection.rs index bde301e5f6..33130476d5 100644 --- a/src-tauri/src/commands/connection.rs +++ b/src-tauri/src/commands/connection.rs @@ -242,6 +242,7 @@ mod tests { visible_database_patterns: None, visible_schemas: None, show_system_schemas: false, + sidebar_auto_load_all_tables: false, attached_databases: Vec::new(), init_script: None, color: None,