You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kyuubi.apache.org by ch...@apache.org on 2022/08/15 07:38:44 UTC

[incubator-kyuubi] branch master updated: [KYUUBI #3230] Flink SQL engine supports run across versions

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

chengpan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-kyuubi.git


The following commit(s) were added to refs/heads/master by this push:
     new db0047d51 [KYUUBI #3230] Flink SQL engine supports run across versions
db0047d51 is described below

commit db0047d51c1d734c288635866934f2f316f2e5d0
Author: Cheng Pan <ch...@apache.org>
AuthorDate: Mon Aug 15 15:38:35 2022 +0800

    [KYUUBI #3230] Flink SQL engine supports run across versions
    
    ### _Why are the changes needed?_
    
    To make sure flink sql engine build against flink-1.15 can run on flink-1.14.
    
    ### _How was this patch tested?_
    - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible
    
    - [ ] Add screenshots for manual tests if appropriate
    
    - [x] [Run test](https://kyuubi.apache.org/docs/latest/develop_tools/testing.html#running-tests) locally before make a pull request
    
    Closes #3230 from pan3793/flink.
    
    Closes #3230
    
    5c3f859c [Cheng Pan] updated
    e776ee63 [Cheng Pan] review
    a5cfb0c8 [Cheng Pan] review
    004c05d9 [Cheng Pan] nit
    9c019554 [Cheng Pan] nit
    7c3f553c [Cheng Pan] nit
    8e1fb04b [Cheng Pan] nit
    6adc7dbe [Cheng Pan] nit
    bc10739c [Cheng Pan] fix
    cf98111a [Cheng Pan] workflow
    a8895e68 [Cheng Pan] nit
    8cab5188 [Cheng Pan] Flink SQL engine supports run across versions
    
    Authored-by: Cheng Pan <ch...@apache.org>
    Signed-off-by: Cheng Pan <ch...@apache.org>
---
 .github/workflows/master.yml                       |  31 +-
 .../plugin/spark/authz/util/SemanticVersion.scala  |   2 +
 .../spark/connector/common/SemanticVersion.scala   |   2 +
 .../kyuubi/engine/flink/FlinkEngineUtils.scala     |  25 +-
 .../engine/flink/operation/ExecuteStatement.scala  |  35 +-
 .../flink/operation/FlinkOperationSuite.scala      |  40 +-
 .../org/apache/kyuubi/reflection/DynClasses.java   | 120 +++++
 .../apache/kyuubi/reflection/DynConstructors.java  | 306 ++++++++++++
 .../org/apache/kyuubi/reflection/DynFields.java    | 430 +++++++++++++++++
 .../org/apache/kyuubi/reflection/DynMethods.java   | 529 +++++++++++++++++++++
 .../org/apache/kyuubi/engine/SemanticVersion.scala |   2 +
 11 files changed, 1469 insertions(+), 53 deletions(-)

diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml
index 0c6ed989b..8857d22d4 100644
--- a/.github/workflows/master.yml
+++ b/.github/workflows/master.yml
@@ -56,12 +56,12 @@ jobs:
             spark: '3.2'
             spark-archive: '-Dspark.archive.mirror=https://archive.apache.org/dist/spark/spark-3.1.3 -Dspark.archive.name=spark-3.1.3-bin-hadoop3.2.tgz'
             exclude-tags: '-Dmaven.plugin.scalatest.exclude.tags=org.scalatest.tags.Slow,org.apache.kyuubi.tags.DeltaTest,org.apache.kyuubi.tags.HudiTest,org.apache.kyuubi.tags.IcebergTest'
-            comment: 'verify-spark-3.1'
+            comment: 'verify-on-spark-3.1-binary'
           - java: 8
             spark: '3.2'
             spark-archive: '-Dspark.archive.mirror=https://archive.apache.org/dist/spark/spark-3.3.0 -Dspark.archive.name=spark-3.3.0-bin-hadoop3.tgz'
             exclude-tags: '-Dmaven.plugin.scalatest.exclude.tags=org.scalatest.tags.Slow,org.apache.kyuubi.tags.DeltaTest,org.apache.kyuubi.tags.HudiTest,org.apache.kyuubi.tags.IcebergTest'
-            comment: 'verify-spark-3.3'
+            comment: 'verify-on-spark-3.3-binary'
     env:
       SPARK_LOCAL_IP: localhost
     steps:
@@ -111,12 +111,11 @@ jobs:
           - '1.15'
         flink-archive: [ "" ]
         comment: [ "normal" ]
-        # FIXME: Cross Flink versions verification is not supported yet
-        # include:
-          # - java: 8
-            # flink: '1.15'
-            # flink-archive: '-Dflink.archive.mirror=https://archive.apache.org/dist/flink/flink-1.14.5 -Dflink.archive.name=flink-1.14.5-bin-scala_2.12.tgz'
-            # comment: 'verify-flink-1.14'
+        include:
+          - java: 8
+            flink: '1.15'
+            flink-archive: '-Dflink.archive.mirror=https://archive.apache.org/dist/flink/flink-1.14.5 -Dflink.archive.name=flink-1.14.5-bin-scala_2.12.tgz'
+            comment: 'verify-on-flink-1.14-binary'
     steps:
       - uses: actions/checkout@v2
       - name: Tune Runner VM
@@ -128,11 +127,21 @@ jobs:
           java-version: ${{ matrix.java }}
           cache: 'maven'
           check-latest: false
-      - name: Build and test Flink with maven w/o linters
-        run: |       
+      - name: Build Flink with maven w/o linters
+        run: |
+          TEST_MODULES="externals/kyuubi-flink-sql-engine,integration-tests/kyuubi-flink-it"
+          ./build/mvn ${MVN_OPT} -pl ${TEST_MODULES} -Pflink-${{ matrix.flink }} ${{ matrix.flink-archive }} -am clean install -DskipTests
+      - name: Test Flink
+        if: matrix.flink-archive == ''
+        run: |
           TEST_MODULES="externals/kyuubi-flink-sql-engine,integration-tests/kyuubi-flink-it"
-          ./build/mvn ${MVN_OPT} -pl ${TEST_MODULES} -Pflink-${{ matrix.flink }} ${{ matrix.flink-archive }} -am  clean install -DskipTests
           ./build/mvn ${MVN_OPT} -pl ${TEST_MODULES} -Pflink-${{ matrix.flink }} ${{ matrix.flink-archive }} test
+      - name: Cross-version test Flink
+        if: matrix.flink-archive != ''
+        run: |
+          IT_FLINK=`echo "${{ matrix.flink-archive }}" | grep -E 'flink\-([0-9]+\.[0-9]+.[0-9]+)\-bin' -o | grep -E '[0-9]+\.[0-9]+' -o`
+          IT_MODULE="integration-tests/kyuubi-flink-it"
+          ./build/mvn ${MVN_OPT} -pl ${IT_MODULE} -Pflink-${IT_FLINK} ${{ matrix.flink-archive }} test
       - name: Upload test logs
         if: failure()
         uses: actions/upload-artifact@v2
diff --git a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/util/SemanticVersion.scala b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/util/SemanticVersion.scala
index ab4f95836..4d7e89725 100644
--- a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/util/SemanticVersion.scala
+++ b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/util/SemanticVersion.scala
@@ -56,6 +56,8 @@ case class SemanticVersion(majorVersion: Int, minorVersion: Int) {
     val targetMinor = targetVersion.minorVersion
     callback(targetMajor, targetMinor, this.majorVersion, this.minorVersion)
   }
+
+  override def toString: String = s"$majorVersion.$minorVersion"
 }
 
 object SemanticVersion {
diff --git a/extensions/spark/kyuubi-spark-connector-common/src/main/scala/org/apache/kyuubi/spark/connector/common/SemanticVersion.scala b/extensions/spark/kyuubi-spark-connector-common/src/main/scala/org/apache/kyuubi/spark/connector/common/SemanticVersion.scala
index ee6936ebf..200937ca6 100644
--- a/extensions/spark/kyuubi-spark-connector-common/src/main/scala/org/apache/kyuubi/spark/connector/common/SemanticVersion.scala
+++ b/extensions/spark/kyuubi-spark-connector-common/src/main/scala/org/apache/kyuubi/spark/connector/common/SemanticVersion.scala
@@ -56,6 +56,8 @@ case class SemanticVersion(majorVersion: Int, minorVersion: Int) {
     val targetMinor = targetVersion.minorVersion
     callback(targetMajor, targetMinor, this.majorVersion, this.minorVersion)
   }
+
+  override def toString: String = s"$majorVersion.$minorVersion"
 }
 
 object SemanticVersion {
diff --git a/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/FlinkEngineUtils.scala b/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/FlinkEngineUtils.scala
index a57442d42..a3f60192f 100644
--- a/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/FlinkEngineUtils.scala
+++ b/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/FlinkEngineUtils.scala
@@ -39,18 +39,29 @@ object FlinkEngineUtils extends Logging {
   val MODE_EMBEDDED = "embedded"
   val EMBEDDED_MODE_CLIENT_OPTIONS: Options = getEmbeddedModeClientOptions(new Options);
 
+  val SUPPORTED_FLINK_VERSIONS: Array[SemanticVersion] =
+    Array("1.14", "1.15").map(SemanticVersion.apply)
+
   def checkFlinkVersion(): Unit = {
     val flinkVersion = EnvironmentInformation.getVersion
-    SemanticVersion(flinkVersion) match {
-      case SemanticVersion(1, 14 | 15) =>
-        logger.info(s"The current Flink version is $flinkVersion")
-      case _ =>
-        throw new UnsupportedOperationException(
-          s"The current Flink version is $flinkVersion, " +
-            s"Only Flink 1.14.x and 1.15 are supported, not supported in other versions")
+    if (SUPPORTED_FLINK_VERSIONS.contains(SemanticVersion(flinkVersion))) {
+      info(s"The current Flink version is $flinkVersion")
+    } else {
+      throw new UnsupportedOperationException(
+        s"You are using unsupported Flink version $flinkVersion, " +
+          s"only Flink ${SUPPORTED_FLINK_VERSIONS.mkString(", ")} are supported now.")
     }
   }
 
+  def isFlinkVersionAtMost(targetVersionString: String): Boolean =
+    SemanticVersion(EnvironmentInformation.getVersion).isVersionAtMost(targetVersionString)
+
+  def isFlinkVersionAtLeast(targetVersionString: String): Boolean =
+    SemanticVersion(EnvironmentInformation.getVersion).isVersionAtLeast(targetVersionString)
+
+  def isFlinkVersionEqualTo(targetVersionString: String): Boolean =
+    SemanticVersion(EnvironmentInformation.getVersion).isVersionEqualTo(targetVersionString)
+
   def parseCliOptions(args: Array[String]): CliOptions = {
     val (mode, modeArgs) =
       if (args.isEmpty || args(0).startsWith("-")) (MODE_EMBEDDED, args)
diff --git a/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/operation/ExecuteStatement.scala b/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/operation/ExecuteStatement.scala
index fe3c29f8d..47218276d 100644
--- a/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/operation/ExecuteStatement.scala
+++ b/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/operation/ExecuteStatement.scala
@@ -34,10 +34,12 @@ import org.apache.flink.table.types.logical._
 import org.apache.flink.types.Row
 
 import org.apache.kyuubi.Logging
+import org.apache.kyuubi.engine.flink.FlinkEngineUtils._
 import org.apache.kyuubi.engine.flink.result.ResultSet
 import org.apache.kyuubi.engine.flink.schema.RowSet.toHiveString
 import org.apache.kyuubi.operation.OperationState
 import org.apache.kyuubi.operation.log.OperationLog
+import org.apache.kyuubi.reflection.DynMethods
 import org.apache.kyuubi.session.Session
 import org.apache.kyuubi.util.RowSetUtils
 
@@ -116,17 +118,16 @@ class ExecuteStatement(
             (1 to result.getPayload).foreach { page =>
               if (rows.size < resultMaxRows) {
                 // FLINK-24461 retrieveResultPage method changes the return type from Row to RowData
-                val result = executor.retrieveResultPage(resultId, page).asScala.toList
-                result.headOption match {
-                  case None =>
-                  case Some(r) =>
-                    // for flink 1.14
-                    if (r.getClass == classOf[Row]) {
-                      rows ++= result.asInstanceOf[List[Row]]
-                    } else {
-                      // for flink 1.15+
-                      rows ++= result.map(r => convertToRow(r.asInstanceOf[RowData], dataTypes))
-                    }
+                val retrieveResultPage = DynMethods.builder("retrieveResultPage")
+                  .impl(executor.getClass, classOf[String], classOf[Int])
+                  .build(executor)
+                val _page = Integer.valueOf(page)
+                if (isFlinkVersionEqualTo("1.14")) {
+                  val result = retrieveResultPage.invoke[util.List[Row]](resultId, _page)
+                  rows ++= result.asScala
+                } else if (isFlinkVersionAtLeast("1.15")) {
+                  val result = retrieveResultPage.invoke[util.List[RowData]](resultId, _page)
+                  rows ++= result.asScala.map(r => convertToRow(r, dataTypes))
                 }
               } else {
                 loop = false
@@ -216,7 +217,7 @@ class ExecuteStatement(
               case d: BinaryMapData =>
                 val kvArray = toArray(keyType, valueType, d)
                 val map: util.Map[Any, Any] = new util.HashMap[Any, Any]
-                for (i <- 0 until kvArray._1.length) {
+                for (i <- kvArray._1.indices) {
                   val value: Any = kvArray._2(i)
                   map.put(kvArray._1(i), value)
                 }
@@ -229,11 +230,13 @@ class ExecuteStatement(
         case _: DoubleType =>
           row.setField(i, r.getDouble(i))
         case t: RowType =>
-          val clazz = Class.forName("org.apache.flink.table.types.DataType")
-          val fieldDataTypes = clazz.getDeclaredMethod("getFieldDataTypes", classOf[DataType])
-            .invoke(null, dataType).asInstanceOf[java.util.List[DataType]]
+          val fieldDataTypes = DynMethods.builder("getFieldDataTypes")
+            .impl(classOf[DataType], classOf[DataType])
+            .buildStatic
+            .invoke[util.List[DataType]](dataType)
+            .asScala.toList
           val internalRowData = r.getRow(i, t.getFieldCount)
-          val internalRow = convertToRow(internalRowData, fieldDataTypes.asScala.toList)
+          val internalRow = convertToRow(internalRowData, fieldDataTypes)
           row.setField(i, internalRow)
         case t =>
           val hiveString = toHiveString((row.getField(i), t))
diff --git a/externals/kyuubi-flink-sql-engine/src/test/scala/org/apache/kyuubi/engine/flink/operation/FlinkOperationSuite.scala b/externals/kyuubi-flink-sql-engine/src/test/scala/org/apache/kyuubi/engine/flink/operation/FlinkOperationSuite.scala
index 7dd77b3dd..c402d5cd5 100644
--- a/externals/kyuubi-flink-sql-engine/src/test/scala/org/apache/kyuubi/engine/flink/operation/FlinkOperationSuite.scala
+++ b/externals/kyuubi-flink-sql-engine/src/test/scala/org/apache/kyuubi/engine/flink/operation/FlinkOperationSuite.scala
@@ -23,7 +23,6 @@ import java.util.UUID
 
 import scala.collection.JavaConverters._
 
-import org.apache.flink.runtime.util.EnvironmentInformation
 import org.apache.flink.table.types.logical.LogicalTypeRoot
 import org.apache.hive.service.rpc.thrift._
 import org.scalatest.concurrent.PatienceConfiguration.Timeout
@@ -31,7 +30,7 @@ import org.scalatest.time.SpanSugar._
 
 import org.apache.kyuubi.config.KyuubiConf._
 import org.apache.kyuubi.config.KyuubiConf.OperationModes.NONE
-import org.apache.kyuubi.engine.SemanticVersion
+import org.apache.kyuubi.engine.flink.FlinkEngineUtils._
 import org.apache.kyuubi.engine.flink.WithFlinkSQLEngine
 import org.apache.kyuubi.engine.flink.result.Constants
 import org.apache.kyuubi.engine.flink.util.TestUserClassLoaderJar
@@ -46,8 +45,6 @@ class FlinkOperationSuite extends WithFlinkSQLEngine with HiveJDBCTestHelper {
   override protected def jdbcUrl: String =
     s"jdbc:hive2://${engine.frontendServices.head.connectionUrl}/;"
 
-  val runtimeVersion = SemanticVersion(EnvironmentInformation.getVersion)
-
   ignore("release session if shared level is CONNECTION") {
     logger.info(s"jdbc url is $jdbcUrl")
     assert(engine.getServiceState == STARTED)
@@ -579,10 +576,13 @@ class FlinkOperationSuite extends WithFlinkSQLEngine with HiveJDBCTestHelper {
       val metaData = resultSet.getMetaData
       assert(metaData.getColumnType(1) === java.sql.Types.ARRAY)
       assert(resultSet.next())
-      runtimeVersion.minorVersion match {
-        case 14 =>
-          assert(resultSet.getObject(1).toString == """["v1","v2","v3"]""")
-        case _ => assert(resultSet.getObject(1).toString == "[v1,v2,v3]")
+      if (isFlinkVersionEqualTo("1.14")) {
+        val expected = """["v1","v2","v3"]"""
+        assert(resultSet.getObject(1).toString == expected)
+      }
+      if (isFlinkVersionAtLeast("1.15")) {
+        val expected = "[v1,v2,v3]"
+        assert(resultSet.getObject(1).toString == expected)
       }
     }
   }
@@ -603,12 +603,13 @@ class FlinkOperationSuite extends WithFlinkSQLEngine with HiveJDBCTestHelper {
       val resultSet =
         statement.executeQuery("select (1, '2', true)")
       assert(resultSet.next())
-      runtimeVersion.minorVersion match {
-        case 14 => assert(resultSet.getString(
-            1) == "{INT NOT NULL:1,CHAR(1) NOT NULL:\"2\",BOOLEAN NOT NULL:true}")
-        case _ =>
-          assert(
-            resultSet.getString(1) == "{INT NOT NULL:1,CHAR(1) NOT NULL:2,BOOLEAN NOT NULL:true}")
+      if (isFlinkVersionEqualTo("1.14")) {
+        val expected = """{INT NOT NULL:1,CHAR(1) NOT NULL:"2",BOOLEAN NOT NULL:true}"""
+        assert(resultSet.getString(1) == expected)
+      }
+      if (isFlinkVersionAtLeast("1.15")) {
+        val expected = """{INT NOT NULL:1,CHAR(1) NOT NULL:2,BOOLEAN NOT NULL:true}"""
+        assert(resultSet.getString(1) == expected)
       }
       val metaData = resultSet.getMetaData
       assert(metaData.getColumnType(1) === java.sql.Types.STRUCT)
@@ -619,11 +620,12 @@ class FlinkOperationSuite extends WithFlinkSQLEngine with HiveJDBCTestHelper {
     withJdbcStatement() { statement =>
       val resultSet = statement.executeQuery("select encode('kyuubi', 'UTF-8')")
       assert(resultSet.next())
-      runtimeVersion.minorVersion match {
-        case 14 => assert(resultSet.getString(1) == "kyuubi")
-        case _ =>
-          // TODO: validate table results after FLINK-28882 is resolved
-          assert(resultSet.getString(1) == "k")
+      if (isFlinkVersionEqualTo("1.14")) {
+        assert(resultSet.getString(1) == "kyuubi")
+      }
+      if (isFlinkVersionAtLeast("1.15")) {
+        // TODO: validate table results after FLINK-28882 is resolved
+        assert(resultSet.getString(1) == "k")
       }
       val metaData = resultSet.getMetaData
       assert(metaData.getColumnType(1) === java.sql.Types.BINARY)
diff --git a/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynClasses.java b/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynClasses.java
new file mode 100644
index 000000000..05661e6a6
--- /dev/null
+++ b/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynClasses.java
@@ -0,0 +1,120 @@
+/*
+ * 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.kyuubi.reflection;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/** Copied from iceberg-common */
+public class DynClasses {
+
+  private DynClasses() {}
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+    private ClassLoader loader = Thread.currentThread().getContextClassLoader();
+    private Class<?> foundClass = null;
+    private boolean nullOk = false;
+    private Set<String> classNames = new LinkedHashSet<>();
+
+    private Builder() {}
+
+    /**
+     * Set the {@link ClassLoader} used to lookup classes by name.
+     *
+     * <p>If not set, the current thread's ClassLoader is used.
+     *
+     * @param newLoader a ClassLoader
+     * @return this Builder for method chaining
+     */
+    public Builder loader(ClassLoader newLoader) {
+      this.loader = newLoader;
+      return this;
+    }
+
+    /**
+     * Checks for an implementation of the class by name.
+     *
+     * @param className name of a class
+     * @return this Builder for method chaining
+     */
+    public Builder impl(String className) {
+      classNames.add(className);
+
+      if (foundClass != null) {
+        return this;
+      }
+
+      try {
+        this.foundClass = Class.forName(className, true, loader);
+      } catch (ClassNotFoundException e) {
+        // not the right implementation
+      }
+
+      return this;
+    }
+
+    /**
+     * Instructs this builder to return null if no class is found, rather than throwing an
+     * Exception.
+     *
+     * @return this Builder for method chaining
+     */
+    public Builder orNull() {
+      this.nullOk = true;
+      return this;
+    }
+
+    /**
+     * Returns the first implementation or throws ClassNotFoundException if one was not found.
+     *
+     * @param <S> Java superclass
+     * @return a {@link Class} for the first implementation found
+     * @throws ClassNotFoundException if no implementation was found
+     */
+    @SuppressWarnings("unchecked")
+    public <S> Class<? extends S> buildChecked() throws ClassNotFoundException {
+      if (!nullOk && foundClass == null) {
+        throw new ClassNotFoundException(
+            "Cannot find class; alternatives: " + String.join(", ", classNames));
+      }
+      return (Class<? extends S>) foundClass;
+    }
+
+    /**
+     * Returns the first implementation or throws RuntimeException if one was not found.
+     *
+     * @param <S> Java superclass
+     * @return a {@link Class} for the first implementation found
+     * @throws RuntimeException if no implementation was found
+     */
+    @SuppressWarnings("unchecked")
+    public <S> Class<? extends S> build() {
+      if (!nullOk && foundClass == null) {
+        throw new RuntimeException(
+            "Cannot find class; alternatives: " + String.join(", ", classNames));
+      }
+      return (Class<? extends S>) foundClass;
+    }
+  }
+}
diff --git a/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynConstructors.java b/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynConstructors.java
new file mode 100644
index 000000000..7495ce0ff
--- /dev/null
+++ b/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynConstructors.java
@@ -0,0 +1,306 @@
+/*
+ * 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.kyuubi.reflection;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Copied from parquet-common */
+public class DynConstructors {
+
+  private DynConstructors() {}
+
+  public static class Ctor<C> extends DynMethods.UnboundMethod {
+    private final Constructor<C> ctor;
+    private final Class<? extends C> constructed;
+
+    private Ctor(Constructor<C> constructor, Class<? extends C> constructed) {
+      super(null, "newInstance");
+      this.ctor = constructor;
+      this.constructed = constructed;
+    }
+
+    public Class<? extends C> getConstructedClass() {
+      return constructed;
+    }
+
+    public C newInstanceChecked(Object... args) throws Exception {
+      try {
+        if (args.length > ctor.getParameterCount()) {
+          return ctor.newInstance(Arrays.copyOfRange(args, 0, ctor.getParameterCount()));
+        } else {
+          return ctor.newInstance(args);
+        }
+      } catch (InstantiationException | IllegalAccessException e) {
+        throw e;
+      } catch (InvocationTargetException e) {
+        if (e.getCause() instanceof Exception) {
+          throw (Exception) e.getCause();
+        }
+        if (e.getCause() instanceof RuntimeException) {
+          throw (RuntimeException) e.getCause();
+        }
+        throw new RuntimeException(e.getCause());
+      }
+    }
+
+    public C newInstance(Object... args) {
+      try {
+        return newInstanceChecked(args);
+      } catch (Exception e) {
+        if (e instanceof RuntimeException) {
+          throw (RuntimeException) e;
+        }
+        throw new RuntimeException(e);
+      }
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <R> R invoke(Object target, Object... args) {
+      if (target != null) {
+        throw new IllegalArgumentException("Invalid call to constructor: target must be null");
+      }
+      return (R) newInstance(args);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <R> R invokeChecked(Object target, Object... args) throws Exception {
+      if (target != null) {
+        throw new IllegalArgumentException("Invalid call to constructor: target must be null");
+      }
+      return (R) newInstanceChecked(args);
+    }
+
+    @Override
+    public DynMethods.BoundMethod bind(Object receiver) {
+      throw new IllegalStateException("Cannot bind constructors");
+    }
+
+    @Override
+    public boolean isStatic() {
+      return true;
+    }
+
+    @Override
+    public String toString() {
+      return getClass().getSimpleName() + "(constructor=" + ctor + ", class=" + constructed + ")";
+    }
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static Builder builder(Class<?> baseClass) {
+    return new Builder(baseClass);
+  }
+
+  public static class Builder {
+    private final Class<?> baseClass;
+    private ClassLoader loader = Thread.currentThread().getContextClassLoader();
+    private Ctor ctor = null;
+    private Map<String, Throwable> problems = new HashMap<>();
+
+    public Builder(Class<?> baseClass) {
+      this.baseClass = baseClass;
+    }
+
+    public Builder() {
+      this.baseClass = null;
+    }
+
+    /**
+     * Set the {@link ClassLoader} used to lookup classes by name.
+     *
+     * <p>If not set, the current thread's ClassLoader is used.
+     *
+     * @param newLoader a ClassLoader
+     * @return this Builder for method chaining
+     */
+    public Builder loader(ClassLoader newLoader) {
+      this.loader = newLoader;
+      return this;
+    }
+
+    public Builder impl(String className, Class<?>... types) {
+      // don't do any work if an implementation has been found
+      if (ctor != null) {
+        return this;
+      }
+
+      try {
+        Class<?> targetClass = Class.forName(className, true, loader);
+        impl(targetClass, types);
+      } catch (NoClassDefFoundError | ClassNotFoundException e) {
+        // cannot load this implementation
+        problems.put(className, e);
+      }
+      return this;
+    }
+
+    public <T> Builder impl(Class<T> targetClass, Class<?>... types) {
+      // don't do any work if an implementation has been found
+      if (ctor != null) {
+        return this;
+      }
+
+      try {
+        ctor = new Ctor<T>(targetClass.getConstructor(types), targetClass);
+      } catch (NoSuchMethodException e) {
+        // not the right implementation
+        problems.put(methodName(targetClass, types), e);
+      }
+      return this;
+    }
+
+    public Builder hiddenImpl(Class<?>... types) {
+      hiddenImpl(baseClass, types);
+      return this;
+    }
+
+    @SuppressWarnings("unchecked")
+    public Builder hiddenImpl(String className, Class<?>... types) {
+      // don't do any work if an implementation has been found
+      if (ctor != null) {
+        return this;
+      }
+
+      try {
+        Class targetClass = Class.forName(className, true, loader);
+        hiddenImpl(targetClass, types);
+      } catch (NoClassDefFoundError | ClassNotFoundException e) {
+        // cannot load this implementation
+        problems.put(className, e);
+      }
+      return this;
+    }
+
+    public <T> Builder hiddenImpl(Class<T> targetClass, Class<?>... types) {
+      // don't do any work if an implementation has been found
+      if (ctor != null) {
+        return this;
+      }
+
+      try {
+        Constructor<T> hidden = targetClass.getDeclaredConstructor(types);
+        AccessController.doPrivileged(new MakeAccessible(hidden));
+        ctor = new Ctor<T>(hidden, targetClass);
+      } catch (SecurityException e) {
+        // unusable
+        problems.put(methodName(targetClass, types), e);
+      } catch (NoSuchMethodException e) {
+        // not the right implementation
+        problems.put(methodName(targetClass, types), e);
+      }
+      return this;
+    }
+
+    @SuppressWarnings("unchecked")
+    public <C> Ctor<C> buildChecked() throws NoSuchMethodException {
+      if (ctor != null) {
+        return ctor;
+      }
+      throw buildCheckedException(baseClass, problems);
+    }
+
+    @SuppressWarnings("unchecked")
+    public <C> Ctor<C> build() {
+      if (ctor != null) {
+        return ctor;
+      }
+      throw buildRuntimeException(baseClass, problems);
+    }
+  }
+
+  private static class MakeAccessible implements PrivilegedAction<Void> {
+    private Constructor<?> hidden;
+
+    MakeAccessible(Constructor<?> hidden) {
+      this.hidden = hidden;
+    }
+
+    @Override
+    public Void run() {
+      hidden.setAccessible(true);
+      return null;
+    }
+  }
+
+  private static NoSuchMethodException buildCheckedException(
+      Class<?> baseClass, Map<String, Throwable> problems) {
+    NoSuchMethodException exc =
+        new NoSuchMethodException(
+            "Cannot find constructor for " + baseClass + "\n" + formatProblems(problems));
+    problems.values().forEach(exc::addSuppressed);
+    return exc;
+  }
+
+  private static RuntimeException buildRuntimeException(
+      Class<?> baseClass, Map<String, Throwable> problems) {
+    RuntimeException exc =
+        new RuntimeException(
+            "Cannot find constructor for " + baseClass + "\n" + formatProblems(problems));
+    problems.values().forEach(exc::addSuppressed);
+    return exc;
+  }
+
+  private static String formatProblems(Map<String, Throwable> problems) {
+    StringBuilder sb = new StringBuilder();
+    boolean first = true;
+    for (Map.Entry<String, Throwable> problem : problems.entrySet()) {
+      if (first) {
+        first = false;
+      } else {
+        sb.append("\n");
+      }
+      sb.append("\tMissing ")
+          .append(problem.getKey())
+          .append(" [")
+          .append(problem.getValue().getClass().getName())
+          .append(": ")
+          .append(problem.getValue().getMessage())
+          .append("]");
+    }
+    return sb.toString();
+  }
+
+  private static String methodName(Class<?> targetClass, Class<?>... types) {
+    StringBuilder sb = new StringBuilder();
+    sb.append(targetClass.getName()).append("(");
+    boolean first = true;
+    for (Class<?> type : types) {
+      if (first) {
+        first = false;
+      } else {
+        sb.append(",");
+      }
+      sb.append(type.getName());
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+}
diff --git a/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynFields.java b/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynFields.java
new file mode 100644
index 000000000..39c83b162
--- /dev/null
+++ b/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynFields.java
@@ -0,0 +1,430 @@
+/*
+ * 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.kyuubi.reflection;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+/** Copied from iceberg-common */
+public class DynFields {
+
+  private DynFields() {}
+
+  /**
+   * Convenience wrapper class around {@link Field}.
+   *
+   * <p>Allows callers to invoke the wrapped method with all Exceptions wrapped by RuntimeException,
+   * or with a single Exception catch block.
+   */
+  public static class UnboundField<T> {
+    private final Field field;
+    private final String name;
+
+    private UnboundField(Field field, String name) {
+      this.field = field;
+      this.name = name;
+    }
+
+    @SuppressWarnings("unchecked")
+    public T get(Object target) {
+      try {
+        return (T) field.get(target);
+      } catch (IllegalAccessException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    public void set(Object target, T value) {
+      try {
+        field.set(target, value);
+      } catch (IllegalAccessException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    @Override
+    public String toString() {
+      return new ToStringBuilder(this)
+          .append("class", field.getDeclaringClass().toString())
+          .append("name", name)
+          .append("type", field.getType())
+          .toString();
+    }
+
+    /**
+     * Returns this method as a BoundMethod for the given receiver.
+     *
+     * @param target an Object on which to get or set this field
+     * @return a {@link BoundField} for this field and the target
+     * @throws IllegalStateException if the method is static
+     * @throws IllegalArgumentException if the receiver's class is incompatible
+     */
+    public BoundField<T> bind(Object target) {
+      if (isStatic() && this != AlwaysNull.INSTANCE) {
+        throw new IllegalStateException("Cannot bind static field " + name);
+      }
+      if (!field.getDeclaringClass().isAssignableFrom(target.getClass())) {
+        throw new IllegalArgumentException(
+            "Cannot bind field " + name + " to instance of " + target.getClass());
+      }
+
+      return new BoundField<>(this, target);
+    }
+
+    /**
+     * Returns this field as a StaticField.
+     *
+     * @return a {@link StaticField} for this field
+     * @throws IllegalStateException if the method is not static
+     */
+    public StaticField<T> asStatic() {
+      if (!isStatic()) {
+        throw new IllegalStateException("Field " + name + " is not static");
+      }
+      return new StaticField<>(this);
+    }
+
+    /** Returns whether the field is a static field. */
+    public boolean isStatic() {
+      return Modifier.isStatic(field.getModifiers());
+    }
+
+    /** Returns whether the field is always null. */
+    public boolean isAlwaysNull() {
+      return this == AlwaysNull.INSTANCE;
+    }
+  }
+
+  private static class AlwaysNull extends UnboundField<Void> {
+    private static final AlwaysNull INSTANCE = new AlwaysNull();
+
+    private AlwaysNull() {
+      super(null, "AlwaysNull");
+    }
+
+    @Override
+    public Void get(Object target) {
+      return null;
+    }
+
+    @Override
+    public void set(Object target, Void value) {}
+
+    @Override
+    public String toString() {
+      return "Field(AlwaysNull)";
+    }
+
+    @Override
+    public boolean isStatic() {
+      return true;
+    }
+
+    @Override
+    public boolean isAlwaysNull() {
+      return true;
+    }
+  }
+
+  public static class StaticField<T> {
+    private final UnboundField<T> field;
+
+    private StaticField(UnboundField<T> field) {
+      this.field = field;
+    }
+
+    public T get() {
+      return field.get(null);
+    }
+
+    public void set(T value) {
+      field.set(null, value);
+    }
+  }
+
+  public static class BoundField<T> {
+    private final UnboundField<T> field;
+    private final Object target;
+
+    private BoundField(UnboundField<T> field, Object target) {
+      this.field = field;
+      this.target = target;
+    }
+
+    public T get() {
+      return field.get(target);
+    }
+
+    public void set(T value) {
+      field.set(target, value);
+    }
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+    private ClassLoader loader = Thread.currentThread().getContextClassLoader();
+    private UnboundField<?> field = null;
+    private final Set<String> candidates = new HashSet<>();
+    private boolean defaultAlwaysNull = false;
+
+    private Builder() {}
+
+    /**
+     * Set the {@link ClassLoader} used to lookup classes by name.
+     *
+     * <p>If not set, the current thread's ClassLoader is used.
+     *
+     * @param newLoader a ClassLoader
+     * @return this Builder for method chaining
+     */
+    public Builder loader(ClassLoader newLoader) {
+      this.loader = newLoader;
+      return this;
+    }
+
+    /**
+     * Instructs this builder to return AlwaysNull if no implementation is found.
+     *
+     * @return this Builder for method chaining
+     */
+    public Builder defaultAlwaysNull() {
+      this.defaultAlwaysNull = true;
+      return this;
+    }
+
+    /**
+     * Checks for an implementation, first finding the class by name.
+     *
+     * @param className name of a class
+     * @param fieldName name of the field
+     * @return this Builder for method chaining
+     * @see Class#forName(String)
+     * @see Class#getField(String)
+     */
+    public Builder impl(String className, String fieldName) {
+      // don't do any work if an implementation has been found
+      if (field != null) {
+        return this;
+      }
+
+      try {
+        Class<?> targetClass = Class.forName(className, true, loader);
+        impl(targetClass, fieldName);
+      } catch (ClassNotFoundException e) {
+        // not the right implementation
+        candidates.add(className + "." + fieldName);
+      }
+      return this;
+    }
+
+    /**
+     * Checks for an implementation.
+     *
+     * @param targetClass a class instance
+     * @param fieldName name of a field (different from constructor)
+     * @return this Builder for method chaining
+     * @see Class#forName(String)
+     * @see Class#getField(String)
+     */
+    public Builder impl(Class<?> targetClass, String fieldName) {
+      // don't do any work if an implementation has been found
+      if (field != null || targetClass == null) {
+        return this;
+      }
+
+      try {
+        this.field = new UnboundField<>(targetClass.getField(fieldName), fieldName);
+      } catch (NoSuchFieldException e) {
+        // not the right implementation
+        candidates.add(targetClass.getName() + "." + fieldName);
+      }
+      return this;
+    }
+
+    /**
+     * Checks for a hidden implementation, first finding the class by name.
+     *
+     * @param className name of a class
+     * @param fieldName name of a field (different from constructor)
+     * @return this Builder for method chaining
+     * @see Class#forName(String)
+     * @see Class#getField(String)
+     */
+    public Builder hiddenImpl(String className, String fieldName) {
+      // don't do any work if an implementation has been found
+      if (field != null) {
+        return this;
+      }
+
+      try {
+        Class<?> targetClass = Class.forName(className, true, loader);
+        hiddenImpl(targetClass, fieldName);
+      } catch (ClassNotFoundException e) {
+        // not the right implementation
+        candidates.add(className + "." + fieldName);
+      }
+      return this;
+    }
+
+    /**
+     * Checks for a hidden implementation.
+     *
+     * @param targetClass a class instance
+     * @param fieldName name of a field (different from constructor)
+     * @return this Builder for method chaining
+     * @see Class#forName(String)
+     * @see Class#getField(String)
+     */
+    public Builder hiddenImpl(Class<?> targetClass, String fieldName) {
+      // don't do any work if an implementation has been found
+      if (field != null || targetClass == null) {
+        return this;
+      }
+
+      try {
+        Field hidden = targetClass.getDeclaredField(fieldName);
+        AccessController.doPrivileged(new MakeFieldAccessible(hidden));
+        this.field = new UnboundField(hidden, fieldName);
+      } catch (SecurityException | NoSuchFieldException e) {
+        // unusable
+        candidates.add(targetClass.getName() + "." + fieldName);
+      }
+      return this;
+    }
+
+    /**
+     * Returns the first valid implementation as a UnboundField or throws a NoSuchFieldException if
+     * there is none.
+     *
+     * @param <T> Java class stored in the field
+     * @return a {@link UnboundField} with a valid implementation
+     * @throws NoSuchFieldException if no implementation was found
+     */
+    @SuppressWarnings("unchecked")
+    public <T> UnboundField<T> buildChecked() throws NoSuchFieldException {
+      if (field != null) {
+        return (UnboundField<T>) field;
+      } else if (defaultAlwaysNull) {
+        return (UnboundField<T>) AlwaysNull.INSTANCE;
+      } else {
+        throw new NoSuchFieldException(
+            "Cannot find field from candidates: " + String.join(", ", candidates));
+      }
+    }
+
+    /**
+     * Returns the first valid implementation as a BoundMethod or throws a NoSuchMethodException if
+     * there is none.
+     *
+     * @param target an Object on which to get and set the field
+     * @param <T> Java class stored in the field
+     * @return a {@link BoundField} with a valid implementation and target
+     * @throws IllegalStateException if the method is static
+     * @throws IllegalArgumentException if the receiver's class is incompatible
+     * @throws NoSuchFieldException if no implementation was found
+     */
+    public <T> BoundField<T> buildChecked(Object target) throws NoSuchFieldException {
+      return this.<T>buildChecked().bind(target);
+    }
+
+    /**
+     * Returns the first valid implementation as a UnboundField or throws a NoSuchFieldException if
+     * there is none.
+     *
+     * @param <T> Java class stored in the field
+     * @return a {@link UnboundField} with a valid implementation
+     * @throws RuntimeException if no implementation was found
+     */
+    @SuppressWarnings("unchecked")
+    public <T> UnboundField<T> build() {
+      if (field != null) {
+        return (UnboundField<T>) field;
+      } else if (defaultAlwaysNull) {
+        return (UnboundField<T>) AlwaysNull.INSTANCE;
+      } else {
+        throw new RuntimeException(
+            "Cannot find field from candidates: " + String.join(", ", candidates));
+      }
+    }
+
+    /**
+     * Returns the first valid implementation as a BoundMethod or throws a RuntimeException if there
+     * is none.
+     *
+     * @param target an Object on which to get and set the field
+     * @param <T> Java class stored in the field
+     * @return a {@link BoundField} with a valid implementation and target
+     * @throws IllegalStateException if the method is static
+     * @throws IllegalArgumentException if the receiver's class is incompatible
+     * @throws RuntimeException if no implementation was found
+     */
+    public <T> BoundField<T> build(Object target) {
+      return this.<T>build().bind(target);
+    }
+
+    /**
+     * Returns the first valid implementation as a StaticField or throws a NoSuchFieldException if
+     * there is none.
+     *
+     * @param <T> Java class stored in the field
+     * @return a {@link StaticField} with a valid implementation
+     * @throws IllegalStateException if the method is not static
+     * @throws NoSuchFieldException if no implementation was found
+     */
+    public <T> StaticField<T> buildStaticChecked() throws NoSuchFieldException {
+      return this.<T>buildChecked().asStatic();
+    }
+
+    /**
+     * Returns the first valid implementation as a StaticField or throws a RuntimeException if there
+     * is none.
+     *
+     * @param <T> Java class stored in the field
+     * @return a {@link StaticField} with a valid implementation
+     * @throws IllegalStateException if the method is not static
+     * @throws RuntimeException if no implementation was found
+     */
+    public <T> StaticField<T> buildStatic() {
+      return this.<T>build().asStatic();
+    }
+  }
+
+  private static class MakeFieldAccessible implements PrivilegedAction<Void> {
+    private Field hidden;
+
+    MakeFieldAccessible(Field hidden) {
+      this.hidden = hidden;
+    }
+
+    @Override
+    public Void run() {
+      hidden.setAccessible(true);
+      return null;
+    }
+  }
+}
diff --git a/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynMethods.java b/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynMethods.java
new file mode 100644
index 000000000..85264191c
--- /dev/null
+++ b/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynMethods.java
@@ -0,0 +1,529 @@
+/*
+ * 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.kyuubi.reflection;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Arrays;
+
+/** Copied from parquet-common */
+public class DynMethods {
+
+  private DynMethods() {}
+
+  /**
+   * Convenience wrapper class around {@link Method}.
+   *
+   * <p>Allows callers to invoke the wrapped method with all Exceptions wrapped by RuntimeException,
+   * or with a single Exception catch block.
+   */
+  public static class UnboundMethod {
+
+    private final Method method;
+    private final String name;
+    private final int argLength;
+
+    UnboundMethod(Method method, String name) {
+      this.method = method;
+      this.name = name;
+      this.argLength =
+          (method == null || method.isVarArgs()) ? -1 : method.getParameterTypes().length;
+    }
+
+    @SuppressWarnings("unchecked")
+    public <R> R invokeChecked(Object target, Object... args) throws Exception {
+      try {
+        if (argLength < 0) {
+          return (R) method.invoke(target, args);
+        } else {
+          return (R) method.invoke(target, Arrays.copyOfRange(args, 0, argLength));
+        }
+
+      } catch (InvocationTargetException e) {
+        if (e.getCause() instanceof Exception) {
+          throw (Exception) e.getCause();
+        }
+        if (e.getCause() instanceof RuntimeException) {
+          throw (RuntimeException) e.getCause();
+        }
+        throw new RuntimeException(e.getCause());
+      }
+    }
+
+    public <R> R invoke(Object target, Object... args) {
+      try {
+        return this.invokeChecked(target, args);
+      } catch (Exception e) {
+        if (e.getCause() instanceof RuntimeException) {
+          throw (RuntimeException) e.getCause();
+        }
+        throw new RuntimeException(e.getCause());
+      }
+    }
+
+    /**
+     * Returns this method as a BoundMethod for the given receiver.
+     *
+     * @param receiver an Object to receive the method invocation
+     * @return a {@link BoundMethod} for this method and the receiver
+     * @throws IllegalStateException if the method is static
+     * @throws IllegalArgumentException if the receiver's class is incompatible
+     */
+    public BoundMethod bind(Object receiver) {
+      if (isStatic()) {
+        throw new IllegalStateException("Cannot bind static method " + method.toGenericString());
+      }
+      if (!method.getDeclaringClass().isAssignableFrom(receiver.getClass())) {
+        throw new IllegalArgumentException(
+            "Cannot bind" + method.toGenericString() + "to instance of " + receiver.getClass());
+      }
+      return new BoundMethod(this, receiver);
+    }
+
+    /** Returns whether the method is a static method. */
+    public boolean isStatic() {
+      return Modifier.isStatic(method.getModifiers());
+    }
+
+    /** Returns whether the method is a noop. */
+    public boolean isNoop() {
+      return this == NOOP;
+    }
+
+    /**
+     * Returns this method as a StaticMethod.
+     *
+     * @return a {@link StaticMethod} for this method
+     * @throws IllegalStateException if the method is not static
+     */
+    public StaticMethod asStatic() {
+      if (!isStatic()) {
+        throw new IllegalStateException("Method is not static");
+      }
+      return new StaticMethod(this);
+    }
+
+    @Override
+    public String toString() {
+      return "DynMethods.UnboundMethod(name=" + name + " method=" + method.toGenericString() + ")";
+    }
+
+    /** Singleton {@link UnboundMethod}, performs no operation and returns null. */
+    private static final UnboundMethod NOOP =
+        new UnboundMethod(null, "NOOP") {
+          @Override
+          public <R> R invokeChecked(Object target, Object... args) throws Exception {
+            return null;
+          }
+
+          @Override
+          public BoundMethod bind(Object receiver) {
+            return new BoundMethod(this, receiver);
+          }
+
+          @Override
+          public StaticMethod asStatic() {
+            return new StaticMethod(this);
+          }
+
+          @Override
+          public boolean isStatic() {
+            return true;
+          }
+
+          @Override
+          public String toString() {
+            return "DynMethods.UnboundMethod(NOOP)";
+          }
+        };
+  }
+
+  public static class BoundMethod {
+    private final UnboundMethod method;
+    private final Object receiver;
+
+    private BoundMethod(UnboundMethod method, Object receiver) {
+      this.method = method;
+      this.receiver = receiver;
+    }
+
+    public <R> R invokeChecked(Object... args) throws Exception {
+      return method.invokeChecked(receiver, args);
+    }
+
+    public <R> R invoke(Object... args) {
+      return method.invoke(receiver, args);
+    }
+  }
+
+  public static class StaticMethod {
+    private final UnboundMethod method;
+
+    private StaticMethod(UnboundMethod method) {
+      this.method = method;
+    }
+
+    public <R> R invokeChecked(Object... args) throws Exception {
+      return method.invokeChecked(null, args);
+    }
+
+    public <R> R invoke(Object... args) {
+      return method.invoke(null, args);
+    }
+  }
+
+  /**
+   * Constructs a new builder for calling methods dynamically.
+   *
+   * @param methodName name of the method the builder will locate
+   * @return a Builder for finding a method
+   */
+  public static Builder builder(String methodName) {
+    return new Builder(methodName);
+  }
+
+  public static class Builder {
+    private final String name;
+    private ClassLoader loader = Thread.currentThread().getContextClassLoader();
+    private UnboundMethod method = null;
+
+    public Builder(String methodName) {
+      this.name = methodName;
+    }
+
+    /**
+     * Set the {@link ClassLoader} used to lookup classes by name.
+     *
+     * <p>If not set, the current thread's ClassLoader is used.
+     *
+     * @param newLoader a ClassLoader
+     * @return this Builder for method chaining
+     */
+    public Builder loader(ClassLoader newLoader) {
+      this.loader = newLoader;
+      return this;
+    }
+
+    /**
+     * If no implementation has been found, adds a NOOP method.
+     *
+     * <p>Note: calls to impl will not match after this method is called!
+     *
+     * @return this Builder for method chaining
+     */
+    public Builder orNoop() {
+      if (method == null) {
+        this.method = UnboundMethod.NOOP;
+      }
+      return this;
+    }
+
+    /**
+     * Checks for an implementation, first finding the given class by name.
+     *
+     * @param className name of a class
+     * @param methodName name of a method (different from constructor)
+     * @param argClasses argument classes for the method
+     * @return this Builder for method chaining
+     * @see Class#forName(String)
+     * @see Class#getMethod(String, Class[])
+     */
+    public Builder impl(String className, String methodName, Class<?>... argClasses) {
+      // don't do any work if an implementation has been found
+      if (method != null) {
+        return this;
+      }
+
+      try {
+        Class<?> targetClass = Class.forName(className, true, loader);
+        impl(targetClass, methodName, argClasses);
+      } catch (ClassNotFoundException e) {
+        // not the right implementation
+      }
+      return this;
+    }
+
+    /**
+     * Checks for an implementation, first finding the given class by name.
+     *
+     * <p>The name passed to the constructor is the method name used.
+     *
+     * @param className name of a class
+     * @param argClasses argument classes for the method
+     * @return this Builder for method chaining
+     * @see Class#forName(String)
+     * @see Class#getMethod(String, Class[])
+     */
+    public Builder impl(String className, Class<?>... argClasses) {
+      impl(className, name, argClasses);
+      return this;
+    }
+
+    /**
+     * Checks for a method implementation.
+     *
+     * @param targetClass a class instance
+     * @param methodName name of a method (different from constructor)
+     * @param argClasses argument classes for the method
+     * @return this Builder for method chaining
+     * @see Class#forName(String)
+     * @see Class#getMethod(String, Class[])
+     */
+    public Builder impl(Class<?> targetClass, String methodName, Class<?>... argClasses) {
+      // don't do any work if an implementation has been found
+      if (method != null) {
+        return this;
+      }
+
+      try {
+        this.method = new UnboundMethod(targetClass.getMethod(methodName, argClasses), name);
+      } catch (NoSuchMethodException e) {
+        // not the right implementation
+      }
+      return this;
+    }
+
+    /**
+     * Checks for a method implementation.
+     *
+     * <p>The name passed to the constructor is the method name used.
+     *
+     * @param targetClass a class instance
+     * @param argClasses argument classes for the method
+     * @return this Builder for method chaining
+     * @see Class#forName(String)
+     * @see Class#getMethod(String, Class[])
+     */
+    public Builder impl(Class<?> targetClass, Class<?>... argClasses) {
+      impl(targetClass, name, argClasses);
+      return this;
+    }
+
+    public Builder ctorImpl(Class<?> targetClass, Class<?>... argClasses) {
+      // don't do any work if an implementation has been found
+      if (method != null) {
+        return this;
+      }
+
+      try {
+        this.method = new DynConstructors.Builder().impl(targetClass, argClasses).buildChecked();
+      } catch (NoSuchMethodException e) {
+        // not the right implementation
+      }
+      return this;
+    }
+
+    public Builder ctorImpl(String className, Class<?>... argClasses) {
+      // don't do any work if an implementation has been found
+      if (method != null) {
+        return this;
+      }
+
+      try {
+        this.method = new DynConstructors.Builder().impl(className, argClasses).buildChecked();
+      } catch (NoSuchMethodException e) {
+        // not the right implementation
+      }
+      return this;
+    }
+
+    /**
+     * Checks for an implementation, first finding the given class by name.
+     *
+     * @param className name of a class
+     * @param methodName name of a method (different from constructor)
+     * @param argClasses argument classes for the method
+     * @return this Builder for method chaining
+     * @see Class#forName(String)
+     * @see Class#getMethod(String, Class[])
+     */
+    public Builder hiddenImpl(String className, String methodName, Class<?>... argClasses) {
+      // don't do any work if an implementation has been found
+      if (method != null) {
+        return this;
+      }
+
+      try {
+        Class<?> targetClass = Class.forName(className, true, loader);
+        hiddenImpl(targetClass, methodName, argClasses);
+      } catch (ClassNotFoundException e) {
+        // not the right implementation
+      }
+      return this;
+    }
+
+    /**
+     * Checks for an implementation, first finding the given class by name.
+     *
+     * <p>The name passed to the constructor is the method name used.
+     *
+     * @param className name of a class
+     * @param argClasses argument classes for the method
+     * @return this Builder for method chaining
+     * @see Class#forName(String)
+     * @see Class#getMethod(String, Class[])
+     */
+    public Builder hiddenImpl(String className, Class<?>... argClasses) {
+      hiddenImpl(className, name, argClasses);
+      return this;
+    }
+
+    /**
+     * Checks for a method implementation.
+     *
+     * @param targetClass a class instance
+     * @param methodName name of a method (different from constructor)
+     * @param argClasses argument classes for the method
+     * @return this Builder for method chaining
+     * @see Class#forName(String)
+     * @see Class#getMethod(String, Class[])
+     */
+    public Builder hiddenImpl(Class<?> targetClass, String methodName, Class<?>... argClasses) {
+      // don't do any work if an implementation has been found
+      if (method != null) {
+        return this;
+      }
+
+      try {
+        Method hidden = targetClass.getDeclaredMethod(methodName, argClasses);
+        AccessController.doPrivileged(new MakeAccessible(hidden));
+        this.method = new UnboundMethod(hidden, name);
+      } catch (SecurityException | NoSuchMethodException e) {
+        // unusable or not the right implementation
+      }
+      return this;
+    }
+
+    /**
+     * Checks for a method implementation.
+     *
+     * <p>The name passed to the constructor is the method name used.
+     *
+     * @param targetClass a class instance
+     * @param argClasses argument classes for the method
+     * @return this Builder for method chaining
+     * @see Class#forName(String)
+     * @see Class#getMethod(String, Class[])
+     */
+    public Builder hiddenImpl(Class<?> targetClass, Class<?>... argClasses) {
+      hiddenImpl(targetClass, name, argClasses);
+      return this;
+    }
+
+    /**
+     * Returns the first valid implementation as a UnboundMethod or throws a RuntimeError if there
+     * is none.
+     *
+     * @return a {@link UnboundMethod} with a valid implementation
+     * @throws RuntimeException if no implementation was found
+     */
+    public UnboundMethod build() {
+      if (method != null) {
+        return method;
+      } else {
+        throw new RuntimeException("Cannot find method: " + name);
+      }
+    }
+
+    /**
+     * Returns the first valid implementation as a BoundMethod or throws a RuntimeError if there is
+     * none.
+     *
+     * @param receiver an Object to receive the method invocation
+     * @return a {@link BoundMethod} with a valid implementation and receiver
+     * @throws IllegalStateException if the method is static
+     * @throws IllegalArgumentException if the receiver's class is incompatible
+     * @throws RuntimeException if no implementation was found
+     */
+    public BoundMethod build(Object receiver) {
+      return build().bind(receiver);
+    }
+
+    /**
+     * Returns the first valid implementation as a UnboundMethod or throws a NoSuchMethodException
+     * if there is none.
+     *
+     * @return a {@link UnboundMethod} with a valid implementation
+     * @throws NoSuchMethodException if no implementation was found
+     */
+    public UnboundMethod buildChecked() throws NoSuchMethodException {
+      if (method != null) {
+        return method;
+      } else {
+        throw new NoSuchMethodException("Cannot find method: " + name);
+      }
+    }
+
+    /**
+     * Returns the first valid implementation as a BoundMethod or throws a NoSuchMethodException if
+     * there is none.
+     *
+     * @param receiver an Object to receive the method invocation
+     * @return a {@link BoundMethod} with a valid implementation and receiver
+     * @throws IllegalStateException if the method is static
+     * @throws IllegalArgumentException if the receiver's class is incompatible
+     * @throws NoSuchMethodException if no implementation was found
+     */
+    public BoundMethod buildChecked(Object receiver) throws NoSuchMethodException {
+      return buildChecked().bind(receiver);
+    }
+
+    /**
+     * Returns the first valid implementation as a StaticMethod or throws a NoSuchMethodException if
+     * there is none.
+     *
+     * @return a {@link StaticMethod} with a valid implementation
+     * @throws IllegalStateException if the method is not static
+     * @throws NoSuchMethodException if no implementation was found
+     */
+    public StaticMethod buildStaticChecked() throws NoSuchMethodException {
+      return buildChecked().asStatic();
+    }
+
+    /**
+     * Returns the first valid implementation as a StaticMethod or throws a RuntimeException if
+     * there is none.
+     *
+     * @return a {@link StaticMethod} with a valid implementation
+     * @throws IllegalStateException if the method is not static
+     * @throws RuntimeException if no implementation was found
+     */
+    public StaticMethod buildStatic() {
+      return build().asStatic();
+    }
+  }
+
+  private static class MakeAccessible implements PrivilegedAction<Void> {
+    private Method hidden;
+
+    MakeAccessible(Method hidden) {
+      this.hidden = hidden;
+    }
+
+    @Override
+    public Void run() {
+      hidden.setAccessible(true);
+      return null;
+    }
+  }
+}
diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/engine/SemanticVersion.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/engine/SemanticVersion.scala
index d22c993d2..7eedf0bb8 100644
--- a/kyuubi-common/src/main/scala/org/apache/kyuubi/engine/SemanticVersion.scala
+++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/engine/SemanticVersion.scala
@@ -56,6 +56,8 @@ case class SemanticVersion(majorVersion: Int, minorVersion: Int) {
     val targetMinor = targetVersion.minorVersion
     callback(targetMajor, targetMinor, this.majorVersion, this.minorVersion)
   }
+
+  override def toString: String = s"$majorVersion.$minorVersion"
 }
 
 object SemanticVersion {