mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 06:00:39 +00:00
feat(sidebar): auto-load all tables per connection when expanded
Closes #9834
This commit is contained in:
@@ -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<DatabaseType>(["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<HTMLTextAreaElement | null>(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) {
|
||||
<span class="text-xs text-muted-foreground">{{ t("connection.showSystemSchemasHint") }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div v-if="supportsAutomaticTableLoading" class="grid grid-cols-4 items-start gap-4">
|
||||
<Label :class="connectionLabelSmallPaddedClass">{{ t("connection.tableLoading") }}</Label>
|
||||
<div class="col-span-3 grid gap-1.5">
|
||||
<label class="flex cursor-pointer items-center gap-2">
|
||||
<Switch v-model="form.sidebar_auto_load_all_tables" />
|
||||
<span class="text-sm font-medium">{{ t("connection.autoLoadAllTables") }}</span>
|
||||
</label>
|
||||
<p class="text-xs leading-5 text-muted-foreground">{{ t("connection.autoLoadAllTablesHint") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Documentation notes are a relational-only feature, so this
|
||||
follows the same isSchemaAware gate as the row above. -->
|
||||
<div v-if="isSchemaAware(form.db_type)" class="grid grid-cols-4 items-start gap-4">
|
||||
|
||||
+2
-1
@@ -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",
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
+36
@@ -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<InstanceType<typeof SidebarTreeRuntimeHost> | 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",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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 文档。",
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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", {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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")}`,
|
||||
|
||||
@@ -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<void> {
|
||||
async function loadObjectGroupFirstPage(node: TreeNode, options?: LoadTreeOptions): Promise<void> {
|
||||
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<void> {
|
||||
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,
|
||||
|
||||
@@ -105,6 +105,8 @@ export interface ConnectionConfig {
|
||||
visible_database_patterns?: string[];
|
||||
visible_schemas?: Record<string, string[]>;
|
||||
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;
|
||||
|
||||
@@ -173,6 +173,7 @@ fn connection_config(id: &str, database: BenchDatabase) -> Result<ConnectionConf
|
||||
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,
|
||||
|
||||
@@ -198,6 +198,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,
|
||||
|
||||
@@ -940,6 +940,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,
|
||||
|
||||
@@ -385,6 +385,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,
|
||||
|
||||
@@ -661,6 +661,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,
|
||||
@@ -743,6 +744,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,
|
||||
|
||||
@@ -1657,6 +1657,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,
|
||||
|
||||
@@ -1049,6 +1049,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,
|
||||
|
||||
@@ -6358,22 +6358,26 @@ fn session_scoped_pool_key_for(
|
||||
|
||||
/// 两个运行态连接配置是否视为同一连接(用于决定是否销毁连接池)。
|
||||
///
|
||||
/// 仅当双方都是 `save_password=false` 时才忽略 `password` 字段的差异:这类连接
|
||||
/// 在 connect 时运行态配置可能携带会话密码,持久化同步后为空,这种空值差异不应
|
||||
/// 触发池重建。若任一方 `save_password=true`,密码是真实的连接参数,任何密码变更
|
||||
/// (包括用户保存了新密码)都必须销毁旧池,否则旧池会继续用旧密码认证。
|
||||
/// 始终忽略只影响前端导航的表加载策略。仅当双方都是 `save_password=false` 时才忽略
|
||||
/// `password` 字段的差异:这类连接在 connect 时运行态配置可能携带会话密码,持久化
|
||||
/// 同步后为空,这种空值差异不应触发池重建。若任一方 `save_password=true`,密码是
|
||||
/// 真实的连接参数,任何密码变更(包括用户保存了新密码)都必须销毁旧池,否则旧池会
|
||||
/// 继续用旧密码认证。
|
||||
pub fn connection_configs_pool_equivalent(a: &ConnectionConfig, b: &ConnectionConfig) -> 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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -123,6 +123,9 @@ pub struct ConnectionConfig {
|
||||
pub visible_schemas: Option<HashMap<String, Vec<String>>>,
|
||||
#[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<AttachedDatabaseConfig>,
|
||||
/// 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<AttachedDatabaseConfig>,
|
||||
#[serde(default)]
|
||||
pub init_script: Option<String>,
|
||||
@@ -691,6 +696,7 @@ impl From<ConnectionConfigData> 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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user