From 5630f8b7f83ad2fc969c9e76dfd48223e9f3278c Mon Sep 17 00:00:00 2001 From: zipg Date: Thu, 8 Oct 2026 13:06:25 +0800 Subject: [PATCH] fix(jdbc): fall back to unscoped schema listing for HANA Closes #11209 --- .../com/dbx/agent/StandardJdbcMetadata.java | 9 +++ .../dbx/agent/StandardJdbcMetadataTest.java | 55 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/agents/common/src/main/java/com/dbx/agent/StandardJdbcMetadata.java b/agents/common/src/main/java/com/dbx/agent/StandardJdbcMetadata.java index 61e1f1069d..b52a6bf2ff 100644 --- a/agents/common/src/main/java/com/dbx/agent/StandardJdbcMetadata.java +++ b/agents/common/src/main/java/com/dbx/agent/StandardJdbcMetadata.java @@ -65,6 +65,15 @@ public final class StandardJdbcMetadata { } catch (Exception | AbstractMethodError ignored) { } } + // HANA's databaseName (for example HXE) is not a JDBC catalog. + // Its driver returns no rows, rather than throwing, for that catalog. + // Retry without it before falling back to only the current schema. + if (names.isEmpty() && catalog != null && profile.getCatalogFallbackEnabled()) { + try { + appendSchemas(names, meta.getSchemas(null, null)); + } catch (Exception | AbstractMethodError ignored) { + } + } try { addNonBlank(names, conn.getSchema()); } catch (Exception | AbstractMethodError ignored) { diff --git a/agents/common/src/test/java/com/dbx/agent/StandardJdbcMetadataTest.java b/agents/common/src/test/java/com/dbx/agent/StandardJdbcMetadataTest.java index 6b1a8f8edc..67b37a0731 100644 --- a/agents/common/src/test/java/com/dbx/agent/StandardJdbcMetadataTest.java +++ b/agents/common/src/test/java/com/dbx/agent/StandardJdbcMetadataTest.java @@ -10,12 +10,14 @@ import java.lang.reflect.Proxy; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicInteger; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -48,10 +50,12 @@ class StandardJdbcMetadataTest { @Test void scopesSchemasToTheConnectionCatalog() { AtomicReference capturedArgs = new AtomicReference<>(); + AtomicInteger schemaCalls = new AtomicInteger(); DatabaseMetaData meta = proxy(DatabaseMetaData.class, new MethodHandler() { @Override public Object handle(Method method, Object[] args) { if ("getSchemas".equals(method.getName())) { + schemaCalls.incrementAndGet(); capturedArgs.set(args); return rows(row("TABLE_SCHEM", "APP")); } @@ -74,6 +78,57 @@ class StandardJdbcMetadataTest { assertEquals(List.of("APP"), StandardJdbcMetadata.INSTANCE.listSchemas(conn, profile, "initial_catalog")); assertEquals("regular_catalog", capturedArgs.get()[0]); assertEquals(null, capturedArgs.get()[1]); + assertEquals(1, schemaCalls.get()); + } + + @ParameterizedTest + @CsvSource({"false, false, true", "true, false, true", "false, true, true", "false, false, false"}) + void retriesEmptyCatalogSchemasBeforeAddingCurrentSchema(boolean fallbackFails, boolean currentCatalog, boolean fallbackEnabled) { + List catalogs = new ArrayList<>(); + DatabaseMetaData meta = proxy(DatabaseMetaData.class, new MethodHandler() { + @Override + public Object handle(Method method, Object[] args) { + if ("getSchemas".equals(method.getName())) { + String catalog = (String) args[0]; + catalogs.add(catalog); + if (catalog != null) { + return rows(); + } + if (fallbackFails) { + throw new UnsupportedOperationException("Unscoped schemas unavailable"); + } + return rows(row("TABLE_SCHEM", "DBX_USER"), row("TABLE_SCHEM", "DBX_DEMO"), row("TABLE_SCHEM", "SYS")); + } + return defaultValue(method.getReturnType()); + } + }); + Connection conn = proxy(Connection.class, new MethodHandler() { + @Override + public Object handle(Method method, Object[] args) { + if ("getMetaData".equals(method.getName())) { + return meta; + } + if ("getCatalog".equals(method.getName())) { + return currentCatalog ? "HXE" : null; + } + if ("getSchema".equals(method.getName())) { + return "DBX_USER"; + } + return defaultValue(method.getReturnType()); + } + }); + + JdbcAgentProfile effectiveProfile = fallbackEnabled ? profile : new JdbcAgentProfile("example.Driver", "jdbc:example:") { + @Override + public boolean getCatalogFallbackEnabled() { + return false; + } + }; + assertEquals( + fallbackFails || !fallbackEnabled ? List.of("DBX_USER") : List.of("DBX_DEMO", "DBX_USER"), + StandardJdbcMetadata.INSTANCE.listSchemas(conn, effectiveProfile, currentCatalog ? "initial_catalog" : "HXE") + ); + assertEquals(fallbackEnabled ? Arrays.asList("HXE", null) : List.of("HXE"), catalogs); } @Test