Skip to content

Commit

Permalink
add jdbc support for Oracle
Browse files Browse the repository at this point in the history
  • Loading branch information
naive-zhang committed Nov 18, 2024
1 parent 27e7254 commit 2f1b5ed
Show file tree
Hide file tree
Showing 16 changed files with 668 additions and 1 deletion.
12 changes: 12 additions & 0 deletions externals/kyuubi-jdbc-engine/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.dimafeng</groupId>
<artifactId>testcontainers-scala-oracle-xe_${scala.binary.version}</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.dimafeng</groupId>
<artifactId>testcontainers-scala-clickhouse_${scala.binary.version}</artifactId>
Expand Down Expand Up @@ -115,6 +121,12 @@
<classifier>http</classifier>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ org.apache.kyuubi.engine.jdbc.clickhouse.ClickHouseConnectionProvider
org.apache.kyuubi.engine.jdbc.doris.DorisConnectionProvider
org.apache.kyuubi.engine.jdbc.impala.ImpalaConnectionProvider
org.apache.kyuubi.engine.jdbc.mysql.MySQLConnectionProvider
org.apache.kyuubi.engine.jdbc.oracle.OracleConnectionProvider
org.apache.kyuubi.engine.jdbc.phoenix.PhoenixConnectionProvider
org.apache.kyuubi.engine.jdbc.postgresql.PostgreSQLConnectionProvider
org.apache.kyuubi.engine.jdbc.starrocks.StarRocksConnectionProvider
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ org.apache.kyuubi.engine.jdbc.dialect.ClickHouseDialect
org.apache.kyuubi.engine.jdbc.dialect.DorisDialect
org.apache.kyuubi.engine.jdbc.dialect.ImpalaDialect
org.apache.kyuubi.engine.jdbc.dialect.MySQLDialect
org.apache.kyuubi.engine.jdbc.dialect.OracleSQLDialect
org.apache.kyuubi.engine.jdbc.dialect.PhoenixDialect
org.apache.kyuubi.engine.jdbc.dialect.PostgreSQLDialect
org.apache.kyuubi.engine.jdbc.dialect.StarRocksDialect
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* 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.KyuubiSQLException
import org.apache.kyuubi.engine.jdbc.oracle.{OracleSchemaHelper, OracleTRowSetGenerator}
import org.apache.kyuubi.engine.jdbc.schema.{JdbcTRowSetGenerator, SchemaHelper}
import org.apache.kyuubi.operation.Operation
import org.apache.kyuubi.operation.meta.ResultSetSchemaConstant._
import org.apache.kyuubi.session.Session

class OracleSQLDialect extends JdbcDialect {

override def createStatement(connection: Connection, fetchSize: Int): Statement = {
val statement =
connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)
if (connection.getAutoCommit) {
statement.setFetchSize(fetchSize)
}
statement
}

override def getTablesQuery(
catalog: String,
schema: String,
tableName: String,
tableTypes: util.List[String]): String = {
val tTypes =
if (tableTypes == null || tableTypes.isEmpty) {
Set()
} else {
tableTypes.asScala.toSet
}
val query = new StringBuilder(
s"""SELECT OWNER AS TABLE_SCHEMA,
| TABLE_NAME,
| TABLE_TYPE AS TABLE_TYPE
|FROM ALL_CATALOG
|""".stripMargin)

val filters = ArrayBuffer[String]()
if (StringUtils.isNotBlank(schema)) {
filters += s"OWNER 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 getTableTypesOperation(session: Session): Operation = {
throw KyuubiSQLException.featureNotSupported()
}

override def getColumnsQuery(
session: Session,
catalogName: String,
schemaName: String,
tableName: String,
columnName: String): String = {
val query = new StringBuilder(
"""
|SELECT OWNER AS TABLE_SCHEMA
| , TABLE_NAME
| , COLUMN_NAME
|FROM ALL_TAB_COLUMNS
|""".stripMargin)

val filters = ArrayBuffer[String]()
if (StringUtils.isNotEmpty(schemaName)) {
filters += s"OWNER 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 getFunctionsOperation(session: Session): Operation = {
throw KyuubiSQLException.featureNotSupported()
}

override def getPrimaryKeysOperation(session: Session): Operation = {
throw KyuubiSQLException.featureNotSupported()
}

override def getCrossReferenceOperation(session: Session): Operation = {
throw KyuubiSQLException.featureNotSupported()
}

override def getTRowSetGenerator(): JdbcTRowSetGenerator = new OracleTRowSetGenerator

override def getSchemaHelper(): SchemaHelper = {
// throw KyuubiSQLException.featureNotSupported()
new OracleSchemaHelper
}

override def name(): String = {
"oracle"
}
}
// class OracleSchemaHelper extends SchemaHelper {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* 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.oracle

import org.apache.kyuubi.engine.jdbc.connection.JdbcConnectionProvider

class OracleConnectionProvider extends JdbcConnectionProvider {
override val name: String = classOf[OracleConnectionProvider].getName

override val driverClass: String = "oracle.jdbc.OracleDriver"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.oracle

import java.sql.Types

import org.apache.kyuubi.engine.jdbc.schema.SchemaHelper
import org.apache.kyuubi.shaded.hive.service.rpc.thrift.TTypeDesc

class OracleSchemaHelper extends SchemaHelper {
override protected def toTTypeDesc(sqlType: Int, precision: Int, scale: Int): TTypeDesc = {
sqlType match {
case Types.NUMERIC if scale == 0 =>
super.toTTypeDesc(Types.INTEGER, precision, scale)
case Types.NUMERIC =>
super.toTTypeDesc(Types.DECIMAL, precision, scale)
case _ => super.toTTypeDesc(sqlType, precision, scale)
}
}
}
Original file line number Diff line number Diff line change
@@ -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.oracle

import org.apache.kyuubi.engine.jdbc.schema.DefaultJdbcTRowSetGenerator

class OracleTRowSetGenerator extends DefaultJdbcTRowSetGenerator {}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ abstract class SchemaHelper {
tColumnDesc
}

private def toTTypeDesc(sqlType: Int, precision: Int, scale: Int): TTypeDesc = {
protected def toTTypeDesc(sqlType: Int, precision: Int, scale: Int): TTypeDesc = {
val typeEntry = new TPrimitiveTypeEntry(toTTypeId(sqlType))
typeEntry.setTypeQualifiers(toTTypeQualifiers(sqlType, precision, scale))
val tTypeDesc = new TTypeDesc()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* 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.oracle

import org.apache.kyuubi.config.KyuubiConf
import org.apache.kyuubi.engine.jdbc.connection.ConnectionProvider
import org.apache.kyuubi.operation.HiveJDBCTestHelper
import org.apache.kyuubi.shaded.hive.service.rpc.thrift.{TGetInfoReq, TGetInfoType}

class OperationWithOracleEngineSuite extends OracleOperationSuite with HiveJDBCTestHelper {

override protected def jdbcUrl: String = jdbcConnectionUrl

test("oracle - 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)
}
}
}
}
Loading

0 comments on commit 2f1b5ed

Please sign in to comment.