diff --git a/docs/configuration/settings.md b/docs/configuration/settings.md
index 435416d0bdf..580ad170055 100644
--- a/docs/configuration/settings.md
+++ b/docs/configuration/settings.md
@@ -150,7 +150,7 @@ You can configure the Kyuubi properties in `$KYUUBI_HOME/conf/kyuubi-defaults.co
| kyuubi.engine.jdbc.connection.password | <undefined> | The password is used for connecting to server | string | 1.6.0 |
| kyuubi.engine.jdbc.connection.propagateCredential | false | Whether to use the session's user and password to connect to database | boolean | 1.8.0 |
| kyuubi.engine.jdbc.connection.properties || The additional properties are used for connecting to server | seq | 1.6.0 |
-| kyuubi.engine.jdbc.connection.provider | <undefined> | The connection provider is used for getting a connection from the server | string | 1.6.0 |
+| kyuubi.engine.jdbc.connection.provider | <undefined> | A JDBC connection provider plugin for the Kyuubi Server to establish a connection to the JDBC URL. The configuration value should be a subclass of `org.apache.kyuubi.engine.jdbc.connection.JdbcConnectionProvider`. Kyuubi provides the following built-in implementations:
doris: For establishing Doris connections. mysql: For establishing MySQL connections. phoenix: For establishing Phoenix connections. postgresql: For establishing PostgreSQL connections. | string | 1.6.0 |
| kyuubi.engine.jdbc.connection.url | <undefined> | The server url that engine will connect to | string | 1.6.0 |
| kyuubi.engine.jdbc.connection.user | <undefined> | The user is used for connecting to server | string | 1.6.0 |
| kyuubi.engine.jdbc.driver.class | <undefined> | The driver class for JDBC engine connection | string | 1.6.0 |
diff --git a/externals/kyuubi-jdbc-engine/pom.xml b/externals/kyuubi-jdbc-engine/pom.xml
index 69870c27870..0ec905f202d 100644
--- a/externals/kyuubi-jdbc-engine/pom.xml
+++ b/externals/kyuubi-jdbc-engine/pom.xml
@@ -58,6 +58,12 @@
test
+
+ com.dimafeng
+ testcontainers-scala-mysql_${scala.binary.version}
+ test
+
+
org.apache.kyuubi
${hive.jdbc.artifact}
diff --git a/externals/kyuubi-jdbc-engine/src/main/resources/META-INF/services/org.apache.kyuubi.engine.jdbc.connection.JdbcConnectionProvider b/externals/kyuubi-jdbc-engine/src/main/resources/META-INF/services/org.apache.kyuubi.engine.jdbc.connection.JdbcConnectionProvider
index db146a42cd6..1a7ac9467c4 100644
--- a/externals/kyuubi-jdbc-engine/src/main/resources/META-INF/services/org.apache.kyuubi.engine.jdbc.connection.JdbcConnectionProvider
+++ b/externals/kyuubi-jdbc-engine/src/main/resources/META-INF/services/org.apache.kyuubi.engine.jdbc.connection.JdbcConnectionProvider
@@ -16,5 +16,6 @@
#
org.apache.kyuubi.engine.jdbc.doris.DorisConnectionProvider
+org.apache.kyuubi.engine.jdbc.mysql.MySQLConnectionProvider
org.apache.kyuubi.engine.jdbc.phoenix.PhoenixConnectionProvider
org.apache.kyuubi.engine.jdbc.postgresql.PostgreSQLConnectionProvider
diff --git a/externals/kyuubi-jdbc-engine/src/main/resources/META-INF/services/org.apache.kyuubi.engine.jdbc.dialect.JdbcDialect b/externals/kyuubi-jdbc-engine/src/main/resources/META-INF/services/org.apache.kyuubi.engine.jdbc.dialect.JdbcDialect
index 1529f859824..9f97ab5d728 100644
--- a/externals/kyuubi-jdbc-engine/src/main/resources/META-INF/services/org.apache.kyuubi.engine.jdbc.dialect.JdbcDialect
+++ b/externals/kyuubi-jdbc-engine/src/main/resources/META-INF/services/org.apache.kyuubi.engine.jdbc.dialect.JdbcDialect
@@ -16,5 +16,6 @@
#
org.apache.kyuubi.engine.jdbc.dialect.DorisDialect
+org.apache.kyuubi.engine.jdbc.dialect.MySQLDialect
org.apache.kyuubi.engine.jdbc.dialect.PhoenixDialect
org.apache.kyuubi.engine.jdbc.dialect.PostgreSQLDialect
diff --git a/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/connection/ConnectionProvider.scala b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/connection/ConnectionProvider.scala
index cb6e4b6c551..f8ec72dca93 100644
--- a/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/connection/ConnectionProvider.scala
+++ b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/connection/ConnectionProvider.scala
@@ -27,7 +27,7 @@ import org.apache.kyuubi.util.reflect.ReflectUtils._
abstract class AbstractConnectionProvider extends Logging {
protected val providers = loadProviders()
- def getProviderClass(kyuubiConf: KyuubiConf): String = {
+ def getDriverClass(kyuubiConf: KyuubiConf): String = {
val driverClass: Class[_ <: Driver] = Option(
DynClasses.builder().impl(kyuubiConf.get(ENGINE_JDBC_DRIVER_CLASS).get)
.orNull().build[Driver]()).getOrElse {
@@ -38,7 +38,7 @@ abstract class AbstractConnectionProvider extends Logging {
}
def create(kyuubiConf: KyuubiConf): Connection = {
- val filteredProviders = providers.filter(_.canHandle(getProviderClass(kyuubiConf)))
+ val filteredProviders = providers.filter(_.canHandle(getDriverClass(kyuubiConf)))
if (filteredProviders.isEmpty) {
throw new IllegalArgumentException(
"Empty list of JDBC connection providers for the specified driver and options")
@@ -57,10 +57,9 @@ abstract class AbstractConnectionProvider extends Logging {
case None =>
// TODO
if (filteredProviders.size != 1) {
- throw new IllegalArgumentException(
- "JDBC connection initiated but more than one connection provider was found. Use " +
- s"${ENGINE_JDBC_CONNECTION_PROVIDER.key} option to select a specific provider. " +
- s"Found active providers ${filteredProviders.mkString("[", ", ", "]")}")
+ warn("JDBC connection initiated but more than one connection provider was found. Use " +
+ s"${ENGINE_JDBC_CONNECTION_PROVIDER.key} option to select a specific provider. " +
+ s"Found active providers ${filteredProviders.mkString("[", ", ", "]")}")
}
filteredProviders.head
}
diff --git a/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/dialect/MySQLDialect.scala b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/dialect/MySQLDialect.scala
new file mode 100644
index 00000000000..1cafcd9a9a9
--- /dev/null
+++ b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/dialect/MySQLDialect.scala
@@ -0,0 +1,136 @@
+/*
+ * 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.engine.jdbc.dialect
+import java.sql.{Connection, ResultSet, Statement}
+import java.util
+
+import scala.collection.JavaConverters._
+import scala.collection.mutable.ArrayBuffer
+
+import org.apache.commons.lang3.StringUtils
+
+import org.apache.kyuubi.engine.jdbc.mysql.{MySQLRowSetHelper, MySQLSchemaHelper}
+import org.apache.kyuubi.engine.jdbc.schema.{RowSetHelper, SchemaHelper}
+import org.apache.kyuubi.operation.meta.ResultSetSchemaConstant._
+import org.apache.kyuubi.session.Session
+
+class MySQLDialect extends JdbcDialect {
+ override def createStatement(connection: Connection, fetchSize: Int): Statement = {
+ val statement =
+ connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)
+ statement.setFetchSize(Integer.MIN_VALUE)
+ statement
+ }
+
+ override def getTablesQuery(
+ catalog: String,
+ schema: String,
+ tableName: String,
+ tableTypes: util.List[String]): String = {
+ val tTypes =
+ if (tableTypes == null || tableTypes.isEmpty) {
+ Set("BASE TABLE", "SYSTEM VIEW", "VIEW")
+ } else {
+ tableTypes.asScala.toSet
+ }
+ val query = new StringBuilder(
+ s"""
+ |SELECT TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE, ENGINE,
+ |TABLE_ROWS, AVG_ROW_LENGTH, DATA_LENGTH,
+ |CREATE_TIME, UPDATE_TIME, TABLE_COLLATION, TABLE_COMMENT
+ |FROM INFORMATION_SCHEMA.TABLES
+ |""".stripMargin)
+
+ val filters = ArrayBuffer[String]()
+ if (StringUtils.isNotBlank(catalog)) {
+ filters += s"$TABLE_CATALOG = '$catalog'"
+ }
+
+ if (StringUtils.isNotBlank(schema)) {
+ filters += s"$TABLE_SCHEMA LIKE '$schema'"
+ }
+
+ if (StringUtils.isNotBlank(tableName)) {
+ filters += s"$TABLE_NAME LIKE '$tableName'"
+ }
+
+ if (tTypes.nonEmpty) {
+ filters += s"(${
+ tTypes.map { tableType => s"$TABLE_TYPE = '$tableType'" }
+ .mkString(" OR ")
+ })"
+ }
+
+ if (filters.nonEmpty) {
+ query.append(" WHERE ")
+ query.append(filters.mkString(" AND "))
+ }
+
+ query.toString()
+ }
+
+ override def getColumnsQuery(
+ session: Session,
+ catalogName: String,
+ schemaName: String,
+ tableName: String,
+ columnName: String): String = {
+ val query = new StringBuilder(
+ """
+ |SELECT
+ |`TABLE_CATALOG`,`TABLE_SCHEMA`,`TABLE_NAME`, `COLUMN_NAME`,`ORDINAL_POSITION`,
+ |`COLUMN_DEFAULT`,`IS_NULLABLE`,`DATA_TYPE`,`CHARACTER_MAXIMUM_LENGTH`,
+ |`CHARACTER_OCTET_LENGTH`,`NUMERIC_PRECISION`,`NUMERIC_SCALE`,`DATETIME_PRECISION`,
+ |`CHARACTER_SET_NAME`,`COLLATION_NAME`,`COLUMN_TYPE`,`COLUMN_KEY`,`EXTRA`,`PRIVILEGES`,
+ |`COLUMN_COMMENT`,`GENERATION_EXPRESSION`
+ |FROM information_schema.columns
+ |""".stripMargin)
+
+ val filters = ArrayBuffer[String]()
+ if (StringUtils.isNotEmpty(catalogName)) {
+ filters += s"$TABLE_CATALOG = '$catalogName'"
+ }
+ if (StringUtils.isNotEmpty(schemaName)) {
+ filters += s"$TABLE_SCHEMA LIKE '$schemaName'"
+ }
+ if (StringUtils.isNotEmpty(tableName)) {
+ filters += s"$TABLE_NAME LIKE '$tableName'"
+ }
+ if (StringUtils.isNotEmpty(columnName)) {
+ filters += s"$COLUMN_NAME LIKE '$columnName'"
+ }
+
+ if (filters.nonEmpty) {
+ query.append(" WHERE ")
+ query.append(filters.mkString(" AND "))
+ }
+
+ query.toString()
+ }
+
+ override def getRowSetHelper(): RowSetHelper = {
+ new MySQLRowSetHelper
+ }
+
+ override def getSchemaHelper(): SchemaHelper = {
+ new MySQLSchemaHelper
+ }
+
+ override def name(): String = {
+ "mysql"
+ }
+}
diff --git a/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/mysql/MySQLConnectionProvider.scala b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/mysql/MySQLConnectionProvider.scala
new file mode 100644
index 00000000000..249ea0c31f6
--- /dev/null
+++ b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/mysql/MySQLConnectionProvider.scala
@@ -0,0 +1,22 @@
+/*
+ * 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.engine.jdbc.mysql
+
+class MySQLConnectionProvider extends Mysql8ConnectionProvider {
+
+ override val name: String = classOf[MySQLConnectionProvider].getSimpleName
+}
diff --git a/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/mysql/MySQLRowSetHelper.scala b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/mysql/MySQLRowSetHelper.scala
new file mode 100644
index 00000000000..1c85cb009dd
--- /dev/null
+++ b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/mysql/MySQLRowSetHelper.scala
@@ -0,0 +1,69 @@
+/*
+ * 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.engine.jdbc.mysql
+
+import java.sql.Types
+
+import org.apache.hive.service.rpc.thrift.{TColumn, TColumnValue}
+
+import org.apache.kyuubi.engine.jdbc.schema.RowSetHelper
+
+class MySQLRowSetHelper extends RowSetHelper {
+
+ override def toTinyIntTColumn(rows: Seq[Seq[Any]], ordinal: Int): TColumn =
+ toIntegerTColumn(rows, ordinal)
+
+ override def toSmallIntTColumn(rows: Seq[Seq[Any]], ordinal: Int): TColumn =
+ toIntegerTColumn(rows, ordinal)
+
+ override def toTinyIntTColumnValue(row: List[Any], ordinal: Int): TColumnValue =
+ toIntegerTColumnValue(row, ordinal)
+
+ override def toSmallIntTColumnValue(row: List[Any], ordinal: Int): TColumnValue =
+ toIntegerTColumnValue(row, ordinal)
+
+ override protected def toIntegerTColumn(rows: Seq[Seq[Any]], ordinal: Int): TColumn = {
+ val colHead = if (rows.isEmpty) None else rows.head(ordinal)
+ colHead match {
+ case v: Integer => super.toIntegerTColumn(rows, ordinal)
+ case v: java.lang.Long => super.toBigIntTColumn(rows, ordinal)
+ case _ => super.toDefaultTColumn(rows, ordinal, Types.INTEGER)
+ }
+ }
+
+ override protected def toIntegerTColumnValue(row: List[Any], ordinal: Int): TColumnValue = {
+ row(ordinal) match {
+ case v: Integer => super.toIntegerTColumnValue(row, ordinal)
+ case v: java.lang.Long => super.toBigIntTColumnValue(row, ordinal)
+ case _ => super.toDefaultTColumnValue(row, ordinal, Types.INTEGER)
+ }
+ }
+
+ override protected def toBigIntTColumn(rows: Seq[Seq[Any]], ordinal: Int): TColumn = {
+ val colHead = if (rows.isEmpty) None else rows.head(ordinal)
+ colHead match {
+ case v: java.lang.Long => super.toBigIntTColumn(rows, ordinal)
+ case _ => super.toDefaultTColumn(rows, ordinal, Types.BIGINT)
+ }
+ }
+
+ override protected def toBigIntTColumnValue(row: List[Any], ordinal: Int): TColumnValue =
+ row(ordinal) match {
+ case v: java.lang.Long => super.toBigIntTColumnValue(row, ordinal)
+ case _ => super.toDefaultTColumnValue(row, ordinal, Types.BIGINT)
+ }
+}
diff --git a/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/mysql/MySQLSchemaHelper.scala b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/mysql/MySQLSchemaHelper.scala
new file mode 100644
index 00000000000..b7351b26b3e
--- /dev/null
+++ b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/mysql/MySQLSchemaHelper.scala
@@ -0,0 +1,21 @@
+/*
+ * 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.engine.jdbc.mysql
+
+import org.apache.kyuubi.engine.jdbc.schema.SchemaHelper
+
+class MySQLSchemaHelper extends SchemaHelper {}
diff --git a/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/schema/RowSetHelper.scala b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/schema/RowSetHelper.scala
index 74b4cec108d..714b3bb7e76 100644
--- a/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/schema/RowSetHelper.scala
+++ b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/schema/RowSetHelper.scala
@@ -49,7 +49,7 @@ abstract class RowSetHelper {
val columnSize = row.size
var j = 0
while (j < columnSize) {
- val columnValue = toTColumnValue(j, row, columns)
+ val columnValue = toTColumnValue(j, row, columns(i).sqlType)
tRow.addToColVals(columnValue)
j += 1
}
@@ -110,8 +110,8 @@ abstract class RowSetHelper {
}
}
- protected def toTColumnValue(ordinal: Int, row: List[Any], types: List[Column]): TColumnValue = {
- types(ordinal).sqlType match {
+ protected def toTColumnValue(ordinal: Int, row: List[Any], sqlType: Int): TColumnValue = {
+ sqlType match {
case Types.BIT =>
toBitTColumnValue(row, ordinal)
@@ -140,7 +140,7 @@ abstract class RowSetHelper {
toVarcharTColumnValue(row, ordinal)
case _ =>
- toDefaultTColumnValue(row, ordinal, types)
+ toDefaultTColumnValue(row, ordinal, sqlType)
}
}
@@ -299,11 +299,11 @@ abstract class RowSetHelper {
protected def toDefaultTColumnValue(
row: List[Any],
ordinal: Int,
- types: List[Column]): TColumnValue = {
+ sqlType: Int): TColumnValue = {
val tStrValue = new TStringValue
if (row(ordinal) != null) {
tStrValue.setValue(
- toHiveString(row(ordinal), types(ordinal).sqlType))
+ toHiveString(row(ordinal), sqlType))
}
TColumnValue.stringVal(tStrValue)
}
@@ -316,6 +316,8 @@ abstract class RowSetHelper {
formatLocalDateTime(dateTime)
case (decimal: java.math.BigDecimal, Types.DECIMAL) =>
decimal.toPlainString
+ case (bigint: java.math.BigInteger, Types.BIGINT) =>
+ bigint.toString()
case (other, _) =>
other.toString
}
diff --git a/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/mysql/MySQLOperationSuite.scala b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/mysql/MySQLOperationSuite.scala
new file mode 100644
index 00000000000..ffd7c0a0fe8
--- /dev/null
+++ b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/mysql/MySQLOperationSuite.scala
@@ -0,0 +1,253 @@
+/*
+ * 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.engine.jdbc.mysql
+
+import java.sql.ResultSet
+
+import scala.collection.mutable.ArrayBuffer
+
+import org.apache.kyuubi.operation.HiveJDBCTestHelper
+import org.apache.kyuubi.operation.meta.ResultSetSchemaConstant._
+
+abstract class MySQLOperationSuite extends WithMySQLEngine with HiveJDBCTestHelper {
+ test("mysql - get tables") {
+ case class Table(catalog: String, schema: String, tableName: String, tableType: String)
+
+ withJdbcStatement() { statement =>
+ val meta = statement.getConnection.getMetaData
+ val resultBuffer = ArrayBuffer[Table]()
+
+ var tables = meta.getTables(null, null, null, null)
+ while (tables.next()) {
+ resultBuffer +=
+ Table(
+ tables.getString(TABLE_CATALOG),
+ tables.getString(TABLE_SCHEMA),
+ tables.getString(TABLE_NAME),
+ tables.getString(TABLE_TYPE))
+ }
+ assert(resultBuffer.contains(Table("def", "information_schema", "TABLES", "SYSTEM VIEW")))
+ assert(resultBuffer.contains(Table("def", "information_schema", "VIEWS", "SYSTEM VIEW")))
+ resultBuffer.clear()
+
+ statement.execute("create database if not exists db1")
+ statement.execute("create table db1.test1(id bigint)" +
+ "ENGINE=InnoDB DEFAULT CHARSET=utf8;")
+ statement.execute("create table db1.test2(id bigint)" +
+ "ENGINE=InnoDB DEFAULT CHARSET=utf8;")
+
+ statement.execute("create database if not exists db2")
+ statement.execute("create table db2.test1(id bigint)" +
+ "ENGINE=InnoDB DEFAULT CHARSET=utf8;")
+ statement.execute("create table db2.test2(id bigint)" +
+ "ENGINE=InnoDB DEFAULT CHARSET=utf8;")
+
+ statement.execute("create view db1.view1 (k1) as select id from db1.test1")
+
+ tables = meta.getTables(null, "db1", "test1", Array("BASE TABLE"))
+ while (tables.next()) {
+ val table = Table(
+ tables.getString(TABLE_CATALOG),
+ tables.getString(TABLE_SCHEMA),
+ tables.getString(TABLE_NAME),
+ tables.getString(TABLE_TYPE))
+ assert(table == Table("def", "db1", "test1", "BASE TABLE"))
+ }
+
+ tables = meta.getTables("def", "db1", null, null)
+ while (tables.next()) {
+ resultBuffer += Table(
+ tables.getString(TABLE_CATALOG),
+ tables.getString(TABLE_SCHEMA),
+ tables.getString(TABLE_NAME),
+ tables.getString(TABLE_TYPE))
+ }
+ assert(resultBuffer.contains(Table("def", "db1", "test2", "BASE TABLE")))
+ resultBuffer.clear()
+
+ tables = meta.getTables(null, null, "test1", null)
+ while (tables.next()) {
+ resultBuffer += Table(
+ tables.getString(TABLE_CATALOG),
+ tables.getString(TABLE_SCHEMA),
+ tables.getString(TABLE_NAME),
+ tables.getString(TABLE_TYPE))
+ }
+ assert(resultBuffer.contains(Table("def", "db1", "test1", "BASE TABLE")))
+ assert(resultBuffer.contains(Table("def", "db2", "test1", "BASE TABLE")))
+ resultBuffer.clear()
+
+ tables = meta.getTables(null, "db%", "test1", null)
+ while (tables.next()) {
+ resultBuffer += Table(
+ tables.getString(TABLE_CATALOG),
+ tables.getString(TABLE_SCHEMA),
+ tables.getString(TABLE_NAME),
+ tables.getString(TABLE_TYPE))
+ }
+ assert(resultBuffer.contains(Table("def", "db1", "test1", "BASE TABLE")))
+ assert(resultBuffer.contains(Table("def", "db2", "test1", "BASE TABLE")))
+ resultBuffer.clear()
+
+ tables = meta.getTables(null, "db2", "test%", null)
+ while (tables.next()) {
+ resultBuffer += Table(
+ tables.getString(TABLE_CATALOG),
+ tables.getString(TABLE_SCHEMA),
+ tables.getString(TABLE_NAME),
+ tables.getString(TABLE_TYPE))
+ }
+ assert(resultBuffer.contains(Table("def", "db2", "test1", "BASE TABLE")))
+ assert(resultBuffer.contains(Table("def", "db2", "test2", "BASE TABLE")))
+ resultBuffer.clear()
+
+ tables = meta.getTables(null, "fake_db", "test1", null)
+ assert(!tables.next())
+
+ tables = meta.getTables(null, "db1", null, Array("VIEW"))
+ while (tables.next()) {
+ val table = Table(
+ tables.getString(TABLE_CATALOG),
+ tables.getString(TABLE_SCHEMA),
+ tables.getString(TABLE_NAME),
+ tables.getString(TABLE_TYPE))
+ assert(table == Table("def", "db1", "view1", "VIEW"))
+ }
+
+ tables = meta.getTables(null, null, null, Array("VIEW", "BASE TABLE"))
+ while (tables.next()) {
+ resultBuffer += Table(
+ tables.getString(TABLE_CATALOG),
+ tables.getString(TABLE_SCHEMA),
+ tables.getString(TABLE_NAME),
+ tables.getString(TABLE_TYPE))
+ }
+ assert(resultBuffer.contains(Table("def", "db1", "test1", "BASE TABLE")))
+ assert(resultBuffer.contains(Table("def", "db1", "test2", "BASE TABLE")))
+ assert(resultBuffer.contains(Table("def", "db2", "test1", "BASE TABLE")))
+ assert(resultBuffer.contains(Table("def", "db2", "test2", "BASE TABLE")))
+ assert(resultBuffer.contains(Table("def", "db1", "view1", "VIEW")))
+ resultBuffer.clear()
+
+ statement.execute("drop view db1.view1")
+ statement.execute("drop table db1.test1")
+ statement.execute("drop table db1.test2")
+ statement.execute("drop table db2.test1")
+ statement.execute("drop table db2.test2")
+ statement.execute("drop database db1")
+ statement.execute("drop database db2")
+ }
+ }
+
+ test("mysql - get columns") {
+ case class Column(tableSchema: String, tableName: String, columnName: String)
+
+ def buildColumn(resultSet: ResultSet): Column = {
+ val schema = resultSet.getString(TABLE_SCHEMA)
+ val tableName = resultSet.getString(TABLE_NAME)
+ val columnName = resultSet.getString(COLUMN_NAME)
+ val column = Column(schema, tableName, columnName)
+ column
+ }
+
+ withJdbcStatement() { statement =>
+ val metadata = statement.getConnection.getMetaData
+ statement.execute("create database if not exists db1")
+ statement.execute("create table if not exists db1.test1" +
+ "(id bigint, str1 varchar(255), str2 varchar(255), age int)" +
+ "ENGINE=InnoDB DEFAULT CHARSET=utf8;")
+ statement.execute("create table if not exists db1.test2" +
+ "(id bigint, str1 varchar(255), str2 varchar(255), age int)" +
+ "ENGINE=InnoDB DEFAULT CHARSET=utf8;")
+
+ statement.execute("create database if not exists db2")
+
+ statement.execute("create table if not exists db2.test1" +
+ "(id bigint, str1 varchar(255), str2 varchar(255), age int)" +
+ "ENGINE=InnoDB DEFAULT CHARSET=utf8;")
+
+ val resultBuffer = ArrayBuffer[Column]()
+ val resultSet1 = metadata.getColumns(null, "db1", null, null)
+ while (resultSet1.next()) {
+ val column = buildColumn(resultSet1)
+ resultBuffer += column
+ }
+
+ assert(resultBuffer.contains(Column("db1", "test1", "id")))
+ assert(resultBuffer.contains(Column("db1", "test1", "str1")))
+ assert(resultBuffer.contains(Column("db1", "test1", "str2")))
+ assert(resultBuffer.contains(Column("db1", "test1", "age")))
+
+ assert(resultBuffer.contains(Column("db1", "test2", "id")))
+ assert(resultBuffer.contains(Column("db1", "test2", "str1")))
+ assert(resultBuffer.contains(Column("db1", "test2", "str2")))
+ assert(resultBuffer.contains(Column("db1", "test2", "age")))
+
+ resultBuffer.clear()
+
+ val resultSet2 = metadata.getColumns(null, null, "test1", null)
+ while (resultSet2.next()) {
+ val column = buildColumn(resultSet2)
+ resultBuffer += column
+ }
+
+ assert(resultBuffer.contains(Column("db1", "test1", "id")))
+ assert(resultBuffer.contains(Column("db1", "test1", "str1")))
+ assert(resultBuffer.contains(Column("db1", "test1", "str2")))
+ assert(resultBuffer.contains(Column("db1", "test1", "age")))
+
+ assert(resultBuffer.contains(Column("db2", "test1", "id")))
+ assert(resultBuffer.contains(Column("db2", "test1", "str1")))
+ assert(resultBuffer.contains(Column("db2", "test1", "str2")))
+ assert(resultBuffer.contains(Column("db2", "test1", "age")))
+
+ resultBuffer.clear()
+
+ val resultSet3 = metadata.getColumns(null, null, null, "age")
+ while (resultSet3.next()) {
+ val column = buildColumn(resultSet3)
+ resultBuffer += column
+ }
+
+ assert(resultBuffer.contains(Column("db1", "test1", "age")))
+ assert(resultBuffer.contains(Column("db1", "test2", "age")))
+ assert(resultBuffer.contains(Column("db2", "test1", "age")))
+
+ resultBuffer.clear()
+
+ val resultSet4 = metadata.getColumns(null, "d%1", "t%1", "str%")
+ while (resultSet4.next()) {
+ val column = buildColumn(resultSet4)
+ resultBuffer += column
+ }
+
+ assert(resultBuffer.contains(Column("db1", "test1", "str1")))
+ assert(resultBuffer.contains(Column("db1", "test1", "str2")))
+
+ resultBuffer.clear()
+
+ val resultSet5 = metadata.getColumns(null, "d%1", "t%1", "fake")
+ assert(!resultSet5.next())
+
+ statement.execute("drop table db1.test1")
+ statement.execute("drop table db1.test2")
+ statement.execute("drop database db1")
+ statement.execute("drop table db2.test1")
+ statement.execute("drop database db2")
+ }
+ }
+}
diff --git a/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/mysql/OperationWithEngineSuite.scala b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/mysql/OperationWithEngineSuite.scala
new file mode 100644
index 00000000000..4cf76427d60
--- /dev/null
+++ b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/mysql/OperationWithEngineSuite.scala
@@ -0,0 +1,79 @@
+/*
+ * 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.engine.jdbc.mysql
+
+import org.apache.hive.service.rpc.thrift._
+
+import org.apache.kyuubi.config.KyuubiConf
+import org.apache.kyuubi.engine.jdbc.connection.ConnectionProvider
+import org.apache.kyuubi.operation.HiveJDBCTestHelper
+
+class OperationWithEngineSuite extends MySQLOperationSuite with HiveJDBCTestHelper {
+
+ override protected def jdbcUrl: String = jdbcConnectionUrl
+
+ test("Test for Jdbc engine getInfo") {
+ val metaData = ConnectionProvider.create(kyuubiConf).getMetaData
+
+ withSessionConf(Map(KyuubiConf.SERVER_INFO_PROVIDER.key -> "ENGINE"))()() {
+ withSessionHandle { (client, handle) =>
+ val req = new TGetInfoReq()
+ req.setSessionHandle(handle)
+ req.setInfoType(TGetInfoType.CLI_DBMS_NAME)
+ assert(client.GetInfo(req).getInfoValue.getStringValue == metaData.getDatabaseProductName)
+
+ val req2 = new TGetInfoReq()
+ req2.setSessionHandle(handle)
+ req2.setInfoType(TGetInfoType.CLI_DBMS_VER)
+ assert(
+ client.GetInfo(req2).getInfoValue.getStringValue == metaData.getDatabaseProductVersion)
+
+ val req3 = new TGetInfoReq()
+ req3.setSessionHandle(handle)
+ req3.setInfoType(TGetInfoType.CLI_MAX_COLUMN_NAME_LEN)
+ assert(client.GetInfo(req3).getInfoValue.getLenValue == metaData.getMaxColumnNameLength)
+
+ val req4 = new TGetInfoReq()
+ req4.setSessionHandle(handle)
+ req4.setInfoType(TGetInfoType.CLI_MAX_SCHEMA_NAME_LEN)
+ assert(client.GetInfo(req4).getInfoValue.getLenValue == metaData.getMaxSchemaNameLength)
+
+ val req5 = new TGetInfoReq()
+ req5.setSessionHandle(handle)
+ req5.setInfoType(TGetInfoType.CLI_MAX_TABLE_NAME_LEN)
+ assert(client.GetInfo(req5).getInfoValue.getLenValue == metaData.getMaxTableNameLength)
+ }
+ }
+ }
+
+ test("JDBC ExecuteStatement operation should contain operationLog") {
+ withSessionHandle { (client, handle) =>
+ val tExecuteStatementReq = new TExecuteStatementReq()
+ tExecuteStatementReq.setSessionHandle(handle)
+ tExecuteStatementReq.setStatement("SELECT 1")
+ val tExecuteStatementResp = client.ExecuteStatement(tExecuteStatementReq)
+
+ val tFetchResultsReq = new TFetchResultsReq()
+ tFetchResultsReq.setOperationHandle(tExecuteStatementResp.getOperationHandle)
+ tFetchResultsReq.setFetchType(1)
+ tFetchResultsReq.setMaxRows(1)
+
+ val tFetchResultsResp = client.FetchResults(tFetchResultsReq)
+ assert(tFetchResultsResp.getStatus.getStatusCode === TStatusCode.SUCCESS_STATUS)
+ }
+ }
+}
diff --git a/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/mysql/SessionSuite.scala b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/mysql/SessionSuite.scala
new file mode 100644
index 00000000000..65107603d77
--- /dev/null
+++ b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/mysql/SessionSuite.scala
@@ -0,0 +1,39 @@
+/*
+ * 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.engine.jdbc.mysql
+
+import org.apache.kyuubi.operation.HiveJDBCTestHelper
+
+class SessionSuite extends WithMySQLEngine with HiveJDBCTestHelper {
+
+ test("test session") {
+ withJdbcStatement() { statement =>
+ val resultSet = statement.executeQuery(
+ "select '1' as id")
+ val metadata = resultSet.getMetaData
+ for (i <- 1 to metadata.getColumnCount) {
+ assert(metadata.getColumnName(i) == "id")
+ }
+ while (resultSet.next()) {
+ val id = resultSet.getObject(1)
+ assert(id == "1")
+ }
+ }
+ }
+
+ override protected def jdbcUrl: String = jdbcConnectionUrl
+}
diff --git a/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/mysql/StatementSuite.scala b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/mysql/StatementSuite.scala
new file mode 100644
index 00000000000..56ae737fc80
--- /dev/null
+++ b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/mysql/StatementSuite.scala
@@ -0,0 +1,95 @@
+/*
+ * 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.engine.jdbc.mysql
+
+import java.sql.{Date, Timestamp}
+
+import org.apache.kyuubi.operation.HiveJDBCTestHelper
+
+class StatementSuite extends WithMySQLEngine with HiveJDBCTestHelper {
+
+ test("test select") {
+ withJdbcStatement("test1") { statement =>
+ statement.execute("create database if not exists db1")
+ statement.execute("use db1")
+ statement.execute("create table db1.test1(id bigint, name varchar(255), age int, " +
+ "PRIMARY KEY ( `id` ))" +
+ "ENGINE=InnoDB " +
+ "DEFAULT CHARSET=utf8;")
+ statement.execute("insert into db1.test1 values(1, 'a', 11)")
+
+ val resultSet1 = statement.executeQuery("select * from db1.test1")
+ while (resultSet1.next()) {
+ val id = resultSet1.getObject(1)
+ assert(id == 1)
+ val name = resultSet1.getObject(2)
+ assert(name == "a")
+ val age = resultSet1.getObject(3)
+ assert(age == 11)
+ }
+ }
+ }
+
+ test("test types") {
+ withJdbcStatement("test1") { statement =>
+ statement.execute("create database if not exists db1")
+ statement.execute("use db1")
+ statement.execute("create table db1.type_test(" +
+ "id bigint, " +
+ "tiny_col tinyint, smallint_col smallint, " +
+ "int_col int, bigint_col bigint, " +
+ "decimal_col decimal(27, 9)," +
+ "date_col date, datetime_col datetime, timestamp_col timestamp," +
+ "char_col char, varchar_col varchar(255), " +
+ "boolean_col boolean, " +
+ "double_col double, float_col float," +
+ "PRIMARY KEY ( `id` )) " +
+ "ENGINE=InnoDB " +
+ "DEFAULT CHARSET=utf8")
+ statement.execute("insert into db1.type_test" +
+ "(id, " +
+ "tiny_col, smallint_col, int_col, bigint_col, " +
+ "decimal_col, " +
+ "date_col, datetime_col, timestamp_col," +
+ "char_col, varchar_col, " +
+ "boolean_col, " +
+ "double_col, float_col) " +
+ "VALUES (1, 2, 3, 4, 5, 6.6, '2023-10-23', '2023-10-23 15:31:45', " +
+ "'2023-10-23 15:31:45', 'a', 'Hello', true, 7.7, 8.8)")
+
+ val resultSet1 = statement.executeQuery("select * from db1.type_test")
+ while (resultSet1.next()) {
+ assert(resultSet1.getObject(1) == 1)
+ assert(resultSet1.getObject(2) == 2)
+ assert(resultSet1.getObject(3) == 3)
+ assert(resultSet1.getObject(4) == 4)
+ assert(resultSet1.getObject(5) == 5)
+ assert(resultSet1.getObject(6) == new java.math.BigDecimal("6.600000000"))
+ assert(resultSet1.getObject(7) == Date.valueOf("2023-10-23"))
+ assert(resultSet1.getObject(8) == Timestamp.valueOf("2023-10-23 15:31:45"))
+ assert(resultSet1.getObject(9) == Timestamp.valueOf("2023-10-23 15:31:45"))
+ assert(resultSet1.getObject(10) == "a")
+ assert(resultSet1.getObject(11) == "Hello")
+ assert(resultSet1.getObject(12) == true)
+ assert(resultSet1.getObject(13) == 7.7)
+ assert(resultSet1.getObject(14) == 8.8)
+ }
+ }
+ }
+
+ override protected def jdbcUrl: String = jdbcConnectionUrl
+}
diff --git a/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/mysql/WithMySQLEngine.scala b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/mysql/WithMySQLEngine.scala
new file mode 100644
index 00000000000..bd79e0a27b6
--- /dev/null
+++ b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/mysql/WithMySQLEngine.scala
@@ -0,0 +1,45 @@
+/*
+ * 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.engine.jdbc.mysql
+
+import com.dimafeng.testcontainers.MySQLContainer
+import com.dimafeng.testcontainers.scalatest.TestContainerForAll
+import org.testcontainers.utility.DockerImageName
+
+import org.apache.kyuubi.config.KyuubiConf._
+import org.apache.kyuubi.engine.jdbc.WithJdbcEngine
+
+trait WithMySQLEngine extends WithJdbcEngine with TestContainerForAll {
+
+ private val mysqlDockerImage = "mysql:8.0.32"
+
+ override val containerDef = MySQLContainer.Def(
+ dockerImageName = DockerImageName.parse(mysqlDockerImage),
+ username = "root",
+ password = "kyuubi")
+
+ override def withKyuubiConf: Map[String, String] = withContainers { mysqlContainer =>
+ Map(
+ ENGINE_SHARE_LEVEL.key -> "SERVER",
+ ENGINE_JDBC_CONNECTION_URL.key -> mysqlContainer.jdbcUrl,
+ ENGINE_JDBC_CONNECTION_USER.key -> "root",
+ ENGINE_JDBC_CONNECTION_PASSWORD.key -> "kyuubi",
+ ENGINE_TYPE.key -> "jdbc",
+ ENGINE_JDBC_SHORT_NAME.key -> "mysql",
+ ENGINE_JDBC_DRIVER_CLASS.key -> "com.mysql.cj.jdbc.Driver")
+ }
+}
diff --git a/integration-tests/kyuubi-jdbc-it/pom.xml b/integration-tests/kyuubi-jdbc-it/pom.xml
index 820429cc4a0..8734c853bc9 100644
--- a/integration-tests/kyuubi-jdbc-it/pom.xml
+++ b/integration-tests/kyuubi-jdbc-it/pom.xml
@@ -78,6 +78,18 @@
testcontainers-scala-scalatest_${scala.binary.version}
test
+
+
+ com.dimafeng
+ testcontainers-scala-mysql_${scala.binary.version}
+ test
+
+
+
+ com.mysql
+ mysql-connector-j
+ test
+
diff --git a/integration-tests/kyuubi-jdbc-it/src/test/scala/org/apache/kyuubi/it/jdbc/mysql/OperationWithServerSuite.scala b/integration-tests/kyuubi-jdbc-it/src/test/scala/org/apache/kyuubi/it/jdbc/mysql/OperationWithServerSuite.scala
new file mode 100644
index 00000000000..263de3d1528
--- /dev/null
+++ b/integration-tests/kyuubi-jdbc-it/src/test/scala/org/apache/kyuubi/it/jdbc/mysql/OperationWithServerSuite.scala
@@ -0,0 +1,27 @@
+/*
+ * 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.it.jdbc.mysql
+
+import org.apache.kyuubi.engine.jdbc.mysql.MySQLOperationSuite
+
+class OperationWithServerSuite extends MySQLOperationSuite
+ with WithKyuubiServerAndMySQLContainer {
+
+ override protected def jdbcUrl: String = getJdbcUrl
+
+}
diff --git a/integration-tests/kyuubi-jdbc-it/src/test/scala/org/apache/kyuubi/it/jdbc/mysql/WithKyuubiServerAndMySQLContainer.scala b/integration-tests/kyuubi-jdbc-it/src/test/scala/org/apache/kyuubi/it/jdbc/mysql/WithKyuubiServerAndMySQLContainer.scala
new file mode 100644
index 00000000000..da94df8e799
--- /dev/null
+++ b/integration-tests/kyuubi-jdbc-it/src/test/scala/org/apache/kyuubi/it/jdbc/mysql/WithKyuubiServerAndMySQLContainer.scala
@@ -0,0 +1,59 @@
+/*
+ * 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.it.jdbc.mysql
+
+import java.nio.file.{Files, Path, Paths}
+
+import org.apache.kyuubi.{Utils, WithKyuubiServer}
+import org.apache.kyuubi.config.KyuubiConf
+import org.apache.kyuubi.config.KyuubiConf.{ENGINE_JDBC_EXTRA_CLASSPATH, KYUUBI_ENGINE_ENV_PREFIX, KYUUBI_HOME}
+import org.apache.kyuubi.engine.jdbc.mysql.WithMySQLEngine
+
+trait WithKyuubiServerAndMySQLContainer extends WithKyuubiServer with WithMySQLEngine {
+
+ private val kyuubiHome: String = Utils
+ .getCodeSourceLocation(getClass).split("integration-tests").head
+
+ private val mysqlJdbcConnectorPath: String = {
+ val keyword = "mysql-connector"
+
+ val jarsDir = Paths.get(kyuubiHome)
+ .resolve("integration-tests")
+ .resolve("kyuubi-jdbc-it")
+ .resolve("target")
+
+ Files.list(jarsDir)
+ .filter { p: Path => p.getFileName.toString contains keyword }
+ .findFirst
+ .orElseThrow { () => new IllegalStateException(s"Can not find $keyword in $jarsDir.") }
+ .toAbsolutePath
+ .toString
+ }
+
+ override protected val conf: KyuubiConf = {
+ KyuubiConf()
+ .set(s"$KYUUBI_ENGINE_ENV_PREFIX.$KYUUBI_HOME", kyuubiHome)
+ .set(ENGINE_JDBC_EXTRA_CLASSPATH, mysqlJdbcConnectorPath)
+ }
+
+ override def beforeAll(): Unit = {
+ val configs = withKyuubiConf
+ configs.foreach(config => conf.set(config._1, config._2))
+ super.beforeAll()
+ }
+}
diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala
index 2bc27b20c5f..323fd222c86 100644
--- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala
+++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala
@@ -2829,9 +2829,28 @@ object KyuubiConf {
val ENGINE_JDBC_CONNECTION_PROVIDER: OptionalConfigEntry[String] =
buildConf("kyuubi.engine.jdbc.connection.provider")
- .doc("The connection provider is used for getting a connection from the server")
+ .doc("A JDBC connection provider plugin for the Kyuubi Server " +
+ "to establish a connection to the JDBC URL." +
+ " The configuration value should be a subclass of " +
+ "`org.apache.kyuubi.engine.jdbc.connection.JdbcConnectionProvider`. " +
+ "Kyuubi provides the following built-in implementations: " +
+ "doris: For establishing Doris connections. " +
+ "mysql: For establishing MySQL connections. " +
+ "phoenix: For establishing Phoenix connections. " +
+ "postgresql: For establishing PostgreSQL connections.")
.version("1.6.0")
.stringConf
+ .transform {
+ case "Doris" | "doris" | "DorisConnectionProvider" =>
+ "org.apache.kyuubi.engine.jdbc.doris.DorisConnectionProvider"
+ case "MySQL" | "mysql" | "MySQLConnectionProvider" =>
+ "org.apache.kyuubi.engine.jdbc.mysql.MySQLConnectionProvider"
+ case "Phoenix" | "phoenix" | "PhoenixConnectionProvider" =>
+ "org.apache.kyuubi.engine.jdbc.phoenix.PhoenixConnectionProvider"
+ case "PostgreSQL" | "postgresql" | "PostgreSQLConnectionProvider" =>
+ "org.apache.kyuubi.engine.jdbc.postgresql.PostgreSQLConnectionProvider"
+ case other => other
+ }
.createOptional
val ENGINE_JDBC_SHORT_NAME: OptionalConfigEntry[String] =
diff --git a/pom.xml b/pom.xml
index ace19c289df..b7ba11018e1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -562,6 +562,12 @@
${testcontainers-scala.version}
+
+ com.dimafeng
+ testcontainers-scala-mysql_${scala.binary.version}
+ ${testcontainers-scala.version}
+
+
com.dimafeng
testcontainers-scala-trino_${scala.binary.version}