This is an automated email from the ASF dual-hosted git repository.

FrankChen021 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/master by this push:
     new fbf472cb677 build(deps): bump org.mariadb.jdbc:mariadb-java-client 
from 2.7.3 to 3.5.10 (#20036)
fbf472cb677 is described below

commit fbf472cb6778aec81e853dafa7f03a8a63595123
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
AuthorDate: Sun Aug 30 09:57:41 2026 +0800

    build(deps): bump org.mariadb.jdbc:mariadb-java-client from 2.7.3 to 3.5.10 
(#20036)
    
    * build(deps): bump org.mariadb.jdbc:mariadb-java-client
    
    Bumps 
[org.mariadb.jdbc:mariadb-java-client](https://github.com/mariadb-corporation/mariadb-connector-j)
 from 2.7.3 to 3.5.10.
    - [Release 
notes](https://github.com/mariadb-corporation/mariadb-connector-j/releases)
    - 
[Changelog](https://github.com/mariadb-corporation/mariadb-connector-j/blob/main/CHANGELOG.md)
    - 
[Commits](https://github.com/mariadb-corporation/mariadb-connector-j/compare/2.7.3...3.5.10)
    
    ---
    updated-dependencies:
    - dependency-name: org.mariadb.jdbc:mariadb-java-client
      dependency-version: 3.5.10
      dependency-type: direct:production
      update-type: version-update:semver-major
    ...
    
    Signed-off-by: dependabot[bot] <[email protected]>
    
    * fix: support MariaDB Connector/J 3.x
    
    * style: order MariaDB connector imports
    
    * fix: support MariaDB 3.x streaming fetch size
    
    * docs: explain MariaDB streaming fetch size
    
    * test: explain MariaDB 3.x URL rejection
    
    * test: cover MariaDB Connector/J 2.x compatibility
    
    * style: normalize MariaDB compatibility test header
    
    * fix: support MariaDB 3.x MySQL URI validation
    
    * fix: allow MariaDB 3.x SSL mode property
    
    ---------
    
    Signed-off-by: dependabot[bot] <[email protected]>
    Co-authored-by: dependabot[bot] 
<49699333+dependabot[bot]@users.noreply.github.com>
    Co-authored-by: Frank Chen <[email protected]>
---
 distribution/docker/Dockerfile.mariadb             |   6 +-
 docs/configuration/index.md                        |   2 +-
 docs/development/extensions-core/mysql.md          |  10 +-
 .../metadata/storage/mysql/MySQLConnector.java     |  12 ++
 .../MySQLInputSourceDatabaseConnectorTest.java     |  22 ++++
 .../metadata/storage/mysql/MySQLConnectorTest.java |  36 +++++-
 pom.xml                                            |   2 +-
 processing/pom.xml                                 |  23 ++++
 .../org/apache/druid/utils/ConnectionUriUtils.java |  34 ++++--
 .../utils/ConnectionUriUtilsMariaDb2xTest.java     | 123 +++++++++++++++++++++
 .../apache/druid/utils/ConnectionUriUtilsTest.java | 104 ++++++++---------
 .../initialization/JdbcAccessSecurityConfig.java   |   3 +-
 .../druid/metadata/input/SqlInputSourceTest.java   |   2 +-
 13 files changed, 300 insertions(+), 79 deletions(-)

diff --git a/distribution/docker/Dockerfile.mariadb 
b/distribution/docker/Dockerfile.mariadb
index 68a2ca73a3e..27852f4a43c 100644
--- a/distribution/docker/Dockerfile.mariadb
+++ b/distribution/docker/Dockerfile.mariadb
@@ -22,9 +22,9 @@ FROM $DRUID_RELEASE
 
 WORKDIR /opt/druid/extensions/mysql-metadata-storage
 
-ARG 
MARIA_URL=https://repo1.maven.org/maven2/org/mariadb/jdbc/mariadb-java-client/2.7.3/mariadb-java-client-2.7.3.jar
-ARG MARIA_JAR=mariadb-java-client-2.7.3.jar
-ARG MARIA_SHA=4a2edc05bd882ad19371d2615c2635dccf8d74f0
+ARG 
MARIA_URL=https://repo1.maven.org/maven2/org/mariadb/jdbc/mariadb-java-client/3.5.10/mariadb-java-client-3.5.10.jar
+ARG MARIA_JAR=mariadb-java-client-3.5.10.jar
+ARG MARIA_SHA=c6a687f7ccb65394103cd516e0583e61e6d92c24
 
 RUN wget -q ${MARIA_URL} \
  && echo "${MARIA_SHA}  ${MARIA_JAR}" | sha1sum -c \
diff --git a/docs/configuration/index.md b/docs/configuration/index.md
index 4c565b55bdb..0bbc7fc7958 100644
--- a/docs/configuration/index.md
+++ b/docs/configuration/index.md
@@ -487,7 +487,7 @@ These properties do not apply to metadata storage 
connections.
 |Property|Possible values| Description                                         
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
           |Default|
 
|--------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------|
 |`druid.access.jdbc.enforceAllowedProperties`|Boolean| When true, Druid 
applies `druid.access.jdbc.allowedProperties` to JDBC connections starting with 
`jdbc:postgresql:`, `jdbc:mysql:`, or `jdbc:mariadb:`. When false, Druid allows 
any kind of JDBC connections without JDBC property validation. This config is 
for backward compatibility especially during upgrades since enforcing allow 
list can break existing ingestion jobs or lookups based on JDBC. This config is 
deprecated and will be rem [...]
-|`druid.access.jdbc.allowedProperties`|List of JDBC properties| Defines a list 
of allowed JDBC properties. Druid always enforces the list for all JDBC 
connections starting with `jdbc:postgresql:`, `jdbc:mysql:`, and 
`jdbc:mariadb:` if `druid.access.jdbc.enforceAllowedProperties` is set to 
true.<br/><br/>This option is tested against MySQL connector 8.2.0, MariaDB 
connector 2.7.4, and PostgreSQL connector 42.2.14. Other connector versions 
might not work.                                    [...]
+|`druid.access.jdbc.allowedProperties`|List of JDBC properties| Defines a list 
of allowed JDBC properties. Druid always enforces the list for all JDBC 
connections starting with `jdbc:postgresql:`, `jdbc:mysql:`, and 
`jdbc:mariadb:` if `druid.access.jdbc.enforceAllowedProperties` is set to 
true.<br/><br/>This option is tested against MySQL connector 8.2.0, MariaDB 
connector 3.5.10, and PostgreSQL connector 42.2.14. Other connector versions 
might not work.                                   [...]
 |`druid.access.jdbc.allowUnknownJdbcUrlFormat`|Boolean| When false, Druid only 
accepts JDBC connections starting with `jdbc:postgresql:` or `jdbc:mysql:`. 
When true, Druid allows JDBC connections to any kind of database, but only 
enforces `druid.access.jdbc.allowedProperties` for PostgreSQL and 
MySQL/MariaDB.                                                                  
                                                                                
                                     [...]
 
 ### Task logging
diff --git a/docs/development/extensions-core/mysql.md 
b/docs/development/extensions-core/mysql.md
index 77e26446bbb..8782e30b5ab 100644
--- a/docs/development/extensions-core/mysql.md
+++ b/docs/development/extensions-core/mysql.md
@@ -55,22 +55,22 @@ Copy or create a symbolic link to this file inside the 
`lib` folder in the distr
 ### Install MariaDB Connector/J
 
 This extension also supports using the MariaDB connector jar.
-The current version of Druid uses version 2.7.3.
+The current version of Druid uses version 3.5.10.
 Other versions may not work with this extension.
 
 You can download the library from one of the following sources:
 
 - [MariaDB 
website](https://mariadb.com/downloads/connectors/connectors-data-access/java8-connector)
  
   Click **Show All Files** to access older product versions.
-- [Maven Central (direct 
download)](https://repo1.maven.org/maven2/org/mariadb/jdbc/mariadb-java-client/2.7.3/mariadb-java-client-2.7.3.jar)
+- [Maven Central (direct 
download)](https://repo1.maven.org/maven2/org/mariadb/jdbc/mariadb-java-client/3.5.10/mariadb-java-client-3.5.10.jar)
 
-The download includes the MariaDB connector JAR file with a name like 
`maria-java-client-2.7.3.jar`.
+The download includes the MariaDB connector JAR file with a name like 
`mariadb-java-client-3.5.10.jar`.
 Copy or create a symbolic link to this file inside the `lib` folder in the 
distribution root directory.
 
 To configure the `mysql-metadata-storage` extension to use the MariaDB 
connector library instead of MySQL, set 
`druid.metadata.mysql.driver.driverClassName=org.mariadb.jdbc.Driver`.
 
-The protocol of the connection string is `jdbc:mysql:` or `jdbc:mariadb:`,
-depending on your specific version of the MariaDB client library.
+MariaDB Connector/J 3.x accepts `jdbc:mariadb:` connection strings by default.
+To use a `jdbc:mysql:` connection string, add the `permitMysqlScheme` option 
to the URL.
 For more information on the parameters to configure a connection,
 [see the MariaDB 
documentation](https://mariadb.com/kb/en/about-mariadb-connector-j/#connection-strings)
 for your connector version.
diff --git 
a/extensions-core/mysql-metadata-storage/src/main/java/org/apache/druid/metadata/storage/mysql/MySQLConnector.java
 
b/extensions-core/mysql-metadata-storage/src/main/java/org/apache/druid/metadata/storage/mysql/MySQLConnector.java
index c9599eb0fd7..3030c124d0a 100644
--- 
a/extensions-core/mysql-metadata-storage/src/main/java/org/apache/druid/metadata/storage/mysql/MySQLConnector.java
+++ 
b/extensions-core/mysql-metadata-storage/src/main/java/org/apache/druid/metadata/storage/mysql/MySQLConnector.java
@@ -50,12 +50,15 @@ public class MySQLConnector extends SQLMetadataConnector
   private static final String MYSQL_TRANSIENT_EXCEPTION_CLASS_NAME
       = "java.sql.SQLTransientException";
   private static final String MARIA_DB_PACKET_EXCEPTION_CLASS_NAME
+      = "org.mariadb.jdbc.export.MaxAllowedPacketException";
+  private static final String LEGACY_MARIA_DB_PACKET_EXCEPTION_CLASS_NAME
       = "org.mariadb.jdbc.internal.util.exceptions.MaxAllowedPacketException";
   private static final String MYSQL_PACKET_EXCEPTION_CLASS_NAME
       = "com.mysql.jdbc.PacketTooBigException";
 
   @Nullable
   private final Class<?> myTransientExceptionClass;
+  private final boolean mariaDbDriver;
   private final DBI dbi;
 
   @Inject
@@ -70,6 +73,7 @@ public class MySQLConnector extends SQLMetadataConnector
     super(config, dbTables, centralizedDatasourceSchemaConfig);
     log.info("Loading MySQL metadata connector driver %s", 
driverConfig.getDriverClassName());
     tryLoadDriverClass(driverConfig.getDriverClassName(), true);
+    mariaDbDriver = 
MySQLConnectorDriverConfig.MARIA_DB_DRIVER.equals(driverConfig.getDriverClassName());
 
     if (driverConfig.getDriverClassName().contains("mysql")) {
       myTransientExceptionClass = 
tryLoadDriverClass(MYSQL_TRANSIENT_EXCEPTION_CLASS_NAME, false);
@@ -180,6 +184,13 @@ public class MySQLConnector extends SQLMetadataConnector
   @Override
   public int getStreamingFetchSize()
   {
+    if (mariaDbDriver) {
+      // MariaDB Connector/J 2.7.3 had a compatibility branch in
+      // Statement.setFetchSize(Integer.MIN_VALUE) that stored fetchSize=1, 
while 3.x requires non-negative values.
+      // Source: 
https://github.com/mariadb-corporation/mariadb-connector-j/blob/2.7.3/src/main/java/org/mariadb/jdbc/MariaDbStatement.java#L1296-L1305
+      return 1;
+    }
+
     // this is MySQL's way of indicating you want results streamed back
     // see 
http://dev.mysql.com/doc/connector-j/5.1/en/connector-j-reference-implementation-notes.html
     return Integer.MIN_VALUE;
@@ -241,6 +252,7 @@ public class MySQLConnector extends SQLMetadataConnector
 
     final String className = t.getClass().getName();
     return MARIA_DB_PACKET_EXCEPTION_CLASS_NAME.equals(className)
+           || LEGACY_MARIA_DB_PACKET_EXCEPTION_CLASS_NAME.equals(className)
            || MYSQL_PACKET_EXCEPTION_CLASS_NAME.equals(className)
            || isRootCausePacketTooBigException(t.getCause());
   }
diff --git 
a/extensions-core/mysql-metadata-storage/src/test/java/org/apache/druid/metadata/input/MySQLInputSourceDatabaseConnectorTest.java
 
b/extensions-core/mysql-metadata-storage/src/test/java/org/apache/druid/metadata/input/MySQLInputSourceDatabaseConnectorTest.java
index ae4783f138e..a804e7b5112 100644
--- 
a/extensions-core/mysql-metadata-storage/src/test/java/org/apache/druid/metadata/input/MySQLInputSourceDatabaseConnectorTest.java
+++ 
b/extensions-core/mysql-metadata-storage/src/test/java/org/apache/druid/metadata/input/MySQLInputSourceDatabaseConnectorTest.java
@@ -33,6 +33,8 @@ import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 
@@ -228,6 +230,26 @@ public class MySQLInputSourceDatabaseConnectorTest
     );
   }
 
+  @ParameterizedTest
+  @ValueSource(strings = {"useSSL=true", "sslMode=verify-full"})
+  public void testSuccessMariaDbSslPropertyWithDefaultAllowlist(final String 
sslProperty)
+  {
+    final MetadataStorageConnectorConfig connectorConfig = new 
MetadataStorageConnectorConfig()
+    {
+      @Override
+      public String getConnectURI()
+      {
+        return "jdbc:mariadb://localhost:3306/test?" + sslProperty;
+      }
+    };
+
+    new MySQLInputSourceDatabaseConnector(
+        connectorConfig,
+        MySQLConnectorDriverConfig.MARIA_DB_DRIVER,
+        new JdbcAccessSecurityConfig(),
+        mySQLConnectorDriverConfig
+    );
+  }
 
   @Test
   public void testFailOnlyInvalidProperty()
diff --git 
a/extensions-core/mysql-metadata-storage/src/test/java/org/apache/druid/metadata/storage/mysql/MySQLConnectorTest.java
 
b/extensions-core/mysql-metadata-storage/src/test/java/org/apache/druid/metadata/storage/mysql/MySQLConnectorTest.java
index 5251347110f..f0b61321fc8 100644
--- 
a/extensions-core/mysql-metadata-storage/src/test/java/org/apache/druid/metadata/storage/mysql/MySQLConnectorTest.java
+++ 
b/extensions-core/mysql-metadata-storage/src/test/java/org/apache/druid/metadata/storage/mysql/MySQLConnectorTest.java
@@ -26,6 +26,7 @@ import 
org.apache.druid.segment.metadata.CentralizedDatasourceSchemaConfig;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
+import org.mariadb.jdbc.export.MaxAllowedPacketException;
 
 import java.sql.SQLException;
 import java.sql.SQLTransientConnectionException;
@@ -123,10 +124,14 @@ public class MySQLConnectorTest
         centralizedDatasourceSchemaConfig
     );
 
-    // The test method should return true only for
-    // mariadb.MaxAllowedPacketException or mysql.PacketTooBigException.
-    // Verifying this requires creating a mock Class object, but Class is final
-    // and has only a private constructor. It would be overkill to try to mock 
it.
+    Assertions.assertTrue(
+        connector.isRootCausePacketTooBigException(new 
MaxAllowedPacketException("packet too large", false))
+    );
+    Assertions.assertTrue(
+        connector.isRootCausePacketTooBigException(
+            new SQLException("packet too large", new 
MaxAllowedPacketException("packet too large", false))
+        )
+    );
 
     // Verify some of the false cases
     Assertions.assertFalse(
@@ -186,4 +191,27 @@ public class MySQLConnectorTest
     );
     Assertions.assertEquals("LIMIT 100", connector.limitClause(100));
   }
+
+  @MethodSource("constructorFeeder")
+  @ParameterizedTest(name = "{0}")
+  public void testGetStreamingFetchSize(CentralizedDatasourceSchemaConfig 
centralizedDatasourceSchemaConfig)
+  {
+    final MySQLConnector mysqlConnector = new MySQLConnector(
+        CONNECTOR_CONFIG_SUPPLIER,
+        TABLES_CONFIG_SUPPLIER,
+        new MySQLConnectorSslConfig(),
+        MYSQL_DRIVER_CONFIG,
+        centralizedDatasourceSchemaConfig
+    );
+    final MySQLConnector mariaDbConnector = new MySQLConnector(
+        CONNECTOR_CONFIG_SUPPLIER,
+        TABLES_CONFIG_SUPPLIER,
+        new MySQLConnectorSslConfig(),
+        MARIADB_DRIVER_CONFIG,
+        centralizedDatasourceSchemaConfig
+    );
+
+    Assertions.assertEquals(Integer.MIN_VALUE, 
mysqlConnector.getStreamingFetchSize());
+    Assertions.assertEquals(1, mariaDbConnector.getStreamingFetchSize());
+  }
 }
diff --git a/pom.xml b/pom.xml
index 4913b3f8b64..8e7f9bc198f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -111,7 +111,7 @@
         <codehaus.jackson.version>1.9.13</codehaus.jackson.version>
         <log4j.version>2.26.1</log4j.version>
         <mysql.version>26.7.0</mysql.version>
-        <mariadb.version>2.7.3</mariadb.version>
+        <mariadb.version>3.5.10</mariadb.version>
         <netty3.version>3.10.6.Final</netty3.version>
         <netty4.version>4.2.17.Final</netty4.version>
         <postgresql.version>42.7.13</postgresql.version>
diff --git a/processing/pom.xml b/processing/pom.xml
index 2d43137e955..55b533ccdcd 100644
--- a/processing/pom.xml
+++ b/processing/pom.xml
@@ -37,6 +37,7 @@
     <sigar.base.version>1.6.5</sigar.base.version>
     <sigar.version>1.6.5.132-6</sigar.version>
     <oshi.version>7.5.0</oshi.version>
+    <mariadb.legacy.version>2.7.3</mariadb.legacy.version>
   </properties>
 
   <dependencies>
@@ -510,6 +511,23 @@
               </outputDirectory>
             </configuration>
           </execution>
+          <execution>
+            <id>copy-mariadb-legacy-test-driver</id>
+            <phase>generate-test-resources</phase>
+            <goals>
+              <goal>copy</goal>
+            </goals>
+            <configuration>
+              <artifactItems>
+                <artifactItem>
+                  <groupId>org.mariadb.jdbc</groupId>
+                  <artifactId>mariadb-java-client</artifactId>
+                  <version>${mariadb.legacy.version}</version>
+                  
<outputDirectory>${project.build.directory}/mariadb-legacy</outputDirectory>
+                </artifactItem>
+              </artifactItems>
+            </configuration>
+          </execution>
         </executions>
       </plugin>
       <plugin>
@@ -520,6 +538,11 @@
 
           <!-- use normal classpath instead of manifest jar for 
JvmUtilsTest.testSystemClassPath -->
           <useManifestOnlyJar>false</useManifestOnlyJar>
+          <systemPropertyVariables>
+            <druid.test.mariadbLegacyDriver>
+              
${project.build.directory}/mariadb-legacy/mariadb-java-client-${mariadb.legacy.version}.jar
+            </druid.test.mariadbLegacyDriver>
+          </systemPropertyVariables>
         </configuration>
       </plugin>
       <plugin>
diff --git 
a/processing/src/main/java/org/apache/druid/utils/ConnectionUriUtils.java 
b/processing/src/main/java/org/apache/druid/utils/ConnectionUriUtils.java
index 80c296ef653..5f2dce3b4d8 100644
--- a/processing/src/main/java/org/apache/druid/utils/ConnectionUriUtils.java
+++ b/processing/src/main/java/org/apache/druid/utils/ConnectionUriUtils.java
@@ -36,6 +36,14 @@ import java.util.Set;
 public final class ConnectionUriUtils
 {
   private static final String MARIADB_EXTRAS = "nonMappedOptions";
+  private static final Set<String> MARIADB_3X_EXCLUDED_FIELDS = Set.of(
+      "database",
+      "haMode",
+      "$jacocoData",
+      "addresses",
+      "initialUrl",
+      "codecs"
+  );
   // Note: MySQL JDBC connector 8 supports 7 other protocols than just 
`jdbc:mysql:`
   // 
(https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-jdbc-url-format.html).
   // We should consider either expanding recognized mysql protocols or 
restricting allowed protocols to
@@ -79,8 +87,7 @@ public final class ConnectionUriUtils
    * parameter whitelist validation.
    * <p>
    * uris starting with {@link #MYSQL_PREFIX} will first try to use the MySQL 
Connector/J driver (5.x), then fallback
-   * to MariaDB Connector/J (version 2.x) which also accepts jdbc:mysql uris. 
This method does not attempt to use
-   * MariaDB Connector/J 3.x alpha driver (at the time of these javadocs, it 
only handles the jdbc:mariadb prefix)
+   * to MariaDB Connector/J (version 2.x or 3.x) which also accepts jdbc:mysql 
uris.
    * <p>
    * uris starting with {@link #POSTGRES_PREFIX} will use the postgresql 
driver to parse the uri
    * <p>
@@ -101,10 +108,21 @@ public final class ConnectionUriUtils
           return tryParseMariaDb2xConnectionUri(connectionUri);
         }
         catch (ClassNotFoundException notFoundMaria2x) {
-          throw new RuntimeException(
-              "Failed to find MySQL driver class. Please check the MySQL 
connector version 8.2.0 is in the classpath",
-              notFoundMysql
-          );
+          try {
+            return tryParseMariaDb3xConnectionUri(connectionUri);
+          }
+          catch (ClassNotFoundException notFoundMaria3x) {
+            throw new RuntimeException(
+                "Failed to find MySQL driver class. Please check the MySQL 
connector version 8.2.0 is in the classpath",
+                notFoundMysql
+            );
+          }
+          catch (IllegalArgumentException iaeMaria3x) {
+            throw iaeMaria3x;
+          }
+          catch (Throwable otherMaria3x) {
+            throw new RuntimeException(otherMaria3x);
+          }
         }
         catch (IllegalArgumentException iaeMaria2x) {
           throw iaeMaria2x;
@@ -129,7 +147,7 @@ public final class ConnectionUriUtils
         }
         catch (ClassNotFoundException notFoundMaria3x) {
           throw new RuntimeException(
-              "Failed to find MariaDB driver class. Please check the MariaDB 
connector version 2.7.3 is in the classpath",
+              "Failed to find MariaDB driver class. Please check the MariaDB 
connector is in the classpath",
               notFoundMaria2x
           );
         }
@@ -310,7 +328,7 @@ public final class ConnectionUriUtils
 
     Object defaultConfiguration = 
buildMethod.invoke(configurationBuilderClass.getConstructor().newInstance());
     for (Field field : fields) {
-      if (field.getName().equals(MARIADB_EXTRAS)) {
+      if (field.getName().equals(MARIADB_EXTRAS) || 
MARIADB_3X_EXCLUDED_FIELDS.contains(field.getName())) {
         continue;
       }
       try {
diff --git 
a/processing/src/test/java/org/apache/druid/utils/ConnectionUriUtilsMariaDb2xTest.java
 
b/processing/src/test/java/org/apache/druid/utils/ConnectionUriUtilsMariaDb2xTest.java
new file mode 100644
index 00000000000..340fb35d1d8
--- /dev/null
+++ 
b/processing/src/test/java/org/apache/druid/utils/ConnectionUriUtilsMariaDb2xTest.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.utils;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Set;
+import java.util.jar.JarFile;
+
+public class ConnectionUriUtilsMariaDb2xTest
+{
+  private static final String MYSQL_URI =
+      
"jdbc:mysql://localhost:3306/test?user=druid&password=diurd&keyonly&otherOptions=wat";
+  private static final String MARIA_URI =
+      
"jdbc:mariadb://localhost:3306/test?user=druid&password=diurd&keyonly&otherOptions=wat";
+  private static final Set<String> EXPECTED_PROPERTIES = Set.of("user", 
"password", "keyonly", "otherOptions");
+
+  @Test
+  public void testMariaDb2xDriverUriDispatch() throws Exception
+  {
+    final Path driverJar = 
Path.of(System.getProperty("druid.test.mariadbLegacyDriver"));
+    Assertions.assertTrue(Files.isRegularFile(driverJar), "MariaDB Connector/J 
2.x test driver is missing");
+
+    try (final JarFile jarFile = new JarFile(driverJar.toFile())) {
+      Assertions.assertEquals(
+          "2.7.3",
+          jarFile.getManifest().getMainAttributes().getValue("Bundle-Version")
+      );
+    }
+
+    final URL processingClasses = 
ConnectionUriUtils.class.getProtectionDomain().getCodeSource().getLocation();
+    try (final MariaDb2xClassLoader classLoader = new MariaDb2xClassLoader(
+        new URL[]{processingClasses, driverJar.toUri().toURL()},
+        ConnectionUriUtils.class.getClassLoader()
+    )) {
+      final Class<?> isolatedUtils = 
classLoader.loadClass(ConnectionUriUtils.class.getName());
+      final Method directParser = 
isolatedUtils.getMethod("tryParseMariaDb2xConnectionUri", String.class);
+      final Method dispatcher = 
isolatedUtils.getMethod("tryParseJdbcUriParameters", String.class, 
boolean.class);
+
+      for (final String uri : new String[]{MYSQL_URI, MARIA_URI}) {
+        Assertions.assertEquals(EXPECTED_PROPERTIES, invoke(directParser, 
uri));
+        Assertions.assertEquals(EXPECTED_PROPERTIES, invoke(dispatcher, uri, 
false));
+      }
+
+      Assertions.assertThrows(
+          ClassNotFoundException.class,
+          () -> 
ConnectionUriUtils.class.getClassLoader().loadClass("org.mariadb.jdbc.UrlParser")
+      );
+      final Class<?> urlParser = 
classLoader.loadClass("org.mariadb.jdbc.UrlParser");
+      Assertions.assertSame(classLoader, urlParser.getClassLoader());
+      Assertions.assertNotSame(
+          classLoader,
+          Class.forName("org.mariadb.jdbc.Configuration").getClassLoader()
+      );
+      Assertions.assertThrows(
+          ClassNotFoundException.class,
+          () -> classLoader.loadClass("org.mariadb.jdbc.Configuration")
+      );
+      Assertions.assertEquals(
+          driverJar.toRealPath(),
+          
Path.of(urlParser.getProtectionDomain().getCodeSource().getLocation().toURI()).toRealPath()
+      );
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  private static Set<String> invoke(final Method method, final Object... 
arguments) throws Exception
+  {
+    return (Set<String>) method.invoke(null, arguments);
+  }
+
+  private static final class MariaDb2xClassLoader extends URLClassLoader
+  {
+    private MariaDb2xClassLoader(final URL[] urls, final ClassLoader parent)
+    {
+      super(urls, parent);
+    }
+
+    @Override
+    protected Class<?> loadClass(final String name, final boolean resolve) 
throws ClassNotFoundException
+    {
+      synchronized (getClassLoadingLock(name)) {
+        Class<?> loadedClass = findLoadedClass(name);
+        if (loadedClass == null) {
+          if (name.equals(ConnectionUriUtils.class.getName()) || 
name.startsWith("org.mariadb.jdbc.")) {
+            loadedClass = findClass(name);
+          } else if (name.startsWith("com.mysql.cj.")) {
+            throw new ClassNotFoundException(name);
+          } else {
+            loadedClass = super.loadClass(name, false);
+          }
+        }
+        if (resolve) {
+          resolveClass(loadedClass);
+        }
+        return loadedClass;
+      }
+    }
+  }
+}
diff --git 
a/processing/src/test/java/org/apache/druid/utils/ConnectionUriUtilsTest.java 
b/processing/src/test/java/org/apache/druid/utils/ConnectionUriUtilsTest.java
index 963561b8a2e..eec4767e607 100644
--- 
a/processing/src/test/java/org/apache/druid/utils/ConnectionUriUtilsTest.java
+++ 
b/processing/src/test/java/org/apache/druid/utils/ConnectionUriUtilsTest.java
@@ -33,6 +33,8 @@ public class ConnectionUriUtilsTest
   public static class ThrowIfURLHasNotAllowedPropertiesTest
   {
     private static final String MYSQL_URI = 
"jdbc:mysql://localhost:3306/test?user=druid&password=diurd&keyonly&otherOptions=wat";
+    private static final String MYSQL_URI_WITH_MARIA_DB_3X_SCHEME =
+        
"jdbc:mysql://localhost:3306/test?user=druid&password=diurd&keyonly&otherOptions=wat&permitMysqlScheme";
     private static final String MARIA_URI = 
"jdbc:mariadb://localhost:3306/test?user=druid&password=diurd&keyonly&otherOptions=wat";
     private static final String POSTGRES_URI = 
"jdbc:postgresql://localhost:3306/test?user=druid&password=diurd&keyonly&otherOptions=wat";
     private static final String UNKNOWN_URI = 
"jdbc:druid://localhost:8888/query/v2/sql/avatica?user=druid&password=diurd&keyonly&otherOptions=wat";
@@ -122,35 +124,53 @@ public class ConnectionUriUtilsTest
     @Test
     public void testMySqlFallbackMySqlMaria2x()
     {
-      MockedStatic<ConnectionUriUtils> utils = 
Mockito.mockStatic(ConnectionUriUtils.class);
-      utils.when(() -> ConnectionUriUtils.tryParseJdbcUriParameters(MYSQL_URI, 
false)).thenCallRealMethod();
-      utils.when(() -> 
ConnectionUriUtils.tryParseMySqlConnectionUri(MYSQL_URI)).thenThrow(ClassNotFoundException.class);
-      utils.when(() -> 
ConnectionUriUtils.tryParseMariaDb2xConnectionUri(MYSQL_URI)).thenCallRealMethod();
+      try (MockedStatic<ConnectionUriUtils> utils = 
Mockito.mockStatic(ConnectionUriUtils.class)) {
+        utils.when(() -> 
ConnectionUriUtils.tryParseJdbcUriParameters(MYSQL_URI, 
false)).thenCallRealMethod();
+        utils.when(() -> 
ConnectionUriUtils.tryParseMySqlConnectionUri(MYSQL_URI))
+             .thenThrow(ClassNotFoundException.class);
+        utils.when(() -> 
ConnectionUriUtils.tryParseMariaDb2xConnectionUri(MYSQL_URI))
+             .thenReturn(ImmutableSet.of("user", "password", "keyonly", 
"otherOptions"));
 
-      Set<String> props = 
ConnectionUriUtils.tryParseJdbcUriParameters(MYSQL_URI, false);
-      // this would be 9 if didn't fall back to mariadb
-      Assertions.assertEquals(4, props.size());
-      utils.close();
+        Set<String> props = 
ConnectionUriUtils.tryParseJdbcUriParameters(MYSQL_URI, false);
+        // this would be 9 if didn't fall back to mariadb
+        Assertions.assertEquals(4, props.size());
+      }
+    }
+
+    @Test
+    public void testMySqlFallbackMySqlMaria3x()
+    {
+      try (MockedStatic<ConnectionUriUtils> utils = 
Mockito.mockStatic(ConnectionUriUtils.class)) {
+        utils.when(() -> 
ConnectionUriUtils.tryParseJdbcUriParameters(MYSQL_URI_WITH_MARIA_DB_3X_SCHEME, 
false))
+             .thenCallRealMethod();
+        utils.when(() -> 
ConnectionUriUtils.tryParseMySqlConnectionUri(MYSQL_URI_WITH_MARIA_DB_3X_SCHEME))
+             .thenThrow(ClassNotFoundException.class);
+        utils.when(() -> 
ConnectionUriUtils.tryParseMariaDb2xConnectionUri(MYSQL_URI_WITH_MARIA_DB_3X_SCHEME))
+             .thenThrow(ClassNotFoundException.class);
+        utils.when(() -> 
ConnectionUriUtils.tryParseMariaDb3xConnectionUri(MYSQL_URI_WITH_MARIA_DB_3X_SCHEME))
+             .thenCallRealMethod();
+
+        Set<String> props = 
ConnectionUriUtils.tryParseJdbcUriParameters(MYSQL_URI_WITH_MARIA_DB_3X_SCHEME, 
false);
+        Assertions.assertEquals(
+            ImmutableSet.of("user", "password", "keyonly", "otherOptions", 
"permitMysqlScheme"),
+            props
+        );
+      }
     }
 
     @Test
     public void testMariaFallbackMaria3x()
     {
-      MockedStatic<ConnectionUriUtils> utils = 
Mockito.mockStatic(ConnectionUriUtils.class);
-      utils.when(() -> ConnectionUriUtils.tryParseJdbcUriParameters(MARIA_URI, 
false)).thenCallRealMethod();
-      utils.when(() -> 
ConnectionUriUtils.tryParseMariaDb2xConnectionUri(MARIA_URI)).thenThrow(ClassNotFoundException.class);
-      utils.when(() -> 
ConnectionUriUtils.tryParseMariaDb3xConnectionUri(MARIA_URI)).thenCallRealMethod();
+      try (MockedStatic<ConnectionUriUtils> utils = 
Mockito.mockStatic(ConnectionUriUtils.class)) {
+        utils.when(() -> 
ConnectionUriUtils.tryParseJdbcUriParameters(MARIA_URI, 
false)).thenCallRealMethod();
+        utils.when(() -> 
ConnectionUriUtils.tryParseMariaDb2xConnectionUri(MARIA_URI))
+             .thenThrow(ClassNotFoundException.class);
+        utils.when(() -> 
ConnectionUriUtils.tryParseMariaDb3xConnectionUri(MARIA_URI)).thenCallRealMethod();
 
-      try {
         Set<String> props = 
ConnectionUriUtils.tryParseJdbcUriParameters(MARIA_URI, false);
         // this would be 4 if didn't fall back to mariadb 3x
-        Assertions.assertEquals(8, props.size());
+        Assertions.assertEquals(4, props.size());
       }
-      catch (RuntimeException e) {
-
-        Assertions.assertTrue(e.getMessage().contains("Failed to find MariaDB 
driver class"));
-      }
-      utils.close();
     }
 
     @Test
@@ -195,31 +215,12 @@ public class ConnectionUriUtilsTest
       Assertions.assertFalse(props.contains("keyonly"));
     }
 
-    @Test
-    public void testMariaDb2xDriver() throws Throwable
-    {
-      Set<String> props = 
ConnectionUriUtils.tryParseMariaDb2xConnectionUri(MYSQL_URI);
-      // mariadb doesn't spit out any extras other than what the user specified
-      Assertions.assertEquals(4, props.size());
-      Assertions.assertTrue(props.contains("user"));
-      Assertions.assertTrue(props.contains("password"));
-      Assertions.assertTrue(props.contains("otherOptions"));
-      Assertions.assertTrue(props.contains("keyonly"));
-      props = ConnectionUriUtils.tryParseMariaDb2xConnectionUri(MARIA_URI);
-      Assertions.assertEquals(4, props.size());
-      Assertions.assertTrue(props.contains("user"));
-      Assertions.assertTrue(props.contains("password"));
-      Assertions.assertTrue(props.contains("otherOptions"));
-      Assertions.assertTrue(props.contains("keyonly"));
-    }
-
     @Test
     public void testMariaDb3xDriver() throws Exception
     {
-      // at the time of adding this test, mariadb connector/j 3.x does not 
actually parse jdbc:mysql uris
-      // so this would throw an IAE.class instead of 
ClassNotFoundException.class if the connector is swapped out
-      // in maven dependencies
-      Assertions.assertThrows(ClassNotFoundException.class, () ->
+      // MariaDB Connector/J 3.x rejects plain jdbc:mysql URLs by default. 
MYSQL_URI lacks permitMysqlScheme,
+      // so Configuration.parse returns null, which Druid translates to IAE.
+      Assertions.assertThrows(IAE.class, () ->
           ConnectionUriUtils.tryParseMariaDb3xConnectionUri(MYSQL_URI)
       );
     }
@@ -227,19 +228,12 @@ public class ConnectionUriUtilsTest
     @Test
     public void testMariaDb3xDriverMariaUri() throws Exception
     {
-      // mariadb 3.x driver cannot be loaded alongside 2.x, so this will fail 
with class not found
-      // however, if we swap out version in pom then we end up with 8 keys 
where
-      // "database", "addresses", "codecs", and "initialUrl" are added as 
extras
-      // we should perhaps consider adding them to built-in allowed lists in 
the future when this driver is no longer
-      // an alpha release
-      Assertions.assertThrows(ClassNotFoundException.class, () -> {
-        Set<String> props = 
ConnectionUriUtils.tryParseMariaDb3xConnectionUri(MARIA_URI);
-        Assertions.assertEquals(8, props.size());
-        Assertions.assertTrue(props.contains("user"));
-        Assertions.assertTrue(props.contains("password"));
-        Assertions.assertTrue(props.contains("otherOptions"));
-        Assertions.assertTrue(props.contains("keyonly"));
-      });
+      Set<String> props = 
ConnectionUriUtils.tryParseMariaDb3xConnectionUri(MARIA_URI);
+      Assertions.assertEquals(4, props.size());
+      Assertions.assertTrue(props.contains("user"));
+      Assertions.assertTrue(props.contains("password"));
+      Assertions.assertTrue(props.contains("otherOptions"));
+      Assertions.assertTrue(props.contains("keyonly"));
     }
 
     @Test
@@ -257,7 +251,7 @@ public class ConnectionUriUtilsTest
     @Test
     public void testMariaDbInvalidArgs() throws Exception
     {
-      Assertions.assertThrows(IAE.class, () -> 
ConnectionUriUtils.tryParseMariaDb2xConnectionUri(POSTGRES_URI));
+      Assertions.assertThrows(IAE.class, () -> 
ConnectionUriUtils.tryParseMariaDb3xConnectionUri(POSTGRES_URI));
     }
   }
 }
diff --git 
a/server/src/main/java/org/apache/druid/server/initialization/JdbcAccessSecurityConfig.java
 
b/server/src/main/java/org/apache/druid/server/initialization/JdbcAccessSecurityConfig.java
index 42b0044b1d0..6b7213ada50 100644
--- 
a/server/src/main/java/org/apache/druid/server/initialization/JdbcAccessSecurityConfig.java
+++ 
b/server/src/main/java/org/apache/druid/server/initialization/JdbcAccessSecurityConfig.java
@@ -33,9 +33,10 @@ import java.util.Set;
 public class JdbcAccessSecurityConfig
 {
   static final Set<String> DEFAULT_ALLOWED_PROPERTIES = ImmutableSet.of(
-      // MySQL
+      // MySQL and MariaDB
       "useSSL",
       "requireSSL",
+      "sslMode",
 
       // PostgreSQL
       "ssl",
diff --git 
a/server/src/test/java/org/apache/druid/metadata/input/SqlInputSourceTest.java 
b/server/src/test/java/org/apache/druid/metadata/input/SqlInputSourceTest.java
index 5611a06c2e9..b8d3af57f4c 100644
--- 
a/server/src/test/java/org/apache/druid/metadata/input/SqlInputSourceTest.java
+++ 
b/server/src/test/java/org/apache/druid/metadata/input/SqlInputSourceTest.java
@@ -264,7 +264,7 @@ public class SqlInputSourceTest
         )
     );
     Assertions.assertEquals(
-        "The property [user] is not in the allowed list [useSSL, requireSSL, 
ssl, sslmode]",
+        "The property [user] is not in the allowed list [useSSL, requireSSL, 
sslMode, ssl, sslmode]",
         t.getMessage()
     );
   }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to