fix(jdbc): show schemas for generic JDBC connections

This commit is contained in:
t8y2 committed 2026-07-25 21:51:11 +08:00
1 parent 1e21a5ad52
commit 47d48a09e9
5 files changed
+144 -5

No files matched your search

@@ -50,6 +50,21 @@ function oracleConnection(): ConnectionConfig {
} as ConnectionConfig;
}
function genericJdbcConnection(): ConnectionConfig {
return {
id: "jdbc-1",
name: "Generic JDBC",
db_type: "jdbc",
host: "127.0.0.1",
port: 0,
username: "app",
password: "",
database: "testdb",
driver_profile: "jdbc",
connection_string: "jdbc:example://127.0.0.1/testdb",
} as ConnectionConfig;
}
function procedure(name: string): ObjectInfo {
return {
name,
@@ -160,6 +175,77 @@ describe("connectionStore metadata loading", () => {
expect(node.isExpanded).toBe(true);
});
it("discovers schema nodes for unknown generic JDBC databases", async () => {
const listSchemaInfos = vi.fn().mockResolvedValue([
{ name: "app", comment: null },
{ name: "reporting", comment: null },
]);
const listTables = vi.fn().mockResolvedValue([]);
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
vi.doMock("@/lib/backend/api", () => ({
checkConnectionHealth: vi.fn().mockResolvedValue(undefined),
deleteSchemaCachePrefix: vi.fn().mockResolvedValue(undefined),
listSchemaInfos,
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 store = useConnectionStore();
const connection = genericJdbcConnection();
const databaseNode: TreeNode = { id: "jdbc-1:testdb", label: "testdb", type: "database", connectionId: connection.id, database: "testdb", isExpanded: false, children: [] };
store.connections = [connection];
store.connectedIds.add(connection.id);
store.treeNodes = [{ id: connection.id, label: connection.name, type: "connection", connectionId: connection.id, isExpanded: true, children: [databaseNode] }];
await store.loadTreeNodeChildren(databaseNode, { force: true });
expect(listSchemaInfos).toHaveBeenCalledWith(connection.id, "testdb");
expect(listTables).not.toHaveBeenCalled();
expect(databaseNode.children?.map((node) => [node.type, node.label, node.schema])).toEqual([
["schema", "app", "app"],
["schema", "reporting", "reporting"],
]);
});
it("keeps the flat object tree for unknown generic JDBC databases without schemas", async () => {
const listSchemaInfos = vi.fn().mockResolvedValue([]);
const listTables = vi.fn().mockResolvedValue([{ name: "t", table_type: "TABLE", comment: null }]);
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
vi.doMock("@/lib/backend/api", () => ({
checkConnectionHealth: vi.fn().mockResolvedValue(undefined),
deleteSchemaCachePrefix: vi.fn().mockResolvedValue(undefined),
listObjects: vi.fn().mockResolvedValue([]),
listSchemaInfos,
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();
useSettingsStore().editorSettings.sidebarObjectDisplay = "simple";
const connection = genericJdbcConnection();
const databaseNode: TreeNode = { id: "jdbc-1:testdb", label: "testdb", type: "database", connectionId: connection.id, database: "testdb", isExpanded: false, children: [] };
store.connections = [connection];
store.connectedIds.add(connection.id);
store.treeNodes = [{ id: connection.id, label: connection.name, type: "connection", connectionId: connection.id, isExpanded: true, children: [databaseNode] }];
await store.loadTreeNodeChildren(databaseNode, { force: true });
expect(listSchemaInfos).toHaveBeenCalledWith(connection.id, "testdb");
expect(listTables).toHaveBeenCalled();
expect(databaseNode.children?.map((node) => [node.type, node.label, node.schema])).toEqual([["table", "t", undefined]]);
});
it("renders simple-mode table children without waiting for supplemental objects", async () => {
const tables: TableInfo[] = [{ name: "users", table_type: "BASE TABLE", comment: null }];
const listTables = vi.fn().mockResolvedValue(tables);
+9 -2
View File
@@ -47,7 +47,7 @@ import * as api from "@/lib/backend/api";
import { isTauriRuntime } from "@/lib/backend/tauriRuntime";
import { useTunnelProfileStore } from "@/stores/tunnelProfileStore";
import { connectionIsDorisFamilyCatalogCapable, isInternalDorisCatalog, isSchemaAware, normalizeSidebarObjectKind, sidebarObjectKindsForDatabase, usesTreeSchemaMode } from "@/lib/database/databaseCapabilities";
import { connectionObjectTreeNodeSchema, connectionObjectTreeQuerySchema, connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
import { connectionObjectTreeNodeSchema, connectionObjectTreeQuerySchema, connectionShouldDiscoverJdbcSchemas, connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
import { buildDatabaseTreeNodes, buildDuckDbConnectionTreeNodes, compareSidebarNames, sortSidebarDatabases, sortSidebarNames, shouldIncludeDefaultDatabaseNode } from "@/lib/database/databaseTree";
import { buildSqlServerDatabaseTreeNodes } from "@/lib/database/sqlServerTree";
import { collapseExpandedTreeNodes } from "@/lib/sidebar/sidebarTreeCollapse";
@@ -3148,6 +3148,13 @@ export const useConnectionStore = defineStore("connection", () => {
children: [],
};
});
if (schemas.length === 0 && connectionShouldDiscoverJdbcSchemas(getConfig(connectionId))) {
// Generic JDBC drivers vary widely: prefer schema navigation when the
// driver reports schemas, but keep the legacy flat object tree when it
// reports none so non-schema engines do not expand into an empty node.
await loadTables(connectionId, database, undefined, options);
return;
}
if (isPostgresLikeForExtensions(getConfig(connectionId)?.db_type)) {
children.push(buildExtensionManagementNode(connectionId, database));
}
@@ -4395,7 +4402,7 @@ export const useConnectionStore = defineStore("connection", () => {
const effectiveDbType = effectiveDatabaseTypeForConnection(config);
if (config?.db_type === "sqlserver") {
await loadSqlServerDatabaseObjects(node.connectionId, node.database, options);
} else if (usesTreeSchemaMode(effectiveDbType) && !connectionUsesDatabaseObjectTreeMode(config)) {
} else if ((usesTreeSchemaMode(effectiveDbType) && !connectionUsesDatabaseObjectTreeMode(config)) || connectionShouldDiscoverJdbcSchemas(config)) {
await loadSchemas(node.connectionId, node.database, options);
} else {
await loadTables(node.connectionId, node.database, undefined, options);