parent
8f597a0516
commit
1daaac03aa
|
|
@ -7,6 +7,7 @@ import {
|
|||
connectionQueryExecutionSchema,
|
||||
connectionShouldDiscoverJdbcSchemas,
|
||||
connectionShouldLoadIdentifierQuote,
|
||||
connectionUsesConnectionRootSchemaMode,
|
||||
connectionUsesDatabaseObjectTreeMode,
|
||||
effectiveDatabaseTypeForConnection,
|
||||
gaussdbConnectionMode,
|
||||
|
|
@ -64,6 +65,22 @@ describe("jdbc dialect inference", () => {
|
|||
).toBe("sqlserver");
|
||||
});
|
||||
|
||||
it.each([
|
||||
["jdbc:oracle:thin:@//localhost:1521/XE", "oracle"],
|
||||
["jdbc:dm://localhost:5236/DAMENG", "dameng"],
|
||||
] as const)("uses connection-root schemas for %s", (connectionString, dialect) => {
|
||||
const connection = {
|
||||
db_type: "jdbc" as const,
|
||||
connection_string: connectionString,
|
||||
};
|
||||
|
||||
expect(inferJdbcDialect(connection)).toBe(dialect);
|
||||
expect(connectionUsesConnectionRootSchemaMode(connection)).toBe(true);
|
||||
expect(connectionUsesDatabaseObjectTreeMode(connection)).toBe(false);
|
||||
expect(connectionObjectTreeQuerySchema(connection, "DBX_TEST", "DBX_TEST")).toBe("DBX_TEST");
|
||||
expect(connectionObjectTreeNodeSchema(connection, "DBX_TEST", "DBX_TEST")).toBe("DBX_TEST");
|
||||
});
|
||||
|
||||
it("detects GaussDB-compatible JDBC connections as schema-aware", () => {
|
||||
const gaussdbConnection = {
|
||||
db_type: "jdbc" as const,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { filterSchemaNamesForConnection, filterSchemaNamesForVisiblePicker, isSystemSchemaName } from "@/lib/database/visibleDatabases";
|
||||
import { connectionUsesVisibleSchemaFilter, filterSchemaNamesForConnection, filterSchemaNamesForVisiblePicker, isSystemSchemaName } from "@/lib/database/visibleDatabases";
|
||||
|
||||
describe("visibleDatabases schema filtering", () => {
|
||||
it("hides common Kingbase system schemas by default", () => {
|
||||
|
|
@ -19,6 +19,17 @@ describe("visibleDatabases schema filtering", () => {
|
|||
expect(filterSchemaNamesForConnection(["DBX_TEST", "DIP", "SYSTEM"], { db_type: "oracle", database: "XE" }, "XE")).toEqual(["DBX_TEST", "DIP"]);
|
||||
});
|
||||
|
||||
it("uses Oracle schema filtering for inferred JDBC connections", () => {
|
||||
const connection = {
|
||||
db_type: "jdbc" as const,
|
||||
connection_string: "jdbc:oracle:thin:@//localhost:1521/XE",
|
||||
username: "DBX_TEST",
|
||||
};
|
||||
|
||||
expect(connectionUsesVisibleSchemaFilter(connection)).toBe(true);
|
||||
expect(filterSchemaNamesForConnection(["ANONYMOUS", "DBX_TEST", "SYS", "SYSTEM"], connection, "")).toEqual(["DBX_TEST"]);
|
||||
});
|
||||
|
||||
it("keeps the Dameng login schema visible while hiding default system schemas", () => {
|
||||
expect(filterSchemaNamesForConnection(["APP", "SYS", "SYSDBA", "SYSDBO", "SYSAUDITOR"], { db_type: "dameng", username: "SYSDBA" }, "")).toEqual(["APP", "SYSDBA"]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { ConnectionConfig, DatabaseType } from "@/types/database";
|
|||
import { isSchemaAware, usesDatabaseObjectTreeMode, usesTreeSchemaMode } from "@/lib/database/databaseFeatureSupport";
|
||||
import type { CodeMirrorSqlDialectName } from "@/lib/editor/codemirrorSqlDialect";
|
||||
|
||||
type JdbcDialectConnection = Pick<ConnectionConfig, "db_type"> & Partial<Pick<ConnectionConfig, "driver_profile" | "driver_label" | "connection_string" | "jdbc_driver_class" | "jdbc_driver_paths" | "database_info" | "external_config">>;
|
||||
type JdbcDialectConnection = Partial<Pick<ConnectionConfig, "db_type" | "driver_profile" | "driver_label" | "connection_string" | "jdbc_driver_class" | "jdbc_driver_paths" | "database_info" | "external_config">>;
|
||||
|
||||
export type GaussdbIdentifierQuoteStyle = "auto" | "double" | "backtick";
|
||||
export type GaussdbConnectionMode = "native" | "m-jdbc";
|
||||
|
|
@ -12,6 +12,7 @@ export const GAUSSDB_M_JDBC_DRIVER_PROFILE = "gaussdb-m";
|
|||
export const GAUSSDB_M_JDBC_DRIVER_CLASS = "com.huawei.gaussdb.jdbc.Driver";
|
||||
|
||||
const DATABASE_AS_EXECUTION_SCHEMA_TYPES = new Set<DatabaseType>(["hive", "spark"]);
|
||||
const CONNECTION_ROOT_SCHEMA_TYPES = new Set<DatabaseType>(["oracle", "dameng", "oceanbase-oracle"]);
|
||||
|
||||
const JDBC_DIALECT_MATCHERS: Array<{ type: DatabaseType; patterns: RegExp[] }> = [
|
||||
{ type: "databend", patterns: [/jdbc:databend:/i, /com\.databend\.jdbc\.DatabendDriver/i, /databend-jdbc/i] },
|
||||
|
|
@ -67,6 +68,11 @@ export function effectiveDatabaseTypeForConnection(connection?: JdbcDialectConne
|
|||
return inferJdbcDialect(connection) ?? "jdbc";
|
||||
}
|
||||
|
||||
export function connectionUsesConnectionRootSchemaMode(connection?: JdbcDialectConnection): boolean {
|
||||
const type = effectiveDatabaseTypeForConnection(connection);
|
||||
return !!type && CONNECTION_ROOT_SCHEMA_TYPES.has(type);
|
||||
}
|
||||
|
||||
export function connectionShouldLoadIdentifierQuote(connection: JdbcDialectConnection | undefined): boolean {
|
||||
if (!connection) return false;
|
||||
if (connection.db_type === "gbase" && isGbase8sProfile(connection.driver_profile)) return true;
|
||||
|
|
@ -135,6 +141,7 @@ export function tableStructureDatabaseTypeForConnection(connection?: JdbcDialect
|
|||
export function connectionUsesDatabaseObjectTreeMode(connection?: JdbcDialectConnection): boolean {
|
||||
if (!connection) return false;
|
||||
if (connection.db_type !== "jdbc") return usesDatabaseObjectTreeMode(effectiveDatabaseTypeForConnection(connection));
|
||||
if (connectionUsesConnectionRootSchemaMode(connection)) return false;
|
||||
const dialect = inferJdbcDialect(connection);
|
||||
if (!dialect) return true;
|
||||
if (dialect === "hive" || dialect === "trino") return false;
|
||||
|
|
@ -149,7 +156,7 @@ export function connectionShouldDiscoverJdbcSchemas(connection?: JdbcDialectConn
|
|||
return connection?.db_type === "jdbc" && !inferJdbcDialect(connection);
|
||||
}
|
||||
|
||||
export function connectionUsesSchemaExecutionContext(connection?: Pick<ConnectionConfig, "db_type" | "connection_string" | "jdbc_driver_class" | "jdbc_driver_paths">): boolean {
|
||||
export function connectionUsesSchemaExecutionContext(connection?: JdbcDialectConnection): boolean {
|
||||
return connection?.db_type === "jdbc" && inferJdbcDialect(connection) === "databend";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import type { ConnectionConfig, DatabaseType } from "@/types/database";
|
||||
import { connectionUsesConnectionRootSchemaMode, effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
|
||||
|
||||
type VisibleDatabaseConnection = Partial<ConnectionConfig>;
|
||||
|
||||
type SystemNameRules = {
|
||||
exact?: ReadonlySet<string>;
|
||||
|
|
@ -171,7 +174,7 @@ export function isSystemSchemaName(databaseType: DatabaseType | undefined, schem
|
|||
return rules.prefixes?.some((prefix) => normalized.startsWith(prefix)) ?? false;
|
||||
}
|
||||
|
||||
export function filterDatabaseNamesForConnection(databaseNames: string[], connection: Pick<ConnectionConfig, "db_type" | "driver_profile" | "visible_databases"> | undefined): string[] {
|
||||
export function filterDatabaseNamesForConnection(databaseNames: string[], connection: VisibleDatabaseConnection | undefined): string[] {
|
||||
const visibleDatabases = connection?.visible_databases;
|
||||
if (visibleDatabaseFilterIsEnabled(visibleDatabases)) {
|
||||
return filterVisibleDatabaseNames(databaseNames, visibleDatabases);
|
||||
|
|
@ -179,33 +182,29 @@ export function filterDatabaseNamesForConnection(databaseNames: string[], connec
|
|||
return filterDatabaseNamesForVisiblePicker(databaseNames, connection);
|
||||
}
|
||||
|
||||
export function filterDatabaseNamesForVisiblePicker(databaseNames: string[], connection: Pick<ConnectionConfig, "db_type" | "driver_profile"> | undefined): string[] {
|
||||
export function filterDatabaseNamesForVisiblePicker(databaseNames: string[], connection: VisibleDatabaseConnection | undefined): string[] {
|
||||
if (connection?.db_type === "gbase" && connection.driver_profile === "gbase8s") {
|
||||
return databaseNames;
|
||||
}
|
||||
return databaseNames.filter((name) => !isSystemDatabaseName(connection?.db_type, name));
|
||||
return databaseNames.filter((name) => !isSystemDatabaseName(effectiveDatabaseTypeForConnection(connection), name));
|
||||
}
|
||||
|
||||
export function filterSchemaNamesForVisiblePicker(schemaNames: string[], connection: Partial<Pick<ConnectionConfig, "db_type" | "username" | "show_system_schemas">> | undefined, options?: SchemaFilterOptions): string[] {
|
||||
export function filterSchemaNamesForVisiblePicker(schemaNames: string[], connection: VisibleDatabaseConnection | undefined, options?: SchemaFilterOptions): string[] {
|
||||
if (schemaFilterShowSystemSchemas(connection, options)) return schemaNames;
|
||||
const currentSchema = connection?.username?.trim().toLowerCase();
|
||||
return schemaNames.filter((name) => name.toLowerCase() === currentSchema || !isSystemSchemaName(connection?.db_type, name));
|
||||
const databaseType = effectiveDatabaseTypeForConnection(connection);
|
||||
return schemaNames.filter((name) => name.toLowerCase() === currentSchema || !isSystemSchemaName(databaseType, name));
|
||||
}
|
||||
|
||||
export function connectionUsesVisibleSchemaFilter(connection: Pick<ConnectionConfig, "db_type"> | undefined): boolean {
|
||||
return connection?.db_type === "oracle" || connection?.db_type === "dameng" || connection?.db_type === "oceanbase-oracle";
|
||||
export function connectionUsesVisibleSchemaFilter(connection: VisibleDatabaseConnection | undefined): boolean {
|
||||
return connectionUsesConnectionRootSchemaMode(connection);
|
||||
}
|
||||
|
||||
export function visibleSchemaFilterIsEnabled(visibleSchemas: Record<string, string[]> | undefined, database: string): boolean {
|
||||
return Array.isArray(visibleSchemas?.[database]);
|
||||
}
|
||||
|
||||
export function filterSchemaNamesForConnection(
|
||||
schemaNames: string[],
|
||||
connection: (Pick<ConnectionConfig, "db_type" | "visible_schemas" | "visible_databases" | "show_system_schemas"> & Partial<Pick<ConnectionConfig, "username">>) | undefined,
|
||||
database: string,
|
||||
options?: SchemaFilterOptions,
|
||||
): string[] {
|
||||
export function filterSchemaNamesForConnection(schemaNames: string[], connection: VisibleDatabaseConnection | undefined, database: string, options?: SchemaFilterOptions): string[] {
|
||||
const visibleSchemas = connection?.visible_schemas;
|
||||
if (!visibleSchemaFilterIsEnabled(visibleSchemas, database)) {
|
||||
if (connectionUsesVisibleSchemaFilter(connection) && visibleDatabaseFilterIsEnabled(connection?.visible_databases)) {
|
||||
|
|
|
|||
|
|
@ -75,6 +75,18 @@ function genericJdbcConnection(): ConnectionConfig {
|
|||
} as ConnectionConfig;
|
||||
}
|
||||
|
||||
function oracleJdbcConnection(): ConnectionConfig {
|
||||
return {
|
||||
...genericJdbcConnection(),
|
||||
id: "jdbc-oracle-1",
|
||||
name: "Oracle JDBC",
|
||||
username: "DBX_TEST",
|
||||
database: undefined,
|
||||
connection_string: "jdbc:oracle:thin:@//127.0.0.1:1521/XE",
|
||||
jdbc_driver_class: "oracle.jdbc.OracleDriver",
|
||||
} as ConnectionConfig;
|
||||
}
|
||||
|
||||
function informixConnection(): ConnectionConfig {
|
||||
return {
|
||||
id: "informix-1",
|
||||
|
|
@ -247,6 +259,49 @@ describe("connectionStore metadata loading", () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it("loads inferred Oracle JDBC schemas without requesting empty catalogs", async () => {
|
||||
const listDatabases = vi.fn().mockResolvedValue([]);
|
||||
const listSchemas = vi.fn().mockResolvedValue(["ANONYMOUS", "DBX_TEST", "SYS", "SYSTEM"]);
|
||||
const listTables = vi.fn().mockResolvedValue([{ name: "sheet", 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),
|
||||
listDatabases,
|
||||
listObjects: vi.fn().mockResolvedValue([]),
|
||||
listSchemas,
|
||||
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 = oracleJdbcConnection();
|
||||
const connectionNode: TreeNode = { id: connection.id, label: connection.name, type: "connection", connectionId: connection.id, isExpanded: false, children: [] };
|
||||
store.connections = [connection];
|
||||
store.connectedIds.add(connection.id);
|
||||
store.treeNodes = [connectionNode];
|
||||
|
||||
await store.loadDatabases(connection.id, { force: true });
|
||||
|
||||
expect(listDatabases).not.toHaveBeenCalled();
|
||||
expect(listSchemas).toHaveBeenCalledWith(connection.id, "");
|
||||
expect(connectionNode.children?.map((node) => [node.type, node.label, node.database, node.schema])).toEqual([["schema", "DBX_TEST", "DBX_TEST", "DBX_TEST"]]);
|
||||
|
||||
const schemaNode = connectionNode.children?.[0];
|
||||
expect(schemaNode).toBeDefined();
|
||||
await store.loadTreeNodeChildren(schemaNode!, { force: true });
|
||||
|
||||
expect(listTables.mock.calls[0]?.slice(0, 3)).toEqual([connection.id, "DBX_TEST", "DBX_TEST"]);
|
||||
expect(schemaNode?.children?.map((node) => [node.type, node.label, node.schema])).toEqual([["table", "sheet", "DBX_TEST"]]);
|
||||
});
|
||||
|
||||
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 }]);
|
||||
|
|
|
|||
|
|
@ -100,6 +100,18 @@ test("OceanBase Oracle uses schema filtering for visible object selection", () =
|
|||
assert.equal(connectionUsesVisibleSchemaFilter(config({ db_type: "mysql", driver_profile: "oceanbase" })), false);
|
||||
});
|
||||
|
||||
test("Oracle JDBC uses schema filtering for visible object selection", () => {
|
||||
const connection = config({
|
||||
db_type: "jdbc",
|
||||
driver_profile: "jdbc",
|
||||
connection_string: "jdbc:oracle:thin:@//127.0.0.1:1521/XE",
|
||||
username: "DBX_TEST",
|
||||
});
|
||||
|
||||
assert.equal(connectionUsesVisibleSchemaFilter(connection), true);
|
||||
assert.deepEqual(filterSchemaNamesForConnection(["ANONYMOUS", "DBX_TEST", "SYS", "SYSTEM"], connection, ""), ["DBX_TEST"]);
|
||||
});
|
||||
|
||||
test("Vastbase schema filters preserve ordinary schemas and explicit empty selections", () => {
|
||||
const schemas = ["public", "app"];
|
||||
assert.deepEqual(filterSchemaNamesForConnection(schemas, config({ db_type: "vastbase", database: "vastbase" }), "vastbase"), schemas);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {
|
|||
codeMirrorSqlDialectForConnection,
|
||||
connectionShouldDiscoverJdbcSchemas,
|
||||
connectionShouldLoadIdentifierQuote,
|
||||
connectionUsesConnectionRootSchemaMode,
|
||||
connectionUsesDatabaseObjectTreeMode,
|
||||
effectiveDatabaseTypeForConnection,
|
||||
gaussdbIdentifierQuoteOverride,
|
||||
|
|
@ -41,6 +42,16 @@ test("infers JDBC dialect from driver profile", () => {
|
|||
);
|
||||
});
|
||||
|
||||
test("Oracle JDBC uses connection-root schema navigation", () => {
|
||||
const connection = {
|
||||
db_type: "jdbc" as const,
|
||||
connection_string: "jdbc:oracle:thin:@//127.0.0.1:1521/XE",
|
||||
};
|
||||
|
||||
assert.equal(connectionUsesConnectionRootSchemaMode(connection), true);
|
||||
assert.equal(connectionUsesDatabaseObjectTreeMode(connection), false);
|
||||
});
|
||||
|
||||
test("uses dedicated ClickHouse editor syntax for inferred JDBC connections", () => {
|
||||
const connection = {
|
||||
db_type: "jdbc" as const,
|
||||
|
|
|
|||
Loading…
Reference in New Issue