fix(jdbc): pass Phoenix URL properties to driver

Closes #6699
This commit is contained in:
t8y2
2026-08-19 10:24:58 +00:00
parent 9a1ead8ca4
commit 80a808d53c
2 changed files with 152 additions and 2 deletions
@@ -552,6 +552,7 @@ public final class DbxJdbcPlugin {
JdbcUrlCredentials urlCredentials = extractJdbcUrlCredentials(url);
url = urlCredentials.url;
Properties properties = new Properties();
applyPhoenixUrlProperties(url, properties);
String username = optionalText(connection, "username");
String password = optionalText(connection, "password");
if (username == null) {
@@ -586,13 +587,47 @@ public final class DbxJdbcPlugin {
}
private static boolean isPhoenixConnection(JsonNode connection, String url) {
if (urlMatchesPrefix(url, "jdbc:phoenix:")) {
if (isPhoenixUrl(url)) {
return true;
}
String driverClass = optionalText(connection, "jdbc_driver_class");
return driverClass != null && driverClass.equalsIgnoreCase("org.apache.phoenix.jdbc.PhoenixDriver");
}
private static void applyPhoenixUrlProperties(String url, Properties properties) {
if (!isPhoenixDirectUrl(url)) {
return;
}
int propertiesStart = url.indexOf(';');
if (propertiesStart < 0) {
return;
}
// Phoenix builds its HBase client configuration from Driver.connect Properties,
// while arbitrary semicolon URL attributes are not merged into QueryServices.
for (String part : url.substring(propertiesStart + 1).split(";")) {
int equals = part.indexOf('=');
if (equals <= 0) {
continue;
}
String key = part.substring(0, equals).trim();
if (!key.isEmpty()) {
properties.setProperty(key, part.substring(equals + 1).trim());
}
}
}
private static boolean isPhoenixDirectUrl(String url) {
return isPhoenixUrl(url) && !urlMatchesPrefix(url, "jdbc:phoenix:thin:");
}
private static boolean isPhoenixUrl(String url) {
String prefix = "jdbc:phoenix";
if (url == null || !url.regionMatches(true, 0, prefix, 0, prefix.length())) {
return false;
}
return url.length() == prefix.length() || url.charAt(prefix.length()) == ':' || url.charAt(prefix.length()) == ';';
}
private static void applyJdbcxExtensionSecurity(JsonNode connection, String url, Properties properties) {
if (isJdbcxUrl(url) && !jdbcxHighPrivilegeExtensionsEnabled(connection)) {
properties.setProperty(JDBCX_EXTENSION_WHITELIST_PROPERTY, JDBCX_SAFE_EXTENSION_WHITELIST);
@@ -2923,7 +2958,11 @@ public final class DbxJdbcPlugin {
}
private static String jdbcUrlParamSeparator(String base) {
if (urlMatchesPrefix(base, "jdbc:sqlserver:") || urlMatchesPrefix(base, "jdbc:dremio:")) {
if (
urlMatchesPrefix(base, "jdbc:sqlserver:") ||
urlMatchesPrefix(base, "jdbc:dremio:") ||
isPhoenixUrl(base)
) {
return base.endsWith(";") ? "" : ";";
}
if (jdbcUrlUsesColonProperties(base)) {
@@ -953,6 +953,117 @@ final class DbxJdbcPluginTest {
assertEquals(List.of("getAutoCommit", "setAutoCommit:true"), calls);
}
@Test
void phoenixDirectUrlPropertiesArePassedToTheDriver() throws Exception {
RecordingConnectDriver driver = new RecordingConnectDriver("jdbc:phoenix:");
DriverManager.registerDriver(driver);
String connection = """
{
"connection_string": "jdbc:phoenix:ambari01,ambari02,ambari03:2181:/hbase-unsecure;phoenix.schema.isNamespaceMappingEnabled=true;phoenix.schema.mapSystemTablesToNamespace=true;user=url-user",
"username": "phoenix-user",
"connect_timeout_secs": 30
}
""";
try {
JsonNode response = request("testConnection", """
{ "connection": %s }
""".formatted(connection));
assertFalse(response.has("error"), response.toString());
assertEquals(
"jdbc:phoenix:ambari01,ambari02,ambari03:2181:/hbase-unsecure;phoenix.schema.isNamespaceMappingEnabled=true;phoenix.schema.mapSystemTablesToNamespace=true;user=url-user",
driver.urls.get(0)
);
assertEquals("true", driver.properties.get(0).getProperty("phoenix.schema.isNamespaceMappingEnabled"));
assertEquals("true", driver.properties.get(0).getProperty("phoenix.schema.mapSystemTablesToNamespace"));
assertEquals("phoenix-user", driver.properties.get(0).getProperty("user"));
} finally {
closeAndDeregister(connection, driver);
}
}
@Test
void phoenixConnectionUrlParamsUseSemicolonAndReachDriverProperties() throws Exception {
RecordingConnectDriver driver = new RecordingConnectDriver("jdbc:phoenix:");
DriverManager.registerDriver(driver);
String connection = """
{
"connection_string": "jdbc:phoenix:zk1,zk2:2181:/hbase-unsecure",
"url_params": "phoenix.schema.isNamespaceMappingEnabled=true;phoenix.schema.mapSystemTablesToNamespace=true",
"connect_timeout_secs": 30
}
""";
try {
JsonNode response = request("testConnection", """
{ "connection": %s }
""".formatted(connection));
assertFalse(response.has("error"), response.toString());
assertEquals(
"jdbc:phoenix:zk1,zk2:2181:/hbase-unsecure;phoenix.schema.isNamespaceMappingEnabled=true;phoenix.schema.mapSystemTablesToNamespace=true",
driver.urls.get(0)
);
assertEquals("true", driver.properties.get(0).getProperty("phoenix.schema.isNamespaceMappingEnabled"));
assertEquals("true", driver.properties.get(0).getProperty("phoenix.schema.mapSystemTablesToNamespace"));
} finally {
closeAndDeregister(connection, driver);
}
}
@Test
void phoenixUrlPropertyParsingIgnoresMalformedSegmentsAndPreservesEqualsInValue() throws Exception {
Method method = DbxJdbcPlugin.class.getDeclaredMethod("applyPhoenixUrlProperties", String.class, Properties.class);
method.setAccessible(true);
Properties properties = new Properties();
method.invoke(
null,
"jdbc:phoenix;broken;=ignored;phoenix.query.custom=a=b",
properties
);
assertEquals("a=b", properties.getProperty("phoenix.query.custom"));
assertFalse(properties.containsKey("broken"));
assertFalse(properties.containsKey(""));
}
@Test
void phoenixThinUrlParamsUseSemicolonSyntax() throws Exception {
JsonNode connection = MAPPER.readTree("""
{
"connection_string": "jdbc:phoenix:thin:url=http://127.0.0.1:8765;serialization=PROTOBUF",
"url_params": "authentication=SPNEGO"
}
""");
assertEquals(
"jdbc:phoenix:thin:url=http://127.0.0.1:8765;serialization=PROTOBUF;authentication=SPNEGO",
DbxJdbcPlugin.jdbcUrl(connection)
);
}
@Test
void nonPhoenixSemicolonUrlPropertiesAreNotCopiedToDriverProperties() throws Exception {
RecordingConnectDriver driver = new RecordingConnectDriver("jdbc:dbx-semicolon:");
DriverManager.registerDriver(driver);
String connection = """
{
"connection_string": "jdbc:dbx-semicolon:demo;custom.option=true",
"connect_timeout_secs": 30
}
""";
try {
JsonNode response = request("testConnection", """
{ "connection": %s }
""".formatted(connection));
assertFalse(response.has("error"), response.toString());
assertFalse(driver.properties.get(0).containsKey("custom.option"));
} finally {
closeAndDeregister(connection, driver);
}
}
@Test
void phoenixAutoCommitConfigurationSkipsNonPhoenixConnections() throws Exception {
Method method = DbxJdbcPlugin.class.getDeclaredMethod(