From 50420346c6bde4e52a29afa6a78b3e4d0ae9f3a4 Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Tue, 10 Apr 2018 09:02:21 -0500 Subject: [PATCH 01/47] Prepare for next version --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 04edf58..ea706ad 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ name := "ksql-jdbc-driver" -version := "0.2" +version := "0.3-SNAPSHOT" initialize := { assert(Integer.parseInt(sys.props("java.specification.version").split("\\.")(1)) >= 7, "Java 7 or above required") From c5a9b666be4dfbff1235c210a1fb4175cd3ce95c Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Thu, 17 Jan 2019 22:09:45 -0600 Subject: [PATCH 02/47] Upgrade KSQL and Kafka versions --- build.sbt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index ea706ad..027c2b5 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ name := "ksql-jdbc-driver" version := "0.3-SNAPSHOT" initialize := { - assert(Integer.parseInt(sys.props("java.specification.version").split("\\.")(1)) >= 7, "Java 7 or above required") + assert(Integer.parseInt(sys.props("java.specification.version").split("\\.")(1)) >= 8, "Java 8 or above required") } scalaVersion := "2.11.11" @@ -12,8 +12,7 @@ resolvers += "Confluent Maven Repo" at "http://packages.confluent.io/maven/" resolvers += "Confluent Snapshots Maven Repo" at "https://s3-us-west-2.amazonaws.com/confluent-snapshots/" resolvers += Resolver.mavenLocal -libraryDependencies += "io.confluent.ksql" % "ksql-rest-app" % "4.1.0" -libraryDependencies += "org.apache.kafka" %% "kafka" % "1.1.0" % "test" +libraryDependencies += "io.confluent.ksql" % "ksql-rest-app" % "5.1.0" +libraryDependencies += "org.apache.kafka" %% "kafka" % "2.1.0" % "test" libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % "test" libraryDependencies += "org.scalamock" %% "scalamock-scalatest-support" % "3.6.0" % "test" - From f402f90990b255b846c97a77b63fd3abc75ba955 Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Fri, 18 Jan 2019 20:16:24 -0600 Subject: [PATCH 03/47] Updating unit and integration tests --- .../jdbc/embedded/EmbeddedKsqlEngine.scala | 22 +++++++++++-- .../ksql/jdbc/KsqlConnectionSpec.scala | 8 ++--- .../ksql/jdbc/KsqlDatabaseMetaDataSpec.scala | 19 +++++------ .../mmolimar/ksql/jdbc/KsqlDriverSpec.scala | 3 ++ .../ksql/jdbc/KsqlStatementSpec.scala | 27 ++++++++-------- .../jdbc/resultset/KsqlResultSetSpec.scala | 32 +++++++------------ .../mmolimar/ksql/jdbc/utils/TestUtils.scala | 14 ++++++++ 7 files changed, 74 insertions(+), 51 deletions(-) diff --git a/src/it/scala/com/github/mmolimar/ksql/jdbc/embedded/EmbeddedKsqlEngine.scala b/src/it/scala/com/github/mmolimar/ksql/jdbc/embedded/EmbeddedKsqlEngine.scala index 949e7ed..0e1e4b9 100644 --- a/src/it/scala/com/github/mmolimar/ksql/jdbc/embedded/EmbeddedKsqlEngine.scala +++ b/src/it/scala/com/github/mmolimar/ksql/jdbc/embedded/EmbeddedKsqlEngine.scala @@ -4,13 +4,14 @@ import java.io.IOException import com.github.mmolimar.ksql.jdbc.utils.TestUtils import io.confluent.ksql.rest.server.{KsqlRestApplication, KsqlRestConfig} -import io.confluent.ksql.version.metrics.KsqlVersionCheckerAgent +import io.confluent.ksql.version.metrics.VersionCheckerAgent import kafka.utils.Logging import org.apache.kafka.clients.producer.ProducerConfig +import org.scalamock.scalatest.MockFactory import scala.collection.JavaConversions._ -class EmbeddedKsqlEngine(brokerList: String, port: Int = TestUtils.getAvailablePort) extends Logging { +class EmbeddedKsqlEngine(brokerList: String, port: Int = TestUtils.getAvailablePort) extends Logging with MockFactory { val config = new KsqlRestConfig(Map( ProducerConfig.BOOTSTRAP_SERVERS_CONFIG -> brokerList, @@ -23,7 +24,22 @@ class EmbeddedKsqlEngine(brokerList: String, port: Int = TestUtils.getAvailableP "ksql.command.topic.suffix" -> "commands" )) - lazy val ksqlEngine = KsqlRestApplication.buildApplication(config, true, new KsqlVersionCheckerAgent) + import java.util.function.{Function => JFunction, Supplier => JSupplier} + + implicit def toJavaSupplier[A](f: Function0[A]) = new JSupplier[A] { + override def get(): A = f() + } + + implicit def toJavaFunction[A, B](f: Function1[A, B]) = new JFunction[A, B] { + override def apply(a: A): B = f(a) + } + + lazy val ksqlEngine = { + val versionCheckerAgent = mock[VersionCheckerAgent] + (versionCheckerAgent.start _).expects(*, *).returns().anyNumberOfTimes + (versionCheckerAgent.updateLastRequestTime _).expects().returns().anyNumberOfTimes + KsqlRestApplication.buildApplication(config, (_: JSupplier[java.lang.Boolean]) => versionCheckerAgent) + } @throws[IOException] def startup = { diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlConnectionSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlConnectionSpec.scala index 2ddd4d6..d461aca 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlConnectionSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlConnectionSpec.scala @@ -5,21 +5,21 @@ import java.util.{Collections, Properties} import com.github.mmolimar.ksql.jdbc.utils.TestUtils._ import io.confluent.ksql.rest.client.{KsqlRestClient, RestResponse} -import io.confluent.ksql.rest.entity.{CommandStatus, CommandStatuses, ErrorMessage, KsqlEntityList} +import io.confluent.ksql.rest.entity._ import io.confluent.ksql.rest.server.computation.CommandId import org.scalamock.scalatest.MockFactory import org.scalatest.{Matchers, WordSpec} class KsqlConnectionSpec extends WordSpec with Matchers with MockFactory { - val implementedMethods = Seq("createStatement", "getTransactionIsolation", + val implementedMethods = Seq("createStatement", "getAutoCommit", "getTransactionIsolation", "setClientInfo", "isReadOnly", "isValid", "close", "getMetaData") "A KsqlConnection" when { "validating specs" should { val values = KsqlConnectionValues("localhost", 8080, Map.empty[String, String]) - val ksqlRestClient = mock[KsqlRestClient] + val ksqlRestClient = mock[MockableKsqlRestClient] val ksqlConnection = new KsqlConnection(values, new Properties) { override def init: KsqlRestClient = ksqlRestClient } @@ -42,7 +42,7 @@ class KsqlConnectionSpec extends WordSpec with Matchers with MockFactory { ksqlConnection.setClientInfo("", "") assertThrows[SQLException] { (ksqlRestClient.makeKsqlRequest _).expects(*) - .returns(RestResponse.erroneous(new ErrorMessage("", Collections.emptyList[String]))) + .returns(RestResponse.erroneous(new KsqlErrorMessage(-1, "", Collections.emptyList[String]))) ksqlConnection.setClientInfo("", "") } diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala index 70041a9..ac70a44 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala @@ -3,27 +3,28 @@ package com.github.mmolimar.ksql.jdbc import java.io.InputStream import java.sql.{SQLException, SQLFeatureNotSupportedException} import java.util.{Collections, Properties} -import javax.ws.rs.core.Response import com.github.mmolimar.ksql.jdbc.utils.TestUtils._ import io.confluent.ksql.rest.client.{KsqlRestClient, RestResponse} -import io.confluent.ksql.rest.entity.{ErrorMessage, KsqlEntityList} +import io.confluent.ksql.rest.entity.{KsqlEntityList, KsqlErrorMessage} +import javax.ws.rs.core.Response import org.scalamock.scalatest.MockFactory import org.scalatest.{Matchers, OneInstancePerTest, WordSpec} class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory with OneInstancePerTest { - val implementedMethods = Seq("getDriverName", "getDriverVersion", "getDriverMajorVersion", "getDriverMinorVersion", - "getJDBCMajorVersion", "getJDBCMinorVersion", "getConnection", "getCatalogs", "getTableTypes", "getTables", - "getSchemas", "getSuperTables", "getColumns") + val implementedMethods = Seq("getDatabaseProductName", "getDatabaseMajorVersion", "getDatabaseMinorVersion", + "getDatabaseProductVersion", "getDriverName", "getDriverVersion", "getDriverMajorVersion", + "getDriverMinorVersion", "getJDBCMajorVersion", "getJDBCMinorVersion", "getConnection", "getCatalogs", + "getTableTypes", "getTables", "getSchemas", "getSuperTables", "getUDTs", "getColumns", "isReadOnly") "A KsqlDatabaseMetaData" when { val mockResponse = mock[Response] (mockResponse.getEntity _).expects.returns(mock[InputStream]) - val mockKsqlRestClient = mock[KsqlRestClient] + val mockKsqlRestClient = mock[MockableKsqlRestClient] val values = KsqlConnectionValues("localhost", 8080, Map.empty[String, String]) val ksqlConnection = new KsqlConnection(values, new Properties) { @@ -35,7 +36,7 @@ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory w "throw not supported exception if not supported" in { (mockKsqlRestClient.makeQueryRequest _).expects(*) - .returns(RestResponse.successful[KsqlRestClient.QueryStream](new KsqlRestClient.QueryStream(mockResponse))) + .returns(RestResponse.successful[KsqlRestClient.QueryStream](mockQueryStream(mockResponse))) .anyNumberOfTimes reflectMethods[KsqlDatabaseMetaData](implementedMethods, false, metadata) @@ -57,11 +58,11 @@ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory w }) (mockKsqlRestClient.makeQueryRequest _).expects(*) - .returns(RestResponse.successful[KsqlRestClient.QueryStream](new KsqlRestClient.QueryStream(mockResponse))) + .returns(RestResponse.successful[KsqlRestClient.QueryStream](mockQueryStream(mockResponse))) .anyNumberOfTimes (mockKsqlRestClient.makeKsqlRequest _).expects(*) - .returns(RestResponse.erroneous(new ErrorMessage("error message", Collections.emptyList[String]))) + .returns(RestResponse.erroneous(new KsqlErrorMessage(-1, "error message", Collections.emptyList[String]))) .once assertThrows[SQLException] { diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala index 0087539..0171e52 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala @@ -26,6 +26,9 @@ class KsqlDriverSpec extends WordSpec with Matchers { } } "throw an exception when connecting to an invalid URL" in { + assertThrows[SQLException] { + driver.connect("invalid", new Properties) + } assertThrows[SQLException] { driver.connect("jdbc:ksql://localhost:9999999", new Properties) } diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala index ccd1357..09b2100 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala @@ -2,11 +2,11 @@ package com.github.mmolimar.ksql.jdbc import java.io.InputStream import java.sql.{SQLException, SQLFeatureNotSupportedException} -import javax.ws.rs.core.Response import com.github.mmolimar.ksql.jdbc.utils.TestUtils._ import io.confluent.ksql.rest.client.{KsqlRestClient, RestResponse} -import io.confluent.ksql.rest.entity.{ErrorMessage, KsqlEntityList} +import io.confluent.ksql.rest.entity.{KsqlEntityList, KsqlErrorMessage} +import javax.ws.rs.core.Response import org.scalamock.scalatest.MockFactory import org.scalatest.{Matchers, OneInstancePerTest, WordSpec} @@ -19,40 +19,39 @@ class KsqlStatementSpec extends WordSpec with Matchers with MockFactory with One val mockResponse = mock[Response] (mockResponse.getEntity _).expects.returns(mock[InputStream]).anyNumberOfTimes - - val mockKsqlRestClient = mock[KsqlRestClient] - - val statement = new KsqlStatement(mockKsqlRestClient) + val mockedKsqlRestClient = mock[MockableKsqlRestClient] + val statement = new KsqlStatement(mockedKsqlRestClient) "validating specs" should { "throw not supported exception if not supported" in { - (mockKsqlRestClient.makeQueryRequest _).expects(*) - .returns(RestResponse.successful[KsqlRestClient.QueryStream](new KsqlRestClient.QueryStream(mockResponse))) + + (mockedKsqlRestClient.makeQueryRequest _).expects(*) + .returns(RestResponse.successful[KsqlRestClient.QueryStream](mockQueryStream(mockResponse))) .noMoreThanOnce reflectMethods[KsqlStatement](implementedMethods, false, statement) .foreach(method => { assertThrows[SQLFeatureNotSupportedException] { - method() + method() } }) } "work if implemented" in { - (mockKsqlRestClient.makeKsqlRequest _).expects(*) + (mockedKsqlRestClient.makeKsqlRequest _).expects(*) .returns(RestResponse.successful[KsqlEntityList](new KsqlEntityList)) .once statement.execute("") should be(true) - (mockKsqlRestClient.makeQueryRequest _).expects(*) - .returns(RestResponse.successful[KsqlRestClient.QueryStream](new KsqlRestClient.QueryStream(mockResponse))) + (mockedKsqlRestClient.makeQueryRequest _).expects(*) + .returns(RestResponse.successful[KsqlRestClient.QueryStream](mockQueryStream(mockResponse))) .once Option(statement.executeQuery("")) should not be (None) assertThrows[SQLException] { - (mockKsqlRestClient.makeQueryRequest _).expects(*) - .returns(RestResponse.erroneous(new ErrorMessage(null, null))) + (mockedKsqlRestClient.makeQueryRequest _).expects(*) + .returns(RestResponse.erroneous(new KsqlErrorMessage(-1, null, null))) .once statement.executeQuery("") } diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala index 176d056..6968918 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala @@ -1,12 +1,9 @@ package com.github.mmolimar.ksql.jdbc.resultset -import java.io.InputStream import java.sql.{ResultSet, SQLException, SQLFeatureNotSupportedException} -import javax.ws.rs.core.Response import com.github.mmolimar.ksql.jdbc.utils.TestUtils._ import io.confluent.ksql.GenericRow -import io.confluent.ksql.rest.client.KsqlRestClient import io.confluent.ksql.rest.entity.StreamedRow import org.scalamock.scalatest.MockFactory import org.scalatest.{Matchers, OneInstancePerTest, WordSpec} @@ -26,34 +23,30 @@ class KsqlResultSetSpec extends WordSpec with Matchers with MockFactory with One "throw not supported exception if not supported" in { - val resultSet = new KsqlResultSet(null) + val resultSet = new KsqlResultSet(mock[JdbcQueryStream], 0) reflectMethods[KsqlResultSet](implementedMethods, false, resultSet) .foreach(method => { assertThrows[SQLFeatureNotSupportedException] { - method() + method() } }) } "work if implemented" in { - val mockResponse = mock[Response] - (mockResponse.getEntity _).expects.returns(mock[InputStream]).anyNumberOfTimes - - val mockQueryStream = mock[MockQueryStream] + val mockedQueryStream = mock[JdbcQueryStream] inSequence { - (mockQueryStream.hasNext _).expects.returns(true) + (mockedQueryStream.hasNext _).expects.returns(true) val columnValues = Seq[AnyRef]("string", "bytes".getBytes, Boolean.box(true), Byte.box('0'), Short.box(1), Int.box(2), Long.box(3L), Float.box(4.4f), Double.box(5.5d)) - (mockQueryStream.next _).expects.returns(new StreamedRow(new GenericRow(columnValues.asJava), null)) - (mockQueryStream.hasNext _).expects.returns(false) - (mockQueryStream.close _).expects.returns() - (mockQueryStream.close _).expects.throws(new IllegalStateException("Cannot call close() when already closed")) - (mockQueryStream.hasNext _).expects.throws(new IllegalStateException("Cannot call hasNext() once closed")) + (mockedQueryStream.next _).expects.returns(StreamedRow.row(new GenericRow(columnValues.asJava))) + (mockedQueryStream.hasNext _).expects.returns(false) + (mockedQueryStream.close _).expects.returns() + (mockedQueryStream.close _).expects.throws(new IllegalStateException("Cannot call close() when already closed.")) + (mockedQueryStream.hasNext _).expects.throws(new IllegalStateException("Cannot call hasNext() once closed.")) } - val resultSet = new KsqlResultSet(mockQueryStream) - + val resultSet = new KsqlResultSet(mockedQueryStream) resultSet.isLast should be(false) resultSet.isAfterLast should be(false) resultSet.isBeforeFirst should be(false) @@ -113,9 +106,6 @@ class KsqlResultSetSpec extends WordSpec with Matchers with MockFactory with One assertThrows[IllegalStateException] { resultSet.next } - - - class MockQueryStream extends KsqlRestClient.QueryStream(mockResponse) } } } @@ -130,7 +120,7 @@ class KsqlResultSetSpec extends WordSpec with Matchers with MockFactory with One reflectMethods[ResultSetNotSupported](Seq.empty, false, resultSet) .foreach(method => { assertThrows[SQLFeatureNotSupportedException] { - method() + method() } }) } diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/utils/TestUtils.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/utils/TestUtils.scala index 25c0c49..9bf7c71 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/utils/TestUtils.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/utils/TestUtils.scala @@ -7,6 +7,7 @@ import java.nio.channels.ServerSocketChannel import java.util import java.util.{Properties, Random, UUID} +import javax.ws.rs.core.Response import kafka.utils.Logging import kafka.zk.KafkaZkClient import org.apache.kafka.clients.admin.{AdminClient, AdminClientConfig} @@ -17,6 +18,8 @@ import org.apache.kafka.common.utils.Time import scala.reflect.runtime.universe._ import scala.reflect.{ClassTag, _} +import _root_.io.confluent.ksql.rest.client.KsqlRestClient +import javax.ws.rs.client.Client object TestUtils extends Logging { @@ -129,6 +132,17 @@ object TestUtils extends Logging { } } + class MockableKsqlRestClient(client: Client) extends KsqlRestClient("http://localhost:8080") + + def mockQueryStream(mockResponse: Response): KsqlRestClient.QueryStream = { + classOf[KsqlRestClient.QueryStream].getDeclaredConstructors + .filter(_.getParameterCount == 1) + .map(c => { + c.setAccessible(true) + c + }).head.newInstance(mockResponse).asInstanceOf[KsqlRestClient.QueryStream] + } + def reflectMethods[T <: AnyRef](implementedMethods: Seq[String], implemented: Boolean, obj: T)(implicit tt: TypeTag[T], ct: ClassTag[T]): Seq[() => Any] = { From 135b0d829b0ea5f9e93fc4d0f7d00e51414f8750 Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Fri, 18 Jan 2019 20:18:45 -0600 Subject: [PATCH 04/47] Reporting method name when throwing a not supported exception --- .../mmolimar/ksql/jdbc/Exceptions.scala | 4 +- .../mmolimar/ksql/jdbc/KsqlConnection.scala | 92 +++-- .../ksql/jdbc/KsqlDatabaseMetaData.scala | 338 ++++++++-------- .../mmolimar/ksql/jdbc/KsqlStatement.scala | 74 ++-- .../ksql/jdbc/WrapperNotSupported.scala | 4 +- .../ksql/jdbc/resultset/KsqlResultSet.scala | 21 +- .../ksql/jdbc/resultset/ResultSet.scala | 382 +++++++++--------- 7 files changed, 472 insertions(+), 443 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala index 7093f39..0dd4f83 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala @@ -18,7 +18,9 @@ case class InvalidProperty(name: String) extends KsqlException { override def message = s"Invalid property ${name}." } -case class NotSupported(override val message: String = "Feature not supported") extends KsqlException +case class NotSupported(feature: String) extends KsqlException { + override val message = s"Feature not supported: $feature." +} case class KsqlQueryError(override val message: String = "Error executing query") extends KsqlException diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala index 2e55c5b..c322fa4 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala @@ -6,7 +6,8 @@ import java.util.Properties import java.util.concurrent.Executor import com.github.mmolimar.ksql.jdbc.Exceptions._ -import io.confluent.ksql.rest.client.KsqlRestClient +import io.confluent.ksql.rest.client.{KsqlRestClient, RestResponse} +import io.confluent.ksql.rest.entity.KsqlEntityList import scala.collection.JavaConverters._ import scala.util.{Failure, Success, Try} @@ -41,43 +42,43 @@ class KsqlConnection(values: KsqlConnectionValues, properties: Properties) exten } } - private[jdbc] def executeKsqlCommand(ksql: String) = ksqlClient.makeKsqlRequest(ksql) + private[jdbc] def executeKsqlCommand(ksql: String): RestResponse[KsqlEntityList] = ksqlClient.makeKsqlRequest(ksql) - override def setAutoCommit(autoCommit: Boolean): Unit = throw NotSupported() + override def setAutoCommit(autoCommit: Boolean): Unit = throw NotSupported("setAutoCommit") - override def setHoldability(holdability: Int): Unit = throw NotSupported() + override def setHoldability(holdability: Int): Unit = throw NotSupported("setHoldability") - override def clearWarnings: Unit = throw NotSupported() + override def clearWarnings: Unit = throw NotSupported("clearWarnings") - override def getNetworkTimeout: Int = throw NotSupported() + override def getNetworkTimeout: Int = throw NotSupported("getNetworkTimeout") - override def createBlob: Blob = throw NotSupported() + override def createBlob: Blob = throw NotSupported("createBlob") - override def createSQLXML: SQLXML = throw NotSupported() + override def createSQLXML: SQLXML = throw NotSupported("createSQLXML") - override def setSavepoint: Savepoint = throw NotSupported() + override def setSavepoint: Savepoint = throw NotSupported("setSavepoint") - override def setSavepoint(name: String): Savepoint = throw NotSupported() + override def setSavepoint(name: String): Savepoint = throw NotSupported("setSavepoint") - override def createNClob: NClob = throw NotSupported() + override def createNClob: NClob = throw NotSupported("createNClob") override def getTransactionIsolation: Int = Connection.TRANSACTION_NONE - override def getClientInfo(name: String): String = throw NotSupported() + override def getClientInfo(name: String): String = throw NotSupported("getClientInfo") - override def getClientInfo: Properties = throw NotSupported() + override def getClientInfo: Properties = throw NotSupported("getClientInfo") - override def getSchema: String = throw NotSupported() + override def getSchema: String = throw NotSupported("getSchema") - override def setNetworkTimeout(executor: Executor, milliseconds: Int): Unit = throw NotSupported() + override def setNetworkTimeout(executor: Executor, milliseconds: Int): Unit = throw NotSupported("setNetworkTimeout") override def getMetaData: DatabaseMetaData = new KsqlDatabaseMetaData(this) - override def getTypeMap: util.Map[String, Class[_]] = throw NotSupported() + override def getTypeMap: util.Map[String, Class[_]] = throw NotSupported("getTypeMap") - override def rollback: Unit = throw NotSupported() + override def rollback: Unit = throw NotSupported("rollback") - override def rollback(savepoint: Savepoint): Unit = throw NotSupported() + override def rollback(savepoint: Savepoint): Unit = throw NotSupported("rollback") override def createStatement: Statement = createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY) @@ -95,9 +96,9 @@ class KsqlConnection(values: KsqlConnectionValues, properties: Properties) exten new KsqlStatement(ksqlClient, values.timeout) } - override def getHoldability: Int = throw NotSupported() + override def getHoldability: Int = throw NotSupported("getHoldability") - override def setReadOnly(readOnly: Boolean): Unit = throw NotSupported() + override def setReadOnly(readOnly: Boolean): Unit = throw NotSupported("setReadOnly") override def setClientInfo(name: String, value: String): Unit = { val ksql = s"SET '${name.trim}'='${value.trim}';" @@ -112,60 +113,63 @@ class KsqlConnection(values: KsqlConnectionValues, properties: Properties) exten override def isReadOnly: Boolean = true - override def setTypeMap(map: util.Map[String, Class[_]]): Unit = throw NotSupported() + override def setTypeMap(map: util.Map[String, Class[_]]): Unit = throw NotSupported("setTypeMap") - override def getCatalog: String = throw NotSupported() + override def getCatalog: String = throw NotSupported("getCatalog") - override def createClob: Clob = throw NotSupported() + override def createClob: Clob = throw NotSupported("createClob") - override def nativeSQL(sql: String): String = throw NotSupported() + override def nativeSQL(sql: String): String = throw NotSupported("nativeSQL") - override def setTransactionIsolation(level: Int): Unit = throw NotSupported() + override def setTransactionIsolation(level: Int): Unit = throw NotSupported("setTransactionIsolation") - override def prepareCall(sql: String): CallableStatement = throw NotSupported() + override def prepareCall(sql: String): CallableStatement = throw NotSupported("prepareCall") override def prepareCall(sql: String, resultSetType: Int, - resultSetConcurrency: Int): CallableStatement = throw NotSupported() + resultSetConcurrency: Int): CallableStatement = throw NotSupported("prepareCall") override def prepareCall(sql: String, resultSetType: Int, resultSetConcurrency: Int, - resultSetHoldability: Int): CallableStatement = throw NotSupported() + resultSetHoldability: Int): CallableStatement = throw NotSupported("prepareCall") - override def createArrayOf(typeName: String, elements: scala.Array[AnyRef]): Array = throw NotSupported() + override def createArrayOf(typeName: String, elements: scala.Array[AnyRef]): Array = throw NotSupported("createArrayOf") - override def setCatalog(catalog: String): Unit = throw NotSupported() + override def setCatalog(catalog: String): Unit = throw NotSupported("setCatalog") override def close: Unit = ksqlClient.close - override def getAutoCommit: Boolean = throw NotSupported() + override def getAutoCommit: Boolean = false - override def abort(executor: Executor): Unit = throw NotSupported() + override def abort(executor: Executor): Unit = throw NotSupported("abort") override def isValid(timeout: Int): Boolean = ksqlClient.makeStatusRequest.isSuccessful - override def prepareStatement(sql: String): PreparedStatement = throw NotSupported() + override def prepareStatement(sql: String): PreparedStatement = throw NotSupported("prepareStatement") override def prepareStatement(sql: String, resultSetType: Int, - resultSetConcurrency: Int): PreparedStatement = throw NotSupported() + resultSetConcurrency: Int): PreparedStatement = throw NotSupported("prepareStatement") override def prepareStatement(sql: String, resultSetType: Int, resultSetConcurrency: Int, - resultSetHoldability: Int): PreparedStatement = throw NotSupported() + resultSetHoldability: Int): PreparedStatement = throw NotSupported("prepareStatement") - override def prepareStatement(sql: String, autoGeneratedKeys: Int): PreparedStatement = throw NotSupported() + override def prepareStatement(sql: String, autoGeneratedKeys: Int): PreparedStatement = throw NotSupported("prepareStatement") - override def prepareStatement(sql: String, columnIndexes: scala.Array[Int]): PreparedStatement = throw NotSupported() + override def prepareStatement(sql: String, columnIndexes: scala.Array[Int]): PreparedStatement = + throw NotSupported("prepareStatement") - override def prepareStatement(sql: String, columnNames: scala.Array[String]): PreparedStatement = throw NotSupported() + override def prepareStatement(sql: String, columnNames: scala.Array[String]): PreparedStatement = + throw NotSupported("prepareStatement") - override def releaseSavepoint(savepoint: Savepoint): Unit = throw NotSupported() + override def releaseSavepoint(savepoint: Savepoint): Unit = throw NotSupported("releaseSavepoint") - override def isClosed: Boolean = throw NotSupported() + override def isClosed: Boolean = throw NotSupported("isClosed") - override def createStruct(typeName: String, attributes: scala.Array[AnyRef]): Struct = throw NotSupported() + override def createStruct(typeName: String, attributes: scala.Array[AnyRef]): Struct = + throw NotSupported("createStruct") - override def getWarnings: SQLWarning = throw NotSupported() + override def getWarnings: SQLWarning = throw NotSupported("getWarnings") - override def setSchema(schema: String): Unit = throw NotSupported() + override def setSchema(schema: String): Unit = throw NotSupported("setSchema") - override def commit: Unit = throw NotSupported() + override def commit: Unit = throw NotSupported("commit") } diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala index 60ea0be..50a26bb 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala @@ -4,7 +4,7 @@ import java.sql.{Connection, DatabaseMetaData, ResultSet, RowIdLifetime, Types} import com.github.mmolimar.ksql.jdbc.Exceptions._ import com.github.mmolimar.ksql.jdbc.resultset.StaticResultSet -import io.confluent.ksql.rest.entity.{SourceDescription, StreamsList, TablesList} +import io.confluent.ksql.rest.entity.{SourceDescriptionEntity, StreamsList, TablesList} import scala.collection.JavaConverters._ @@ -28,209 +28,211 @@ object TableTypes { class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends DatabaseMetaData with WrapperNotSupported { - override def supportsMultipleOpenResults: Boolean = throw NotSupported() + override def supportsMultipleOpenResults: Boolean = throw NotSupported("supportsMultipleOpenResults") - override def supportsSubqueriesInIns: Boolean = throw NotSupported() + override def supportsSubqueriesInIns: Boolean = throw NotSupported("supportsSubqueriesInIns") override def getSuperTypes(catalog: String, schemaPattern: String, - typeNamePattern: String): ResultSet = throw NotSupported() + typeNamePattern: String): ResultSet = throw NotSupported("getSuperTypes") override def getTablePrivileges(catalog: String, schemaPattern: String, - tableNamePattern: String): ResultSet = throw NotSupported() + tableNamePattern: String): ResultSet = throw NotSupported("getTablePrivileges") - override def supportsFullOuterJoins: Boolean = throw NotSupported() + override def supportsFullOuterJoins: Boolean = throw NotSupported("supportsFullOuterJoins") - override def insertsAreDetected(`type`: Int): Boolean = throw NotSupported() + override def insertsAreDetected(`type`: Int): Boolean = throw NotSupported("insertsAreDetected") override def getDriverMajorVersion: Int = KsqlDriver.majorVersion - override def getDatabaseProductVersion: String = throw NotSupported() + override def getDatabaseProductVersion: String = "5.1.0" override def getIndexInfo(catalog: String, schema: String, table: String, unique: Boolean, - approximate: Boolean): ResultSet = throw NotSupported() + approximate: Boolean): ResultSet = throw NotSupported("getIndexInfo") override def getFunctionColumns(catalog: String, schemaPattern: String, functionNamePattern: String, - columnNamePattern: String): ResultSet = throw NotSupported() + columnNamePattern: String): ResultSet = throw NotSupported("getFunctionColumns") - override def supportsCatalogsInTableDefinitions: Boolean = throw NotSupported() + override def supportsCatalogsInTableDefinitions: Boolean = throw NotSupported("supportsCatalogsInTableDefinitions") - override def isCatalogAtStart: Boolean = throw NotSupported() + override def isCatalogAtStart: Boolean = throw NotSupported("isCatalogAtStart") override def getJDBCMinorVersion: Int = KsqlDriver.jdbcMinorVersion - override def supportsMixedCaseQuotedIdentifiers: Boolean = throw NotSupported() + override def supportsMixedCaseQuotedIdentifiers: Boolean = throw NotSupported("supportsMixedCaseQuotedIdentifiers") - override def storesUpperCaseQuotedIdentifiers: Boolean = throw NotSupported() + override def storesUpperCaseQuotedIdentifiers: Boolean = throw NotSupported("storesUpperCaseQuotedIdentifiers") override def getUDTs(catalog: String, schemaPattern: String, typeNamePattern: String, - types: Array[Int]): ResultSet = throw NotSupported() + types: Array[Int]): ResultSet = new StaticResultSet[String](Map.empty, Iterator.empty) override def getAttributes(catalog: String, schemaPattern: String, typeNamePattern: String, - attributeNamePattern: String): ResultSet = throw NotSupported() + attributeNamePattern: String): ResultSet = throw NotSupported("getAttributes") - override def supportsStoredFunctionsUsingCallSyntax: Boolean = throw NotSupported() + override def supportsStoredFunctionsUsingCallSyntax: Boolean = throw NotSupported("supportsStoredFunctionsUsingCallSyntax") - override def nullsAreSortedAtStart: Boolean = throw NotSupported() + override def nullsAreSortedAtStart: Boolean = throw NotSupported("nullsAreSortedAtStart") - override def getMaxIndexLength: Int = throw NotSupported() + override def getMaxIndexLength: Int = throw NotSupported("getMaxIndexLength") - override def getMaxTablesInSelect: Int = throw NotSupported() + override def getMaxTablesInSelect: Int = throw NotSupported("getMaxTablesInSelect") - override def getClientInfoProperties: ResultSet = throw NotSupported() + override def getClientInfoProperties: ResultSet = throw NotSupported("getClientInfoProperties") - override def supportsSchemasInDataManipulation: Boolean = throw NotSupported() + override def supportsSchemasInDataManipulation: Boolean = throw NotSupported("supportsSchemasInDataManipulation") - override def getDatabaseMinorVersion: Int = throw NotSupported() + override def getDatabaseMinorVersion: Int = 1 - override def supportsSchemasInProcedureCalls: Boolean = throw NotSupported() + override def supportsSchemasInProcedureCalls: Boolean = throw NotSupported("supportsSchemasInProcedureCalls") - override def supportsOuterJoins: Boolean = throw NotSupported() + override def supportsOuterJoins: Boolean = throw NotSupported("supportsOuterJoins") - override def supportsGroupBy: Boolean = throw NotSupported() + override def supportsGroupBy: Boolean = throw NotSupported("supportsGroupBy") - override def doesMaxRowSizeIncludeBlobs: Boolean = throw NotSupported() + override def doesMaxRowSizeIncludeBlobs: Boolean = throw NotSupported("doesMaxRowSizeIncludeBlobs") - override def supportsCatalogsInDataManipulation: Boolean = throw NotSupported() + override def supportsCatalogsInDataManipulation: Boolean = throw NotSupported("supportsCatalogsInDataManipulation") - override def getDatabaseProductName: String = throw NotSupported() + override def getDatabaseProductName: String = "KSQL" - override def supportsOpenCursorsAcrossCommit: Boolean = throw NotSupported() + override def supportsOpenCursorsAcrossCommit: Boolean = throw NotSupported("supportsOpenCursorsAcrossCommit") - override def supportsTableCorrelationNames: Boolean = throw NotSupported() + override def supportsTableCorrelationNames: Boolean = throw NotSupported("supportsTableCorrelationNames") - override def supportsExtendedSQLGrammar: Boolean = throw NotSupported() + override def supportsExtendedSQLGrammar: Boolean = throw NotSupported("supportsExtendedSQLGrammar") override def getJDBCMajorVersion: Int = KsqlDriver.jdbcMajorVersion - override def getUserName: String = throw NotSupported() + override def getUserName: String = throw NotSupported("getUserName") - override def getMaxProcedureNameLength: Int = throw NotSupported() + override def getMaxProcedureNameLength: Int = throw NotSupported("getMaxProcedureNameLength") override def getDriverName: String = KsqlDriver.driverName - override def getMaxRowSize: Int = throw NotSupported() + override def getMaxRowSize: Int = throw NotSupported("getMaxRowSize") - override def dataDefinitionCausesTransactionCommit: Boolean = throw NotSupported() + override def dataDefinitionCausesTransactionCommit: Boolean = throw NotSupported("dataDefinitionCausesTransactionCommit") - override def getMaxColumnNameLength: Int = throw NotSupported() + override def getMaxColumnNameLength: Int = throw NotSupported("getMaxColumnNameLength") - override def getMaxSchemaNameLength: Int = throw NotSupported() + override def getMaxSchemaNameLength: Int = throw NotSupported("getMaxSchemaNameLength") - override def getVersionColumns(catalog: String, schema: String, table: String): ResultSet = throw NotSupported() + override def getVersionColumns(catalog: String, schema: String, table: String): ResultSet = throw NotSupported("getVersionColumns") - override def getNumericFunctions: String = throw NotSupported() + override def getNumericFunctions: String = throw NotSupported("getNumericFunctions") - override def supportsIntegrityEnhancementFacility: Boolean = throw NotSupported() + override def supportsIntegrityEnhancementFacility: Boolean = throw NotSupported("supportsIntegrityEnhancementFacility") - override def getIdentifierQuoteString: String = throw NotSupported() + override def getIdentifierQuoteString: String = throw NotSupported("getIdentifierQuoteString") - override def supportsNonNullableColumns: Boolean = throw NotSupported() + override def supportsNonNullableColumns: Boolean = throw NotSupported("supportsNonNullableColumns") - override def getMaxConnections: Int = throw NotSupported() + override def getMaxConnections: Int = throw NotSupported("getMaxConnections") - override def supportsResultSetHoldability(holdability: Int): Boolean = throw NotSupported() + override def supportsResultSetHoldability(holdability: Int): Boolean = throw NotSupported("supportsResultSetHoldability") - override def supportsGroupByBeyondSelect: Boolean = throw NotSupported() + override def supportsGroupByBeyondSelect: Boolean = throw NotSupported("supportsGroupByBeyondSelect") override def getFunctions(catalog: String, schemaPattern: String, - functionNamePattern: String): ResultSet = throw NotSupported() + functionNamePattern: String): ResultSet = throw NotSupported("getFunctions") - override def supportsSchemasInPrivilegeDefinitions: Boolean = throw NotSupported() + override def supportsSchemasInPrivilegeDefinitions: Boolean = throw NotSupported("supportsSchemasInPrivilegeDefinitions") - override def supportsResultSetConcurrency(`type`: Int, concurrency: Int): Boolean = throw NotSupported() + override def supportsResultSetConcurrency(`type`: Int, concurrency: Int): Boolean = + throw NotSupported("supportsResultSetConcurrency") - override def getURL: String = throw NotSupported() + override def getURL: String = throw NotSupported("getURL") - override def supportsSubqueriesInQuantifieds: Boolean = throw NotSupported() + override def supportsSubqueriesInQuantifieds: Boolean = throw NotSupported("supportsSubqueriesInQuantifieds") - override def supportsBatchUpdates: Boolean = throw NotSupported() + override def supportsBatchUpdates: Boolean = throw NotSupported("supportsBatchUpdates") - override def supportsLikeEscapeClause: Boolean = throw NotSupported() + override def supportsLikeEscapeClause: Boolean = throw NotSupported("supportsLikeEscapeClause") - override def supportsExpressionsInOrderBy: Boolean = throw NotSupported() + override def supportsExpressionsInOrderBy: Boolean = throw NotSupported("supportsExpressionsInOrderBy") - override def allTablesAreSelectable: Boolean = throw NotSupported() + override def allTablesAreSelectable: Boolean = throw NotSupported("allTablesAreSelectable") override def getCrossReference(parentCatalog: String, parentSchema: String, parentTable: String, foreignCatalog: String, foreignSchema: String, - foreignTable: String): ResultSet = throw NotSupported() + foreignTable: String): ResultSet = throw NotSupported("getCrossReference") - override def getDatabaseMajorVersion: Int = throw NotSupported() + override def getDatabaseMajorVersion: Int = 5 - override def supportsColumnAliasing: Boolean = throw NotSupported() + override def supportsColumnAliasing: Boolean = throw NotSupported("supportsColumnAliasing") - override def getMaxCursorNameLength: Int = throw NotSupported() + override def getMaxCursorNameLength: Int = throw NotSupported("getMaxCursorNameLength") - override def getRowIdLifetime: RowIdLifetime = throw NotSupported() + override def getRowIdLifetime: RowIdLifetime = throw NotSupported("getRowIdLifetime") - override def ownDeletesAreVisible(`type`: Int): Boolean = throw NotSupported() + override def ownDeletesAreVisible(`type`: Int): Boolean = throw NotSupported("ownDeletesAreVisible") - override def supportsDifferentTableCorrelationNames: Boolean = throw NotSupported() + override def supportsDifferentTableCorrelationNames: Boolean = throw NotSupported("supportsDifferentTableCorrelationNames") - override def getDefaultTransactionIsolation: Int = throw NotSupported() + override def getDefaultTransactionIsolation: Int = throw NotSupported("getDefaultTransactionIsolation") - override def getSearchStringEscape: String = throw NotSupported() + override def getSearchStringEscape: String = throw NotSupported("getSearchStringEscape") - override def getMaxUserNameLength: Int = throw NotSupported() + override def getMaxUserNameLength: Int = throw NotSupported("getMaxUserNameLength") - override def supportsANSI92EntryLevelSQL: Boolean = throw NotSupported() + override def supportsANSI92EntryLevelSQL: Boolean = throw NotSupported("supportsANSI92EntryLevelSQL") override def getProcedureColumns(catalog: String, schemaPattern: String, procedureNamePattern: String, - columnNamePattern: String): ResultSet = throw NotSupported() + columnNamePattern: String): ResultSet = throw NotSupported("getProcedureColumns") - override def storesMixedCaseQuotedIdentifiers: Boolean = throw NotSupported() + override def storesMixedCaseQuotedIdentifiers: Boolean = throw NotSupported("storesMixedCaseQuotedIdentifiers") - override def supportsANSI92FullSQL: Boolean = throw NotSupported() + override def supportsANSI92FullSQL: Boolean = throw NotSupported("supportsANSI92FullSQL") - override def getMaxStatementLength: Int = throw NotSupported() + override def getMaxStatementLength: Int = throw NotSupported("getMaxStatementLength") - override def othersDeletesAreVisible(`type`: Int): Boolean = throw NotSupported() + override def othersDeletesAreVisible(`type`: Int): Boolean = throw NotSupported("othersDeletesAreVisible") - override def supportsTransactions: Boolean = throw NotSupported() + override def supportsTransactions: Boolean = throw NotSupported("supportsTransactions") - override def deletesAreDetected(`type`: Int): Boolean = throw NotSupported() + override def deletesAreDetected(`type`: Int): Boolean = throw NotSupported("deletesAreDetected") - override def locatorsUpdateCopy: Boolean = throw NotSupported() + override def locatorsUpdateCopy: Boolean = throw NotSupported("locatorsUpdateCopy") - override def allProceduresAreCallable: Boolean = throw NotSupported() + override def allProceduresAreCallable: Boolean = throw NotSupported("allProceduresAreCallable") - override def getImportedKeys(catalog: String, schema: String, table: String): ResultSet = throw NotSupported() + override def getImportedKeys(catalog: String, schema: String, table: String): ResultSet = throw NotSupported("getImportedKeys") - override def usesLocalFiles: Boolean = throw NotSupported() + override def usesLocalFiles: Boolean = throw NotSupported("usesLocalFiles") - override def supportsLimitedOuterJoins: Boolean = throw NotSupported() + override def supportsLimitedOuterJoins: Boolean = throw NotSupported("supportsLimitedOuterJoins") - override def storesMixedCaseIdentifiers: Boolean = throw NotSupported() + override def storesMixedCaseIdentifiers: Boolean = throw NotSupported("storesMixedCaseIdentifiers") - override def getCatalogTerm: String = throw NotSupported() + override def getCatalogTerm: String = throw NotSupported("getCatalogTerm") - override def getMaxColumnsInGroupBy: Int = throw NotSupported() + override def getMaxColumnsInGroupBy: Int = throw NotSupported("getMaxColumnsInGroupBy") - override def supportsSubqueriesInExists: Boolean = throw NotSupported() + override def supportsSubqueriesInExists: Boolean = throw NotSupported("supportsSubqueriesInExists") - override def supportsPositionedUpdate: Boolean = throw NotSupported() + override def supportsPositionedUpdate: Boolean = throw NotSupported("supportsPositionedUpdate") - override def supportsGetGeneratedKeys: Boolean = throw NotSupported() + override def supportsGetGeneratedKeys: Boolean = throw NotSupported("supportsGetGeneratedKeys") - override def supportsUnion: Boolean = throw NotSupported() + override def supportsUnion: Boolean = throw NotSupported("supportsUnion") - override def nullsAreSortedLow: Boolean = throw NotSupported() + override def nullsAreSortedLow: Boolean = throw NotSupported("nullsAreSortedLow") - override def getSQLKeywords: String = throw NotSupported() + override def getSQLKeywords: String = throw NotSupported("getSQLKeywords") - override def supportsCorrelatedSubqueries: Boolean = throw NotSupported() + override def supportsCorrelatedSubqueries: Boolean = throw NotSupported("supportsCorrelatedSubqueries") - override def isReadOnly: Boolean = throw NotSupported() + override def isReadOnly: Boolean = true override def getProcedures(catalog: String, schemaPattern: String, - procedureNamePattern: String): ResultSet = throw NotSupported() + procedureNamePattern: String): ResultSet = throw NotSupported("getProcedures") - override def supportsUnionAll: Boolean = throw NotSupported() + override def supportsUnionAll: Boolean = throw NotSupported("supportsUnionAll") - override def supportsCoreSQLGrammar: Boolean = throw NotSupported() + override def supportsCoreSQLGrammar: Boolean = throw NotSupported("supportsCoreSQLGrammar") override def getPseudoColumns(catalog: String, schemaPattern: String, - tableNamePattern: String, columnNamePattern: String): ResultSet = throw NotSupported() + tableNamePattern: String, columnNamePattern: String): ResultSet = + throw NotSupported("getPseudoColumns") override def getCatalogs: ResultSet = new StaticResultSet[String](Headers.catalogs, Iterator.empty) @@ -241,27 +243,28 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D new StaticResultSet[String](Headers.superTables, Iterator.empty) } - override def getMaxColumnsInOrderBy: Int = throw NotSupported() + override def getMaxColumnsInOrderBy: Int = throw NotSupported("getMaxColumnsInOrderBy") - override def supportsAlterTableWithAddColumn: Boolean = throw NotSupported() + override def supportsAlterTableWithAddColumn: Boolean = throw NotSupported("supportsAlterTableWithAddColumn") - override def getProcedureTerm: String = throw NotSupported() + override def getProcedureTerm: String = throw NotSupported("getProcedureTerm") - override def getMaxCharLiteralLength: Int = throw NotSupported() + override def getMaxCharLiteralLength: Int = throw NotSupported("getMaxCharLiteralLength") - override def supportsMixedCaseIdentifiers: Boolean = throw NotSupported() + override def supportsMixedCaseIdentifiers: Boolean = throw NotSupported("supportsMixedCaseIdentifiers") - override def supportsDataDefinitionAndDataManipulationTransactions: Boolean = throw NotSupported() + override def supportsDataDefinitionAndDataManipulationTransactions: Boolean = + throw NotSupported("supportsDataDefinitionAndDataManipulationTransactions") - override def supportsCatalogsInProcedureCalls: Boolean = throw NotSupported() + override def supportsCatalogsInProcedureCalls: Boolean = throw NotSupported("supportsCatalogsInProcedureCalls") - override def supportsGroupByUnrelated: Boolean = throw NotSupported() + override def supportsGroupByUnrelated: Boolean = throw NotSupported("supportsGroupByUnrelated") - override def getResultSetHoldability: Int = throw NotSupported() + override def getResultSetHoldability: Int = throw NotSupported("getResultSetHoldability") - override def ownUpdatesAreVisible(`type`: Int): Boolean = throw NotSupported() + override def ownUpdatesAreVisible(`type`: Int): Boolean = throw NotSupported("ownUpdatesAreVisible") - override def nullsAreSortedHigh: Boolean = throw NotSupported() + override def nullsAreSortedHigh: Boolean = throw NotSupported("nullsAreSortedHigh") override def getTables(catalog: String, schemaPattern: String, tableNamePattern: String, types: Array[String]): ResultSet = { @@ -299,25 +302,25 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D new StaticResultSet[String](Headers.tables, itTables ++ itStreams) } - override def supportsMultipleTransactions: Boolean = throw NotSupported() + override def supportsMultipleTransactions: Boolean = throw NotSupported("supportsMultipleTransactions") - override def supportsNamedParameters: Boolean = throw NotSupported() + override def supportsNamedParameters: Boolean = throw NotSupported("supportsNamedParameters") - override def getTypeInfo: ResultSet = throw NotSupported() + override def getTypeInfo: ResultSet = throw NotSupported("getTypeInfo") - override def supportsAlterTableWithDropColumn: Boolean = throw NotSupported() + override def supportsAlterTableWithDropColumn: Boolean = throw NotSupported("supportsAlterTableWithDropColumn") - override def getSchemaTerm: String = throw NotSupported() + override def getSchemaTerm: String = throw NotSupported("getSchemaTerm") - override def nullPlusNonNullIsNull: Boolean = throw NotSupported() + override def nullPlusNonNullIsNull: Boolean = throw NotSupported("nullPlusNonNullIsNull") - override def getPrimaryKeys(catalog: String, schema: String, table: String): ResultSet = throw NotSupported() + override def getPrimaryKeys(catalog: String, schema: String, table: String): ResultSet = throw NotSupported("getPrimaryKeys") - override def supportsOpenCursorsAcrossRollback: Boolean = throw NotSupported() + override def supportsOpenCursorsAcrossRollback: Boolean = throw NotSupported("supportsOpenCursorsAcrossRollback") - override def getMaxBinaryLiteralLength: Int = throw NotSupported() + override def getMaxBinaryLiteralLength: Int = throw NotSupported("getMaxBinaryLiteralLength") - override def getExtraNameCharacters: String = throw NotSupported() + override def getExtraNameCharacters: String = throw NotSupported("getExtraNameCharacters") override def getSchemas: ResultSet = new StaticResultSet[String](Headers.schemas, Iterator.empty) @@ -326,78 +329,79 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D getSchemas } - override def supportsMultipleResultSets: Boolean = throw NotSupported() + override def supportsMultipleResultSets: Boolean = throw NotSupported("supportsMultipleResultSets") - override def ownInsertsAreVisible(`type`: Int): Boolean = throw NotSupported() + override def ownInsertsAreVisible(`type`: Int): Boolean = throw NotSupported("ownInsertsAreVisible") - override def nullsAreSortedAtEnd: Boolean = throw NotSupported() + override def nullsAreSortedAtEnd: Boolean = throw NotSupported("nullsAreSortedAtEnd") - override def supportsSavepoints: Boolean = throw NotSupported() + override def supportsSavepoints: Boolean = throw NotSupported("supportsSavepoints") - override def getMaxStatements: Int = throw NotSupported() + override def getMaxStatements: Int = throw NotSupported("getMaxStatements") override def getBestRowIdentifier(catalog: String, schema: String, - table: String, scope: Int, nullable: Boolean): ResultSet = throw NotSupported() + table: String, scope: Int, nullable: Boolean): ResultSet = + throw NotSupported("getBestRowIdentifier") override def getDriverVersion: String = KsqlDriver.version - override def storesUpperCaseIdentifiers: Boolean = throw NotSupported() + override def storesUpperCaseIdentifiers: Boolean = throw NotSupported("storesUpperCaseIdentifiers") - override def storesLowerCaseIdentifiers: Boolean = throw NotSupported() + override def storesLowerCaseIdentifiers: Boolean = throw NotSupported("storesLowerCaseIdentifiers") - override def getMaxCatalogNameLength: Int = throw NotSupported() + override def getMaxCatalogNameLength: Int = throw NotSupported("getMaxCatalogNameLength") - override def supportsDataManipulationTransactionsOnly: Boolean = throw NotSupported() + override def supportsDataManipulationTransactionsOnly: Boolean = throw NotSupported("supportsDataManipulationTransactionsOnly") - override def getSystemFunctions: String = throw NotSupported() + override def getSystemFunctions: String = throw NotSupported("getSystemFunctions") override def getColumnPrivileges(catalog: String, schema: String, - table: String, columnNamePattern: String): ResultSet = throw NotSupported() + table: String, columnNamePattern: String): ResultSet = throw NotSupported("getColumnPrivileges") override def getDriverMinorVersion: Int = KsqlDriver.minorVersion - override def getMaxTableNameLength: Int = throw NotSupported() + override def getMaxTableNameLength: Int = throw NotSupported("getMaxTableNameLength") - override def dataDefinitionIgnoredInTransactions: Boolean = throw NotSupported() + override def dataDefinitionIgnoredInTransactions: Boolean = throw NotSupported("dataDefinitionIgnoredInTransactions") - override def getStringFunctions: String = throw NotSupported() + override def getStringFunctions: String = throw NotSupported("getStringFunctions") - override def getMaxColumnsInSelect: Int = throw NotSupported() + override def getMaxColumnsInSelect: Int = throw NotSupported("getMaxColumnsInSelect") - override def usesLocalFilePerTable: Boolean = throw NotSupported() + override def usesLocalFilePerTable: Boolean = throw NotSupported("usesLocalFilePerTable") - override def autoCommitFailureClosesAllResultSets: Boolean = throw NotSupported() + override def autoCommitFailureClosesAllResultSets: Boolean = throw NotSupported("autoCommitFailureClosesAllResultSets") - override def supportsCatalogsInIndexDefinitions: Boolean = throw NotSupported() + override def supportsCatalogsInIndexDefinitions: Boolean = throw NotSupported("supportsCatalogsInIndexDefinitions") - override def storesLowerCaseQuotedIdentifiers: Boolean = throw NotSupported() + override def storesLowerCaseQuotedIdentifiers: Boolean = throw NotSupported("storesLowerCaseQuotedIdentifiers") - override def othersUpdatesAreVisible(`type`: Int): Boolean = throw NotSupported() + override def othersUpdatesAreVisible(`type`: Int): Boolean = throw NotSupported("othersUpdatesAreVisible") - override def supportsStatementPooling: Boolean = throw NotSupported() + override def supportsStatementPooling: Boolean = throw NotSupported("supportsStatementPooling") - override def supportsCatalogsInPrivilegeDefinitions: Boolean = throw NotSupported() + override def supportsCatalogsInPrivilegeDefinitions: Boolean = throw NotSupported("supportsCatalogsInPrivilegeDefinitions") - override def supportsStoredProcedures: Boolean = throw NotSupported() + override def supportsStoredProcedures: Boolean = throw NotSupported("supportsStoredProcedures") - override def supportsSelectForUpdate: Boolean = throw NotSupported() + override def supportsSelectForUpdate: Boolean = throw NotSupported("supportsSelectForUpdate") - override def supportsOpenStatementsAcrossCommit: Boolean = throw NotSupported() + override def supportsOpenStatementsAcrossCommit: Boolean = throw NotSupported("supportsOpenStatementsAcrossCommit") - override def supportsSubqueriesInComparisons: Boolean = throw NotSupported() + override def supportsSubqueriesInComparisons: Boolean = throw NotSupported("supportsSubqueriesInComparisons") - override def supportsTransactionIsolationLevel(level: Int): Boolean = throw NotSupported() + override def supportsTransactionIsolationLevel(level: Int): Boolean = throw NotSupported("supportsTransactionIsolationLevel") override def getTableTypes: ResultSet = new StaticResultSet[String](Headers.tableTypes, Iterator(Seq(TableTypes.TABLE.name), Seq(TableTypes.STREAM.name))) - override def getMaxColumnsInTable: Int = throw NotSupported() + override def getMaxColumnsInTable: Int = throw NotSupported("getMaxColumnsInTable") override def getConnection: Connection = ksqlConnection - override def updatesAreDetected(`type`: Int): Boolean = throw NotSupported() + override def updatesAreDetected(`type`: Int): Boolean = throw NotSupported("updatesAreDetected") - override def supportsPositionedDelete: Boolean = throw NotSupported() + override def supportsPositionedDelete: Boolean = throw NotSupported("supportsPositionedDelete") override def getColumns(catalog: String, schemaPattern: String, tableNamePattern: String, columnNamePattern: String): ResultSet = { @@ -431,51 +435,51 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D } tableSchemas ++= defaultFields - tableSchemas ++= describe.getResponse.asScala.map(_.asInstanceOf[SourceDescription]) - .flatMap(_.getSchema.asScala) - .filter(sch => columnPattern.matcher(sch.getName.toUpperCase).matches) - .map(sch => { - Seq[AnyRef]("", "", tableName, sch.getName, Int.box(Headers.mapDataType(sch.getType)), sch.getType, + tableSchemas ++= describe.getResponse.asScala.map(_.asInstanceOf[SourceDescriptionEntity]) + .map(_.getSourceDescription) + .filter(sd => columnPattern.matcher(sd.getName.toUpperCase).matches) + .map(sd => { + Seq[AnyRef]("", "", tableName, sd.getName, Int.box(Headers.mapDataType(sd.getType)), sd.getType, Int.box(Int.MaxValue), Int.box(0), "null", Int.box(10), Int.box(DatabaseMetaData.columnNullableUnknown), "", "", Int.box(-1), Int.box(-1), Int.box(32), Int.box(17), "", "", "", "", - Int.box(Headers.mapDataType(sch.getType)), "NO", "NO") + Int.box(Headers.mapDataType(sd.getType)), "NO", "NO") }).toIterator } new StaticResultSet[AnyRef](Headers.columns, tableSchemas) } - override def supportsResultSetType(`type`: Int): Boolean = throw NotSupported() + override def supportsResultSetType(`type`: Int): Boolean = throw NotSupported("supportsResultSetType") - override def supportsMinimumSQLGrammar: Boolean = throw NotSupported() + override def supportsMinimumSQLGrammar: Boolean = throw NotSupported("supportsMinimumSQLGrammar") - override def generatedKeyAlwaysReturned: Boolean = throw NotSupported() + override def generatedKeyAlwaysReturned: Boolean = throw NotSupported("generatedKeyAlwaysReturned") - override def supportsConvert: Boolean = throw NotSupported() + override def supportsConvert: Boolean = throw NotSupported("supportsConvert") - override def supportsConvert(fromType: Int, toType: Int): Boolean = throw NotSupported() + override def supportsConvert(fromType: Int, toType: Int): Boolean = throw NotSupported("supportsConvert") - override def getExportedKeys(catalog: String, schema: String, table: String): ResultSet = throw NotSupported() + override def getExportedKeys(catalog: String, schema: String, table: String): ResultSet = throw NotSupported("getExportedKeys") - override def supportsOrderByUnrelated: Boolean = throw NotSupported() + override def supportsOrderByUnrelated: Boolean = throw NotSupported("supportsOrderByUnrelated") - override def getSQLStateType: Int = throw NotSupported() + override def getSQLStateType: Int = throw NotSupported("getSQLStateType") - override def supportsOpenStatementsAcrossRollback: Boolean = throw NotSupported() + override def supportsOpenStatementsAcrossRollback: Boolean = throw NotSupported("supportsOpenStatementsAcrossRollback") - override def getMaxColumnsInIndex: Int = throw NotSupported() + override def getMaxColumnsInIndex: Int = throw NotSupported("getMaxColumnsInIndex") - override def getTimeDateFunctions: String = throw NotSupported() + override def getTimeDateFunctions: String = throw NotSupported("getTimeDateFunctions") - override def supportsSchemasInIndexDefinitions: Boolean = throw NotSupported() + override def supportsSchemasInIndexDefinitions: Boolean = throw NotSupported("supportsSchemasInIndexDefinitions") - override def supportsANSI92IntermediateSQL: Boolean = throw NotSupported() + override def supportsANSI92IntermediateSQL: Boolean = throw NotSupported("supportsANSI92IntermediateSQL") - override def getCatalogSeparator: String = throw NotSupported() + override def getCatalogSeparator: String = throw NotSupported("getCatalogSeparator") - override def othersInsertsAreVisible(`type`: Int): Boolean = throw NotSupported() + override def othersInsertsAreVisible(`type`: Int): Boolean = throw NotSupported("othersInsertsAreVisible") - override def supportsSchemasInTableDefinitions: Boolean = throw NotSupported() + override def supportsSchemasInTableDefinitions: Boolean = throw NotSupported("supportsSchemasInTableDefinitions") private def validateCatalogAndSchema(catalog: String, schema: String) = { if (catalog != null && catalog != "") throw UnknownCatalog(s"Unknown catalog $catalog") diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala index cb0bd0f..1ddcaf7 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala @@ -9,33 +9,33 @@ import io.confluent.ksql.rest.client.KsqlRestClient class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = 0) extends Statement with WrapperNotSupported { - override def setMaxFieldSize(max: Int): Unit = throw NotSupported() + override def setMaxFieldSize(max: Int): Unit = throw NotSupported("setMaxFieldSize") - override def getMoreResults: Boolean = throw NotSupported() + override def getMoreResults: Boolean = throw NotSupported("getMoreResults") - override def getMoreResults(current: Int): Boolean = throw NotSupported() + override def getMoreResults(current: Int): Boolean = throw NotSupported("getMoreResults") - override def clearWarnings(): Unit = throw NotSupported() + override def clearWarnings(): Unit = throw NotSupported("clearWarnings") - override def getGeneratedKeys: ResultSet = throw NotSupported() + override def getGeneratedKeys: ResultSet = throw NotSupported("getGeneratedKeys") - override def closeOnCompletion(): Unit = throw NotSupported() + override def closeOnCompletion(): Unit = throw NotSupported("closeOnCompletion") - override def cancel(): Unit = throw NotSupported() + override def cancel(): Unit = throw NotSupported("cancel") - override def getResultSet: ResultSet = throw NotSupported() + override def getResultSet: ResultSet = throw NotSupported("getResultSet") - override def isPoolable: Boolean = throw NotSupported() + override def isPoolable: Boolean = throw NotSupported("isPoolable") - override def setPoolable(poolable: Boolean): Unit = throw NotSupported() + override def setPoolable(poolable: Boolean): Unit = throw NotSupported("setPoolable") - override def setCursorName(name: String): Unit = throw NotSupported() + override def setCursorName(name: String): Unit = throw NotSupported("setCursorName") - override def getUpdateCount: Int = throw NotSupported() + override def getUpdateCount: Int = throw NotSupported("getUpdateCount") - override def addBatch(sql: String): Unit = throw NotSupported() + override def addBatch(sql: String): Unit = throw NotSupported("addBatch") - override def getMaxRows: Int = throw NotSupported() + override def getMaxRows: Int = throw NotSupported("getMaxRows") override def execute(sql: String): Boolean = ksqlClient.makeKsqlRequest(fixSql(sql)).isSuccessful @@ -57,51 +57,51 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = response.getResponse } - override def getResultSetType: Int = throw NotSupported() + override def getResultSetType: Int = throw NotSupported("getResultSetType") - override def setMaxRows(max: Int): Unit = throw NotSupported() + override def setMaxRows(max: Int): Unit = throw NotSupported("setMaxRows") - override def getFetchSize: Int = throw NotSupported() + override def getFetchSize: Int = throw NotSupported("getFetchSize") - override def getResultSetHoldability: Int = throw NotSupported() + override def getResultSetHoldability: Int = throw NotSupported("getResultSetHoldability") - override def setFetchDirection(direction: Int): Unit = throw NotSupported() + override def setFetchDirection(direction: Int): Unit = throw NotSupported("setFetchDirection") - override def getFetchDirection: Int = throw NotSupported() + override def getFetchDirection: Int = throw NotSupported("getFetchDirection") - override def getResultSetConcurrency: Int = throw NotSupported() + override def getResultSetConcurrency: Int = throw NotSupported("getResultSetConcurrency") - override def clearBatch(): Unit = throw NotSupported() + override def clearBatch(): Unit = throw NotSupported("clearBatch") - override def close(): Unit = throw NotSupported() + override def close(): Unit = throw NotSupported("close") - override def isClosed: Boolean = throw NotSupported() + override def isClosed: Boolean = throw NotSupported("isClosed") - override def executeUpdate(sql: String): Int = throw NotSupported() + override def executeUpdate(sql: String): Int = throw NotSupported("executeUpdate") - override def executeUpdate(sql: String, autoGeneratedKeys: Int): Int = throw NotSupported() + override def executeUpdate(sql: String, autoGeneratedKeys: Int): Int = throw NotSupported("executeUpdate") - override def executeUpdate(sql: String, columnIndexes: Array[Int]): Int = throw NotSupported() + override def executeUpdate(sql: String, columnIndexes: Array[Int]): Int = throw NotSupported("executeUpdate") - override def executeUpdate(sql: String, columnNames: Array[String]): Int = throw NotSupported() + override def executeUpdate(sql: String, columnNames: Array[String]): Int = throw NotSupported("executeUpdate") - override def getQueryTimeout: Int = throw NotSupported() + override def getQueryTimeout: Int = throw NotSupported("getQueryTimeout") - override def getWarnings: SQLWarning = throw NotSupported() + override def getWarnings: SQLWarning = throw NotSupported("getWarnings") - override def setFetchSize(rows: Int): Unit = throw NotSupported() + override def setFetchSize(rows: Int): Unit = throw NotSupported("setFetchSize") - override def setQueryTimeout(seconds: Int): Unit = throw NotSupported() + override def setQueryTimeout(seconds: Int): Unit = throw NotSupported("setQueryTimeout") - override def executeBatch(): Array[Int] = throw NotSupported() + override def executeBatch(): Array[Int] = throw NotSupported("executeBatch") - override def setEscapeProcessing(enable: Boolean): Unit = throw NotSupported() + override def setEscapeProcessing(enable: Boolean): Unit = throw NotSupported("setEscapeProcessing") - override def getConnection: Connection = throw NotSupported() + override def getConnection: Connection = throw NotSupported("getConnection") - override def getMaxFieldSize: Int = throw NotSupported() + override def getMaxFieldSize: Int = throw NotSupported("getMaxFieldSize") - override def isCloseOnCompletion: Boolean = throw NotSupported() + override def isCloseOnCompletion: Boolean = throw NotSupported("isCloseOnCompletion") private def fixSql(sql: String) = if (sql.trim.endsWith(";")) sql else sql + ";" diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/WrapperNotSupported.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/WrapperNotSupported.scala index 032c911..80c942f 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/WrapperNotSupported.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/WrapperNotSupported.scala @@ -6,8 +6,8 @@ import com.github.mmolimar.ksql.jdbc.Exceptions._ trait WrapperNotSupported extends Wrapper { - override def unwrap[T](iface: Class[T]): T = throw NotSupported() + override def unwrap[T](iface: Class[T]): T = throw NotSupported("unknown") - override def isWrapperFor(iface: Class[_]): Boolean = throw NotSupported() + override def isWrapperFor(iface: Class[_]): Boolean = throw NotSupported("unknown") } \ No newline at end of file diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala index 5e25268..f817c8d 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala @@ -1,6 +1,8 @@ package com.github.mmolimar.ksql.jdbc.resultset +import java.io.Closeable import java.sql.ResultSet +import java.util.Iterator import com.github.mmolimar.ksql.jdbc.Exceptions._ import com.github.mmolimar.ksql.jdbc.NotSupported @@ -14,10 +16,23 @@ import scala.concurrent.duration._ import scala.concurrent.{Await, Future, TimeoutException} import scala.util.{Failure, Success, Try} -class KsqlResultSet(private[jdbc] val stream: KsqlRestClient.QueryStream, val timeout: Long = 0) +private[resultset] class JdbcQueryStream(stream: KsqlRestClient.QueryStream, timeout: Long) + extends Closeable with Iterator[StreamedRow] { + + override def close(): Unit = stream.close + + override def hasNext: Boolean = stream.hasNext + + override def next(): StreamedRow = stream.next + +} + +class KsqlResultSet(private[jdbc] val stream: JdbcQueryStream, val timeout: Long = 0) extends AbstractResultSet[StreamedRow](stream) { - private val emptyRow: StreamedRow = new StreamedRow(new GenericRow, null) + def this(stream: KsqlRestClient.QueryStream, timeout: Long) = this(new JdbcQueryStream(stream, timeout)) + + private val emptyRow: StreamedRow = StreamedRow.row(new GenericRow) private val waitDuration = if (timeout > 0) timeout millis else Duration.Inf @@ -58,6 +73,6 @@ class KsqlResultSet(private[jdbc] val stream: KsqlRestClient.QueryStream, val ti currentRow.get.getRow.getColumns.get(columnIndex - 1).asInstanceOf[T] } - override protected def getColumnIndex(columnLabel: String): Int = throw NotSupported() + override protected def getColumnIndex(columnLabel: String): Int = throw NotSupported("getting column by label") } diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala index b81fcc6..29cb4da 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala @@ -13,383 +13,387 @@ import com.github.mmolimar.ksql.jdbc.{EmptyRow, InvalidColumn, NotSupported, Wra private[resultset] class ResultSetNotSupported extends ResultSet with WrapperNotSupported { - override def getType: Int = throw NotSupported() + override def getType: Int = throw NotSupported("getType") - override def isBeforeFirst: Boolean = throw NotSupported() + override def isBeforeFirst: Boolean = throw NotSupported("isBeforeFirst") - override def next: Boolean = throw NotSupported() + override def next: Boolean = throw NotSupported("next") - override def updateString(columnIndex: Int, x: String): Unit = throw NotSupported() + override def updateString(columnIndex: Int, x: String): Unit = throw NotSupported("updateString") - override def updateString(columnLabel: String, x: String): Unit = throw NotSupported() + override def updateString(columnLabel: String, x: String): Unit = throw NotSupported("updateString") - override def getTimestamp(columnIndex: Int): Timestamp = throw NotSupported() + override def getTimestamp(columnIndex: Int): Timestamp = throw NotSupported("getTimestamp") - override def getTimestamp(columnLabel: String): Timestamp = throw NotSupported() + override def getTimestamp(columnLabel: String): Timestamp = throw NotSupported("getTimestamp") - override def getTimestamp(columnIndex: Int, cal: Calendar): Timestamp = throw NotSupported() + override def getTimestamp(columnIndex: Int, cal: Calendar): Timestamp = throw NotSupported("getTimestamp") - override def getTimestamp(columnLabel: String, cal: Calendar): Timestamp = throw NotSupported() + override def getTimestamp(columnLabel: String, cal: Calendar): Timestamp = throw NotSupported("getTimestamp") - override def updateNString(columnIndex: Int, nString: String): Unit = throw NotSupported() + override def updateNString(columnIndex: Int, nString: String): Unit = throw NotSupported("updateNString") - override def updateNString(columnLabel: String, nString: String): Unit = throw NotSupported() + override def updateNString(columnLabel: String, nString: String): Unit = throw NotSupported("updateNString") - override def clearWarnings(): Unit = throw NotSupported() + override def clearWarnings(): Unit = throw NotSupported("clearWarnings") - override def updateTimestamp(columnIndex: Int, x: Timestamp): Unit = throw NotSupported() + override def updateTimestamp(columnIndex: Int, x: Timestamp): Unit = throw NotSupported("updateTimestamp") - override def updateTimestamp(columnLabel: String, x: Timestamp): Unit = throw NotSupported() + override def updateTimestamp(columnLabel: String, x: Timestamp): Unit = throw NotSupported("updateTimestamp") - override def updateByte(columnIndex: Int, x: Byte): Unit = throw NotSupported() + override def updateByte(columnIndex: Int, x: Byte): Unit = throw NotSupported("updateByte") - override def updateByte(columnLabel: String, x: Byte): Unit = throw NotSupported() + override def updateByte(columnLabel: String, x: Byte): Unit = throw NotSupported("updateByte") - override def updateBigDecimal(columnIndex: Int, x: BigDecimal): Unit = throw NotSupported() + override def updateBigDecimal(columnIndex: Int, x: BigDecimal): Unit = throw NotSupported("updateBigDecimal") - override def updateBigDecimal(columnLabel: String, x: BigDecimal): Unit = throw NotSupported() + override def updateBigDecimal(columnLabel: String, x: BigDecimal): Unit = throw NotSupported("updateBigDecimal") - override def updateDouble(columnIndex: Int, x: Double): Unit = throw NotSupported() + override def updateDouble(columnIndex: Int, x: Double): Unit = throw NotSupported("updateDouble") - override def updateDouble(columnLabel: String, x: Double): Unit = throw NotSupported() + override def updateDouble(columnLabel: String, x: Double): Unit = throw NotSupported("updateDouble") - override def updateDate(columnIndex: Int, x: Date): Unit = throw NotSupported() + override def updateDate(columnIndex: Int, x: Date): Unit = throw NotSupported("updateDate") - override def updateDate(columnLabel: String, x: Date): Unit = throw NotSupported() + override def updateDate(columnLabel: String, x: Date): Unit = throw NotSupported("updateDate") - override def isAfterLast: Boolean = throw NotSupported() + override def isAfterLast: Boolean = throw NotSupported("isAfterLast") - override def updateBoolean(columnIndex: Int, x: Boolean): Unit = throw NotSupported() + override def updateBoolean(columnIndex: Int, x: Boolean): Unit = throw NotSupported("updateBoolean") - override def updateBoolean(columnLabel: String, x: Boolean): Unit = throw NotSupported() + override def updateBoolean(columnLabel: String, x: Boolean): Unit = throw NotSupported("updateBoolean") - override def getBinaryStream(columnIndex: Int): InputStream = throw NotSupported() + override def getBinaryStream(columnIndex: Int): InputStream = throw NotSupported("getBinaryStream") - override def getBinaryStream(columnLabel: String): InputStream = throw NotSupported() + override def getBinaryStream(columnLabel: String): InputStream = throw NotSupported("getBinaryStream") - override def beforeFirst(): Unit = throw NotSupported() + override def beforeFirst(): Unit = throw NotSupported("beforeFirst") - override def updateNCharacterStream(columnIndex: Int, x: Reader, length: Long): Unit = throw NotSupported() + override def updateNCharacterStream(columnIndex: Int, x: Reader, length: Long): Unit = + throw NotSupported("updateNCharacterStream") - override def updateNCharacterStream(columnLabel: String, reader: Reader, length: Long): Unit = throw NotSupported() + override def updateNCharacterStream(columnLabel: String, reader: Reader, length: Long): Unit = + throw NotSupported("updateNCharacterStream") - override def updateNCharacterStream(columnIndex: Int, x: Reader): Unit = throw NotSupported() + override def updateNCharacterStream(columnIndex: Int, x: Reader): Unit = + throw NotSupported("updateNCharacterStream") - override def updateNCharacterStream(columnLabel: String, reader: Reader): Unit = throw NotSupported() + override def updateNCharacterStream(columnLabel: String, reader: Reader): Unit = + throw NotSupported("updateNCharacterStream") - override def updateNClob(columnIndex: Int, nClob: NClob): Unit = throw NotSupported() + override def updateNClob(columnIndex: Int, nClob: NClob): Unit = throw NotSupported("updateNClob") - override def updateNClob(columnLabel: String, nClob: NClob): Unit = throw NotSupported() + override def updateNClob(columnLabel: String, nClob: NClob): Unit = throw NotSupported("updateNClob") - override def updateNClob(columnIndex: Int, reader: Reader, length: Long): Unit = throw NotSupported() + override def updateNClob(columnIndex: Int, reader: Reader, length: Long): Unit = throw NotSupported("updateNClob") - override def updateNClob(columnLabel: String, reader: Reader, length: Long): Unit = throw NotSupported() + override def updateNClob(columnLabel: String, reader: Reader, length: Long): Unit = throw NotSupported("updateNClob") - override def updateNClob(columnIndex: Int, reader: Reader): Unit = throw NotSupported() + override def updateNClob(columnIndex: Int, reader: Reader): Unit = throw NotSupported("updateNClob") - override def updateNClob(columnLabel: String, reader: Reader): Unit = throw NotSupported() + override def updateNClob(columnLabel: String, reader: Reader): Unit = throw NotSupported("updateNClob") - override def last(): Boolean = throw NotSupported() + override def last(): Boolean = throw NotSupported("last") - override def isLast: Boolean = throw NotSupported() + override def isLast: Boolean = throw NotSupported("isLast") - override def getNClob(columnIndex: Int): NClob = throw NotSupported() + override def getNClob(columnIndex: Int): NClob = throw NotSupported("getNClob") - override def getNClob(columnLabel: String): NClob = throw NotSupported() + override def getNClob(columnLabel: String): NClob = throw NotSupported("getNClob") - override def getCharacterStream(columnIndex: Int): Reader = throw NotSupported() + override def getCharacterStream(columnIndex: Int): Reader = throw NotSupported("getCharacterStream") - override def getCharacterStream(columnLabel: String): Reader = throw NotSupported() + override def getCharacterStream(columnLabel: String): Reader = throw NotSupported("getCharacterStream") - override def updateArray(columnIndex: Int, x: Array): Unit = throw NotSupported() + override def updateArray(columnIndex: Int, x: Array): Unit = throw NotSupported("updateArray") - override def updateArray(columnLabel: String, x: Array): Unit = throw NotSupported() + override def updateArray(columnLabel: String, x: Array): Unit = throw NotSupported("updateArray") - override def updateBlob(columnIndex: Int, x: Blob): Unit = throw NotSupported() + override def updateBlob(columnIndex: Int, x: Blob): Unit = throw NotSupported("updateBlob") - override def updateBlob(columnLabel: String, x: Blob): Unit = throw NotSupported() + override def updateBlob(columnLabel: String, x: Blob): Unit = throw NotSupported("updateBlob") - override def updateBlob(columnIndex: Int, inputStream: InputStream, length: Long): Unit = throw NotSupported() + override def updateBlob(columnIndex: Int, inputStream: InputStream, length: Long): Unit = throw NotSupported("updateBlob") - override def updateBlob(columnLabel: String, inputStream: InputStream, length: Long): Unit = throw NotSupported() + override def updateBlob(columnLabel: String, inputStream: InputStream, length: Long): Unit = throw NotSupported("updateBlob") - override def updateBlob(columnIndex: Int, inputStream: InputStream): Unit = throw NotSupported() + override def updateBlob(columnIndex: Int, inputStream: InputStream): Unit = throw NotSupported("updateBlob") - override def updateBlob(columnLabel: String, inputStream: InputStream): Unit = throw NotSupported() + override def updateBlob(columnLabel: String, inputStream: InputStream): Unit = throw NotSupported("updateBlob") - override def getDouble(columnIndex: Int): Double = throw NotSupported() + override def getDouble(columnIndex: Int): Double = throw NotSupported("getDouble") - override def getDouble(columnLabel: String): Double = throw NotSupported() + override def getDouble(columnLabel: String): Double = throw NotSupported("getDouble") - override def getArray(columnIndex: Int): Array = throw NotSupported() + override def getArray(columnIndex: Int): Array = throw NotSupported("getArray") - override def getArray(columnLabel: String): Array = throw NotSupported() + override def getArray(columnLabel: String): Array = throw NotSupported("getArray") - override def isFirst: Boolean = throw NotSupported() + override def isFirst: Boolean = throw NotSupported("isFirst") - override def getURL(columnIndex: Int): URL = throw NotSupported() + override def getURL(columnIndex: Int): URL = throw NotSupported("getURL") - override def getURL(columnLabel: String): URL = throw NotSupported() + override def getURL(columnLabel: String): URL = throw NotSupported("getURL") - override def updateRow(): Unit = throw NotSupported() + override def updateRow(): Unit = throw NotSupported("updateRow") - override def insertRow(): Unit = throw NotSupported() + override def insertRow(): Unit = throw NotSupported("insertRow") - override def getMetaData: ResultSetMetaData = throw NotSupported() + override def getMetaData: ResultSetMetaData = throw NotSupported("getMetaData") - override def updateBinaryStream(columnIndex: Int, x: InputStream, length: Int): Unit = throw NotSupported() + override def updateBinaryStream(columnIndex: Int, x: InputStream, length: Int): Unit = throw NotSupported("updateBinaryStream") - override def updateBinaryStream(columnLabel: String, x: InputStream, length: Int): Unit = throw NotSupported() + override def updateBinaryStream(columnLabel: String, x: InputStream, length: Int): Unit = throw NotSupported("updateBinaryStream") - override def updateBinaryStream(columnIndex: Int, x: InputStream, length: Long): Unit = throw NotSupported() + override def updateBinaryStream(columnIndex: Int, x: InputStream, length: Long): Unit = throw NotSupported("updateBinaryStream") - override def updateBinaryStream(columnLabel: String, x: InputStream, length: Long): Unit = throw NotSupported() + override def updateBinaryStream(columnLabel: String, x: InputStream, length: Long): Unit = throw NotSupported("updateBinaryStream") - override def updateBinaryStream(columnIndex: Int, x: InputStream): Unit = throw NotSupported() + override def updateBinaryStream(columnIndex: Int, x: InputStream): Unit = throw NotSupported("updateBinaryStream") - override def updateBinaryStream(columnLabel: String, x: InputStream): Unit = throw NotSupported() + override def updateBinaryStream(columnLabel: String, x: InputStream): Unit = throw NotSupported("updateBinaryStream") - override def absolute(row: Int): Boolean = throw NotSupported() + override def absolute(row: Int): Boolean = throw NotSupported("absolute") - override def updateRowId(columnIndex: Int, x: RowId): Unit = throw NotSupported() + override def updateRowId(columnIndex: Int, x: RowId): Unit = throw NotSupported("updateRowId") - override def updateRowId(columnLabel: String, x: RowId): Unit = throw NotSupported() + override def updateRowId(columnLabel: String, x: RowId): Unit = throw NotSupported("updateRowId") - override def getRowId(columnIndex: Int): RowId = throw NotSupported() + override def getRowId(columnIndex: Int): RowId = throw NotSupported("getRowId") - override def getRowId(columnLabel: String): RowId = throw NotSupported() + override def getRowId(columnLabel: String): RowId = throw NotSupported("getRowId") - override def moveToInsertRow(): Unit = throw NotSupported() + override def moveToInsertRow(): Unit = throw NotSupported("moveToInsertRow") - override def rowInserted(): Boolean = throw NotSupported() + override def rowInserted(): Boolean = throw NotSupported("rowInserted") - override def getFloat(columnIndex: Int): Float = throw NotSupported() + override def getFloat(columnIndex: Int): Float = throw NotSupported("getFloat") - override def getFloat(columnLabel: String): Float = throw NotSupported() + override def getFloat(columnLabel: String): Float = throw NotSupported("getFloat") - override def getBigDecimal(columnIndex: Int, scale: Int): BigDecimal = throw NotSupported() + override def getBigDecimal(columnIndex: Int, scale: Int): BigDecimal = throw NotSupported("getBigDecimal") - override def getBigDecimal(columnLabel: String, scale: Int): BigDecimal = throw NotSupported() + override def getBigDecimal(columnLabel: String, scale: Int): BigDecimal = throw NotSupported("getBigDecimal") - override def getBigDecimal(columnIndex: Int): BigDecimal = throw NotSupported() + override def getBigDecimal(columnIndex: Int): BigDecimal = throw NotSupported("getBigDecimal") - override def getBigDecimal(columnLabel: String): BigDecimal = throw NotSupported() + override def getBigDecimal(columnLabel: String): BigDecimal = throw NotSupported("getBigDecimal") - override def getClob(columnIndex: Int): Clob = throw NotSupported() + override def getClob(columnIndex: Int): Clob = throw NotSupported("getClob") - override def getClob(columnLabel: String): Clob = throw NotSupported() + override def getClob(columnLabel: String): Clob = throw NotSupported("getClob") - override def getRow: Int = throw NotSupported() + override def getRow: Int = throw NotSupported("getRow") - override def getLong(columnIndex: Int): Long = throw NotSupported() + override def getLong(columnIndex: Int): Long = throw NotSupported("getLong") - override def getLong(columnLabel: String): Long = throw NotSupported() + override def getLong(columnLabel: String): Long = throw NotSupported("getLong") - override def getHoldability: Int = throw NotSupported() + override def getHoldability: Int = throw NotSupported("getHoldability") - override def updateFloat(columnIndex: Int, x: Float): Unit = throw NotSupported() + override def updateFloat(columnIndex: Int, x: Float): Unit = throw NotSupported("updateFloat") - override def updateFloat(columnLabel: String, x: Float): Unit = throw NotSupported() + override def updateFloat(columnLabel: String, x: Float): Unit = throw NotSupported("updateFloat") - override def afterLast(): Unit = throw NotSupported() + override def afterLast: Unit = throw NotSupported("afterLast") - override def refreshRow(): Unit = throw NotSupported() + override def refreshRow: Unit = throw NotSupported("refreshRow") - override def getNString(columnIndex: Int): String = throw NotSupported() + override def getNString(columnIndex: Int): String = throw NotSupported("getNString") - override def getNString(columnLabel: String): String = throw NotSupported() + override def getNString(columnLabel: String): String = throw NotSupported("getNString") - override def deleteRow(): Unit = throw NotSupported() + override def deleteRow: Unit = throw NotSupported("deleteRow") - override def getConcurrency: Int = throw NotSupported() + override def getConcurrency: Int = throw NotSupported("getConcurrency") - override def updateObject(columnIndex: Int, x: scala.Any, scaleOrLength: Int): Unit = throw NotSupported() + override def updateObject(columnIndex: Int, x: scala.Any, scaleOrLength: Int): Unit = throw NotSupported("updateObject") - override def updateObject(columnIndex: Int, x: scala.Any): Unit = throw NotSupported() + override def updateObject(columnIndex: Int, x: scala.Any): Unit = throw NotSupported("updateObject") - override def updateObject(columnLabel: String, x: scala.Any, scaleOrLength: Int): Unit = throw NotSupported() + override def updateObject(columnLabel: String, x: scala.Any, scaleOrLength: Int): Unit = throw NotSupported("updateObject") - override def updateObject(columnLabel: String, x: scala.Any): Unit = throw NotSupported() + override def updateObject(columnLabel: String, x: scala.Any): Unit = throw NotSupported("updateObject") - override def getFetchSize: Int = throw NotSupported() + override def getFetchSize: Int = throw NotSupported("getFetchSize") - override def getTime(columnIndex: Int): Time = throw NotSupported() + override def getTime(columnIndex: Int): Time = throw NotSupported("getTime") - override def getTime(columnLabel: String): Time = throw NotSupported() + override def getTime(columnLabel: String): Time = throw NotSupported("getTime") - override def getTime(columnIndex: Int, cal: Calendar): Time = throw NotSupported() + override def getTime(columnIndex: Int, cal: Calendar): Time = throw NotSupported("getTime") - override def getTime(columnLabel: String, cal: Calendar): Time = throw NotSupported() + override def getTime(columnLabel: String, cal: Calendar): Time = throw NotSupported("getTime") - override def updateCharacterStream(columnIndex: Int, x: Reader, length: Int): Unit = throw NotSupported() + override def updateCharacterStream(columnIndex: Int, x: Reader, length: Int): Unit = throw NotSupported("updateCharacterStream") - override def updateCharacterStream(columnLabel: String, reader: Reader, length: Int): Unit = throw NotSupported() + override def updateCharacterStream(columnLabel: String, reader: Reader, length: Int): Unit = throw NotSupported("updateCharacterStream") - override def updateCharacterStream(columnIndex: Int, x: Reader, length: Long): Unit = throw NotSupported() + override def updateCharacterStream(columnIndex: Int, x: Reader, length: Long): Unit = throw NotSupported("updateCharacterStream") - override def updateCharacterStream(columnLabel: String, reader: Reader, length: Long): Unit = throw NotSupported() + override def updateCharacterStream(columnLabel: String, reader: Reader, length: Long): Unit = throw NotSupported("updateCharacterStream") - override def updateCharacterStream(columnIndex: Int, x: Reader): Unit = throw NotSupported() + override def updateCharacterStream(columnIndex: Int, x: Reader): Unit = throw NotSupported("updateCharacterStream") - override def updateCharacterStream(columnLabel: String, reader: Reader): Unit = throw NotSupported() + override def updateCharacterStream(columnLabel: String, reader: Reader): Unit = throw NotSupported("updateCharacterStream") - override def getByte(columnIndex: Int): Byte = throw NotSupported() + override def getByte(columnIndex: Int): Byte = throw NotSupported("getByte") - override def getByte(columnLabel: String): Byte = throw NotSupported() + override def getByte(columnLabel: String): Byte = throw NotSupported("getByte") - override def getBoolean(columnIndex: Int): Boolean = throw NotSupported() + override def getBoolean(columnIndex: Int): Boolean = throw NotSupported("getBoolean") - override def getBoolean(columnLabel: String): Boolean = throw NotSupported() + override def getBoolean(columnLabel: String): Boolean = throw NotSupported("getBoolean") - override def setFetchDirection(direction: Int): Unit = throw NotSupported() + override def setFetchDirection(direction: Int): Unit = throw NotSupported("setFetchDirection") - override def getFetchDirection: Int = throw NotSupported() + override def getFetchDirection: Int = throw NotSupported("getFetchDirection") - override def updateRef(columnIndex: Int, x: Ref): Unit = throw NotSupported() + override def updateRef(columnIndex: Int, x: Ref): Unit = throw NotSupported("updateRef") - override def updateRef(columnLabel: String, x: Ref): Unit = throw NotSupported() + override def updateRef(columnLabel: String, x: Ref): Unit = throw NotSupported("updateRef") - override def getAsciiStream(columnIndex: Int): InputStream = throw NotSupported() + override def getAsciiStream(columnIndex: Int): InputStream = throw NotSupported("getAsciiStream") - override def getAsciiStream(columnLabel: String): InputStream = throw NotSupported() + override def getAsciiStream(columnLabel: String): InputStream = throw NotSupported("getAsciiStream") - override def getShort(columnIndex: Int): Short = throw NotSupported() + override def getShort(columnIndex: Int): Short = throw NotSupported("getShort") - override def getShort(columnLabel: String): Short = throw NotSupported() + override def getShort(columnLabel: String): Short = throw NotSupported("getShort") - override def getObject(columnIndex: Int): AnyRef = throw NotSupported() + override def getObject(columnIndex: Int): AnyRef = throw NotSupported("getObject") - override def getObject(columnLabel: String): AnyRef = throw NotSupported() + override def getObject(columnLabel: String): AnyRef = throw NotSupported("getObject") - override def getObject(columnIndex: Int, map: util.Map[String, Class[_]]): AnyRef = throw NotSupported() + override def getObject(columnIndex: Int, map: util.Map[String, Class[_]]): AnyRef = throw NotSupported("getObject") - override def getObject(columnLabel: String, map: util.Map[String, Class[_]]): AnyRef = throw NotSupported() + override def getObject(columnLabel: String, map: util.Map[String, Class[_]]): AnyRef = throw NotSupported("getObject") - override def getObject[T](columnIndex: Int, `type`: Class[T]): T = throw NotSupported() + override def getObject[T](columnIndex: Int, `type`: Class[T]): T = throw NotSupported("getObject") - override def getObject[T](columnLabel: String, `type`: Class[T]): T = throw NotSupported() + override def getObject[T](columnLabel: String, `type`: Class[T]): T = throw NotSupported("getObject") - override def updateShort(columnIndex: Int, x: Short): Unit = throw NotSupported() + override def updateShort(columnIndex: Int, x: Short): Unit = throw NotSupported("updateShort") - override def updateShort(columnLabel: String, x: Short): Unit = throw NotSupported() + override def updateShort(columnLabel: String, x: Short): Unit = throw NotSupported("updateShort") - override def getNCharacterStream(columnIndex: Int): Reader = throw NotSupported() + override def getNCharacterStream(columnIndex: Int): Reader = throw NotSupported("getNCharacterStream") - override def getNCharacterStream(columnLabel: String): Reader = throw NotSupported() + override def getNCharacterStream(columnLabel: String): Reader = throw NotSupported("getNCharacterStream") - override def close(): Unit = throw NotSupported() + override def close: Unit = throw NotSupported("close") - override def relative(rows: Int): Boolean = throw NotSupported() + override def relative(rows: Int): Boolean = throw NotSupported("relative") - override def updateInt(columnIndex: Int, x: Int): Unit = throw NotSupported() + override def updateInt(columnIndex: Int, x: Int): Unit = throw NotSupported("updateInt") - override def updateInt(columnLabel: String, x: Int): Unit = throw NotSupported() + override def updateInt(columnLabel: String, x: Int): Unit = throw NotSupported("updateInt") - override def wasNull(): Boolean = throw NotSupported() + override def wasNull: Boolean = throw NotSupported("wasNull") - override def rowUpdated(): Boolean = throw NotSupported() + override def rowUpdated: Boolean = throw NotSupported("rowUpdated") - override def getRef(columnIndex: Int): Ref = throw NotSupported() + override def getRef(columnIndex: Int): Ref = throw NotSupported("getRef") - override def getRef(columnLabel: String): Ref = throw NotSupported() + override def getRef(columnLabel: String): Ref = throw NotSupported("getRef") - override def updateLong(columnIndex: Int, x: Long): Unit = throw NotSupported() + override def updateLong(columnIndex: Int, x: Long): Unit = throw NotSupported("updateLong") - override def updateLong(columnLabel: String, x: Long): Unit = throw NotSupported() + override def updateLong(columnLabel: String, x: Long): Unit = throw NotSupported("updateLong") - override def moveToCurrentRow(): Unit = throw NotSupported() + override def moveToCurrentRow: Unit = throw NotSupported("moveToCurrentRow") - override def isClosed: Boolean = throw NotSupported() + override def isClosed: Boolean = throw NotSupported("isClosed") - override def updateClob(columnIndex: Int, x: Clob): Unit = throw NotSupported() + override def updateClob(columnIndex: Int, x: Clob): Unit = throw NotSupported("updateClob") - override def updateClob(columnLabel: String, x: Clob): Unit = throw NotSupported() + override def updateClob(columnLabel: String, x: Clob): Unit = throw NotSupported("updateClob") - override def updateClob(columnIndex: Int, reader: Reader, length: Long): Unit = throw NotSupported() + override def updateClob(columnIndex: Int, reader: Reader, length: Long): Unit = throw NotSupported("updateClob") - override def updateClob(columnLabel: String, reader: Reader, length: Long): Unit = throw NotSupported() + override def updateClob(columnLabel: String, reader: Reader, length: Long): Unit = throw NotSupported("updateClob") - override def updateClob(columnIndex: Int, reader: Reader): Unit = throw NotSupported() + override def updateClob(columnIndex: Int, reader: Reader): Unit = throw NotSupported("updateClob") - override def updateClob(columnLabel: String, reader: Reader): Unit = throw NotSupported() + override def updateClob(columnLabel: String, reader: Reader): Unit = throw NotSupported("updateClob") - override def findColumn(columnLabel: String): Int = throw NotSupported() + override def findColumn(columnLabel: String): Int = throw NotSupported("findColumn") - override def getWarnings: SQLWarning = throw NotSupported() + override def getWarnings: SQLWarning = throw NotSupported("getWarnings") - override def getDate(columnIndex: Int): Date = throw NotSupported() + override def getDate(columnIndex: Int): Date = throw NotSupported("getDate") - override def getDate(columnLabel: String): Date = throw NotSupported() + override def getDate(columnLabel: String): Date = throw NotSupported("getDate") - override def getDate(columnIndex: Int, cal: Calendar): Date = throw NotSupported() + override def getDate(columnIndex: Int, cal: Calendar): Date = throw NotSupported("getDate") - override def getDate(columnLabel: String, cal: Calendar): Date = throw NotSupported() + override def getDate(columnLabel: String, cal: Calendar): Date = throw NotSupported("getDate") - override def getCursorName: String = throw NotSupported() + override def getCursorName: String = throw NotSupported("getCursorName") - override def updateNull(columnIndex: Int): Unit = throw NotSupported() + override def updateNull(columnIndex: Int): Unit = throw NotSupported("updateNull") - override def updateNull(columnLabel: String): Unit = throw NotSupported() + override def updateNull(columnLabel: String): Unit = throw NotSupported("updateNull") - override def getStatement: Statement = throw NotSupported() + override def getStatement: Statement = throw NotSupported("getStatement") - override def cancelRowUpdates(): Unit = throw NotSupported() + override def cancelRowUpdates: Unit = throw NotSupported("cancelRowUpdates") - override def getSQLXML(columnIndex: Int): SQLXML = throw NotSupported() + override def getSQLXML(columnIndex: Int): SQLXML = throw NotSupported("getSQLXML") - override def getSQLXML(columnLabel: String): SQLXML = throw NotSupported() + override def getSQLXML(columnLabel: String): SQLXML = throw NotSupported("getSQLXML") - override def getUnicodeStream(columnIndex: Int): InputStream = throw NotSupported() + override def getUnicodeStream(columnIndex: Int): InputStream = throw NotSupported("getUnicodeStream") - override def getUnicodeStream(columnLabel: String): InputStream = throw NotSupported() + override def getUnicodeStream(columnLabel: String): InputStream = throw NotSupported("getUnicodeStream") - override def getInt(columnIndex: Int): Int = throw NotSupported() + override def getInt(columnIndex: Int): Int = throw NotSupported("getInt") - override def getInt(columnLabel: String): Int = throw NotSupported() + override def getInt(columnLabel: String): Int = throw NotSupported("getInt") - override def updateTime(columnIndex: Int, x: Time): Unit = throw NotSupported() + override def updateTime(columnIndex: Int, x: Time): Unit = throw NotSupported("updateTime") - override def updateTime(columnLabel: String, x: Time): Unit = throw NotSupported() + override def updateTime(columnLabel: String, x: Time): Unit = throw NotSupported("updateTime") - override def setFetchSize(rows: Int): Unit = throw NotSupported() + override def setFetchSize(rows: Int): Unit = throw NotSupported("setFetchSize") - override def previous(): Boolean = throw NotSupported() + override def previous: Boolean = throw NotSupported("previous") - override def updateAsciiStream(columnIndex: Int, x: InputStream, length: Int): Unit = throw NotSupported() + override def updateAsciiStream(columnIndex: Int, x: InputStream, length: Int): Unit = throw NotSupported("updateAsciiStream") - override def updateAsciiStream(columnLabel: String, x: InputStream, length: Int): Unit = throw NotSupported() + override def updateAsciiStream(columnLabel: String, x: InputStream, length: Int): Unit = throw NotSupported("updateAsciiStream") - override def updateAsciiStream(columnIndex: Int, x: InputStream, length: Long): Unit = throw NotSupported() + override def updateAsciiStream(columnIndex: Int, x: InputStream, length: Long): Unit = throw NotSupported("updateAsciiStream") - override def updateAsciiStream(columnLabel: String, x: InputStream, length: Long): Unit = throw NotSupported() + override def updateAsciiStream(columnLabel: String, x: InputStream, length: Long): Unit = throw NotSupported("updateAsciiStream") - override def updateAsciiStream(columnIndex: Int, x: InputStream): Unit = throw NotSupported() + override def updateAsciiStream(columnIndex: Int, x: InputStream): Unit = throw NotSupported("updateAsciiStream") - override def updateAsciiStream(columnLabel: String, x: InputStream): Unit = throw NotSupported() + override def updateAsciiStream(columnLabel: String, x: InputStream): Unit = throw NotSupported("updateAsciiStream") - override def rowDeleted(): Boolean = throw NotSupported() + override def rowDeleted: Boolean = throw NotSupported("rowDeleted") - override def getBlob(columnIndex: Int): Blob = throw NotSupported() + override def getBlob(columnIndex: Int): Blob = throw NotSupported("getBlob") - override def getBlob(columnLabel: String): Blob = throw NotSupported() + override def getBlob(columnLabel: String): Blob = throw NotSupported("getBlob") - override def first(): Boolean = throw NotSupported() + override def first: Boolean = throw NotSupported("first") - override def getBytes(columnIndex: Int): scala.Array[Byte] = throw NotSupported() + override def getBytes(columnIndex: Int): scala.Array[Byte] = throw NotSupported("getBytes") - override def getBytes(columnLabel: String): scala.Array[Byte] = throw NotSupported() + override def getBytes(columnLabel: String): scala.Array[Byte] = throw NotSupported("getBytes") - override def updateBytes(columnIndex: Int, x: scala.Array[Byte]): Unit = throw NotSupported() + override def updateBytes(columnIndex: Int, x: scala.Array[Byte]): Unit = throw NotSupported("updateBytes") - override def updateBytes(columnLabel: String, x: scala.Array[Byte]): Unit = throw NotSupported() + override def updateBytes(columnLabel: String, x: scala.Array[Byte]): Unit = throw NotSupported("updateBytes") - override def updateSQLXML(columnIndex: Int, xmlObject: SQLXML): Unit = throw NotSupported() + override def updateSQLXML(columnIndex: Int, xmlObject: SQLXML): Unit = throw NotSupported("updateSQLXML") - override def updateSQLXML(columnLabel: String, xmlObject: SQLXML): Unit = throw NotSupported() + override def updateSQLXML(columnLabel: String, xmlObject: SQLXML): Unit = throw NotSupported("updateSQLXML") - override def getString(columnIndex: Int): String = throw NotSupported() + override def getString(columnIndex: Int): String = throw NotSupported("getString") - override def getString(columnLabel: String): String = throw NotSupported() + override def getString(columnLabel: String): String = throw NotSupported("getString") } From 0679140898226aa61c8e4eb1a5d8682f1415caae Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Fri, 18 Jan 2019 20:33:13 -0600 Subject: [PATCH 05/47] Packaging type plugin in sbt --- project/Build.scala | 2 +- project/Configs.scala | 2 +- project/PackagingTypePlugin.scala | 8 ++++++++ project/Testing.scala | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 project/PackagingTypePlugin.scala diff --git a/project/Build.scala b/project/Build.scala index c8fea4a..5e17c99 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -4,4 +4,4 @@ object KsqlJdbcBuild extends Build { lazy val root = Project("root", file(".")) .configs(Configs.all: _*) .settings(Testing.settings: _*) -} \ No newline at end of file +} diff --git a/project/Configs.scala b/project/Configs.scala index bc69765..493ca93 100644 --- a/project/Configs.scala +++ b/project/Configs.scala @@ -3,4 +3,4 @@ import sbt._ object Configs { val IntegrationTest = config("it") extend (Test) val all = Seq(IntegrationTest) -} \ No newline at end of file +} diff --git a/project/PackagingTypePlugin.scala b/project/PackagingTypePlugin.scala new file mode 100644 index 0000000..16cc60d --- /dev/null +++ b/project/PackagingTypePlugin.scala @@ -0,0 +1,8 @@ +import sbt._ + +object PackagingTypePlugin extends AutoPlugin { + override val buildSettings = { + sys.props += "packaging.type" -> "jar" + Nil + } +} diff --git a/project/Testing.scala b/project/Testing.scala index 212a1d5..8492a57 100644 --- a/project/Testing.scala +++ b/project/Testing.scala @@ -23,4 +23,4 @@ object Testing { testAll <<= testAll.dependsOn(test in IntegrationTest), testAll <<= testAll.dependsOn(test in Test) ) -} \ No newline at end of file +} From 5811317091d302afbd77eed9d5fe083b766b524d Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Fri, 18 Jan 2019 20:46:41 -0600 Subject: [PATCH 06/47] Travis config --- .travis.yml | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/.travis.yml b/.travis.yml index cf6096c..a5663e1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,31 +1,3 @@ -before_install: - - git clone https://github.com/confluentinc/common.git - - cd common - - git checkout v4.1.0-rc3 - - mv pom.xml pom.xml.bak && sed '58s/.*/ 1.1.0<\/kafka.version>/' pom.xml.bak > pom.xml - - mvn -B clean install -Dmaven.test.skip=true - - cd .. - - git clone https://github.com/confluentinc/support-metrics-common.git - - cd support-metrics-common - - git checkout v4.1.0-rc3 - - mvn -B clean install -Dmaven.test.skip=true - - cd .. - - git clone https://github.com/confluentinc/rest-utils.git - - cd rest-utils - - git checkout v4.1.0-rc3 - - mvn -B clean install -Dmaven.test.skip=true - - cd .. - - git clone https://github.com/confluentinc/schema-registry.git - - cd schema-registry - - git checkout v4.1.0-rc3 - - mvn -B clean install -Dmaven.test.skip=true - - cd .. - - git clone https://github.com/confluentinc/ksql.git - - cd ksql - - git checkout v4.1.0-rc3 - - ex -sc '55d7|x' ksql-cli/pom.xml && ex -sc '37d7|x' ksql-engine/pom.xml && ex -sc '25d7|x' ksql-parser/pom.xml && ex -sc '84d7|x' ksql-rest-app/pom.xml && ex -sc '37d7|x' ksql-tools/pom.xml - - mvn -B clean install -Dmaven.test.skip=true - - cd .. script: - sbt clean coverage test it:test coverageReport && sbt coverageAggregate after_success: From 247a291fffb52bd4a4585d6fe684640f1fa27a41 Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sun, 20 Jan 2019 20:26:16 -0600 Subject: [PATCH 07/47] Assembly plugin for building fat jars --- build.sbt | 9 +++++++++ project/plugins.sbt | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 027c2b5..7394588 100644 --- a/build.sbt +++ b/build.sbt @@ -16,3 +16,12 @@ libraryDependencies += "io.confluent.ksql" % "ksql-rest-app" % "5.1.0" libraryDependencies += "org.apache.kafka" %% "kafka" % "2.1.0" % "test" libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % "test" libraryDependencies += "org.scalamock" %% "scalamock-scalatest-support" % "3.6.0" % "test" + +assemblyMergeStrategy in assembly := { + case PathList("javax", "inject", xs @ _*) => MergeStrategy.first + case "module-info.class" => MergeStrategy.discard + case "log4j.properties" => MergeStrategy.discard + case x => + val oldStrategy = (assemblyMergeStrategy in assembly).value + oldStrategy(x) +} diff --git a/project/plugins.sbt b/project/plugins.sbt index 414c180..3091b10 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,5 @@ logLevel := Level.Warn addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1") -addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.2.2") \ No newline at end of file +addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.2.2") +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.9") From 4db1a1d5998d48a9c0a4c8c3eb721aeaba868837 Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sun, 20 Jan 2019 20:26:41 -0600 Subject: [PATCH 08/47] Updating documentation --- README.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d055259..01f6754 100644 --- a/README.md +++ b/README.md @@ -14,24 +14,22 @@ First of all, the KSQL lib has to be installed into your local repo. So, cloning the KSQL repo: -``git clone https://github.com/confluentinc/ksql.git && cd ksql && git checkout v4.1.0-rc3`` +``git clone https://github.com/confluentinc/ksql.git && cd ksql && git checkout v5.1.0`` and installing it: -``mvn clean install -Dmaven.skip.test=true`` +``mvn clean install -Dmaven.test.skip=true`` -Probably, you'll have to do the same things for these Confluent projects (previous to the KSQL project installation): -* [Confluent common](https://github.com/confluentinc/common.git) -* [Confluent support-metrics-common](https://github.com/confluentinc/support-metrics-common.git) -* [Confluent rest-utils](https://github.com/confluentinc/rest-utils.git) -* [Confluent schema-registry](https://github.com/confluentinc/schema-registry.git) - -Once you did that, just have to clone the ksql-jdbc-driver repo and package it: +Once you've done that, just clone the ksql-jdbc-driver repo and package it: ``git clone https://github.com/mmolimar/ksql-jdbc-driver.git && cd ksql-jdbc-driver`` ``sbt clean package`` +If you want to build a fat jar containing both classes and dependencies needed, type the following: + +``sbt clean assembly`` + ### Running tests ### To run unit and integration tests, execute the following: @@ -81,4 +79,4 @@ where: ## License -Released under the Apache License, version 2.0. \ No newline at end of file +Released under the Apache License, version 2.0. From f4b8f087c171fdffa19b16087d401a918575f9cb Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Mon, 21 Jan 2019 09:41:31 -0600 Subject: [PATCH 09/47] Fix to resolve javax.ws.rs-api package type --- build.sbt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 7394588..1775686 100644 --- a/build.sbt +++ b/build.sbt @@ -16,9 +16,10 @@ libraryDependencies += "io.confluent.ksql" % "ksql-rest-app" % "5.1.0" libraryDependencies += "org.apache.kafka" %% "kafka" % "2.1.0" % "test" libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % "test" libraryDependencies += "org.scalamock" %% "scalamock-scalatest-support" % "3.6.0" % "test" +libraryDependencies += "javax.ws.rs" % "javax.ws.rs-api" % "2.1.1" artifacts (Artifact("javax.ws.rs-api", "jar", "jar")) assemblyMergeStrategy in assembly := { - case PathList("javax", "inject", xs @ _*) => MergeStrategy.first + case PathList("javax", "inject", xs@_*) => MergeStrategy.first case "module-info.class" => MergeStrategy.discard case "log4j.properties" => MergeStrategy.discard case x => From 69edf9e017049d3552a43eb022ee3de1e8e2dc3d Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Wed, 23 Jan 2019 18:37:15 -0600 Subject: [PATCH 10/47] Support throwable param in driver exceptions --- .../mmolimar/ksql/jdbc/Exceptions.scala | 38 +++++++++++++------ .../ksql/jdbc/KsqlDatabaseMetaData.scala | 2 +- .../ksql/jdbc/KsqlDatabaseMetaDataSpec.scala | 5 ++- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala index 0dd4f83..e86662a 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala @@ -3,45 +3,59 @@ package com.github.mmolimar.ksql.jdbc import java.sql.{SQLException, SQLFeatureNotSupportedException} sealed trait KsqlException { + def message: String = "" + + def cause: Throwable = None.orNull + } -case class InvalidUrl(url: String) extends KsqlException { +case class InvalidUrl(url: String, override val cause: Throwable = None.orNull) extends KsqlException { override def message = s"URL with value ${url} is not valid. It must match de regex ${KsqlDriver.urlRegex}" } -case class CannotConnect(url: String, msg: String) extends KsqlException { +case class CannotConnect(url: String, msg: String, override val cause: Throwable = None.orNull) extends KsqlException { override def message = s"Cannot connect to this URL ${url}. Error message: ${msg}" } -case class InvalidProperty(name: String) extends KsqlException { +case class InvalidProperty(name: String, override val cause: Throwable = None.orNull) extends KsqlException { override def message = s"Invalid property ${name}." } -case class NotSupported(feature: String) extends KsqlException { +case class NotSupported(feature: String, override val cause: Throwable = None.orNull) extends KsqlException { override val message = s"Feature not supported: $feature." } -case class KsqlQueryError(override val message: String = "Error executing query") extends KsqlException +case class KsqlQueryError(override val message: String = "Error executing query", + override val cause: Throwable = None.orNull) extends KsqlException + +case class KsqlCommandError(override val message: String = "Error executing command", + override val cause: Throwable = None.orNull) extends KsqlException -case class KsqlCommandError(override val message: String = "Error executing command") extends KsqlException +case class KsqlEntityListError(override val message: String = "Invalid KSQL entity list", + override val cause: Throwable = None.orNull) extends KsqlException -case class InvalidColumn(override val message: String = "Invalid column") extends KsqlException +case class InvalidColumn(override val message: String = "Invalid column", + override val cause: Throwable = None.orNull) extends KsqlException -case class EmptyRow(override val message: String = "Current row is empty") extends KsqlException +case class EmptyRow(override val message: String = "Current row is empty", + override val cause: Throwable = None.orNull) extends KsqlException -case class UnknownTableType(override val message: String = "Table type does not exist") extends KsqlException +case class UnknownTableType(override val message: String = "Table type does not exist", + override val cause: Throwable = None.orNull) extends KsqlException -case class UnknownCatalog(override val message: String = "Catalog does not exist") extends KsqlException +case class UnknownCatalog(override val message: String = "Catalog does not exist", + override val cause: Throwable = None.orNull) extends KsqlException -case class UnknownSchema(override val message: String = "Schema does not exist") extends KsqlException +case class UnknownSchema(override val message: String = "Schema does not exist", + override val cause: Throwable = None.orNull) extends KsqlException object Exceptions { implicit def wrapException(error: KsqlException): SQLException = { error match { case ns: NotSupported => new SQLFeatureNotSupportedException(ns.message) - case e => new SQLException(e.message) + case e => new SQLException(e.message, e.cause) } } diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala index 50a26bb..999a408 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala @@ -329,7 +329,7 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D getSchemas } - override def supportsMultipleResultSets: Boolean = throw NotSupported("supportsMultipleResultSets") + override def supportsMultipleResultSets: Boolean = false override def ownInsertsAreVisible(`type`: Int): Boolean = throw NotSupported("ownInsertsAreVisible") diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala index ac70a44..9578e1d 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala @@ -17,7 +17,8 @@ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory w val implementedMethods = Seq("getDatabaseProductName", "getDatabaseMajorVersion", "getDatabaseMinorVersion", "getDatabaseProductVersion", "getDriverName", "getDriverVersion", "getDriverMajorVersion", "getDriverMinorVersion", "getJDBCMajorVersion", "getJDBCMinorVersion", "getConnection", "getCatalogs", - "getTableTypes", "getTables", "getSchemas", "getSuperTables", "getUDTs", "getColumns", "isReadOnly") + "getTableTypes", "getTables", "getSchemas", "getSuperTables", "getUDTs", "getColumns", "isReadOnly", + "supportsMultipleResultSets") "A KsqlDatabaseMetaData" when { @@ -118,6 +119,8 @@ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory w metadata.getColumns("", "test", "test", "test") } + metadata.supportsMultipleResultSets should be(false) + } } } From b3f286f853a923c34d0d227bddd90e9d94846284 Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Wed, 23 Jan 2019 19:19:49 -0600 Subject: [PATCH 11/47] New property in the connection URL to ignore or not the properties from the caller --- .../mmolimar/ksql/jdbc/KsqlConnection.scala | 13 +++++++++--- .../mmolimar/ksql/jdbc/KsqlDriver.scala | 4 ++-- .../mmolimar/ksql/jdbc/KsqlDriverSpec.scala | 20 ++++++++++++++++++- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala index c322fa4..d582fcd 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala @@ -2,8 +2,8 @@ package com.github.mmolimar.ksql.jdbc import java.sql._ import java.util -import java.util.Properties import java.util.concurrent.Executor +import java.util.{Collections, Properties} import com.github.mmolimar.ksql.jdbc.Exceptions._ import io.confluent.ksql.rest.client.{KsqlRestClient, RestResponse} @@ -21,6 +21,8 @@ case class KsqlConnectionValues(ksqlServer: String, port: Int, config: Map[Strin def isSecured: Boolean = config.getOrElse("secured", "false").toBoolean + def properties: Boolean = config.getOrElse("properties", "true").toBoolean + def timeout: Long = config.getOrElse("timeout", "0").toLong } @@ -30,7 +32,12 @@ class KsqlConnection(values: KsqlConnectionValues, properties: Properties) exten private val ksqlClient = init private[jdbc] def init: KsqlRestClient = { - new KsqlRestClient(values.getKsqlUrl, properties.asScala.toMap[String, AnyRef].asJava) + val props = if (values.properties) { + properties.asScala.toMap[String, AnyRef].asJava + } else { + Collections.emptyMap[String, AnyRef] + } + new KsqlRestClient(values.getKsqlUrl, props) } private[jdbc] def validate: Unit = { @@ -91,7 +98,7 @@ class KsqlConnection(values: KsqlConnectionValues, properties: Properties) exten resultSetConcurrency != ResultSet.CONCUR_READ_ONLY || resultSetHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT) { throw NotSupported("ResultSetType, ResultSetConcurrency and ResultSetHoldability must be" + - " TYPE_FORWARD_ONLY, CONCUR_READ_ONLY, HOLD_CURSORS_OVER_COMMIT respectively ") + " TYPE_FORWARD_ONLY, CONCUR_READ_ONLY, HOLD_CURSORS_OVER_COMMIT respectively.") } new KsqlStatement(ksqlClient, values.timeout) } diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala index d7aca8e..a5d3da9 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala @@ -11,14 +11,14 @@ import scala.util.Try object KsqlDriver { val driverName = "KSQL JDBC" - val version = "0.1-SNAPSHOT" + val version = "0.3" val ksqlPrefix = "jdbc:ksql://" val majorVersion = 1 val minorVersion = 0 val jdbcMajorVersion = 4 val jdbcMinorVersion = 1 - private val ksqlServerRegex = "([A-Za-z0-9._%+-]+):([0-9]+)" + private val ksqlServerRegex = "([A-Za-z0-9._%+-]+):([0-9]{1,5})" private val ksqlPropsRegex = "(\\?([A-Za-z0-9._-]+=[A-Za-z0-9._-]+(&[A-Za-z0-9._-]+=[A-Za-z0-9._-]+)*)){0,1}" diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala index 0171e52..5d3efe9 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala @@ -68,6 +68,7 @@ class KsqlDriverSpec extends WordSpec with Matchers { connectionValues.config.isEmpty should be(true) connectionValues.getKsqlUrl should be(s"http://${ksqlServer}:${ksqlPort}") connectionValues.isSecured should be(false) + connectionValues.properties should be(true) connectionValues.timeout should be(0) connectionValues = KsqlDriver.parseUrl(s"jdbc:ksql://${ksqlServer}:${ksqlPort}?prop1=value1") @@ -77,6 +78,7 @@ class KsqlDriverSpec extends WordSpec with Matchers { connectionValues.config.get("prop1").get should be("value1") connectionValues.getKsqlUrl should be(s"http://${ksqlServer}:${ksqlPort}") connectionValues.isSecured should be(false) + connectionValues.properties should be(true) connectionValues.timeout should be(0) connectionValues = KsqlDriver.parseUrl(s"jdbc:ksql://${ksqlServer}:${ksqlPort}?prop1=value1&secured=true&prop2=value2") @@ -88,6 +90,7 @@ class KsqlDriverSpec extends WordSpec with Matchers { connectionValues.config.get("secured").get should be("true") connectionValues.getKsqlUrl should be(s"https://${ksqlServer}:${ksqlPort}") connectionValues.isSecured should be(true) + connectionValues.properties should be(true) connectionValues.timeout should be(0) connectionValues = KsqlDriver.parseUrl(s"jdbc:ksql://${ksqlServer}:${ksqlPort}?prop1=value1&timeout=100&prop2=value2") @@ -99,17 +102,32 @@ class KsqlDriverSpec extends WordSpec with Matchers { connectionValues.config.get("timeout").get should be("100") connectionValues.getKsqlUrl should be(s"http://${ksqlServer}:${ksqlPort}") connectionValues.isSecured should be(false) + connectionValues.properties should be(true) connectionValues.timeout should be(100) - connectionValues = KsqlDriver.parseUrl(s"jdbc:ksql://${ksqlServer}:${ksqlPort}?timeout=100&secured=true&prop1=value1") + connectionValues = KsqlDriver.parseUrl(s"jdbc:ksql://${ksqlServer}:${ksqlPort}?prop1=value1&properties=false&prop2=value2") connectionValues.ksqlServer should be(ksqlServer) connectionValues.port should be(ksqlPort) connectionValues.config.size should be(3) connectionValues.config.get("prop1").get should be("value1") + connectionValues.config.get("prop2").get should be("value2") + connectionValues.config.get("properties").get should be("false") + connectionValues.getKsqlUrl should be(s"http://${ksqlServer}:${ksqlPort}") + connectionValues.isSecured should be(false) + connectionValues.properties should be(false) + connectionValues.timeout should be(0) + + connectionValues = KsqlDriver.parseUrl(s"jdbc:ksql://${ksqlServer}:${ksqlPort}?timeout=100&secured=true&properties=false&prop1=value1") + connectionValues.ksqlServer should be(ksqlServer) + connectionValues.port should be(ksqlPort) + connectionValues.config.size should be(4) + connectionValues.config.get("prop1").get should be("value1") connectionValues.config.get("timeout").get should be("100") connectionValues.config.get("secured").get should be("true") + connectionValues.config.get("properties").get should be("false") connectionValues.getKsqlUrl should be(s"https://${ksqlServer}:${ksqlPort}") connectionValues.isSecured should be(true) + connectionValues.properties should be(false) connectionValues.timeout should be(100) } } From 58d156911fa7d9e3f1612991a7c88b1a9fd5585c Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sun, 27 Jan 2019 14:34:48 -0600 Subject: [PATCH 12/47] New KSQL result set error --- .../mmolimar/ksql/jdbc/Exceptions.scala | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala index e86662a..ef46353 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala @@ -11,11 +11,11 @@ sealed trait KsqlException { } case class InvalidUrl(url: String, override val cause: Throwable = None.orNull) extends KsqlException { - override def message = s"URL with value ${url} is not valid. It must match de regex ${KsqlDriver.urlRegex}" + override def message = s"URL with value ${url} is not valid. It must match de regex ${KsqlDriver.urlRegex}." } case class CannotConnect(url: String, msg: String, override val cause: Throwable = None.orNull) extends KsqlException { - override def message = s"Cannot connect to this URL ${url}. Error message: ${msg}" + override def message = s"Cannot connect to this URL ${url}. Error message: ${msg}." } case class InvalidProperty(name: String, override val cause: Throwable = None.orNull) extends KsqlException { @@ -26,28 +26,31 @@ case class NotSupported(feature: String, override val cause: Throwable = None.or override val message = s"Feature not supported: $feature." } -case class KsqlQueryError(override val message: String = "Error executing query", +case class KsqlQueryError(override val message: String = "Error executing query.", override val cause: Throwable = None.orNull) extends KsqlException -case class KsqlCommandError(override val message: String = "Error executing command", +case class KsqlCommandError(override val message: String = "Error executing command.", override val cause: Throwable = None.orNull) extends KsqlException -case class KsqlEntityListError(override val message: String = "Invalid KSQL entity list", - override val cause: Throwable = None.orNull) extends KsqlException +case class KsqlEntityListError(override val message: String = "Invalid KSQL entity list.", + override val cause: Throwable = None.orNull) extends KsqlException -case class InvalidColumn(override val message: String = "Invalid column", +case class InvalidColumn(override val message: String = "Invalid column.", override val cause: Throwable = None.orNull) extends KsqlException -case class EmptyRow(override val message: String = "Current row is empty", +case class EmptyRow(override val message: String = "Current row is empty.", override val cause: Throwable = None.orNull) extends KsqlException -case class UnknownTableType(override val message: String = "Table type does not exist", +case class ResultSetError(override val message: String = "Error accessing to the result set.", + override val cause: Throwable = None.orNull) extends KsqlException + +case class UnknownTableType(override val message: String = "Table type does not exist.", override val cause: Throwable = None.orNull) extends KsqlException -case class UnknownCatalog(override val message: String = "Catalog does not exist", +case class UnknownCatalog(override val message: String = "Catalog does not exist.", override val cause: Throwable = None.orNull) extends KsqlException -case class UnknownSchema(override val message: String = "Schema does not exist", +case class UnknownSchema(override val message: String = "Schema does not exist.", override val cause: Throwable = None.orNull) extends KsqlException object Exceptions { From 81ffe4ad10308acf6d0bb04a716817c5a4b4e9bb Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sun, 27 Jan 2019 14:36:02 -0600 Subject: [PATCH 13/47] Result set iterators improvements --- .../ksql/jdbc/resultset/KsqlResultSet.scala | 10 ++++----- .../ksql/jdbc/resultset/ResultSet.scala | 22 +++++++++++++++++-- .../jdbc/resultset/KsqlResultSetSpec.scala | 13 +++-------- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala index f817c8d..5ceaf12 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala @@ -19,11 +19,11 @@ import scala.util.{Failure, Success, Try} private[resultset] class JdbcQueryStream(stream: KsqlRestClient.QueryStream, timeout: Long) extends Closeable with Iterator[StreamedRow] { - override def close(): Unit = stream.close + override def close: Unit = stream.close override def hasNext: Boolean = stream.hasNext - override def next(): StreamedRow = stream.next + override def next: StreamedRow = stream.next } @@ -36,7 +36,7 @@ class KsqlResultSet(private[jdbc] val stream: JdbcQueryStream, val timeout: Long private val waitDuration = if (timeout > 0) timeout millis else Duration.Inf - override def next: Boolean = { + protected override def nextResult: Boolean = { def hasNext = stream.hasNext match { case true => stream.next match { @@ -65,14 +65,12 @@ class KsqlResultSet(private[jdbc] val stream: JdbcQueryStream, val timeout: Long override def getConcurrency: Int = ResultSet.CONCUR_READ_ONLY - override def close: Unit = stream.close - override protected def getColumnBounds: (Int, Int) = (1, currentRow.getOrElse(emptyRow).getRow.getColumns.size) override protected def getValue[T <: AnyRef](columnIndex: Int): T = { currentRow.get.getRow.getColumns.get(columnIndex - 1).asInstanceOf[T] } - override protected def getColumnIndex(columnLabel: String): Int = throw NotSupported("getting column by label") + override protected def getColumnIndex(columnLabel: String): Int = throw NotSupported("getColumnIndex") } diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala index 29cb4da..12630d1 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala @@ -8,7 +8,7 @@ import java.util import java.util.Calendar import com.github.mmolimar.ksql.jdbc.Exceptions._ -import com.github.mmolimar.ksql.jdbc.{EmptyRow, InvalidColumn, NotSupported, WrapperNotSupported} +import com.github.mmolimar.ksql.jdbc._ private[resultset] class ResultSetNotSupported extends ResultSet with WrapperNotSupported { @@ -401,13 +401,30 @@ abstract class AbstractResultSet[T](private val iterator: Iterator[T]) extends R protected var currentRow: Option[T] = None - override def next: Boolean = iterator.hasNext match { + private var closed: Boolean = false + + protected def nextResult: Boolean = iterator.hasNext match { case true => currentRow = Some(iterator.next) true case false => false } + override final def next: Boolean = closed match { + case true => throw ResultSetError("Result set is already closed.") + case false => nextResult + } + + override final def close: Unit = closed match { + case true => // do nothing + case false => + currentRow = None + closeInherit + closed = true + } + + protected def closeInherit: Unit = {} + override def getBoolean(columnIndex: Int): Boolean = getColumn(columnIndex) override def getBoolean(columnLabel: String): Boolean = getColumn(columnLabel) @@ -471,4 +488,5 @@ abstract class AbstractResultSet[T](private val iterator: Iterator[T]) extends R protected def getValue[T <: AnyRef](columnIndex: Int): T protected def getColumnIndex(columnLabel: String): Int + } diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala index 6968918..a23c0ec 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala @@ -15,7 +15,7 @@ class KsqlResultSetSpec extends WordSpec with Matchers with MockFactory with One val implementedMethods = Seq("isLast", "isAfterLast", "isBeforeFirst", "isFirst", "next", "getConcurrency", "close", "getString", "getBytes", "getByte", "getBytes", "getBoolean", "getShort", - "getInt", "getLong", "getFloat", "getDouble") + "getInt", "getLong", "getFloat", "getDouble", "getResultSet", "getUpdateCount") "A KsqlResultSet" when { @@ -41,9 +41,6 @@ class KsqlResultSetSpec extends WordSpec with Matchers with MockFactory with One Short.box(1), Int.box(2), Long.box(3L), Float.box(4.4f), Double.box(5.5d)) (mockedQueryStream.next _).expects.returns(StreamedRow.row(new GenericRow(columnValues.asJava))) (mockedQueryStream.hasNext _).expects.returns(false) - (mockedQueryStream.close _).expects.returns() - (mockedQueryStream.close _).expects.throws(new IllegalStateException("Cannot call close() when already closed.")) - (mockedQueryStream.hasNext _).expects.throws(new IllegalStateException("Cannot call hasNext() once closed.")) } val resultSet = new KsqlResultSet(mockedQueryStream) @@ -100,10 +97,8 @@ class KsqlResultSetSpec extends WordSpec with Matchers with MockFactory with One resultSet.next should be(false) resultSet.isFirst should be(false) resultSet.close - assertThrows[IllegalStateException] { - resultSet.close - } - assertThrows[IllegalStateException] { + resultSet.close + assertThrows[SQLException] { resultSet.next } } @@ -124,9 +119,7 @@ class KsqlResultSetSpec extends WordSpec with Matchers with MockFactory with One } }) } - } } } - From cbd38e23e14ac0aad0b4bf3b26c5c07087e641b3 Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sun, 27 Jan 2019 23:41:22 -0600 Subject: [PATCH 14/47] Metadata class for result sets --- .../resultset/KsqlResultSetMetadata.scala | 91 ++++++++++++++++ .../resultset/KsqlResultSetMetadataSpec.scala | 100 ++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetadata.scala create mode 100644 src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetadataSpec.scala diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetadata.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetadata.scala new file mode 100644 index 0000000..842b433 --- /dev/null +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetadata.scala @@ -0,0 +1,91 @@ +package com.github.mmolimar.ksql.jdbc.resultset + +import java.sql.{ResultSetMetaData, Types} + +import com.github.mmolimar.ksql.jdbc.Exceptions._ +import com.github.mmolimar.ksql.jdbc.{InvalidColumn, NotSupported, WrapperNotSupported} +import io.confluent.ksql.rest.entity.SchemaInfo.Type._ +import io.confluent.ksql.rest.entity.{FieldInfo, QueryDescription} + +import scala.collection.JavaConverters._ + +class KsqlResultSetMetadata(queryDesc: QueryDescription) extends ResultSetMetaData with WrapperNotSupported { + + private lazy val fields: Map[Int, FieldInfo] = queryDesc.getFields.asScala.zipWithIndex + .map { case (field, index) => (index + 1) -> field }.toMap + + private def getField(index: Int): FieldInfo = fields.get(index) + .getOrElse(throw InvalidColumn(s"Column with index $index does not exist")) + + override def getSchemaName(column: Int): String = getField(column).getSchema.getTypeName + + override def getCatalogName(column: Int): String = queryDesc.getSources.asScala.mkString(", ") + + override def getColumnLabel(column: Int): String = getField(column).getName + + override def getColumnName(column: Int): String = getField(column).getName + + override def getColumnTypeName(column: Int): String = getField(column).getSchema.getTypeName + + override def getColumnClassName(column: Int): String = { + getField(column).getSchema.getType match { + case INTEGER => classOf[java.lang.Integer] + case BIGINT => classOf[java.lang.Long] + case DOUBLE => classOf[java.lang.Double] + case BOOLEAN => classOf[java.lang.Boolean] + case STRING => classOf[java.lang.String] + case MAP => classOf[java.util.Map[AnyRef, AnyRef]] + case ARRAY => classOf[java.sql.Array] + case STRUCT => classOf[java.sql.Struct] + } + }.getCanonicalName + + override def isCaseSensitive(column: Int): Boolean = getField(column).getSchema.getType match { + case STRING => true + case _ => false + } + + override def getTableName(column: Int): String = queryDesc.getTopology + + override def getColumnType(column: Int): Int = getField(column).getSchema.getType match { + case INTEGER => Types.INTEGER + case BIGINT => Types.BIGINT + case DOUBLE => Types.DOUBLE + case BOOLEAN => Types.BOOLEAN + case STRING => Types.VARCHAR + case MAP => Types.JAVA_OBJECT + case ARRAY => Types.ARRAY + case STRUCT => Types.STRUCT + } + + override def getColumnCount: Int = fields.size + + override def getPrecision(column: Int): Int = getField(column).getSchema.getType match { + case DOUBLE => -1 + case _ => 0 + } + + override def getScale(column: Int): Int = getField(column).getSchema.getType match { + case DOUBLE => -1 + case _ => 0 + } + + override def isSigned(column: Int): Boolean = throw NotSupported("isSigned") + + override def isWritable(column: Int): Boolean = throw NotSupported("isWritable") + + override def isAutoIncrement(column: Int): Boolean = throw NotSupported("isAutoIncrement") + + override def isReadOnly(column: Int): Boolean = throw NotSupported("isReadOnly") + + override def isCurrency(column: Int): Boolean = throw NotSupported("isCurrency") + + override def isSearchable(column: Int): Boolean = throw NotSupported("isSearchable") + + override def isDefinitelyWritable(column: Int): Boolean = throw NotSupported("isDefinitelyWritable") + + override def isNullable(column: Int): Int = throw NotSupported("isNullable") + + override def getColumnDisplaySize(column: Int): Int = throw NotSupported("getColumnDisplaySize") + +} diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetadataSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetadataSpec.scala new file mode 100644 index 0000000..0f77961 --- /dev/null +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetadataSpec.scala @@ -0,0 +1,100 @@ +package com.github.mmolimar.ksql.jdbc.resultset + +import java.sql.{SQLException, SQLFeatureNotSupportedException} +import java.util +import java.util.Collections + +import com.github.mmolimar.ksql.jdbc.utils.TestUtils._ +import io.confluent.ksql.query.QueryId +import io.confluent.ksql.rest.entity.SchemaInfo.Type +import io.confluent.ksql.rest.entity.{EntityQueryId, FieldInfo, QueryDescription, SchemaInfo} +import org.scalamock.scalatest.MockFactory +import org.scalatest.{Matchers, OneInstancePerTest, WordSpec} + +class KsqlResultSetMetadataSpec extends WordSpec with Matchers with MockFactory with OneInstancePerTest { + + val implementedMethods = Seq("getSchemaName", "getCatalogName", "getColumnLabel", "getColumnName", + "getColumnTypeName", "getColumnClassName", "isCaseSensitive", "getTableName", "getColumnType", + "getColumnCount", "getPrecision", "getScale") + + "A KsqlResultSetMetadata" when { + + "validating specs" should { + + val fields = util.Arrays.asList( + new FieldInfo("field1", new SchemaInfo(Type.INTEGER, None.orNull, None.orNull)), + new FieldInfo("field2", new SchemaInfo(Type.BIGINT, None.orNull, None.orNull)), + new FieldInfo("field3", new SchemaInfo(Type.DOUBLE, None.orNull, None.orNull)), + new FieldInfo("field4", new SchemaInfo(Type.BOOLEAN, None.orNull, None.orNull)), + new FieldInfo("field5", new SchemaInfo(Type.STRING, None.orNull, None.orNull)), + new FieldInfo("field6", new SchemaInfo(Type.MAP, None.orNull, None.orNull)), + new FieldInfo("field7", new SchemaInfo(Type.ARRAY, None.orNull, None.orNull)), + new FieldInfo("field8", new SchemaInfo(Type.STRUCT, None.orNull, None.orNull)) + ) + val queryDesc = new QueryDescription( + new EntityQueryId(new QueryId("testquery")), + "select * from text;", + fields, + Collections.singleton("TEST"), + Collections.emptySet[String], + "test", + "executionPlan", + Collections.emptyMap[String, AnyRef] + ) + val resultSet = new KsqlResultSetMetadata(queryDesc) + + "throw not supported exception if not supported" in { + + reflectMethods[KsqlResultSetMetadata](implementedMethods, false, resultSet) + .foreach(method => { + assertThrows[SQLFeatureNotSupportedException] { + method() + } + }) + } + + "work if implemented" in { + + resultSet.getCatalogName(1) should be("TEST") + resultSet.getSchemaName(2) should be("BIGINT") + resultSet.getColumnLabel(3) should be("field3") + resultSet.getColumnName(3) should be("field3") + resultSet.getColumnTypeName(3) should be("DOUBLE") + + resultSet.getColumnClassName(1) should be("java.lang.Integer") + resultSet.getColumnType(1) should be(java.sql.Types.INTEGER) + resultSet.getColumnClassName(2) should be("java.lang.Long") + resultSet.getColumnType(2) should be(java.sql.Types.BIGINT) + resultSet.getColumnClassName(3) should be("java.lang.Double") + resultSet.getColumnType(3) should be(java.sql.Types.DOUBLE) + resultSet.getColumnClassName(4) should be("java.lang.Boolean") + resultSet.getColumnType(4) should be(java.sql.Types.BOOLEAN) + resultSet.getColumnClassName(5) should be("java.lang.String") + resultSet.getColumnType(5) should be(java.sql.Types.VARCHAR) + resultSet.getColumnClassName(6) should be("java.util.Map") + resultSet.getColumnType(6) should be(java.sql.Types.JAVA_OBJECT) + resultSet.getColumnClassName(7) should be("java.sql.Array") + resultSet.getColumnType(7) should be(java.sql.Types.ARRAY) + resultSet.getColumnClassName(8) should be("java.sql.Struct") + resultSet.getColumnType(8) should be(java.sql.Types.STRUCT) + + resultSet.isCaseSensitive(2) should be(false) + resultSet.isCaseSensitive(5) should be(true) + resultSet.getTableName(3) should be("test") + resultSet.getColumnType(3) should be(java.sql.Types.DOUBLE) + resultSet.getColumnCount should be(8) + resultSet.getPrecision(3) should be(-1) + resultSet.getPrecision(2) should be(0) + resultSet.getScale(3) should be(-1) + resultSet.getScale(4) should be(0) + + assertThrows[SQLException] { + resultSet.getSchemaName(0) + } + assertThrows[SQLException] { + resultSet.getSchemaName(9) + } + } + } + } +} From 693ce50fb6dfb117e0423efcd14c1620088566a7 Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Fri, 1 Feb 2019 15:35:23 -0600 Subject: [PATCH 15/47] KSQL entity headers for metadata --- .../github/mmolimar/ksql/jdbc/Headers.scala | 157 +++++++++++++++--- 1 file changed, 134 insertions(+), 23 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala index 56bd14d..78ee0eb 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala @@ -5,16 +5,41 @@ import java.sql.Types import scala.collection.immutable -case class HeaderField(name: String, jdbcType: Int, length: Int, index: Int) { - def this(name: String, jdbcType: Int, length: Int) = this(name, jdbcType, length, -1) -} +case class HeaderField(name: String, jdbcType: Int, length: Int, index: Int) object HeaderField { - def apply(name: String, jdbcType: Int, length: Int) = new HeaderField(name, jdbcType, length, -1) + def apply(name: String, jdbcType: Int, length: Int): HeaderField = HeaderField(name, jdbcType, length, -1) } -object Headers { - val tableTypes: Map[String, HeaderField] = Seq(new HeaderField("TABLE_TYPE", Types.VARCHAR, 0)) +private object implicits { + + implicit def toMap(headers: Seq[HeaderField]): Map[String, HeaderField] = { + headers.zipWithIndex.map { case (header, index) => { + (header.name, HeaderField(header.name, header.jdbcType, header.length, index + 1)) + } + }.toCaseInsensitiveMap + } + + implicit class ToTreeMap[+A](tuples: TraversableOnce[A]) { + + def toCaseInsensitiveMap[U](implicit ev: A <:< (String, U)): immutable.Map[String, U] = { + val b = immutable.TreeMap.newBuilder[String, U](new Ordering[String] { + def compare(x: String, y: String): Int = String.CASE_INSENSITIVE_ORDER.compare(x.toString, y.toString) + }) + for (x <- tuples) + b += x + + b.result() + } + } + +} + +object DatabaseMetadataHeaders { + + import implicits._ + + val tableTypes: Map[String, HeaderField] = Seq(HeaderField("TABLE_TYPE", Types.VARCHAR, 0)) val catalogs: Map[String, HeaderField] = Seq(HeaderField("TABLE_CAT", Types.VARCHAR, 0)) @@ -71,7 +96,7 @@ object Headers { ) def mapDataType(dataType: String): Int = dataType match { - case "BOOL" | "BOOLEAN" => Types.INTEGER + case "BOOL" | "BOOLEAN" => Types.BOOLEAN case "INT" | "INTEGER" => Types.INTEGER case "LONG" | "BIGINT" => Types.BIGINT case "DOUBLE" => Types.DOUBLE @@ -81,24 +106,110 @@ object Headers { case _ => Types.OTHER } - private implicit def toMap(headers: Seq[HeaderField]): Map[String, HeaderField] = { - headers.zipWithIndex.map { case (header, index) => { - (header.name, HeaderField(header.name, header.jdbcType, header.length, index + 1)) - } - }.toCaseInsensitiveMap - } +} - private implicit class ToTreeMap[+A](tuples: TraversableOnce[A]) { +object KsqlEntityHeaders { - def toCaseInsensitiveMap[U](implicit ev: A <:< (String, U)): immutable.Map[String, U] = { - val b = immutable.TreeMap.newBuilder[String, U](new Ordering[String] { - def compare(x: String, y: String): Int = String.CASE_INSENSITIVE_ORDER.compare(x.toString, y.toString) - }) - for (x <- tuples) - b += x + import implicits._ - b.result() - } - } + val commandStatusEntity: Map[String, HeaderField] = Seq( + HeaderField("COMMAND_STATUS_ID_TYPE", Types.VARCHAR, 16), + HeaderField("COMMAND_STATUS_ID_ENTITY", Types.VARCHAR, 32), + HeaderField("COMMAND_STATUS_ID_ACTION", Types.VARCHAR, 16), + HeaderField("COMMAND_STATUS_STATUS", Types.VARCHAR, 16), + HeaderField("COMMAND_STATUS_MESSAGE", Types.VARCHAR, 128) + ) + + val executionPlanEntity: Map[String, HeaderField] = Seq( + HeaderField("EXECUTION_PLAN", Types.VARCHAR, 256) + ) + + val functionDescriptionListEntity: Map[String, HeaderField] = Seq( + HeaderField("FUNCTION_DESCRIPTION_AUTHOR", Types.VARCHAR, 32), + HeaderField("FUNCTION_DESCRIPTION_DESCRIPTION", Types.VARCHAR, 64), + HeaderField("FUNCTION_DESCRIPTION_NAME", Types.VARCHAR, 16), + HeaderField("FUNCTION_DESCRIPTION_PATH", Types.VARCHAR, 64), + HeaderField("FUNCTION_DESCRIPTION_VERSION", Types.VARCHAR, 8), + HeaderField("FUNCTION_DESCRIPTION_TYPE", Types.VARCHAR, 16), + HeaderField("FUNCTION_DESCRIPTION_FN_DESC", Types.VARCHAR, 128), + HeaderField("FUNCTION_DESCRIPTION_FN_RETURN_TYPE", Types.VARCHAR, 16), + HeaderField("FUNCTION_DESCRIPTION_FN_ARGS", Types.VARCHAR, 128) + ) + + val functionNameListEntity: Map[String, HeaderField] = Seq( + HeaderField("FUNCTION_NAME_FN_NAME", Types.VARCHAR, 16), + HeaderField("FUNCTION_NAME_FN_TYPE", Types.VARCHAR, 16) + ) + + val kafkaTopicsListEntity: Map[String, HeaderField] = Seq( + HeaderField("KAFKA_TOPIC_NAME", Types.VARCHAR, 16), + HeaderField("KAFKA_TOPIC_CONSUMER_COUNT", Types.INTEGER, 16), + HeaderField("KAFKA_TOPIC_CONSUMER_GROUP_COUNT", Types.INTEGER, 16), + HeaderField("KAFKA_TOPIC_REGISTERED", Types.BOOLEAN, 5), + HeaderField("KAFKA_TOPIC_REPLICA_INFO", Types.VARCHAR, 32) + ) + + val ksqlTopicsListEntity: Map[String, HeaderField] = Seq( + HeaderField("KSQL_TOPIC_NAME", Types.VARCHAR, 16), + HeaderField("KSQL_TOPIC_KAFKA_TOPIC", Types.VARCHAR, 16), + HeaderField("KSQL_TOPIC_FORMAT", Types.VARCHAR, 16) + ) + + val propertiesListEntity: Map[String, HeaderField] = Seq( + HeaderField("PROPERTY_NAME", Types.VARCHAR, 16), + HeaderField("PROPERTY_VALUE", Types.VARCHAR, 32) + ) + + val queriesEntity: Map[String, HeaderField] = Seq( + HeaderField("QUERY_ID", Types.VARCHAR, 16), + HeaderField("QUERY_STRING", Types.VARCHAR, 64), + HeaderField("QUERY_SINKS", Types.VARCHAR, 32) + ) + + val queryDescriptionEntity: Map[String, HeaderField] = Seq( + HeaderField("QUERY_DESCRIPTION_ID", Types.VARCHAR, 16), + HeaderField("QUERY_DESCRIPTION_FIELDS", Types.VARCHAR, 128), + HeaderField("QUERY_DESCRIPTION_SOURCES", Types.VARCHAR, 32), + HeaderField("QUERY_DESCRIPTION_SINKS", Types.VARCHAR, 32), + HeaderField("QUERY_DESCRIPTION_TOPOLOGY", Types.VARCHAR, 256), + HeaderField("QUERY_DESCRIPTION_EXECUTION_PLAN", Types.VARCHAR, 256) + ) + + val queryDescriptionEntityList: Map[String, HeaderField] = queryDescriptionEntity + + val sourceDescriptionEntity: Map[String, HeaderField] = Seq( + HeaderField("SOURCE_DESCRIPTION_KEY", Types.VARCHAR, 16), + HeaderField("SOURCE_DESCRIPTION_NAME", Types.VARCHAR, 16), + HeaderField("SOURCE_DESCRIPTION_TOPIC", Types.VARCHAR, 16), + HeaderField("SOURCE_DESCRIPTION_TYPE", Types.VARCHAR, 16), + HeaderField("SOURCE_DESCRIPTION_FORMAT", Types.VARCHAR, 16), + HeaderField("SOURCE_DESCRIPTION_FIELDS", Types.VARCHAR, 128), + HeaderField("SOURCE_DESCRIPTION_PARTITIONS", Types.INTEGER, 16), + HeaderField("SOURCE_DESCRIPTION_STATISTICS", Types.VARCHAR, 128), + HeaderField("SOURCE_DESCRIPTION_ERROR_STATS", Types.VARCHAR, 128), + HeaderField("SOURCE_DESCRIPTION_TIMESTAMP", Types.VARCHAR, 32) + ) + + val sourceDescriptionEntityList: Map[String, HeaderField] = sourceDescriptionEntity + + val streamsListEntity: Map[String, HeaderField] = Seq( + HeaderField("STREAM_NAME", Types.VARCHAR, 16), + HeaderField("STREAM_TOPIC", Types.VARCHAR, 16), + HeaderField("STREAM_FORMAT", Types.VARCHAR, 16) + ) + + val tablesListEntity: Map[String, HeaderField] = Seq( + HeaderField("TABLE_NAME", Types.VARCHAR, 16), + HeaderField("TABLE_TOPIC", Types.VARCHAR, 16), + HeaderField("TABLE_FORMAT", Types.VARCHAR, 16), + HeaderField("TABLE_WINDOWS", Types.BOOLEAN, 5) + ) + + val topicDescriptionEntity: Map[String, HeaderField] = Seq( + HeaderField("TOPIC_DESCRIPTION_NAME", Types.VARCHAR, 16), + HeaderField("TOPIC_DESCRIPTION_KAFKA_TOPIC", Types.VARCHAR, 16), + HeaderField("TOPIC_DESCRIPTION_FORMAT", Types.VARCHAR, 16), + HeaderField("TOPIC_DESCRIPTION_SCHEMA_STRING", Types.BOOLEAN, 64) + ) } From 18b258104381dbd4c47d1c397fa56f064709ac00 Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Fri, 1 Feb 2019 15:37:22 -0600 Subject: [PATCH 16/47] Updating database metadata headers --- .../ksql/jdbc/KsqlDatabaseMetaData.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala index 999a408..7860a06 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala @@ -234,13 +234,13 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D tableNamePattern: String, columnNamePattern: String): ResultSet = throw NotSupported("getPseudoColumns") - override def getCatalogs: ResultSet = new StaticResultSet[String](Headers.catalogs, Iterator.empty) + override def getCatalogs: ResultSet = new StaticResultSet[String](DatabaseMetadataHeaders.catalogs, Iterator.empty) override def getSuperTables(catalog: String, schemaPattern: String, tableNamePattern: String): ResultSet = { validateCatalogAndSchema(catalog, schemaPattern) - new StaticResultSet[String](Headers.superTables, Iterator.empty) + new StaticResultSet[String](DatabaseMetadataHeaders.superTables, Iterator.empty) } override def getMaxColumnsInOrderBy: Int = throw NotSupported("getMaxColumnsInOrderBy") @@ -299,7 +299,7 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D }).toIterator } else Iterator.empty - new StaticResultSet[String](Headers.tables, itTables ++ itStreams) + new StaticResultSet[String](DatabaseMetadataHeaders.tables, itTables ++ itStreams) } override def supportsMultipleTransactions: Boolean = throw NotSupported("supportsMultipleTransactions") @@ -322,7 +322,7 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def getExtraNameCharacters: String = throw NotSupported("getExtraNameCharacters") - override def getSchemas: ResultSet = new StaticResultSet[String](Headers.schemas, Iterator.empty) + override def getSchemas: ResultSet = new StaticResultSet[String](DatabaseMetadataHeaders.schemas, Iterator.empty) override def getSchemas(catalog: String, schemaPattern: String): ResultSet = { validateCatalogAndSchema(catalog, schemaPattern) @@ -392,7 +392,7 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def supportsTransactionIsolationLevel(level: Int): Boolean = throw NotSupported("supportsTransactionIsolationLevel") - override def getTableTypes: ResultSet = new StaticResultSet[String](Headers.tableTypes, + override def getTableTypes: ResultSet = new StaticResultSet[String](DatabaseMetadataHeaders.tableTypes, Iterator(Seq(TableTypes.TABLE.name), Seq(TableTypes.STREAM.name))) override def getMaxColumnsInTable: Int = throw NotSupported("getMaxColumnsInTable") @@ -439,14 +439,14 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D .map(_.getSourceDescription) .filter(sd => columnPattern.matcher(sd.getName.toUpperCase).matches) .map(sd => { - Seq[AnyRef]("", "", tableName, sd.getName, Int.box(Headers.mapDataType(sd.getType)), sd.getType, + Seq[AnyRef]("", "", tableName, sd.getName, Int.box(DatabaseMetadataHeaders.mapDataType(sd.getType)), sd.getType, Int.box(Int.MaxValue), Int.box(0), "null", Int.box(10), Int.box(DatabaseMetaData.columnNullableUnknown), "", "", Int.box(-1), Int.box(-1), Int.box(32), Int.box(17), "", "", "", "", - Int.box(Headers.mapDataType(sd.getType)), "NO", "NO") + Int.box(DatabaseMetadataHeaders.mapDataType(sd.getType)), "NO", "NO") }).toIterator } - new StaticResultSet[AnyRef](Headers.columns, tableSchemas) + new StaticResultSet[AnyRef](DatabaseMetadataHeaders.columns, tableSchemas) } override def supportsResultSetType(`type`: Int): Boolean = throw NotSupported("supportsResultSetType") From 16276c82c221ef29043181ceec98648f7610c28f Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sat, 2 Feb 2019 19:20:52 -0600 Subject: [PATCH 17/47] Ordered list in metadata headers instead of using maps --- .../github/mmolimar/ksql/jdbc/Headers.scala | 79 ++++++++----------- .../ksql/jdbc/KsqlDatabaseMetaData.scala | 2 +- 2 files changed, 34 insertions(+), 47 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala index 78ee0eb..ecaa174 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala @@ -2,60 +2,49 @@ package com.github.mmolimar.ksql.jdbc import java.sql.Types -import scala.collection.immutable - -case class HeaderField(name: String, jdbcType: Int, length: Int, index: Int) +case class HeaderField(name: String, label: String, jdbcType: Int, length: Int, index: Int) object HeaderField { - def apply(name: String, jdbcType: Int, length: Int): HeaderField = HeaderField(name, jdbcType, length, -1) -} - -private object implicits { - implicit def toMap(headers: Seq[HeaderField]): Map[String, HeaderField] = { - headers.zipWithIndex.map { case (header, index) => { - (header.name, HeaderField(header.name, header.jdbcType, header.length, index + 1)) - } - }.toCaseInsensitiveMap + def apply(name: String, jdbcType: Int, length: Int): HeaderField = { + HeaderField(name, name.toUpperCase, jdbcType, length, -1) } - implicit class ToTreeMap[+A](tuples: TraversableOnce[A]) { + def apply(name: String, label: String, jdbcType: Int, length: Int): HeaderField = { + HeaderField(name, label, jdbcType, length, -1) + } +} - def toCaseInsensitiveMap[U](implicit ev: A <:< (String, U)): immutable.Map[String, U] = { - val b = immutable.TreeMap.newBuilder[String, U](new Ordering[String] { - def compare(x: String, y: String): Int = String.CASE_INSENSITIVE_ORDER.compare(x.toString, y.toString) - }) - for (x <- tuples) - b += x +private object implicits { - b.result() + implicit def toIndexedMap(headers: List[HeaderField]): Map[Int, HeaderField] = { + headers.zipWithIndex.map { case (header, index) => { + HeaderField(header.name, header.label, header.jdbcType, header.length, index + 1) } + }.map(h => h.index -> h).toMap } - } object DatabaseMetadataHeaders { - import implicits._ - - val tableTypes: Map[String, HeaderField] = Seq(HeaderField("TABLE_TYPE", Types.VARCHAR, 0)) + val tableTypes = List(HeaderField("TABLE_TYPE", Types.VARCHAR, 0)) - val catalogs: Map[String, HeaderField] = Seq(HeaderField("TABLE_CAT", Types.VARCHAR, 0)) + val catalogs = List(HeaderField("TABLE_CAT", Types.VARCHAR, 0)) - val schemas: Map[String, HeaderField] = Seq( + val schemas = List( HeaderField("TABLE_SCHEM", Types.VARCHAR, 0), HeaderField("TABLE_CATALOG", Types.VARCHAR, 0) ) - val superTables: Map[String, HeaderField] = Seq( + val superTables = List( HeaderField("TABLE_CAT", Types.VARCHAR, 0), HeaderField("TABLE_SCHEM", Types.VARCHAR, 0), HeaderField("TABLE_NAME", Types.VARCHAR, 255), HeaderField("SUPERTABLE_NAME", Types.VARCHAR, 0) ) - val tables: Map[String, HeaderField] = Seq( + val tables = List( HeaderField("TABLE_CAT", java.sql.Types.VARCHAR, 0), HeaderField("TABLE_SCHEM", java.sql.Types.VARCHAR, 0), HeaderField("TABLE_NAME", java.sql.Types.VARCHAR, 255), @@ -68,7 +57,7 @@ object DatabaseMetadataHeaders { HeaderField("REF_GENERATION", java.sql.Types.VARCHAR, 0) ) - val columns: Map[String, HeaderField] = Seq( + val columns = List( HeaderField("TABLE_CAT", Types.VARCHAR, 0), HeaderField("TABLE_SCHEM", Types.VARCHAR, 0), HeaderField("TABLE_NAME", Types.VARCHAR, 255), @@ -110,9 +99,7 @@ object DatabaseMetadataHeaders { object KsqlEntityHeaders { - import implicits._ - - val commandStatusEntity: Map[String, HeaderField] = Seq( + val commandStatusEntity = List( HeaderField("COMMAND_STATUS_ID_TYPE", Types.VARCHAR, 16), HeaderField("COMMAND_STATUS_ID_ENTITY", Types.VARCHAR, 32), HeaderField("COMMAND_STATUS_ID_ACTION", Types.VARCHAR, 16), @@ -120,11 +107,11 @@ object KsqlEntityHeaders { HeaderField("COMMAND_STATUS_MESSAGE", Types.VARCHAR, 128) ) - val executionPlanEntity: Map[String, HeaderField] = Seq( + val executionPlanEntity = List( HeaderField("EXECUTION_PLAN", Types.VARCHAR, 256) ) - val functionDescriptionListEntity: Map[String, HeaderField] = Seq( + val functionDescriptionListEntity = List( HeaderField("FUNCTION_DESCRIPTION_AUTHOR", Types.VARCHAR, 32), HeaderField("FUNCTION_DESCRIPTION_DESCRIPTION", Types.VARCHAR, 64), HeaderField("FUNCTION_DESCRIPTION_NAME", Types.VARCHAR, 16), @@ -136,12 +123,12 @@ object KsqlEntityHeaders { HeaderField("FUNCTION_DESCRIPTION_FN_ARGS", Types.VARCHAR, 128) ) - val functionNameListEntity: Map[String, HeaderField] = Seq( + val functionNameListEntity = List( HeaderField("FUNCTION_NAME_FN_NAME", Types.VARCHAR, 16), HeaderField("FUNCTION_NAME_FN_TYPE", Types.VARCHAR, 16) ) - val kafkaTopicsListEntity: Map[String, HeaderField] = Seq( + val kafkaTopicsListEntity = List( HeaderField("KAFKA_TOPIC_NAME", Types.VARCHAR, 16), HeaderField("KAFKA_TOPIC_CONSUMER_COUNT", Types.INTEGER, 16), HeaderField("KAFKA_TOPIC_CONSUMER_GROUP_COUNT", Types.INTEGER, 16), @@ -149,24 +136,24 @@ object KsqlEntityHeaders { HeaderField("KAFKA_TOPIC_REPLICA_INFO", Types.VARCHAR, 32) ) - val ksqlTopicsListEntity: Map[String, HeaderField] = Seq( + val ksqlTopicsListEntity = List( HeaderField("KSQL_TOPIC_NAME", Types.VARCHAR, 16), HeaderField("KSQL_TOPIC_KAFKA_TOPIC", Types.VARCHAR, 16), HeaderField("KSQL_TOPIC_FORMAT", Types.VARCHAR, 16) ) - val propertiesListEntity: Map[String, HeaderField] = Seq( + val propertiesListEntity = List( HeaderField("PROPERTY_NAME", Types.VARCHAR, 16), HeaderField("PROPERTY_VALUE", Types.VARCHAR, 32) ) - val queriesEntity: Map[String, HeaderField] = Seq( + val queriesEntity = List( HeaderField("QUERY_ID", Types.VARCHAR, 16), HeaderField("QUERY_STRING", Types.VARCHAR, 64), HeaderField("QUERY_SINKS", Types.VARCHAR, 32) ) - val queryDescriptionEntity: Map[String, HeaderField] = Seq( + val queryDescriptionEntity = List( HeaderField("QUERY_DESCRIPTION_ID", Types.VARCHAR, 16), HeaderField("QUERY_DESCRIPTION_FIELDS", Types.VARCHAR, 128), HeaderField("QUERY_DESCRIPTION_SOURCES", Types.VARCHAR, 32), @@ -175,9 +162,9 @@ object KsqlEntityHeaders { HeaderField("QUERY_DESCRIPTION_EXECUTION_PLAN", Types.VARCHAR, 256) ) - val queryDescriptionEntityList: Map[String, HeaderField] = queryDescriptionEntity + val queryDescriptionEntityList = queryDescriptionEntity - val sourceDescriptionEntity: Map[String, HeaderField] = Seq( + val sourceDescriptionEntity = List( HeaderField("SOURCE_DESCRIPTION_KEY", Types.VARCHAR, 16), HeaderField("SOURCE_DESCRIPTION_NAME", Types.VARCHAR, 16), HeaderField("SOURCE_DESCRIPTION_TOPIC", Types.VARCHAR, 16), @@ -190,22 +177,22 @@ object KsqlEntityHeaders { HeaderField("SOURCE_DESCRIPTION_TIMESTAMP", Types.VARCHAR, 32) ) - val sourceDescriptionEntityList: Map[String, HeaderField] = sourceDescriptionEntity + val sourceDescriptionEntityList = sourceDescriptionEntity - val streamsListEntity: Map[String, HeaderField] = Seq( + val streamsListEntity = List( HeaderField("STREAM_NAME", Types.VARCHAR, 16), HeaderField("STREAM_TOPIC", Types.VARCHAR, 16), HeaderField("STREAM_FORMAT", Types.VARCHAR, 16) ) - val tablesListEntity: Map[String, HeaderField] = Seq( + val tablesListEntity = List( HeaderField("TABLE_NAME", Types.VARCHAR, 16), HeaderField("TABLE_TOPIC", Types.VARCHAR, 16), HeaderField("TABLE_FORMAT", Types.VARCHAR, 16), HeaderField("TABLE_WINDOWS", Types.BOOLEAN, 5) ) - val topicDescriptionEntity: Map[String, HeaderField] = Seq( + val topicDescriptionEntity = List( HeaderField("TOPIC_DESCRIPTION_NAME", Types.VARCHAR, 16), HeaderField("TOPIC_DESCRIPTION_KAFKA_TOPIC", Types.VARCHAR, 16), HeaderField("TOPIC_DESCRIPTION_FORMAT", Types.VARCHAR, 16), diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala index 7860a06..63e1617 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala @@ -63,7 +63,7 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def storesUpperCaseQuotedIdentifiers: Boolean = throw NotSupported("storesUpperCaseQuotedIdentifiers") override def getUDTs(catalog: String, schemaPattern: String, typeNamePattern: String, - types: Array[Int]): ResultSet = new StaticResultSet[String](Map.empty, Iterator.empty) + types: Array[Int]): ResultSet = new StaticResultSet[String](List.empty[HeaderField], Iterator.empty) override def getAttributes(catalog: String, schemaPattern: String, typeNamePattern: String, attributeNamePattern: String): ResultSet = throw NotSupported("getAttributes") From 3ecdef97b112198af1897645f98aee74e8f991c7 Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sat, 2 Feb 2019 19:33:15 -0600 Subject: [PATCH 18/47] Support for metadata in result sets --- .../ksql/jdbc/resultset/KsqlResultSet.scala | 36 +++--- .../ksql/jdbc/resultset/ResultSet.scala | 105 ++++++++++----- .../jdbc/resultset/ResultSetMetadata.scala | 122 ++++++++++++++++++ .../ksql/jdbc/resultset/StaticResultSet.scala | 17 ++- 4 files changed, 226 insertions(+), 54 deletions(-) create mode 100644 src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSetMetadata.scala diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala index 5ceaf12..6a9a42b 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala @@ -1,11 +1,11 @@ package com.github.mmolimar.ksql.jdbc.resultset import java.io.Closeable -import java.sql.ResultSet +import java.sql.{ResultSet, ResultSetMetaData} import java.util.Iterator import com.github.mmolimar.ksql.jdbc.Exceptions._ -import com.github.mmolimar.ksql.jdbc.NotSupported +import com.github.mmolimar.ksql.jdbc.InvalidColumn import io.confluent.ksql.GenericRow import io.confluent.ksql.rest.client.KsqlRestClient import io.confluent.ksql.rest.entity.StreamedRow @@ -16,7 +16,7 @@ import scala.concurrent.duration._ import scala.concurrent.{Await, Future, TimeoutException} import scala.util.{Failure, Success, Try} -private[resultset] class JdbcQueryStream(stream: KsqlRestClient.QueryStream, timeout: Long) +private[resultset] class KsqlQueryStream(stream: KsqlRestClient.QueryStream) extends Closeable with Iterator[StreamedRow] { override def close: Unit = stream.close @@ -27,10 +27,12 @@ private[resultset] class JdbcQueryStream(stream: KsqlRestClient.QueryStream, tim } -class KsqlResultSet(private[jdbc] val stream: JdbcQueryStream, val timeout: Long = 0) - extends AbstractResultSet[StreamedRow](stream) { +class KsqlResultSet(private val metadata: ResultSetMetaData, + private val stream: KsqlQueryStream, val timeout: Long = 0) + extends AbstractResultSet[StreamedRow](metadata, stream) { - def this(stream: KsqlRestClient.QueryStream, timeout: Long) = this(new JdbcQueryStream(stream, timeout)) + def this(metadata: ResultSetMetaData, stream: KsqlRestClient.QueryStream, timeout: Long) = + this(metadata, new KsqlQueryStream(stream), timeout) private val emptyRow: StreamedRow = StreamedRow.row(new GenericRow) @@ -55,22 +57,24 @@ class KsqlResultSet(private[jdbc] val stream: JdbcQueryStream, val timeout: Long } } - override def isBeforeFirst: Boolean = false - - override def isAfterLast: Boolean = false + override protected def closeInherit: Unit = stream.close - override def isLast: Boolean = false + override protected def getColumnBounds: (Int, Int) = (1, currentRow.getOrElse(emptyRow).getRow.getColumns.size) - override def isFirst: Boolean = currentRow.isEmpty + override protected def getValue[T](columnIndex: Int): T = { + currentRow.map(_.getRow.getColumns.get(columnIndex - 1)).getOrElse(throw InvalidColumn()).asInstanceOf[T] + } override def getConcurrency: Int = ResultSet.CONCUR_READ_ONLY - override protected def getColumnBounds: (Int, Int) = (1, currentRow.getOrElse(emptyRow).getRow.getColumns.size) + override def getMetaData: ResultSetMetaData = metadata - override protected def getValue[T <: AnyRef](columnIndex: Int): T = { - currentRow.get.getRow.getColumns.get(columnIndex - 1).asInstanceOf[T] - } + override def isAfterLast: Boolean = false + + override def isBeforeFirst: Boolean = false + + override def isFirst: Boolean = currentRow.isEmpty - override protected def getColumnIndex(columnLabel: String): Int = throw NotSupported("getColumnIndex") + override def isLast: Boolean = !stream.hasNext } diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala index 12630d1..576acd8 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala @@ -1,6 +1,7 @@ package com.github.mmolimar.ksql.jdbc.resultset import java.io.{InputStream, Reader} +import java.lang.{Boolean => JBoolean, Byte => JByte, Double => JDouble, Float => JFloat, Integer => JInt, Long => JLong, Short => JShort} import java.math.BigDecimal import java.net.URL import java.sql.{Array, Blob, Clob, Date, NClob, Ref, ResultSet, ResultSetMetaData, RowId, SQLWarning, SQLXML, Statement, Time, Timestamp} @@ -10,6 +11,8 @@ import java.util.Calendar import com.github.mmolimar.ksql.jdbc.Exceptions._ import com.github.mmolimar.ksql.jdbc._ +import scala.reflect.ClassTag + private[resultset] class ResultSetNotSupported extends ResultSet with WrapperNotSupported { @@ -319,7 +322,7 @@ private[resultset] class ResultSetNotSupported extends ResultSet with WrapperNot override def findColumn(columnLabel: String): Int = throw NotSupported("findColumn") - override def getWarnings: SQLWarning = throw NotSupported("getWarnings") + override def getWarnings: SQLWarning = None.orNull override def getDate(columnIndex: Int): Date = throw NotSupported("getDate") @@ -397,15 +400,19 @@ private[resultset] class ResultSetNotSupported extends ResultSet with WrapperNot } -abstract class AbstractResultSet[T](private val iterator: Iterator[T]) extends ResultSetNotSupported { +abstract class AbstractResultSet[T](private val metadata: ResultSetMetaData, private val records: Iterator[T]) + extends ResultSetNotSupported { + + private val indexByLabel: Map[String, Int] = (1 to metadata.getColumnCount) + .map(index => (metadata.getColumnLabel(index).toUpperCase -> index)).toMap protected var currentRow: Option[T] = None private var closed: Boolean = false - protected def nextResult: Boolean = iterator.hasNext match { + protected def nextResult: Boolean = records.hasNext match { case true => - currentRow = Some(iterator.next) + currentRow = Some(records.next) true case false => false } @@ -423,70 +430,106 @@ abstract class AbstractResultSet[T](private val iterator: Iterator[T]) extends R closed = true } - protected def closeInherit: Unit = {} + protected def closeInherit: Unit + + override def getBoolean(columnIndex: Int): Boolean = getColumn[JBoolean](columnIndex) - override def getBoolean(columnIndex: Int): Boolean = getColumn(columnIndex) + override def getBoolean(columnLabel: String): Boolean = getColumn[JBoolean](columnLabel) - override def getBoolean(columnLabel: String): Boolean = getColumn(columnLabel) + override def getByte(columnIndex: Int): Byte = getColumn[JByte](columnIndex) - override def getByte(columnIndex: Int): Byte = getColumn(columnIndex) + override def getByte(columnLabel: String): Byte = getColumn[JByte](columnLabel) - override def getByte(columnLabel: String): Byte = getColumn(columnLabel) + override def getShort(columnIndex: Int): Short = getColumn[JShort](columnIndex) - override def getShort(columnIndex: Int): Short = getColumn(columnIndex) + override def getShort(columnLabel: String): Short = getColumn[JShort](columnLabel) - override def getShort(columnLabel: String): Short = getColumn(columnLabel) + override def getInt(columnIndex: Int): Int = getColumn[JInt](columnIndex) - override def getInt(columnIndex: Int): Int = getColumn(columnIndex) + override def getInt(columnLabel: String): Int = getColumn[JInt](columnLabel) - override def getInt(columnLabel: String): Int = getColumn(columnLabel) + override def getLong(columnIndex: Int): Long = getColumn[JLong](columnIndex) - override def getLong(columnIndex: Int): Long = getColumn(columnIndex) + override def getLong(columnLabel: String): Long = getColumn[JLong](columnLabel) - override def getLong(columnLabel: String): Long = getColumn(columnLabel) + override def getFloat(columnIndex: Int): Float = getColumn[JFloat](columnIndex) - override def getFloat(columnIndex: Int): Float = getColumn(columnIndex) + override def getFloat(columnLabel: String): Float = getColumn[JFloat](columnLabel) - override def getFloat(columnLabel: String): Float = getColumn(columnLabel) + override def getDouble(columnIndex: Int): Double = getColumn[JDouble](columnIndex) - override def getDouble(columnIndex: Int): Double = getColumn(columnIndex) + override def getDouble(columnLabel: String): Double = getColumn[JDouble](columnLabel) - override def getDouble(columnLabel: String): Double = getColumn(columnLabel) + override def getBytes(columnIndex: Int): scala.Array[Byte] = getColumn[scala.Array[Byte]](columnIndex) - override def getBytes(columnIndex: Int): scala.Array[Byte] = getColumn(columnIndex) + override def getBytes(columnLabel: String): scala.Array[Byte] = getColumn[scala.Array[Byte]](columnLabel) - override def getBytes(columnLabel: String): scala.Array[Byte] = getColumn(columnLabel) + override def getString(columnIndex: Int): String = getColumn[String](columnIndex) - override def getString(columnIndex: Int): String = getColumn(columnIndex) + override def getString(columnLabel: String): String = getColumn[String](columnLabel) - override def getString(columnLabel: String): String = getColumn(columnLabel) + override def getMetaData: ResultSetMetaData = metadata - private def getColumn[T <: AnyRef](columnLabel: String): T = getColumn(getColumnIndex(columnLabel)) + override def wasNull: Boolean = false - private def getColumn[T <: AnyRef](columnIndex: Int): T = { + private def getColumn[T <: AnyRef](columnLabel: String)(implicit ev: ClassTag[T]): T = { + getColumn[T](getColumnIndex(columnLabel)) + } + + private def getColumn[T <: AnyRef](columnIndex: Int)(implicit ev: ClassTag[T]): T = { checkRow(columnIndex) - getValue(columnIndex) + inferValue[T](columnIndex) } private def checkRow(columnIndex: Int) = { def checkIfEmpty = if (isEmpty) throw EmptyRow() - def checkColumnBounds(columnIndex: Int) = { + def checkColumnBounds(index: Int) = { val (min, max) = getColumnBounds - if (columnIndex < min || columnIndex > max) - throw InvalidColumn(s"Column with index ${columnIndex} does not exist") + if (index < min || index > max) + throw InvalidColumn(s"Column with index $index does not exist") } checkIfEmpty checkColumnBounds(columnIndex) } + private def inferValue[T <: AnyRef](columnIndex: Int)(implicit ev: ClassTag[T]): T = { + val value = getValue[T](columnIndex) + + import ImplicitClasses._ + ev.runtimeClass match { + case Any_ if ev.runtimeClass == value.getClass => value + case String_ => value.toString + case JShort_ if value.isInstanceOf[Number] => value.asInstanceOf[Number].shortValue + case JInt_ if value.isInstanceOf[Number] => value.asInstanceOf[Number].intValue + case JLong_ if value.isInstanceOf[Number] => value.asInstanceOf[Number].longValue + case JDouble_ if value.isInstanceOf[Number] => value.asInstanceOf[Number].doubleValue + case JFloat_ if value.isInstanceOf[Number] => value.asInstanceOf[Number].floatValue + case JByte_ if value.isInstanceOf[Number] => value.asInstanceOf[Number].byteValue + case _ => value + } + }.asInstanceOf[T] + + private object ImplicitClasses { + val Any_ = classOf[Any] + val String_ = classOf[String] + val JShort_ = classOf[JShort] + val JInt_ = classOf[JInt] + val JLong_ = classOf[JLong] + val JDouble_ = classOf[JDouble] + val JFloat_ = classOf[JFloat] + val JByte_ = classOf[JByte] + } + protected def isEmpty: Boolean = currentRow.isEmpty + protected def getColumnIndex(columnLabel: String): Int = { + indexByLabel.get(columnLabel.toUpperCase).getOrElse(throw InvalidColumn()) + } + protected def getColumnBounds: (Int, Int) protected def getValue[T <: AnyRef](columnIndex: Int): T - protected def getColumnIndex(columnLabel: String): Int - } diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSetMetadata.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSetMetadata.scala new file mode 100644 index 0000000..8ece92f --- /dev/null +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSetMetadata.scala @@ -0,0 +1,122 @@ +package com.github.mmolimar.ksql.jdbc.resultset + +import java.sql.{ResultSetMetaData, Types} + +import com.github.mmolimar.ksql.jdbc.Exceptions._ +import com.github.mmolimar.ksql.jdbc.implicits.toIndexedMap +import com.github.mmolimar.ksql.jdbc.{HeaderField, InvalidColumn, NotSupported, WrapperNotSupported} +import io.confluent.ksql.rest.entity.SchemaInfo.{Type => KsqlType} + +private[resultset] class ResultSetMetadataNotSupported extends ResultSetMetaData with WrapperNotSupported { + + override def getCatalogName(column: Int): String = throw NotSupported("getCatalogName") + + override def getColumnClassName(column: Int): String = throw NotSupported("getColumnClassName") + + override def getColumnCount: Int = throw NotSupported("getColumnCount") + + override def getColumnDisplaySize(column: Int): Int = throw NotSupported("getColumnDisplaySize") + + override def getColumnLabel(column: Int): String = throw NotSupported("getColumnLabel") + + override def getColumnName(column: Int): String = throw NotSupported("getColumnName") + + override def getColumnTypeName(column: Int): String = throw NotSupported("getColumnTypeName") + + override def getColumnType(column: Int): Int = throw NotSupported("getColumnType") + + override def getPrecision(column: Int): Int = throw NotSupported("getPrecision") + + override def getSchemaName(column: Int): String = throw NotSupported("getSchemaName") + + override def getScale(column: Int): Int = throw NotSupported("getScale") + + override def getTableName(column: Int): String = throw NotSupported("getTableName") + + override def isAutoIncrement(column: Int): Boolean = throw NotSupported("isAutoIncrement") + + override def isCaseSensitive(column: Int): Boolean = throw NotSupported("isCaseSensitive") + + override def isCurrency(column: Int): Boolean = throw NotSupported("isCurrency") + + override def isDefinitelyWritable(column: Int): Boolean = throw NotSupported("isDefinitelyWritable") + + override def isNullable(column: Int): Int = throw NotSupported("isNullable") + + override def isReadOnly(column: Int): Boolean = throw NotSupported("isReadOnly") + + override def isSearchable(column: Int): Boolean = throw NotSupported("isSearchable") + + override def isSigned(column: Int): Boolean = throw NotSupported("isSigned") + + override def isWritable(column: Int): Boolean = throw NotSupported("isWritable") + +} + +class KsqlResultSetMetadata(private[jdbc] val columns: List[HeaderField]) extends ResultSetMetadataNotSupported { + + private val fieldByIndex: Map[Int, HeaderField] = columns + + private def getField(index: Int): HeaderField = fieldByIndex.get(index) + .getOrElse(throw InvalidColumn(s"Column with index '$index' does not exist.")) + + override def getColumnClassName(column: Int): String = { + getField(column).jdbcType match { + case Types.INTEGER => classOf[java.lang.Integer] + case Types.BIGINT => classOf[java.lang.Long] + case Types.DOUBLE => classOf[java.lang.Double] + case Types.BOOLEAN => classOf[java.lang.Boolean] + case Types.VARCHAR => classOf[java.lang.String] + case Types.JAVA_OBJECT => classOf[java.util.Map[AnyRef, AnyRef]] + case Types.ARRAY => classOf[java.sql.Array] + case Types.STRUCT => classOf[java.sql.Struct] + case _ => classOf[java.lang.String] + } + }.getName + + override def getColumnCount: Int = columns.size + + override def getColumnDisplaySize(column: Int): Int = getField(column).jdbcType match { + case Types.INTEGER | Types.BIGINT | Types.DOUBLE => 16 + case Types.BOOLEAN => 5 + case _ => 64 + } + + override def getColumnLabel(column: Int): String = getField(column).label + + override def getColumnName(column: Int): String = getField(column).name + + override def getColumnTypeName(column: Int): String = { + getField(column).jdbcType match { + case Types.INTEGER => KsqlType.INTEGER + case Types.BIGINT => KsqlType.BIGINT + case Types.DOUBLE => KsqlType.DOUBLE + case Types.BOOLEAN => KsqlType.BOOLEAN + case Types.VARCHAR => KsqlType.STRING + case Types.JAVA_OBJECT => KsqlType.MAP + case Types.ARRAY => KsqlType.ARRAY + case Types.STRUCT => KsqlType.STRUCT + case _ => KsqlType.STRING + } + }.name + + override def getColumnType(column: Int): Int = getField(column).jdbcType + + override def getPrecision(column: Int): Int = getField(column).jdbcType match { + case Types.DOUBLE => -1 + case _ => 0 + } + + override def getScale(column: Int): Int = getField(column).jdbcType match { + case Types.DOUBLE => -1 + case _ => 0 + } + + override def isCaseSensitive(column: Int): Boolean = getField(column).jdbcType match { + case Types.VARCHAR => true + case _ => false + } + + override def isNullable(column: Int): Int = ResultSetMetaData.columnNullableUnknown + +} diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/StaticResultSet.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/StaticResultSet.scala index 1cc7d0a..217c887 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/StaticResultSet.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/StaticResultSet.scala @@ -1,17 +1,20 @@ package com.github.mmolimar.ksql.jdbc.resultset -import com.github.mmolimar.ksql.jdbc.Exceptions._ -import com.github.mmolimar.ksql.jdbc.{HeaderField, InvalidColumn} +import java.sql.ResultSetMetaData +import com.github.mmolimar.ksql.jdbc.HeaderField -class StaticResultSet[T <: AnyRef](private[jdbc] val columns: Map[String, HeaderField], - private[jdbc] val rows: Iterator[Seq[T]]) extends AbstractResultSet(rows) { + +class StaticResultSet[T <: Any](private val metadata: ResultSetMetaData, + private[jdbc] val rows: Iterator[Seq[T]]) extends AbstractResultSet(metadata, rows) { + + def this(columns: List[HeaderField], rows: Iterator[Seq[T]]) = + this(new KsqlResultSetMetadata(columns), rows) override protected def getValue[V <: AnyRef](columnIndex: Int): V = currentRow.get(columnIndex - 1).asInstanceOf[V] override protected def getColumnBounds: (Int, Int) = (1, currentRow.getOrElse(Seq.empty).size) - override protected def getColumnIndex(columnLabel: String): Int = { - columns.get(columnLabel).getOrElse(throw InvalidColumn()).index - } + override protected def closeInherit: Unit = {} + } From 2e8857cad2ef4cf40e8a37399648aed0737a0f02 Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sat, 2 Feb 2019 20:03:51 -0600 Subject: [PATCH 19/47] Unit tests for result set metadata --- .../resultset/KsqlResultSetMetadata.scala | 91 --------------- .../resultset/KsqlResultSetMetadataSpec.scala | 106 +++++++++++------- 2 files changed, 65 insertions(+), 132 deletions(-) delete mode 100644 src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetadata.scala diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetadata.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetadata.scala deleted file mode 100644 index 842b433..0000000 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetadata.scala +++ /dev/null @@ -1,91 +0,0 @@ -package com.github.mmolimar.ksql.jdbc.resultset - -import java.sql.{ResultSetMetaData, Types} - -import com.github.mmolimar.ksql.jdbc.Exceptions._ -import com.github.mmolimar.ksql.jdbc.{InvalidColumn, NotSupported, WrapperNotSupported} -import io.confluent.ksql.rest.entity.SchemaInfo.Type._ -import io.confluent.ksql.rest.entity.{FieldInfo, QueryDescription} - -import scala.collection.JavaConverters._ - -class KsqlResultSetMetadata(queryDesc: QueryDescription) extends ResultSetMetaData with WrapperNotSupported { - - private lazy val fields: Map[Int, FieldInfo] = queryDesc.getFields.asScala.zipWithIndex - .map { case (field, index) => (index + 1) -> field }.toMap - - private def getField(index: Int): FieldInfo = fields.get(index) - .getOrElse(throw InvalidColumn(s"Column with index $index does not exist")) - - override def getSchemaName(column: Int): String = getField(column).getSchema.getTypeName - - override def getCatalogName(column: Int): String = queryDesc.getSources.asScala.mkString(", ") - - override def getColumnLabel(column: Int): String = getField(column).getName - - override def getColumnName(column: Int): String = getField(column).getName - - override def getColumnTypeName(column: Int): String = getField(column).getSchema.getTypeName - - override def getColumnClassName(column: Int): String = { - getField(column).getSchema.getType match { - case INTEGER => classOf[java.lang.Integer] - case BIGINT => classOf[java.lang.Long] - case DOUBLE => classOf[java.lang.Double] - case BOOLEAN => classOf[java.lang.Boolean] - case STRING => classOf[java.lang.String] - case MAP => classOf[java.util.Map[AnyRef, AnyRef]] - case ARRAY => classOf[java.sql.Array] - case STRUCT => classOf[java.sql.Struct] - } - }.getCanonicalName - - override def isCaseSensitive(column: Int): Boolean = getField(column).getSchema.getType match { - case STRING => true - case _ => false - } - - override def getTableName(column: Int): String = queryDesc.getTopology - - override def getColumnType(column: Int): Int = getField(column).getSchema.getType match { - case INTEGER => Types.INTEGER - case BIGINT => Types.BIGINT - case DOUBLE => Types.DOUBLE - case BOOLEAN => Types.BOOLEAN - case STRING => Types.VARCHAR - case MAP => Types.JAVA_OBJECT - case ARRAY => Types.ARRAY - case STRUCT => Types.STRUCT - } - - override def getColumnCount: Int = fields.size - - override def getPrecision(column: Int): Int = getField(column).getSchema.getType match { - case DOUBLE => -1 - case _ => 0 - } - - override def getScale(column: Int): Int = getField(column).getSchema.getType match { - case DOUBLE => -1 - case _ => 0 - } - - override def isSigned(column: Int): Boolean = throw NotSupported("isSigned") - - override def isWritable(column: Int): Boolean = throw NotSupported("isWritable") - - override def isAutoIncrement(column: Int): Boolean = throw NotSupported("isAutoIncrement") - - override def isReadOnly(column: Int): Boolean = throw NotSupported("isReadOnly") - - override def isCurrency(column: Int): Boolean = throw NotSupported("isCurrency") - - override def isSearchable(column: Int): Boolean = throw NotSupported("isSearchable") - - override def isDefinitelyWritable(column: Int): Boolean = throw NotSupported("isDefinitelyWritable") - - override def isNullable(column: Int): Int = throw NotSupported("isNullable") - - override def getColumnDisplaySize(column: Int): Int = throw NotSupported("getColumnDisplaySize") - -} diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetadataSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetadataSpec.scala index 0f77961..33c170c 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetadataSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetadataSpec.scala @@ -1,47 +1,35 @@ package com.github.mmolimar.ksql.jdbc.resultset -import java.sql.{SQLException, SQLFeatureNotSupportedException} -import java.util -import java.util.Collections +import java.sql.{ResultSetMetaData, SQLFeatureNotSupportedException, Types} +import com.github.mmolimar.ksql.jdbc.HeaderField import com.github.mmolimar.ksql.jdbc.utils.TestUtils._ -import io.confluent.ksql.query.QueryId -import io.confluent.ksql.rest.entity.SchemaInfo.Type -import io.confluent.ksql.rest.entity.{EntityQueryId, FieldInfo, QueryDescription, SchemaInfo} +import io.confluent.ksql.rest.entity.SchemaInfo.{Type => KsqlType} import org.scalamock.scalatest.MockFactory import org.scalatest.{Matchers, OneInstancePerTest, WordSpec} class KsqlResultSetMetadataSpec extends WordSpec with Matchers with MockFactory with OneInstancePerTest { - val implementedMethods = Seq("getSchemaName", "getCatalogName", "getColumnLabel", "getColumnName", - "getColumnTypeName", "getColumnClassName", "isCaseSensitive", "getTableName", "getColumnType", - "getColumnCount", "getPrecision", "getScale") + val implementedMethods = Seq("getColumnLabel", "getColumnName", + "getColumnTypeName", "getColumnClassName", "isCaseSensitive", "getColumnType", + "getColumnCount", "getPrecision", "getScale", "getColumnDisplaySize", "isNullable") "A KsqlResultSetMetadata" when { "validating specs" should { - val fields = util.Arrays.asList( - new FieldInfo("field1", new SchemaInfo(Type.INTEGER, None.orNull, None.orNull)), - new FieldInfo("field2", new SchemaInfo(Type.BIGINT, None.orNull, None.orNull)), - new FieldInfo("field3", new SchemaInfo(Type.DOUBLE, None.orNull, None.orNull)), - new FieldInfo("field4", new SchemaInfo(Type.BOOLEAN, None.orNull, None.orNull)), - new FieldInfo("field5", new SchemaInfo(Type.STRING, None.orNull, None.orNull)), - new FieldInfo("field6", new SchemaInfo(Type.MAP, None.orNull, None.orNull)), - new FieldInfo("field7", new SchemaInfo(Type.ARRAY, None.orNull, None.orNull)), - new FieldInfo("field8", new SchemaInfo(Type.STRUCT, None.orNull, None.orNull)) - ) - val queryDesc = new QueryDescription( - new EntityQueryId(new QueryId("testquery")), - "select * from text;", - fields, - Collections.singleton("TEST"), - Collections.emptySet[String], - "test", - "executionPlan", - Collections.emptyMap[String, AnyRef] - ) - val resultSet = new KsqlResultSetMetadata(queryDesc) + val resultSet = new KsqlResultSetMetadata( + List( + HeaderField("field1", Types.INTEGER, 16), + HeaderField("field2", Types.BIGINT, 16), + HeaderField("field3", Types.DOUBLE, 16), + HeaderField("field4", Types.BOOLEAN, 16), + HeaderField("field5", Types.VARCHAR, 16), + HeaderField("field6", Types.JAVA_OBJECT, 16), + HeaderField("field7", Types.ARRAY, 16), + HeaderField("field8", Types.STRUCT, 16), + HeaderField("field9", -999, 16) + )) "throw not supported exception if not supported" in { @@ -55,45 +43,81 @@ class KsqlResultSetMetadataSpec extends WordSpec with Matchers with MockFactory "work if implemented" in { - resultSet.getCatalogName(1) should be("TEST") - resultSet.getSchemaName(2) should be("BIGINT") - resultSet.getColumnLabel(3) should be("field3") + resultSet.getColumnLabel(3) should be("FIELD3") resultSet.getColumnName(3) should be("field3") resultSet.getColumnTypeName(3) should be("DOUBLE") resultSet.getColumnClassName(1) should be("java.lang.Integer") resultSet.getColumnType(1) should be(java.sql.Types.INTEGER) + resultSet.getColumnTypeName(1) should be(KsqlType.INTEGER.name) + resultSet.getColumnDisplaySize(1) should be(16) + resultSet.getColumnClassName(2) should be("java.lang.Long") resultSet.getColumnType(2) should be(java.sql.Types.BIGINT) + resultSet.getColumnTypeName(2) should be(KsqlType.BIGINT.name) + resultSet.getColumnDisplaySize(2) should be(16) + resultSet.getColumnClassName(3) should be("java.lang.Double") resultSet.getColumnType(3) should be(java.sql.Types.DOUBLE) + resultSet.getColumnTypeName(3) should be(KsqlType.DOUBLE.name) + resultSet.getColumnDisplaySize(3) should be(16) + resultSet.getColumnClassName(4) should be("java.lang.Boolean") resultSet.getColumnType(4) should be(java.sql.Types.BOOLEAN) + resultSet.getColumnTypeName(4) should be(KsqlType.BOOLEAN.name) + resultSet.getColumnDisplaySize(4) should be(5) + resultSet.getColumnClassName(5) should be("java.lang.String") resultSet.getColumnType(5) should be(java.sql.Types.VARCHAR) + resultSet.getColumnTypeName(5) should be(KsqlType.STRING.name) + resultSet.getColumnDisplaySize(5) should be(64) + resultSet.getColumnClassName(6) should be("java.util.Map") resultSet.getColumnType(6) should be(java.sql.Types.JAVA_OBJECT) + resultSet.getColumnTypeName(6) should be(KsqlType.MAP.name) + resultSet.getColumnDisplaySize(6) should be(64) + resultSet.getColumnClassName(7) should be("java.sql.Array") resultSet.getColumnType(7) should be(java.sql.Types.ARRAY) + resultSet.getColumnTypeName(7) should be(KsqlType.ARRAY.name) + resultSet.getColumnDisplaySize(7) should be(64) + resultSet.getColumnClassName(8) should be("java.sql.Struct") resultSet.getColumnType(8) should be(java.sql.Types.STRUCT) + resultSet.getColumnTypeName(8) should be(KsqlType.STRUCT.name) + resultSet.getColumnDisplaySize(8) should be(64) + + resultSet.getColumnClassName(9) should be("java.lang.String") + resultSet.getColumnType(9) should be(-999) + resultSet.getColumnTypeName(9) should be(KsqlType.STRING.name) + resultSet.getColumnDisplaySize(9) should be(64) resultSet.isCaseSensitive(2) should be(false) resultSet.isCaseSensitive(5) should be(true) - resultSet.getTableName(3) should be("test") resultSet.getColumnType(3) should be(java.sql.Types.DOUBLE) - resultSet.getColumnCount should be(8) + resultSet.getColumnCount should be(9) resultSet.getPrecision(3) should be(-1) resultSet.getPrecision(2) should be(0) resultSet.getScale(3) should be(-1) resultSet.getScale(4) should be(0) + resultSet.isNullable(1) should be(ResultSetMetaData.columnNullableUnknown) + } + } + } - assertThrows[SQLException] { - resultSet.getSchemaName(0) - } - assertThrows[SQLException] { - resultSet.getSchemaName(9) - } + "A ResultSetMetadataNotSupported" when { + + "validating specs" should { + + "throw not supported exception if not supported" in { + + val resultSet = new ResultSetMetadataNotSupported + reflectMethods[ResultSetMetadataNotSupported](Seq.empty, false, resultSet) + .foreach(method => { + assertThrows[SQLFeatureNotSupportedException] { + method() + } + }) } } } From 57f0bb7307a46c2283df6bb335c143cbf324ffdb Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sat, 2 Feb 2019 21:28:46 -0600 Subject: [PATCH 20/47] Updating tests for static and KSQL result sets --- .../jdbc/resultset/KsqlResultSetSpec.scala | 95 +++++++++++++------ .../jdbc/resultset/StaticResultSetSpec.scala | 13 +-- 2 files changed, 74 insertions(+), 34 deletions(-) diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala index a23c0ec..b107fe8 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala @@ -1,7 +1,8 @@ package com.github.mmolimar.ksql.jdbc.resultset -import java.sql.{ResultSet, SQLException, SQLFeatureNotSupportedException} +import java.sql._ +import com.github.mmolimar.ksql.jdbc.HeaderField import com.github.mmolimar.ksql.jdbc.utils.TestUtils._ import io.confluent.ksql.GenericRow import io.confluent.ksql.rest.entity.StreamedRow @@ -15,35 +16,57 @@ class KsqlResultSetSpec extends WordSpec with Matchers with MockFactory with One val implementedMethods = Seq("isLast", "isAfterLast", "isBeforeFirst", "isFirst", "next", "getConcurrency", "close", "getString", "getBytes", "getByte", "getBytes", "getBoolean", "getShort", - "getInt", "getLong", "getFloat", "getDouble", "getResultSet", "getUpdateCount") + "getInt", "getLong", "getFloat", "getDouble", "getMetaData", "getResultSet", "getUpdateCount", "getWarnings") "A KsqlResultSet" when { "validating specs" should { + val resultSetMetadata = new KsqlResultSetMetadata( + List( + HeaderField("field1", Types.INTEGER, 16), + HeaderField("field2", Types.BIGINT, 16), + HeaderField("field3", Types.DOUBLE, 16), + HeaderField("field4", Types.BOOLEAN, 16), + HeaderField("field5", Types.VARCHAR, 16), + HeaderField("field6", Types.JAVA_OBJECT, 16), + HeaderField("field7", Types.ARRAY, 16), + HeaderField("field8", Types.STRUCT, 16), + HeaderField("field9", -999, 16) + )) + "throw not supported exception if not supported" in { - val resultSet = new KsqlResultSet(mock[JdbcQueryStream], 0) + val resultSet = new KsqlResultSet(resultSetMetadata, mock[KsqlQueryStream], 0) reflectMethods[KsqlResultSet](implementedMethods, false, resultSet) .foreach(method => { assertThrows[SQLFeatureNotSupportedException] { - method() + try { + method() + println("") + } catch { + case e: Throwable => throw e + } } }) } "work if implemented" in { - val mockedQueryStream = mock[JdbcQueryStream] + val mockedQueryStream = mock[KsqlQueryStream] inSequence { (mockedQueryStream.hasNext _).expects.returns(true) - val columnValues = Seq[AnyRef]("string", "bytes".getBytes, Boolean.box(true), Byte.box('0'), - Short.box(1), Int.box(2), Long.box(3L), Float.box(4.4f), Double.box(5.5d)) - (mockedQueryStream.next _).expects.returns(StreamedRow.row(new GenericRow(columnValues.asJava))) + (mockedQueryStream.hasNext _).expects.returns(true) + val columnValues = Seq[AnyRef](Int.box(1), Long.box(2L), Double.box(3.3d), Boolean.box(true), + "1", Map.empty, scala.Array.empty, Map.empty, None.orNull) + val row = StreamedRow.row(new GenericRow(columnValues.asJava)) + (mockedQueryStream.next _).expects.returns(row) (mockedQueryStream.hasNext _).expects.returns(false) + (mockedQueryStream.close _).expects } - val resultSet = new KsqlResultSet(mockedQueryStream) + val resultSet = new KsqlResultSet(resultSetMetadata, mockedQueryStream) + resultSet.getMetaData should be(resultSetMetadata) resultSet.isLast should be(false) resultSet.isAfterLast should be(false) resultSet.isBeforeFirst should be(false) @@ -52,50 +75,66 @@ class KsqlResultSetSpec extends WordSpec with Matchers with MockFactory with One resultSet.isFirst should be(true) resultSet.next should be(true) - resultSet.getString(1) should be("string") - resultSet.getBytes(2) should be("bytes".getBytes) - resultSet.getBoolean(3) should be(Boolean.box(true)) - resultSet.getByte(4) should be(Byte.box('0')) - resultSet.getShort(5) should be(Short.box(1)) - resultSet.getInt(6) should be(Int.box(2)) - resultSet.getLong(7) should be(Long.box(3L)) - resultSet.getFloat(8) should be(Float.box(4.4f)) - resultSet.getDouble(9) should be(Double.box(5.5d)) + // just to validate proper maps in data types + val expected = Seq( + Seq("1", scala.Array(1.byteValue), Boolean.box(true), Byte.box(1), + Short.box(1), Int.box(1), Long.box(1L), Float.box(1.0f), Double.box(1.0d)), + Seq("2", scala.Array(2L.byteValue), Boolean.box(true), Byte.box(2), + Short.box(2), Int.box(2), Long.box(2L), Float.box(2.0f), Double.box(2.0d)), + Seq("3.3", scala.Array(3L.byteValue), Boolean.box(true), Byte.box(3), + Short.box(3), Int.box(3), Long.box(3L), Float.box(3.3f), Double.box(3.3d)), + Seq("true", scala.Array(1.byteValue), Boolean.box(true), Byte.box(1), + Short.box(1), Int.box(1), Long.box(1L), Float.box(1.0f), Double.box(1.0d)), + Seq("1", "1".getBytes, Boolean.box(false), Byte.box(1), + Short.box(1), Int.box(1), Long.box(1L), Float.box(1.0f), Double.box(1.0d)) + ) + expected.zipWithIndex.map { case (e, index) => { + resultSet.getString(index + 1) should be(e(0)) + resultSet.getBytes(index + 1) should be(e(1)) + resultSet.getBoolean(index + 1) should be(e(2)) + resultSet.getByte(index + 1) should be(e(3)) + resultSet.getShort(index + 1) should be(e(4)) + resultSet.getInt(index + 1) should be(e(5)) + resultSet.getLong(index + 1) should be(e(6)) + resultSet.getFloat(index + 1) should be(e(7)) + resultSet.getDouble(index + 1) should be(e(8)) + } + } assertThrows[SQLException] { resultSet.getString(1000) } - - assertThrows[SQLFeatureNotSupportedException] { + assertThrows[SQLException] { resultSet.getString("UNKNOWN") } - assertThrows[SQLFeatureNotSupportedException] { + assertThrows[SQLException] { resultSet.getBytes("UNKNOWN") } - assertThrows[SQLFeatureNotSupportedException] { + assertThrows[SQLException] { resultSet.getBoolean("UNKNOWN") } - assertThrows[SQLFeatureNotSupportedException] { + assertThrows[SQLException] { resultSet.getByte("UNKNOWN") } - assertThrows[SQLFeatureNotSupportedException] { + assertThrows[SQLException] { resultSet.getShort("UNKNOWN") } - assertThrows[SQLFeatureNotSupportedException] { + assertThrows[SQLException] { resultSet.getInt("UNKNOWN") } - assertThrows[SQLFeatureNotSupportedException] { + assertThrows[SQLException] { resultSet.getLong("UNKNOWN") } - assertThrows[SQLFeatureNotSupportedException] { + assertThrows[SQLException] { resultSet.getFloat("UNKNOWN") } - assertThrows[SQLFeatureNotSupportedException] { + assertThrows[SQLException] { resultSet.getDouble("UNKNOWN") } resultSet.next should be(false) resultSet.isFirst should be(false) + resultSet.getWarnings should be(None.orNull) resultSet.close resultSet.close assertThrows[SQLException] { diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/StaticResultSetSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/StaticResultSetSpec.scala index 0d91b1b..a7b52a7 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/StaticResultSetSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/StaticResultSetSpec.scala @@ -3,7 +3,7 @@ package com.github.mmolimar.ksql.jdbc.resultset import java.sql.{SQLException, SQLFeatureNotSupportedException} import com.github.mmolimar.ksql.jdbc.utils.TestUtils._ -import com.github.mmolimar.ksql.jdbc.{Headers, TableTypes} +import com.github.mmolimar.ksql.jdbc.{DatabaseMetadataHeaders, HeaderField, TableTypes} import org.scalamock.scalatest.MockFactory import org.scalatest.{Matchers, OneInstancePerTest, WordSpec} @@ -11,7 +11,7 @@ import org.scalatest.{Matchers, OneInstancePerTest, WordSpec} class StaticResultSetSpec extends WordSpec with Matchers with MockFactory with OneInstancePerTest { val implementedMethods = Seq("next", "getString", "getBytes", "getByte", "getBytes", "getBoolean", "getShort", - "getInt", "getLong", "getFloat", "getDouble") + "getInt", "getLong", "getFloat", "getDouble", "getMetaData", "close", "getWarnings") "A StaticResultSet" when { @@ -19,18 +19,18 @@ class StaticResultSetSpec extends WordSpec with Matchers with MockFactory with O "throw not supported exception if not supported" in { - val resultSet = new StaticResultSet[String](Map.empty, Iterator(Seq(""))) + val resultSet = new StaticResultSet[String](List.empty[HeaderField], Iterator.empty) reflectMethods[StaticResultSet[String]](implementedMethods, false, resultSet) .foreach(method => { assertThrows[SQLFeatureNotSupportedException] { - method() + method() } }) } "work if implemented" in { - val resultSet = new StaticResultSet[String](Headers.tableTypes, Iterator(Seq(TableTypes.TABLE.name), + val resultSet = new StaticResultSet[String](DatabaseMetadataHeaders.tableTypes, Iterator(Seq(TableTypes.TABLE.name), Seq(TableTypes.STREAM.name))) resultSet.next should be(true) resultSet.getString(1) should be(TableTypes.TABLE.name) @@ -44,9 +44,10 @@ class StaticResultSetSpec extends WordSpec with Matchers with MockFactory with O resultSet.getString("UNKNOWN") } resultSet.next should be(false) + resultSet.getWarnings should be(None.orNull) + resultSet.close } } } } - From d42823535de6b310223faaf675b82d1b9a2161e4 Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sun, 3 Feb 2019 15:01:58 -0600 Subject: [PATCH 21/47] Properties in driver default to false to force the flag --- .../mmolimar/ksql/jdbc/KsqlConnection.scala | 6 +-- .../ksql/jdbc/KsqlConnectionSpec.scala | 8 +++- .../mmolimar/ksql/jdbc/KsqlDriverSpec.scala | 41 ++++++++++--------- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala index d582fcd..d598962 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala @@ -21,7 +21,7 @@ case class KsqlConnectionValues(ksqlServer: String, port: Int, config: Map[Strin def isSecured: Boolean = config.getOrElse("secured", "false").toBoolean - def properties: Boolean = config.getOrElse("properties", "true").toBoolean + def properties: Boolean = config.getOrElse("properties", "false").toBoolean def timeout: Long = config.getOrElse("timeout", "0").toLong @@ -51,7 +51,7 @@ class KsqlConnection(values: KsqlConnectionValues, properties: Properties) exten private[jdbc] def executeKsqlCommand(ksql: String): RestResponse[KsqlEntityList] = ksqlClient.makeKsqlRequest(ksql) - override def setAutoCommit(autoCommit: Boolean): Unit = throw NotSupported("setAutoCommit") + override def setAutoCommit(autoCommit: Boolean): Unit = {} override def setHoldability(holdability: Int): Unit = throw NotSupported("setHoldability") @@ -173,7 +173,7 @@ class KsqlConnection(values: KsqlConnectionValues, properties: Properties) exten override def createStruct(typeName: String, attributes: scala.Array[AnyRef]): Struct = throw NotSupported("createStruct") - override def getWarnings: SQLWarning = throw NotSupported("getWarnings") + override def getWarnings: SQLWarning = None.orNull override def setSchema(schema: String): Unit = throw NotSupported("setSchema") diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlConnectionSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlConnectionSpec.scala index d461aca..451ed00 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlConnectionSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlConnectionSpec.scala @@ -13,7 +13,7 @@ import org.scalatest.{Matchers, WordSpec} class KsqlConnectionSpec extends WordSpec with Matchers with MockFactory { val implementedMethods = Seq("createStatement", "getAutoCommit", "getTransactionIsolation", - "setClientInfo", "isReadOnly", "isValid", "close", "getMetaData") + "setClientInfo", "isReadOnly", "isValid", "close", "getMetaData", "getWarnings", "setAutoCommit") "A KsqlConnection" when { @@ -28,7 +28,7 @@ class KsqlConnectionSpec extends WordSpec with Matchers with MockFactory { reflectMethods[KsqlConnection](implementedMethods, false, ksqlConnection) .foreach(method => { assertThrows[SQLFeatureNotSupportedException] { - method() + method() } }) } @@ -59,6 +59,10 @@ class KsqlConnectionSpec extends WordSpec with Matchers with MockFactory { assertThrows[SQLFeatureNotSupportedException] { ksqlConnection.createStatement(-1, -1) } + ksqlConnection.setAutoCommit(true) + ksqlConnection.setAutoCommit(false) + ksqlConnection.getAutoCommit should be(false) + ksqlConnection.getWarnings should be(None.orNull) (ksqlRestClient.close _).expects ksqlConnection.close diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala index 5d3efe9..2f6569b 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala @@ -9,13 +9,14 @@ class KsqlDriverSpec extends WordSpec with Matchers { "A KsqlDriver" when { val driver = new KsqlDriver + "validating specs" should { "not be JDBC compliant" in { - driver.jdbcCompliant shouldBe (false) + driver.jdbcCompliant should be(false) } "have a major and minor version" in { - driver.getMinorVersion shouldBe (0) - driver.getMajorVersion shouldBe (1) + driver.getMinorVersion should be(0) + driver.getMajorVersion should be(1) } "have no properties" in { driver.getPropertyInfo("", new Properties).length should be(0) @@ -34,18 +35,19 @@ class KsqlDriverSpec extends WordSpec with Matchers { } } } + "accepting an URL" should { - val driver = new KsqlDriver "return false if invalid" in { - driver.acceptsURL(null) shouldBe (false) - driver.acceptsURL("") shouldBe (false) - driver.acceptsURL("jdbc:invalid://ksql-server:8080") shouldBe (false) + driver.acceptsURL(null) should be(false) + driver.acceptsURL("") should be(false) + driver.acceptsURL("jdbc:invalid://ksql-server:8080") should be(false) } "return true if valid" in { - driver.acceptsURL("jdbc:ksql://ksql-server:8080") shouldBe (true) - driver.acceptsURL("jdbc:ksql://") shouldBe (true) + driver.acceptsURL("jdbc:ksql://ksql-server:8080") should be(true) + driver.acceptsURL("jdbc:ksql://") should be(true) } } + "parsing an URL" should { "throw an SQLException if invalid" in { assertThrows[SQLException] { @@ -68,7 +70,7 @@ class KsqlDriverSpec extends WordSpec with Matchers { connectionValues.config.isEmpty should be(true) connectionValues.getKsqlUrl should be(s"http://${ksqlServer}:${ksqlPort}") connectionValues.isSecured should be(false) - connectionValues.properties should be(true) + connectionValues.properties should be(false) connectionValues.timeout should be(0) connectionValues = KsqlDriver.parseUrl(s"jdbc:ksql://${ksqlServer}:${ksqlPort}?prop1=value1") @@ -78,7 +80,7 @@ class KsqlDriverSpec extends WordSpec with Matchers { connectionValues.config.get("prop1").get should be("value1") connectionValues.getKsqlUrl should be(s"http://${ksqlServer}:${ksqlPort}") connectionValues.isSecured should be(false) - connectionValues.properties should be(true) + connectionValues.properties should be(false) connectionValues.timeout should be(0) connectionValues = KsqlDriver.parseUrl(s"jdbc:ksql://${ksqlServer}:${ksqlPort}?prop1=value1&secured=true&prop2=value2") @@ -90,7 +92,7 @@ class KsqlDriverSpec extends WordSpec with Matchers { connectionValues.config.get("secured").get should be("true") connectionValues.getKsqlUrl should be(s"https://${ksqlServer}:${ksqlPort}") connectionValues.isSecured should be(true) - connectionValues.properties should be(true) + connectionValues.properties should be(false) connectionValues.timeout should be(0) connectionValues = KsqlDriver.parseUrl(s"jdbc:ksql://${ksqlServer}:${ksqlPort}?prop1=value1&timeout=100&prop2=value2") @@ -102,35 +104,34 @@ class KsqlDriverSpec extends WordSpec with Matchers { connectionValues.config.get("timeout").get should be("100") connectionValues.getKsqlUrl should be(s"http://${ksqlServer}:${ksqlPort}") connectionValues.isSecured should be(false) - connectionValues.properties should be(true) + connectionValues.properties should be(false) connectionValues.timeout should be(100) - connectionValues = KsqlDriver.parseUrl(s"jdbc:ksql://${ksqlServer}:${ksqlPort}?prop1=value1&properties=false&prop2=value2") + connectionValues = KsqlDriver.parseUrl(s"jdbc:ksql://${ksqlServer}:${ksqlPort}?prop1=value1&properties=true&prop2=value2") connectionValues.ksqlServer should be(ksqlServer) connectionValues.port should be(ksqlPort) connectionValues.config.size should be(3) connectionValues.config.get("prop1").get should be("value1") connectionValues.config.get("prop2").get should be("value2") - connectionValues.config.get("properties").get should be("false") + connectionValues.config.get("properties").get should be("true") connectionValues.getKsqlUrl should be(s"http://${ksqlServer}:${ksqlPort}") connectionValues.isSecured should be(false) - connectionValues.properties should be(false) + connectionValues.properties should be(true) connectionValues.timeout should be(0) - connectionValues = KsqlDriver.parseUrl(s"jdbc:ksql://${ksqlServer}:${ksqlPort}?timeout=100&secured=true&properties=false&prop1=value1") + connectionValues = KsqlDriver.parseUrl(s"jdbc:ksql://${ksqlServer}:${ksqlPort}?timeout=100&secured=true&properties=true&prop1=value1") connectionValues.ksqlServer should be(ksqlServer) connectionValues.port should be(ksqlPort) connectionValues.config.size should be(4) connectionValues.config.get("prop1").get should be("value1") connectionValues.config.get("timeout").get should be("100") connectionValues.config.get("secured").get should be("true") - connectionValues.config.get("properties").get should be("false") + connectionValues.config.get("properties").get should be("true") connectionValues.getKsqlUrl should be(s"https://${ksqlServer}:${ksqlPort}") connectionValues.isSecured should be(true) - connectionValues.properties should be(false) + connectionValues.properties should be(true) connectionValues.timeout should be(100) } } } - } From 0c2b6fbe879eb03c4d571c089f95cf928f8a271d Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sun, 3 Feb 2019 15:03:21 -0600 Subject: [PATCH 22/47] KSQL entities implemented in statements when querying commands --- .../mmolimar/ksql/jdbc/KsqlStatement.scala | 258 ++++++++++++++++-- .../ksql/jdbc/resultset/KsqlResultSet.scala | 7 +- 2 files changed, 237 insertions(+), 28 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala index 1ddcaf7..6a3176a 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala @@ -1,29 +1,48 @@ package com.github.mmolimar.ksql.jdbc -import java.sql.{Connection, ResultSet, SQLWarning, Statement} +import java.sql.{Connection, ResultSet, SQLWarning, Statement, Types} import com.github.mmolimar.ksql.jdbc.Exceptions._ -import com.github.mmolimar.ksql.jdbc.resultset.KsqlResultSet -import io.confluent.ksql.rest.client.KsqlRestClient +import com.github.mmolimar.ksql.jdbc.resultset.{KsqlQueryStream, KsqlResultSet, KsqlResultSetMetadata, StaticResultSet} +import io.confluent.ksql.parser.KsqlParser +import io.confluent.ksql.rest.client.{KsqlRestClient, RestResponse} +import io.confluent.ksql.rest.entity.SchemaInfo.{Type => KsqlType} +import io.confluent.ksql.rest.entity._ + +import scala.collection.JavaConverters._ +import scala.util.{Failure, Success, Try} + +private object KsqlStatement { + + private val ksqlParser: KsqlParser = new KsqlParser + +} class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = 0) extends Statement with WrapperNotSupported { + import KsqlStatement._ + + private[this] var currentResultSet: Option[ResultSet] = None + override def setMaxFieldSize(max: Int): Unit = throw NotSupported("setMaxFieldSize") override def getMoreResults: Boolean = throw NotSupported("getMoreResults") override def getMoreResults(current: Int): Boolean = throw NotSupported("getMoreResults") - override def clearWarnings(): Unit = throw NotSupported("clearWarnings") + override def clearWarnings: Unit = throw NotSupported("clearWarnings") override def getGeneratedKeys: ResultSet = throw NotSupported("getGeneratedKeys") - override def closeOnCompletion(): Unit = throw NotSupported("closeOnCompletion") + override def closeOnCompletion: Unit = throw NotSupported("closeOnCompletion") - override def cancel(): Unit = throw NotSupported("cancel") + override def cancel: Unit = { + currentResultSet.map(_.close) + currentResultSet = None + } - override def getResultSet: ResultSet = throw NotSupported("getResultSet") + override def getResultSet: ResultSet = currentResultSet.getOrElse(throw ResultSetError("Result set not initialized.")) override def isPoolable: Boolean = throw NotSupported("isPoolable") @@ -31,13 +50,61 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = override def setCursorName(name: String): Unit = throw NotSupported("setCursorName") - override def getUpdateCount: Int = throw NotSupported("getUpdateCount") + override def getUpdateCount: Int = -1 override def addBatch(sql: String): Unit = throw NotSupported("addBatch") override def getMaxRows: Int = throw NotSupported("getMaxRows") - override def execute(sql: String): Boolean = ksqlClient.makeKsqlRequest(fixSql(sql)).isSuccessful + private def executeKsqlRequest(sql: String): Unit = { + currentResultSet = None + val fixedSql = fixSql(sql) + val stmt = Try(ksqlParser.getStatements(fixedSql)) match { + case Failure(e) => throw KsqlQueryError(s"Error parsing query '$fixedSql': ${e.getMessage}.", e) + case Success(s) if s.size() != 1 => throw KsqlQueryError("You have to execute just one query at a time. " + + s"Number of queries sent: ${s.size}.") + case Success(s) => s.get(0) + } + + val response = stmt.getStatement.statement.getStart.getText.trim.toUpperCase match { + case "SELECT" => + ksqlClient.makeQueryRequest(fixedSql).asInstanceOf[RestResponse[AnyRef]] + case "PRINT" => + ksqlClient.makeQueryRequest(fixedSql).asInstanceOf[RestResponse[AnyRef]] + case _ => + ksqlClient.makeKsqlRequest(fixedSql).asInstanceOf[RestResponse[AnyRef]] + } + if (response.isErroneous) { + import scala.collection.JavaConverters._ + throw KsqlQueryError(s"Error executing KSQL request: '$fixedSql'. " + + s"Error: ${response.getErrorMessage.getStackTrace.asScala.mkString("\n")}") + } + + val resultSet: ResultSet = response.get match { + case e: KsqlRestClient.QueryStream => { + implicit lazy val queryDesc = { + val response = ksqlClient.makeKsqlRequest(s"EXPLAIN $fixedSql") + if (response.isErroneous) { + import scala.collection.JavaConverters._ + throw KsqlQueryError(s"Error getting metadata for query: '$fixedSql'. " + + s"Error: ${response.getErrorMessage.getStackTrace.asScala.mkString("\n")}.") + } else if (response.getResponse.size > 1) { + throw KsqlEntityListError("Invalid metadata for result set.") + } + response.getResponse.get(0).asInstanceOf[QueryDescriptionEntity] + }.getQueryDescription + + e.asInstanceOf[KsqlRestClient.QueryStream] + } + case e: KsqlEntityList => e.asInstanceOf[KsqlEntityList] + } + currentResultSet = Some(resultSet) + } + + override def execute(sql: String): Boolean = { + executeKsqlRequest(sql) + currentResultSet.nonEmpty + } //TODO override def execute(sql: String, autoGeneratedKeys: Int): Boolean = execute(sql) @@ -49,15 +116,11 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = override def execute(sql: String, columnNames: Array[String]): Boolean = execute(sql) override def executeQuery(sql: String): ResultSet = { - val response = ksqlClient.makeQueryRequest(fixSql(sql)) - if (response.isErroneous) - throw KsqlQueryError(s"Error executing query '${sql}'. " + - s"Error: ${response.getErrorMessage.getMessage}") - - response.getResponse + executeKsqlRequest(sql) + currentResultSet.getOrElse(throw ResultSetError("Result set not initialized.")) } - override def getResultSetType: Int = throw NotSupported("getResultSetType") + override def getResultSetType: Int = ResultSet.TYPE_FORWARD_ONLY override def setMaxRows(max: Int): Unit = throw NotSupported("setMaxRows") @@ -71,9 +134,9 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = override def getResultSetConcurrency: Int = throw NotSupported("getResultSetConcurrency") - override def clearBatch(): Unit = throw NotSupported("clearBatch") + override def clearBatch: Unit = throw NotSupported("clearBatch") - override def close(): Unit = throw NotSupported("close") + override def close: Unit = throw NotSupported("close") override def isClosed: Boolean = throw NotSupported("isClosed") @@ -87,13 +150,13 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = override def getQueryTimeout: Int = throw NotSupported("getQueryTimeout") - override def getWarnings: SQLWarning = throw NotSupported("getWarnings") + override def getWarnings: SQLWarning = None.orNull override def setFetchSize(rows: Int): Unit = throw NotSupported("setFetchSize") override def setQueryTimeout(seconds: Int): Unit = throw NotSupported("setQueryTimeout") - override def executeBatch(): Array[Int] = throw NotSupported("executeBatch") + override def executeBatch: Array[Int] = throw NotSupported("executeBatch") override def setEscapeProcessing(enable: Boolean): Unit = throw NotSupported("setEscapeProcessing") @@ -103,8 +166,159 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = override def isCloseOnCompletion: Boolean = throw NotSupported("isCloseOnCompletion") - private def fixSql(sql: String) = if (sql.trim.endsWith(";")) sql else sql + ";" + private def fixSql(sql: String): String = Option(sql).map(s => if (s.trim.last == ';') s else s + ";").getOrElse("") + + private implicit def toResultSet(stream: KsqlRestClient.QueryStream) + (implicit queryDesc: QueryDescription): ResultSet = { + def mapType(ksqlType: KsqlType) = ksqlType match { + case KsqlType.INTEGER => Types.INTEGER + case KsqlType.BIGINT => Types.BIGINT + case KsqlType.DOUBLE => Types.DOUBLE + case KsqlType.BOOLEAN => Types.BOOLEAN + case KsqlType.STRING => Types.VARCHAR + case KsqlType.MAP => Types.JAVA_OBJECT + case KsqlType.ARRAY => Types.ARRAY + case KsqlType.STRUCT => Types.STRUCT + } + + val columns = queryDesc.getFields.asScala.map(f => HeaderField(f.getName, mapType(f.getSchema.getType), 0)).toList + new KsqlResultSet(new KsqlResultSetMetadata(columns), new KsqlQueryStream(stream), timeout) + } - private implicit def toResultSet(stream: KsqlRestClient.QueryStream): ResultSet = new KsqlResultSet(stream, timeout) + private implicit def toResultSet(list: KsqlEntityList): ResultSet = { + import KsqlEntityHeaders._ + + if (list.size > 1) throw KsqlEntityListError("KSQL entity list with more than one record.") + list.asScala.headOption.map(_ match { + case e: CommandStatusEntity => { + val rows = Iterator(Seq( + e.getCommandId.getType.name, + e.getCommandId.getEntity, + e.getCommandId.getAction.name, + e.getCommandStatus.getStatus.name, + e.getCommandStatus.getMessage + )) + new StaticResultSet[String](commandStatusEntity, rows) + } + case e: ExecutionPlan => new StaticResultSet[String](executionPlanEntity, Iterator(Seq(e.getExecutionPlan))) + case e: FunctionDescriptionList => { + val rows = e.getFunctions.asScala.map(f => Seq( + e.getAuthor, + e.getDescription, + e.getName, + e.getPath, + e.getVersion, + f.getDescription, + f.getReturnType, + f.getArguments.asScala.map(arg => s"${arg.getName}:${arg.getType}").mkString(", ") + )).toIterator + new StaticResultSet[String](functionDescriptionListEntity, rows) + } + case e: FunctionNameList => { + val rows = e.getFunctions.asScala.map(f => Seq( + f.getName, + f.getType.name + )).toIterator + new StaticResultSet[String](functionNameListEntity, rows) + } + case e: KafkaTopicsList => { + val rows = e.getTopics.asScala.map(t => Seq( + t.getName, + t.getConsumerCount, + t.getConsumerGroupCount, + t.getRegistered.booleanValue, + t.getReplicaInfo.asScala.mkString(", ") + )).toIterator + new StaticResultSet[Any](kafkaTopicsListEntity, rows) + } + case e: KsqlTopicsList => { + val rows = e.getTopics.asScala.map(t => Seq( + t.getName, + t.getKafkaTopic, + t.getFormat.name + )).toIterator + new StaticResultSet[String](ksqlTopicsListEntity, rows) + } + case e: PropertiesList => { + val rows = e.getProperties.asScala.map(p => Seq( + p._1, + p._2.toString + )).toIterator + new StaticResultSet[String](propertiesListEntity, rows) + } + case e: Queries => { + val rows = e.getQueries.asScala.map(q => Seq( + q.getId.getId, + q.getQueryString, + q.getSinks.asScala.mkString(", ") + )).toIterator + new StaticResultSet[String](queriesEntity, rows) + } + case e@(_: QueryDescriptionEntity | _: QueryDescriptionList) => { + val descriptions: Seq[QueryDescription] = if (e.isInstanceOf[QueryDescriptionEntity]) { + Seq(e.asInstanceOf[QueryDescriptionEntity].getQueryDescription) + } else { + e.asInstanceOf[QueryDescriptionList].getQueryDescriptions.asScala + } + + val rows = descriptions.map(d => Seq( + d.getId.getId, + d.getFields.asScala.map(_.getName).mkString(", "), + d.getSources.asScala.mkString(", "), + d.getSinks.asScala.mkString(", "), + d.getTopology, + d.getExecutionPlan + )).toIterator + new StaticResultSet[String](queryDescriptionEntityList, rows) + } + case e@(_: SourceDescriptionEntity | _: SourceDescriptionList) => { + val descriptions: Seq[SourceDescription] = if (e.isInstanceOf[SourceDescriptionEntity]) { + Seq(e.asInstanceOf[SourceDescriptionEntity].getSourceDescription) + } else { + e.asInstanceOf[SourceDescriptionList].getSourceDescriptions.asScala + } + + val rows = descriptions.map(d => Seq( + d.getKey, + d.getName, + d.getTopic, + d.getType, + d.getFormat, + d.getFields.asScala.map(f => s"${f.getName}:${f.getSchema.getTypeName}").mkString(", "), + d.getPartitions, + d.getStatistics, + d.getErrorStats, + d.getTimestamp + )).toIterator + new StaticResultSet[Any](sourceDescriptionEntityList, rows) + } + case e: StreamsList => { + val rows = e.getStreams.asScala.map(s => Seq( + s.getName, + s.getTopic, + s.getFormat + )).toIterator + new StaticResultSet[String](streamsListEntity, rows) + } + case e: TablesList => { + val rows = e.getTables.asScala.map(t => Seq( + t.getName, + t.getTopic, + t.getFormat, + t.getIsWindowed + )).toIterator + new StaticResultSet[Any](tablesListEntity, rows) + } + case e: TopicDescription => { + val rows = Iterator(Seq( + e.getName, + e.getKafkaTopic, + e.getFormat, + e.getSchemaString + )) + new StaticResultSet[String](topicDescriptionEntity, rows) + } + }).getOrElse(throw KsqlCommandError(s"Cannot build result set for '${list.get(0).getStatementText}'.")) + } } diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala index 6a9a42b..c0bc687 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala @@ -16,7 +16,7 @@ import scala.concurrent.duration._ import scala.concurrent.{Await, Future, TimeoutException} import scala.util.{Failure, Success, Try} -private[resultset] class KsqlQueryStream(stream: KsqlRestClient.QueryStream) +private[jdbc] class KsqlQueryStream(stream: KsqlRestClient.QueryStream) extends Closeable with Iterator[StreamedRow] { override def close: Unit = stream.close @@ -31,9 +31,6 @@ class KsqlResultSet(private val metadata: ResultSetMetaData, private val stream: KsqlQueryStream, val timeout: Long = 0) extends AbstractResultSet[StreamedRow](metadata, stream) { - def this(metadata: ResultSetMetaData, stream: KsqlRestClient.QueryStream, timeout: Long) = - this(metadata, new KsqlQueryStream(stream), timeout) - private val emptyRow: StreamedRow = StreamedRow.row(new GenericRow) private val waitDuration = if (timeout > 0) timeout millis else Duration.Inf @@ -67,8 +64,6 @@ class KsqlResultSet(private val metadata: ResultSetMetaData, override def getConcurrency: Int = ResultSet.CONCUR_READ_ONLY - override def getMetaData: ResultSetMetaData = metadata - override def isAfterLast: Boolean = false override def isBeforeFirst: Boolean = false From 2a02b743c0d9eae0fe12e5348243335de3e8123c Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sun, 3 Feb 2019 17:05:05 -0600 Subject: [PATCH 23/47] Improvements in KSQL statements and added coverage in tests --- .../mmolimar/ksql/jdbc/KsqlStatement.scala | 8 +- .../ksql/jdbc/KsqlStatementSpec.scala | 236 +++++++++++++++++- 2 files changed, 230 insertions(+), 14 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala index 6a3176a..178e8a9 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala @@ -88,7 +88,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = import scala.collection.JavaConverters._ throw KsqlQueryError(s"Error getting metadata for query: '$fixedSql'. " + s"Error: ${response.getErrorMessage.getStackTrace.asScala.mkString("\n")}.") - } else if (response.getResponse.size > 1) { + } else if (response.getResponse.size != 1) { throw KsqlEntityListError("Invalid metadata for result set.") } response.getResponse.get(0).asInstanceOf[QueryDescriptionEntity] @@ -166,7 +166,9 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = override def isCloseOnCompletion: Boolean = throw NotSupported("isCloseOnCompletion") - private def fixSql(sql: String): String = Option(sql).map(s => if (s.trim.last == ';') s else s + ";").getOrElse("") + private def fixSql(sql: String): String = { + Option(sql).filter(_.trim.nonEmpty).map(s => if (s.trim.last == ';') s else s + ";").getOrElse("") + } private implicit def toResultSet(stream: KsqlRestClient.QueryStream) (implicit queryDesc: QueryDescription): ResultSet = { @@ -188,7 +190,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = private implicit def toResultSet(list: KsqlEntityList): ResultSet = { import KsqlEntityHeaders._ - if (list.size > 1) throw KsqlEntityListError("KSQL entity list with more than one record.") + if (list.size != 1) throw KsqlEntityListError(s"KSQL entity list with an invalid number of entities: '${list.size}'.") list.asScala.headOption.map(_ match { case e: CommandStatusEntity => { val rows = Iterator(Seq( diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala index 09b2100..2d46746 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala @@ -1,19 +1,27 @@ package com.github.mmolimar.ksql.jdbc import java.io.InputStream -import java.sql.{SQLException, SQLFeatureNotSupportedException} +import java.sql.{ResultSet, SQLException, SQLFeatureNotSupportedException} import com.github.mmolimar.ksql.jdbc.utils.TestUtils._ +import io.confluent.ksql.metastore.{KsqlStream, KsqlTopic} import io.confluent.ksql.rest.client.{KsqlRestClient, RestResponse} -import io.confluent.ksql.rest.entity.{KsqlEntityList, KsqlErrorMessage} +import io.confluent.ksql.rest.entity.{ExecutionPlan, KafkaTopicsList, QueryDescriptionEntity, QueryDescriptionList, _} +import io.confluent.ksql.serde.DataSource.DataSourceSerDe +import io.confluent.ksql.serde.json.KsqlJsonTopicSerDe +import io.confluent.ksql.util.timestamp.LongColumnTimestampExtractionPolicy import javax.ws.rs.core.Response +import org.apache.kafka.connect.data.SchemaBuilder import org.scalamock.scalatest.MockFactory import org.scalatest.{Matchers, OneInstancePerTest, WordSpec} +import scala.collection.JavaConverters._ + class KsqlStatementSpec extends WordSpec with Matchers with MockFactory with OneInstancePerTest { - val implementedMethods = Seq("execute", "executeQuery") + val implementedMethods = Seq("execute", "executeQuery", "getResultSet", "getUpdateCount", "getResultSetType", + "getWarnings", "cancel") "A KsqlStatement" when { @@ -38,23 +46,229 @@ class KsqlStatementSpec extends WordSpec with Matchers with MockFactory with One }) } - "work if implemented" in { + "work when executing queries" in { + + assertThrows[SQLException] { + statement.getResultSet + } + assertThrows[SQLException] { + statement.execute(null, -1) + } + assertThrows[SQLException] { + statement.execute("", Array[Int]()) + } + assertThrows[SQLException] { + statement.execute("invalid query", Array[String]()) + } + + assertThrows[SQLException] { + (mockedKsqlRestClient.makeQueryRequest _).expects(*) + .returns(RestResponse.erroneous(new KsqlErrorMessage(-1, "error"))) + .once + statement.execute("select * from test") + } + + assertThrows[SQLException] { + (mockedKsqlRestClient.makeQueryRequest _).expects(*) + .returns(RestResponse.successful[KsqlRestClient.QueryStream](mockQueryStream(mockResponse))) + .once + (mockedKsqlRestClient.makeKsqlRequest _).expects(*) + .returns(RestResponse.erroneous(new KsqlErrorMessage(-1, "error"))) + .once + statement.execute("select * from test") + } + + assertThrows[SQLException] { + (mockedKsqlRestClient.makeQueryRequest _).expects(*) + .returns(RestResponse.successful[KsqlRestClient.QueryStream](mockQueryStream(mockResponse))) + .once + (mockedKsqlRestClient.makeKsqlRequest _).expects(*) + .returns(RestResponse.successful[KsqlEntityList](new KsqlEntityList)) + .once + statement.execute("select * from test") + } + + val queryDesc = new QueryDescription( + new EntityQueryId("id"), + "select * from test;", + List( + new FieldInfo("field1", new SchemaInfo(SchemaInfo.Type.INTEGER, List.empty.asJava, None.orNull)), + new FieldInfo("field2", new SchemaInfo(SchemaInfo.Type.BIGINT, List.empty.asJava, None.orNull)), + new FieldInfo("field3", new SchemaInfo(SchemaInfo.Type.DOUBLE, List.empty.asJava, None.orNull)), + new FieldInfo("field4", new SchemaInfo(SchemaInfo.Type.BOOLEAN, List.empty.asJava, None.orNull)), + new FieldInfo("field5", new SchemaInfo(SchemaInfo.Type.STRING, List.empty.asJava, None.orNull)), + new FieldInfo("field6", new SchemaInfo(SchemaInfo.Type.MAP, List.empty.asJava, None.orNull)), + new FieldInfo("field7", new SchemaInfo(SchemaInfo.Type.ARRAY, List.empty.asJava, None.orNull)), + new FieldInfo("field7", new SchemaInfo(SchemaInfo.Type.STRUCT, List.empty.asJava, None.orNull)) + + ).asJava, + Set("test").asJava, + Set("sink1").asJava, + "topologyTest", + "executionPlanTest", + Map.empty[String, AnyRef].asJava + ) + + assertThrows[SQLException] { + (mockedKsqlRestClient.makeQueryRequest _).expects(*) + .returns(RestResponse.successful[KsqlRestClient.QueryStream](mockQueryStream(mockResponse))) + .once + val multipleResults = new KsqlEntityList + multipleResults.add(new QueryDescriptionEntity("select * from test;", queryDesc)) + multipleResults.add(new QueryDescriptionEntity("select * from test;", queryDesc)) + (mockedKsqlRestClient.makeKsqlRequest _).expects(*) + .returns(RestResponse.successful[KsqlEntityList](multipleResults)) + .once + statement.execute("select * from test") + } + assertThrows[SQLException] { + statement.getResultSet + } + statement.cancel + + val entityList = new KsqlEntityList + entityList.add(new QueryDescriptionEntity("select * from test;", queryDesc)) + + (mockedKsqlRestClient.makeQueryRequest _).expects(*) + .returns(RestResponse.successful[KsqlRestClient.QueryStream](mockQueryStream(mockResponse))) + .once (mockedKsqlRestClient.makeKsqlRequest _).expects(*) - .returns(RestResponse.successful[KsqlEntityList](new KsqlEntityList)) + .returns(RestResponse.successful[KsqlEntityList](entityList)) .once - statement.execute("") should be(true) + statement.execute("select * from test") should be(true) (mockedKsqlRestClient.makeQueryRequest _).expects(*) .returns(RestResponse.successful[KsqlRestClient.QueryStream](mockQueryStream(mockResponse))) .once - Option(statement.executeQuery("")) should not be (None) + (mockedKsqlRestClient.makeKsqlRequest _).expects(*) + .returns(RestResponse.successful[KsqlEntityList](entityList)) + .once + Option(statement.executeQuery("select * from test;")) should not be (None) - assertThrows[SQLException] { - (mockedKsqlRestClient.makeQueryRequest _).expects(*) - .returns(RestResponse.erroneous(new KsqlErrorMessage(-1, null, null))) + statement.getResultSet shouldNot be(None.orNull) + + statement.getUpdateCount should be(-1) + statement.getResultSetType should be(ResultSet.TYPE_FORWARD_ONLY) + statement.getWarnings should be(None.orNull) + } + + "work when executing KSQL commands" in { + import KsqlEntityHeaders._ + + def validateCommand(entity: KsqlEntity, headers: List[HeaderField]): Unit = { + val entityList = new KsqlEntityList + entityList.add(entity) + (mockedKsqlRestClient.makeKsqlRequest _).expects(*) + .returns(RestResponse.successful[KsqlEntityList](entityList)) .once - statement.executeQuery("") + statement.execute(entity.getStatementText) should be(true) + statement.getResultSet.getMetaData.getColumnCount should be(headers.size) + headers.zipWithIndex.map { case (c, index) => { + statement.getResultSet.getMetaData.getColumnName(index + 1) should be(c.name) + statement.getResultSet.getMetaData.getColumnLabel(index + 1).toUpperCase should be(c.name) + } + } } + + val commandStatus = new CommandStatusEntity("REGISTER TOPIC TEST", "topic/1/create", "SUCCESS", "Success Message") + val executionPlan = new ExecutionPlan("DESCRIBE test") + val functionDescriptionList = new FunctionDescriptionList("DESCRIBE FUNCTION test;", + "TEST", "Description", "author", "version", "path", + List( + new FunctionInfo(List(new ArgumentInfo("arg1", "INT", "Description")).asJava, "LONG", "Description") + ).asJava, + FunctionType.scalar + ) + val functionNameList = new FunctionNameList( + "LIST FUNCTIONS;", + List(new SimpleFunctionInfo("TESTFN", FunctionType.scalar)).asJava + ) + val kafkaTopicsList = new KafkaTopicsList( + "SHOW TOPICS;", + List(new KafkaTopicInfo("test", false, List(Int.box(1)).asJava, 1, 1)).asJava + ) + val ksqlTopicsList = new KsqlTopicsList( + "SHOW TOPICS;", + List(new KsqlTopicInfo("ksqltopic", "kafkatopic", DataSourceSerDe.JSON)).asJava + ) + val propertiesList = new PropertiesList( + "list properties;", + Map("key" -> "earliest").asJava, + List.empty.asJava, + List.empty.asJava + ) + val queries = new Queries( + "EXPLAIN select * from test", + List(new RunningQuery("select * from test;", Set("Test").asJava, new EntityQueryId("id"))).asJava + ) + val queryDescription = new QueryDescriptionEntity( + "EXPLAIN select * from test;", + new QueryDescription( + new EntityQueryId("id"), + "select * from test;", + List( + new FieldInfo("field1", new SchemaInfo(SchemaInfo.Type.INTEGER, List.empty.asJava, None.orNull)), + new FieldInfo("field2", new SchemaInfo(SchemaInfo.Type.BIGINT, List.empty.asJava, None.orNull)), + new FieldInfo("field3", new SchemaInfo(SchemaInfo.Type.DOUBLE, List.empty.asJava, None.orNull)), + new FieldInfo("field4", new SchemaInfo(SchemaInfo.Type.BOOLEAN, List.empty.asJava, None.orNull)), + new FieldInfo("field5", new SchemaInfo(SchemaInfo.Type.STRING, List.empty.asJava, None.orNull)), + new FieldInfo("field6", new SchemaInfo(SchemaInfo.Type.MAP, List.empty.asJava, None.orNull)), + new FieldInfo("field7", new SchemaInfo(SchemaInfo.Type.ARRAY, List.empty.asJava, None.orNull)), + new FieldInfo("field7", new SchemaInfo(SchemaInfo.Type.STRUCT, List.empty.asJava, None.orNull)) + + ).asJava, + Set("test").asJava, + Set("sink1").asJava, + "topologyTest", + "executionPlanTest", + Map.empty[String, AnyRef].asJava + ) + ) + val queryDescriptionList = new QueryDescriptionList( + "EXPLAIN select * from test;", + List(queryDescription.getQueryDescription).asJava + ) + val sourceDescEntity = new SourceDescriptionEntity( + "DESCRIBE TEST;", + new SourceDescription( + new KsqlStream("sqlExpression", + "datasource", + SchemaBuilder.struct, + SchemaBuilder.struct.field("key"), + new LongColumnTimestampExtractionPolicy("timestamp"), + new KsqlTopic("input", "input", new KsqlJsonTopicSerDe)), + true, + "JSON", + List.empty.asJava, + List.empty.asJava, + None.orNull) + ) + val sourceDescList = new SourceDescriptionList( + "EXPLAIN select * from test;", + List(sourceDescEntity.getSourceDescription).asJava + ) + val streams = new StreamsList("SHOW STREAMS", List(new SourceInfo.Stream("TestStream", "TestTopic", "AVRO")).asJava) + val tables = new TablesList("SHOW TABLES", List(new SourceInfo.Table("TestTable", "TestTopic", "JSON", false)).asJava) + val topicDesc = new TopicDescription("DESCRIBE TEST", "TestTopic", "TestTopic", "AVRO", "schema") + + val commands = Seq( + (commandStatus, commandStatusEntity), + (executionPlan, executionPlanEntity), + (functionDescriptionList, functionDescriptionListEntity), + (functionNameList, functionNameListEntity), + (kafkaTopicsList, kafkaTopicsListEntity), + (ksqlTopicsList, ksqlTopicsListEntity), + (propertiesList, propertiesListEntity), + (queries, queriesEntity), + (queryDescription, queryDescriptionEntity), + (queryDescriptionList, queryDescriptionEntityList), + (sourceDescEntity, sourceDescriptionEntity), + (sourceDescList, sourceDescriptionEntityList), + (streams, streamsListEntity), + (tables, tablesListEntity), + (topicDesc, topicDescriptionEntity) + ) + commands.foreach(c => validateCommand(c._1, c._2)) } } } From d6e36a268ede201d9ae343bb033b7a7bb75e0971 Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sun, 3 Feb 2019 17:27:47 -0600 Subject: [PATCH 24/47] Data type implicit conversions in result sets --- .../ksql/jdbc/resultset/ResultSet.scala | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala index 576acd8..6f964f5 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala @@ -322,7 +322,7 @@ private[resultset] class ResultSetNotSupported extends ResultSet with WrapperNot override def findColumn(columnLabel: String): Int = throw NotSupported("findColumn") - override def getWarnings: SQLWarning = None.orNull + override def getWarnings: SQLWarning = throw NotSupported("getWarnings") override def getDate(columnIndex: Int): Date = throw NotSupported("getDate") @@ -470,7 +470,7 @@ abstract class AbstractResultSet[T](private val metadata: ResultSetMetaData, pri override def getMetaData: ResultSetMetaData = metadata - override def wasNull: Boolean = false + override def getWarnings: SQLWarning = None.orNull private def getColumn[T <: AnyRef](columnLabel: String)(implicit ev: ClassTag[T]): T = { getColumn[T](getColumnIndex(columnLabel)) @@ -501,12 +501,30 @@ abstract class AbstractResultSet[T](private val metadata: ResultSetMetaData, pri ev.runtimeClass match { case Any_ if ev.runtimeClass == value.getClass => value case String_ => value.toString + case JBoolean_ if value.isInstanceOf[String] => JBoolean.parseBoolean(value.asInstanceOf[String]) + case JBoolean_ if value.isInstanceOf[Number] => value.asInstanceOf[Number].intValue != 0 + case JShort_ if value.isInstanceOf[String] => JShort.parseShort(value.asInstanceOf[String]) case JShort_ if value.isInstanceOf[Number] => value.asInstanceOf[Number].shortValue + case JShort_ if value.isInstanceOf[JBoolean] => value.asInstanceOf[JBoolean].compareTo(false).shortValue + case JInt_ if value.isInstanceOf[String] => JInt.parseInt(value.asInstanceOf[String]) case JInt_ if value.isInstanceOf[Number] => value.asInstanceOf[Number].intValue + case JInt_ if value.isInstanceOf[JBoolean] => value.asInstanceOf[JBoolean].compareTo(false).intValue + case JLong_ if value.isInstanceOf[String] => JLong.parseLong(value.asInstanceOf[String]) case JLong_ if value.isInstanceOf[Number] => value.asInstanceOf[Number].longValue + case JLong_ if value.isInstanceOf[JBoolean] => value.asInstanceOf[JBoolean].compareTo(false).longValue + case JDouble_ if value.isInstanceOf[String] => JDouble.parseDouble(value.asInstanceOf[String]) case JDouble_ if value.isInstanceOf[Number] => value.asInstanceOf[Number].doubleValue + case JDouble_ if value.isInstanceOf[JBoolean] => value.asInstanceOf[JBoolean].compareTo(false).doubleValue + case JFloat_ if value.isInstanceOf[String] => JFloat.parseFloat(value.asInstanceOf[String]) case JFloat_ if value.isInstanceOf[Number] => value.asInstanceOf[Number].floatValue + case JFloat_ if value.isInstanceOf[JBoolean] => value.asInstanceOf[JBoolean].compareTo(false).floatValue + case JByte_ if value.isInstanceOf[String] => value.asInstanceOf[String].toByte case JByte_ if value.isInstanceOf[Number] => value.asInstanceOf[Number].byteValue + case JByte_ if value.isInstanceOf[JBoolean] => value.asInstanceOf[JBoolean].compareTo(false).byteValue + case JByteArray_ if value.isInstanceOf[String] => value.asInstanceOf[String].getBytes + case JByteArray_ if value.isInstanceOf[Number] => scala.Array[Byte](value.asInstanceOf[Number].byteValue) + case JByteArray_ if value.isInstanceOf[JBoolean] => + scala.Array[Byte](value.asInstanceOf[JBoolean].compareTo(false).byteValue) case _ => value } }.asInstanceOf[T] @@ -514,12 +532,14 @@ abstract class AbstractResultSet[T](private val metadata: ResultSetMetaData, pri private object ImplicitClasses { val Any_ = classOf[Any] val String_ = classOf[String] + val JBoolean_ = classOf[JBoolean] val JShort_ = classOf[JShort] val JInt_ = classOf[JInt] val JLong_ = classOf[JLong] val JDouble_ = classOf[JDouble] val JFloat_ = classOf[JFloat] val JByte_ = classOf[JByte] + val JByteArray_ = classOf[scala.Array[Byte]] } protected def isEmpty: Boolean = currentRow.isEmpty From f8847de5ccb4fb4419c7551a4759f51be51a4fa6 Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sun, 3 Feb 2019 17:30:24 -0600 Subject: [PATCH 25/47] Adding test coverage in KsqlDriver class --- .../mmolimar/ksql/jdbc/KsqlDriver.scala | 6 ++- .../mmolimar/ksql/jdbc/KsqlDriverSpec.scala | 41 ++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala index a5d3da9..2845797 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala @@ -52,8 +52,12 @@ class KsqlDriver extends Driver { override def connect(url: String, properties: Properties): Connection = { if (!acceptsURL(url)) throw InvalidUrl(url) - val connection = new KsqlConnection(KsqlDriver.parseUrl(url), properties) + val connection = buildConnection(KsqlDriver.parseUrl(url), properties) connection.validate connection } + + private[jdbc] def buildConnection(values: KsqlConnectionValues, properties: Properties): KsqlConnection = { + new KsqlConnection(values, properties) + } } diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala index 2f6569b..5583ae6 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala @@ -3,9 +3,15 @@ package com.github.mmolimar.ksql.jdbc import java.sql.{SQLException, SQLFeatureNotSupportedException} import java.util.Properties +import com.github.mmolimar.ksql.jdbc.utils.TestUtils.MockableKsqlRestClient +import io.confluent.ksql.rest.client.{KsqlRestClient, RestResponse} +import io.confluent.ksql.rest.entity.{KsqlErrorMessage, ServerInfo} +import org.scalamock.scalatest.MockFactory import org.scalatest.{Matchers, WordSpec} -class KsqlDriverSpec extends WordSpec with Matchers { +import scala.collection.JavaConverters._ + +class KsqlDriverSpec extends WordSpec with Matchers with MockFactory { "A KsqlDriver" when { val driver = new KsqlDriver @@ -36,6 +42,39 @@ class KsqlDriverSpec extends WordSpec with Matchers { } } + "connecting to an URL" should { + val mockKsqlRestClient = mock[MockableKsqlRestClient] + val driver = new KsqlDriver { + override private[jdbc] def buildConnection(values: KsqlConnectionValues, properties: Properties) = { + new KsqlConnection(values, new Properties) { + override def init: KsqlRestClient = mockKsqlRestClient + } + } + } + "throw an exception if cannot connect to the URL" in { + assertThrows[SQLException] { + (mockKsqlRestClient.makeRootRequest _).expects() + .throws(new Exception("error")) + .once + driver.connect("jdbc:ksql://localhost:9999", new Properties) + } + } + "throw an exception if there is an error in the response" in { + assertThrows[SQLException] { + (mockKsqlRestClient.makeRootRequest _).expects() + .returns(RestResponse.erroneous(new KsqlErrorMessage(-1, "error message", List.empty.asJava))) + .once + driver.connect("jdbc:ksql://localhost:9999", new Properties) + } + } + "connect properly if the response is successful" in { + (mockKsqlRestClient.makeRootRequest _).expects() + .returns(RestResponse.successful[ServerInfo](new ServerInfo("v1", "id1", "svc1"))) + .once + driver.connect("jdbc:ksql://localhost:9999", new Properties) + } + } + "accepting an URL" should { "return false if invalid" in { driver.acceptsURL(null) should be(false) From a847ce30afd27438055039e1d3d9aab44fb81e3f Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Tue, 5 Feb 2019 22:50:01 -0600 Subject: [PATCH 26/47] Refactor result sets --- .../ksql/jdbc/KsqlDatabaseMetaData.scala | 18 +++--- .../mmolimar/ksql/jdbc/KsqlDriver.scala | 2 +- .../mmolimar/ksql/jdbc/KsqlStatement.scala | 30 +++++----- .../ksql/jdbc/resultset/KsqlResultSet.scala | 26 +++++++-- ...data.scala => KsqlResultSetMetaData.scala} | 49 +--------------- .../ksql/jdbc/resultset/ResultSet.scala | 4 +- .../jdbc/resultset/ResultSetMetaData.scala | 53 +++++++++++++++++ .../ksql/jdbc/resultset/StaticResultSet.scala | 20 ------- ....scala => KsqlResultSetMetaDataSpec.scala} | 14 ++--- .../jdbc/resultset/KsqlResultSetSpec.scala | 58 +++++++++++++++---- .../jdbc/resultset/StaticResultSetSpec.scala | 53 ----------------- 11 files changed, 157 insertions(+), 170 deletions(-) rename src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/{ResultSetMetadata.scala => KsqlResultSetMetaData.scala} (55%) create mode 100644 src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSetMetaData.scala delete mode 100644 src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/StaticResultSet.scala rename src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/{KsqlResultSetMetadataSpec.scala => KsqlResultSetMetaDataSpec.scala} (92%) delete mode 100644 src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/StaticResultSetSpec.scala diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala index 63e1617..d0cb042 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala @@ -3,7 +3,7 @@ package com.github.mmolimar.ksql.jdbc import java.sql.{Connection, DatabaseMetaData, ResultSet, RowIdLifetime, Types} import com.github.mmolimar.ksql.jdbc.Exceptions._ -import com.github.mmolimar.ksql.jdbc.resultset.StaticResultSet +import com.github.mmolimar.ksql.jdbc.resultset.IteratorResultSet import io.confluent.ksql.rest.entity.{SourceDescriptionEntity, StreamsList, TablesList} import scala.collection.JavaConverters._ @@ -62,8 +62,8 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def storesUpperCaseQuotedIdentifiers: Boolean = throw NotSupported("storesUpperCaseQuotedIdentifiers") - override def getUDTs(catalog: String, schemaPattern: String, typeNamePattern: String, - types: Array[Int]): ResultSet = new StaticResultSet[String](List.empty[HeaderField], Iterator.empty) + override def getUDTs(catalog: String, schemaPattern: String, typeNamePattern: String, types: Array[Int]): ResultSet = + new IteratorResultSet(List.empty[HeaderField], Iterator.empty) override def getAttributes(catalog: String, schemaPattern: String, typeNamePattern: String, attributeNamePattern: String): ResultSet = throw NotSupported("getAttributes") @@ -234,13 +234,13 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D tableNamePattern: String, columnNamePattern: String): ResultSet = throw NotSupported("getPseudoColumns") - override def getCatalogs: ResultSet = new StaticResultSet[String](DatabaseMetadataHeaders.catalogs, Iterator.empty) + override def getCatalogs: ResultSet = new IteratorResultSet(DatabaseMetadataHeaders.catalogs, Iterator.empty) override def getSuperTables(catalog: String, schemaPattern: String, tableNamePattern: String): ResultSet = { validateCatalogAndSchema(catalog, schemaPattern) - new StaticResultSet[String](DatabaseMetadataHeaders.superTables, Iterator.empty) + new IteratorResultSet(DatabaseMetadataHeaders.superTables, Iterator.empty) } override def getMaxColumnsInOrderBy: Int = throw NotSupported("getMaxColumnsInOrderBy") @@ -299,7 +299,7 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D }).toIterator } else Iterator.empty - new StaticResultSet[String](DatabaseMetadataHeaders.tables, itTables ++ itStreams) + new IteratorResultSet(DatabaseMetadataHeaders.tables, itTables ++ itStreams) } override def supportsMultipleTransactions: Boolean = throw NotSupported("supportsMultipleTransactions") @@ -322,7 +322,7 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def getExtraNameCharacters: String = throw NotSupported("getExtraNameCharacters") - override def getSchemas: ResultSet = new StaticResultSet[String](DatabaseMetadataHeaders.schemas, Iterator.empty) + override def getSchemas: ResultSet = new IteratorResultSet(DatabaseMetadataHeaders.schemas, Iterator.empty) override def getSchemas(catalog: String, schemaPattern: String): ResultSet = { validateCatalogAndSchema(catalog, schemaPattern) @@ -392,7 +392,7 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def supportsTransactionIsolationLevel(level: Int): Boolean = throw NotSupported("supportsTransactionIsolationLevel") - override def getTableTypes: ResultSet = new StaticResultSet[String](DatabaseMetadataHeaders.tableTypes, + override def getTableTypes: ResultSet = new IteratorResultSet(DatabaseMetadataHeaders.tableTypes, Iterator(Seq(TableTypes.TABLE.name), Seq(TableTypes.STREAM.name))) override def getMaxColumnsInTable: Int = throw NotSupported("getMaxColumnsInTable") @@ -446,7 +446,7 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D }).toIterator } - new StaticResultSet[AnyRef](DatabaseMetadataHeaders.columns, tableSchemas) + new IteratorResultSet(DatabaseMetadataHeaders.columns, tableSchemas) } override def supportsResultSetType(`type`: Int): Boolean = throw NotSupported("supportsResultSetType") diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala index 2845797..900fa6e 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala @@ -47,7 +47,7 @@ class KsqlDriver extends Driver { override def getMajorVersion: Int = KsqlDriver.majorVersion - override def getParentLogger: Logger = throw NotSupported("getParentLogger method not supported") + override def getParentLogger: Logger = throw NotSupported("getParentLogger") override def connect(url: String, properties: Properties): Connection = { if (!acceptsURL(url)) throw InvalidUrl(url) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala index 178e8a9..f7ecabc 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala @@ -3,7 +3,7 @@ package com.github.mmolimar.ksql.jdbc import java.sql.{Connection, ResultSet, SQLWarning, Statement, Types} import com.github.mmolimar.ksql.jdbc.Exceptions._ -import com.github.mmolimar.ksql.jdbc.resultset.{KsqlQueryStream, KsqlResultSet, KsqlResultSetMetadata, StaticResultSet} +import com.github.mmolimar.ksql.jdbc.resultset._ import io.confluent.ksql.parser.KsqlParser import io.confluent.ksql.rest.client.{KsqlRestClient, RestResponse} import io.confluent.ksql.rest.entity.SchemaInfo.{Type => KsqlType} @@ -184,7 +184,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = } val columns = queryDesc.getFields.asScala.map(f => HeaderField(f.getName, mapType(f.getSchema.getType), 0)).toList - new KsqlResultSet(new KsqlResultSetMetadata(columns), new KsqlQueryStream(stream), timeout) + new StreamedResultSet(new KsqlResultSetMetaData(columns), new KsqlQueryStream(stream), timeout) } private implicit def toResultSet(list: KsqlEntityList): ResultSet = { @@ -200,9 +200,9 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = e.getCommandStatus.getStatus.name, e.getCommandStatus.getMessage )) - new StaticResultSet[String](commandStatusEntity, rows) + new IteratorResultSet[String](commandStatusEntity, rows) } - case e: ExecutionPlan => new StaticResultSet[String](executionPlanEntity, Iterator(Seq(e.getExecutionPlan))) + case e: ExecutionPlan => new IteratorResultSet[String](executionPlanEntity, Iterator(Seq(e.getExecutionPlan))) case e: FunctionDescriptionList => { val rows = e.getFunctions.asScala.map(f => Seq( e.getAuthor, @@ -214,14 +214,14 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = f.getReturnType, f.getArguments.asScala.map(arg => s"${arg.getName}:${arg.getType}").mkString(", ") )).toIterator - new StaticResultSet[String](functionDescriptionListEntity, rows) + new IteratorResultSet[String](functionDescriptionListEntity, rows) } case e: FunctionNameList => { val rows = e.getFunctions.asScala.map(f => Seq( f.getName, f.getType.name )).toIterator - new StaticResultSet[String](functionNameListEntity, rows) + new IteratorResultSet[String](functionNameListEntity, rows) } case e: KafkaTopicsList => { val rows = e.getTopics.asScala.map(t => Seq( @@ -231,7 +231,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = t.getRegistered.booleanValue, t.getReplicaInfo.asScala.mkString(", ") )).toIterator - new StaticResultSet[Any](kafkaTopicsListEntity, rows) + new IteratorResultSet[Any](kafkaTopicsListEntity, rows) } case e: KsqlTopicsList => { val rows = e.getTopics.asScala.map(t => Seq( @@ -239,14 +239,14 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = t.getKafkaTopic, t.getFormat.name )).toIterator - new StaticResultSet[String](ksqlTopicsListEntity, rows) + new IteratorResultSet[String](ksqlTopicsListEntity, rows) } case e: PropertiesList => { val rows = e.getProperties.asScala.map(p => Seq( p._1, p._2.toString )).toIterator - new StaticResultSet[String](propertiesListEntity, rows) + new IteratorResultSet[String](propertiesListEntity, rows) } case e: Queries => { val rows = e.getQueries.asScala.map(q => Seq( @@ -254,7 +254,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = q.getQueryString, q.getSinks.asScala.mkString(", ") )).toIterator - new StaticResultSet[String](queriesEntity, rows) + new IteratorResultSet[String](queriesEntity, rows) } case e@(_: QueryDescriptionEntity | _: QueryDescriptionList) => { val descriptions: Seq[QueryDescription] = if (e.isInstanceOf[QueryDescriptionEntity]) { @@ -271,7 +271,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = d.getTopology, d.getExecutionPlan )).toIterator - new StaticResultSet[String](queryDescriptionEntityList, rows) + new IteratorResultSet[String](queryDescriptionEntityList, rows) } case e@(_: SourceDescriptionEntity | _: SourceDescriptionList) => { val descriptions: Seq[SourceDescription] = if (e.isInstanceOf[SourceDescriptionEntity]) { @@ -292,7 +292,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = d.getErrorStats, d.getTimestamp )).toIterator - new StaticResultSet[Any](sourceDescriptionEntityList, rows) + new IteratorResultSet[Any](sourceDescriptionEntityList, rows) } case e: StreamsList => { val rows = e.getStreams.asScala.map(s => Seq( @@ -300,7 +300,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = s.getTopic, s.getFormat )).toIterator - new StaticResultSet[String](streamsListEntity, rows) + new IteratorResultSet[String](streamsListEntity, rows) } case e: TablesList => { val rows = e.getTables.asScala.map(t => Seq( @@ -309,7 +309,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = t.getFormat, t.getIsWindowed )).toIterator - new StaticResultSet[Any](tablesListEntity, rows) + new IteratorResultSet[Any](tablesListEntity, rows) } case e: TopicDescription => { val rows = Iterator(Seq( @@ -318,7 +318,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = e.getFormat, e.getSchemaString )) - new StaticResultSet[String](topicDescriptionEntity, rows) + new IteratorResultSet[String](topicDescriptionEntity, rows) } }).getOrElse(throw KsqlCommandError(s"Cannot build result set for '${list.get(0).getStatementText}'.")) } diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala index c0bc687..43b245e 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala @@ -2,10 +2,10 @@ package com.github.mmolimar.ksql.jdbc.resultset import java.io.Closeable import java.sql.{ResultSet, ResultSetMetaData} -import java.util.Iterator +import java.util.{Iterator => JIterator} import com.github.mmolimar.ksql.jdbc.Exceptions._ -import com.github.mmolimar.ksql.jdbc.InvalidColumn +import com.github.mmolimar.ksql.jdbc.{HeaderField, InvalidColumn} import io.confluent.ksql.GenericRow import io.confluent.ksql.rest.client.KsqlRestClient import io.confluent.ksql.rest.entity.StreamedRow @@ -16,8 +16,22 @@ import scala.concurrent.duration._ import scala.concurrent.{Await, Future, TimeoutException} import scala.util.{Failure, Success, Try} -private[jdbc] class KsqlQueryStream(stream: KsqlRestClient.QueryStream) - extends Closeable with Iterator[StreamedRow] { + +class IteratorResultSet[T <: Any](private val metadata: ResultSetMetaData, + private[jdbc] val rows: Iterator[Seq[T]]) extends AbstractResultSet(metadata, rows) { + + def this(columns: List[HeaderField], rows: Iterator[Seq[T]]) = + this(new KsqlResultSetMetaData(columns), rows) + + override protected def getValue[V <: AnyRef](columnIndex: Int): V = currentRow.get(columnIndex - 1).asInstanceOf[V] + + override protected def getColumnBounds: (Int, Int) = (1, currentRow.getOrElse(Seq.empty).size) + + override protected def closeInherit: Unit = {} + +} + +private[jdbc] class KsqlQueryStream(stream: KsqlRestClient.QueryStream) extends Closeable with JIterator[StreamedRow] { override def close: Unit = stream.close @@ -27,8 +41,8 @@ private[jdbc] class KsqlQueryStream(stream: KsqlRestClient.QueryStream) } -class KsqlResultSet(private val metadata: ResultSetMetaData, - private val stream: KsqlQueryStream, val timeout: Long = 0) +class StreamedResultSet(private val metadata: ResultSetMetaData, + private val stream: KsqlQueryStream, val timeout: Long = 0) extends AbstractResultSet[StreamedRow](metadata, stream) { private val emptyRow: StreamedRow = StreamedRow.row(new GenericRow) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSetMetadata.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaData.scala similarity index 55% rename from src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSetMetadata.scala rename to src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaData.scala index 8ece92f..6d7fc22 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSetMetadata.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaData.scala @@ -4,56 +4,11 @@ import java.sql.{ResultSetMetaData, Types} import com.github.mmolimar.ksql.jdbc.Exceptions._ import com.github.mmolimar.ksql.jdbc.implicits.toIndexedMap -import com.github.mmolimar.ksql.jdbc.{HeaderField, InvalidColumn, NotSupported, WrapperNotSupported} +import com.github.mmolimar.ksql.jdbc.{HeaderField, InvalidColumn} import io.confluent.ksql.rest.entity.SchemaInfo.{Type => KsqlType} -private[resultset] class ResultSetMetadataNotSupported extends ResultSetMetaData with WrapperNotSupported { - override def getCatalogName(column: Int): String = throw NotSupported("getCatalogName") - - override def getColumnClassName(column: Int): String = throw NotSupported("getColumnClassName") - - override def getColumnCount: Int = throw NotSupported("getColumnCount") - - override def getColumnDisplaySize(column: Int): Int = throw NotSupported("getColumnDisplaySize") - - override def getColumnLabel(column: Int): String = throw NotSupported("getColumnLabel") - - override def getColumnName(column: Int): String = throw NotSupported("getColumnName") - - override def getColumnTypeName(column: Int): String = throw NotSupported("getColumnTypeName") - - override def getColumnType(column: Int): Int = throw NotSupported("getColumnType") - - override def getPrecision(column: Int): Int = throw NotSupported("getPrecision") - - override def getSchemaName(column: Int): String = throw NotSupported("getSchemaName") - - override def getScale(column: Int): Int = throw NotSupported("getScale") - - override def getTableName(column: Int): String = throw NotSupported("getTableName") - - override def isAutoIncrement(column: Int): Boolean = throw NotSupported("isAutoIncrement") - - override def isCaseSensitive(column: Int): Boolean = throw NotSupported("isCaseSensitive") - - override def isCurrency(column: Int): Boolean = throw NotSupported("isCurrency") - - override def isDefinitelyWritable(column: Int): Boolean = throw NotSupported("isDefinitelyWritable") - - override def isNullable(column: Int): Int = throw NotSupported("isNullable") - - override def isReadOnly(column: Int): Boolean = throw NotSupported("isReadOnly") - - override def isSearchable(column: Int): Boolean = throw NotSupported("isSearchable") - - override def isSigned(column: Int): Boolean = throw NotSupported("isSigned") - - override def isWritable(column: Int): Boolean = throw NotSupported("isWritable") - -} - -class KsqlResultSetMetadata(private[jdbc] val columns: List[HeaderField]) extends ResultSetMetadataNotSupported { +class KsqlResultSetMetaData(private[jdbc] val columns: List[HeaderField]) extends ResultSetMetaDataNotSupported { private val fieldByIndex: Map[Int, HeaderField] = columns diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala index 6f964f5..59c3141 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala @@ -400,8 +400,8 @@ private[resultset] class ResultSetNotSupported extends ResultSet with WrapperNot } -abstract class AbstractResultSet[T](private val metadata: ResultSetMetaData, private val records: Iterator[T]) - extends ResultSetNotSupported { +private[resultset] abstract class AbstractResultSet[T](private val metadata: ResultSetMetaData, + private val records: Iterator[T]) extends ResultSetNotSupported { private val indexByLabel: Map[String, Int] = (1 to metadata.getColumnCount) .map(index => (metadata.getColumnLabel(index).toUpperCase -> index)).toMap diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSetMetaData.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSetMetaData.scala new file mode 100644 index 0000000..30c6fe8 --- /dev/null +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSetMetaData.scala @@ -0,0 +1,53 @@ +package com.github.mmolimar.ksql.jdbc.resultset + +import java.sql.ResultSetMetaData + +import com.github.mmolimar.ksql.jdbc.Exceptions._ +import com.github.mmolimar.ksql.jdbc.{NotSupported, WrapperNotSupported} + + +private[resultset] class ResultSetMetaDataNotSupported extends ResultSetMetaData with WrapperNotSupported { + + override def getCatalogName(column: Int): String = throw NotSupported("getCatalogName") + + override def getColumnClassName(column: Int): String = throw NotSupported("getColumnClassName") + + override def getColumnCount: Int = throw NotSupported("getColumnCount") + + override def getColumnDisplaySize(column: Int): Int = throw NotSupported("getColumnDisplaySize") + + override def getColumnLabel(column: Int): String = throw NotSupported("getColumnLabel") + + override def getColumnName(column: Int): String = throw NotSupported("getColumnName") + + override def getColumnTypeName(column: Int): String = throw NotSupported("getColumnTypeName") + + override def getColumnType(column: Int): Int = throw NotSupported("getColumnType") + + override def getPrecision(column: Int): Int = throw NotSupported("getPrecision") + + override def getSchemaName(column: Int): String = throw NotSupported("getSchemaName") + + override def getScale(column: Int): Int = throw NotSupported("getScale") + + override def getTableName(column: Int): String = throw NotSupported("getTableName") + + override def isAutoIncrement(column: Int): Boolean = throw NotSupported("isAutoIncrement") + + override def isCaseSensitive(column: Int): Boolean = throw NotSupported("isCaseSensitive") + + override def isCurrency(column: Int): Boolean = throw NotSupported("isCurrency") + + override def isDefinitelyWritable(column: Int): Boolean = throw NotSupported("isDefinitelyWritable") + + override def isNullable(column: Int): Int = throw NotSupported("isNullable") + + override def isReadOnly(column: Int): Boolean = throw NotSupported("isReadOnly") + + override def isSearchable(column: Int): Boolean = throw NotSupported("isSearchable") + + override def isSigned(column: Int): Boolean = throw NotSupported("isSigned") + + override def isWritable(column: Int): Boolean = throw NotSupported("isWritable") + +} diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/StaticResultSet.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/StaticResultSet.scala deleted file mode 100644 index 217c887..0000000 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/StaticResultSet.scala +++ /dev/null @@ -1,20 +0,0 @@ -package com.github.mmolimar.ksql.jdbc.resultset - -import java.sql.ResultSetMetaData - -import com.github.mmolimar.ksql.jdbc.HeaderField - - -class StaticResultSet[T <: Any](private val metadata: ResultSetMetaData, - private[jdbc] val rows: Iterator[Seq[T]]) extends AbstractResultSet(metadata, rows) { - - def this(columns: List[HeaderField], rows: Iterator[Seq[T]]) = - this(new KsqlResultSetMetadata(columns), rows) - - override protected def getValue[V <: AnyRef](columnIndex: Int): V = currentRow.get(columnIndex - 1).asInstanceOf[V] - - override protected def getColumnBounds: (Int, Int) = (1, currentRow.getOrElse(Seq.empty).size) - - override protected def closeInherit: Unit = {} - -} diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetadataSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaDataSpec.scala similarity index 92% rename from src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetadataSpec.scala rename to src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaDataSpec.scala index 33c170c..9ba1907 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetadataSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaDataSpec.scala @@ -8,17 +8,17 @@ import io.confluent.ksql.rest.entity.SchemaInfo.{Type => KsqlType} import org.scalamock.scalatest.MockFactory import org.scalatest.{Matchers, OneInstancePerTest, WordSpec} -class KsqlResultSetMetadataSpec extends WordSpec with Matchers with MockFactory with OneInstancePerTest { +class KsqlResultSetMetaDataSpec extends WordSpec with Matchers with MockFactory with OneInstancePerTest { val implementedMethods = Seq("getColumnLabel", "getColumnName", "getColumnTypeName", "getColumnClassName", "isCaseSensitive", "getColumnType", "getColumnCount", "getPrecision", "getScale", "getColumnDisplaySize", "isNullable") - "A KsqlResultSetMetadata" when { + "A KsqlResultSetMetaData" when { "validating specs" should { - val resultSet = new KsqlResultSetMetadata( + val resultSet = new KsqlResultSetMetaData( List( HeaderField("field1", Types.INTEGER, 16), HeaderField("field2", Types.BIGINT, 16), @@ -33,7 +33,7 @@ class KsqlResultSetMetadataSpec extends WordSpec with Matchers with MockFactory "throw not supported exception if not supported" in { - reflectMethods[KsqlResultSetMetadata](implementedMethods, false, resultSet) + reflectMethods[KsqlResultSetMetaData](implementedMethods, false, resultSet) .foreach(method => { assertThrows[SQLFeatureNotSupportedException] { method() @@ -105,14 +105,14 @@ class KsqlResultSetMetadataSpec extends WordSpec with Matchers with MockFactory } } - "A ResultSetMetadataNotSupported" when { + "A ResultSetMetaDataNotSupported" when { "validating specs" should { "throw not supported exception if not supported" in { - val resultSet = new ResultSetMetadataNotSupported - reflectMethods[ResultSetMetadataNotSupported](Seq.empty, false, resultSet) + val resultSet = new ResultSetMetaDataNotSupported + reflectMethods[ResultSetMetaDataNotSupported](Seq.empty, false, resultSet) .foreach(method => { assertThrows[SQLFeatureNotSupportedException] { method() diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala index b107fe8..748072c 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala @@ -2,8 +2,8 @@ package com.github.mmolimar.ksql.jdbc.resultset import java.sql._ -import com.github.mmolimar.ksql.jdbc.HeaderField import com.github.mmolimar.ksql.jdbc.utils.TestUtils._ +import com.github.mmolimar.ksql.jdbc.{DatabaseMetadataHeaders, HeaderField, TableTypes} import io.confluent.ksql.GenericRow import io.confluent.ksql.rest.entity.StreamedRow import org.scalamock.scalatest.MockFactory @@ -12,17 +12,55 @@ import org.scalatest.{Matchers, OneInstancePerTest, WordSpec} import scala.collection.JavaConverters._ -class KsqlResultSetSpec extends WordSpec with Matchers with MockFactory with OneInstancePerTest { +class StreamedResultSetSpec extends WordSpec with Matchers with MockFactory with OneInstancePerTest { - val implementedMethods = Seq("isLast", "isAfterLast", "isBeforeFirst", "isFirst", "next", - "getConcurrency", "close", "getString", "getBytes", "getByte", "getBytes", "getBoolean", "getShort", - "getInt", "getLong", "getFloat", "getDouble", "getMetaData", "getResultSet", "getUpdateCount", "getWarnings") + "A IteratorResultSet" when { + val implementedMethods = Seq("next", "getString", "getBytes", "getByte", "getBytes", "getBoolean", "getShort", + "getInt", "getLong", "getFloat", "getDouble", "getMetaData", "close", "getWarnings") - "A KsqlResultSet" when { + "validating specs" should { + + "throw not supported exception if not supported" in { + + val resultSet = new IteratorResultSet[String](List.empty[HeaderField], Iterator.empty) + reflectMethods[IteratorResultSet[String]](implementedMethods, false, resultSet) + .foreach(method => { + assertThrows[SQLFeatureNotSupportedException] { + method() + } + }) + } + + "work if implemented" in { + + val resultSet = new IteratorResultSet(DatabaseMetadataHeaders.tableTypes, Iterator(Seq(TableTypes.TABLE.name), + Seq(TableTypes.STREAM.name))) + resultSet.next should be(true) + resultSet.getString(1) should be(TableTypes.TABLE.name) + resultSet.getString("TABLE_TYPE") should be(TableTypes.TABLE.name) + resultSet.getString("table_type") should be(TableTypes.TABLE.name) + resultSet.next should be(true) + resultSet.getString(1) should be(TableTypes.STREAM.name) + resultSet.getString("TABLE_TYPE") should be(TableTypes.STREAM.name) + resultSet.getString("table_type") should be(TableTypes.STREAM.name) + assertThrows[SQLException] { + resultSet.getString("UNKNOWN") + } + resultSet.next should be(false) + resultSet.getWarnings should be(None.orNull) + resultSet.close + } + } + } + + "A StreamedResultSet" when { + val implementedMethods = Seq("isLast", "isAfterLast", "isBeforeFirst", "isFirst", "next", + "getConcurrency", "close", "getString", "getBytes", "getByte", "getBytes", "getBoolean", "getShort", + "getInt", "getLong", "getFloat", "getDouble", "getMetaData", "getResultSet", "getUpdateCount", "getWarnings") "validating specs" should { - val resultSetMetadata = new KsqlResultSetMetadata( + val resultSetMetadata = new KsqlResultSetMetaData( List( HeaderField("field1", Types.INTEGER, 16), HeaderField("field2", Types.BIGINT, 16), @@ -37,8 +75,8 @@ class KsqlResultSetSpec extends WordSpec with Matchers with MockFactory with One "throw not supported exception if not supported" in { - val resultSet = new KsqlResultSet(resultSetMetadata, mock[KsqlQueryStream], 0) - reflectMethods[KsqlResultSet](implementedMethods, false, resultSet) + val resultSet = new StreamedResultSet(resultSetMetadata, mock[KsqlQueryStream], 0) + reflectMethods[StreamedResultSet](implementedMethods, false, resultSet) .foreach(method => { assertThrows[SQLFeatureNotSupportedException] { try { @@ -65,7 +103,7 @@ class KsqlResultSetSpec extends WordSpec with Matchers with MockFactory with One (mockedQueryStream.close _).expects } - val resultSet = new KsqlResultSet(resultSetMetadata, mockedQueryStream) + val resultSet = new StreamedResultSet(resultSetMetadata, mockedQueryStream) resultSet.getMetaData should be(resultSetMetadata) resultSet.isLast should be(false) resultSet.isAfterLast should be(false) diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/StaticResultSetSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/StaticResultSetSpec.scala deleted file mode 100644 index a7b52a7..0000000 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/StaticResultSetSpec.scala +++ /dev/null @@ -1,53 +0,0 @@ -package com.github.mmolimar.ksql.jdbc.resultset - -import java.sql.{SQLException, SQLFeatureNotSupportedException} - -import com.github.mmolimar.ksql.jdbc.utils.TestUtils._ -import com.github.mmolimar.ksql.jdbc.{DatabaseMetadataHeaders, HeaderField, TableTypes} -import org.scalamock.scalatest.MockFactory -import org.scalatest.{Matchers, OneInstancePerTest, WordSpec} - - -class StaticResultSetSpec extends WordSpec with Matchers with MockFactory with OneInstancePerTest { - - val implementedMethods = Seq("next", "getString", "getBytes", "getByte", "getBytes", "getBoolean", "getShort", - "getInt", "getLong", "getFloat", "getDouble", "getMetaData", "close", "getWarnings") - - "A StaticResultSet" when { - - "validating specs" should { - - "throw not supported exception if not supported" in { - - val resultSet = new StaticResultSet[String](List.empty[HeaderField], Iterator.empty) - reflectMethods[StaticResultSet[String]](implementedMethods, false, resultSet) - .foreach(method => { - assertThrows[SQLFeatureNotSupportedException] { - method() - } - }) - } - - "work if implemented" in { - - val resultSet = new StaticResultSet[String](DatabaseMetadataHeaders.tableTypes, Iterator(Seq(TableTypes.TABLE.name), - Seq(TableTypes.STREAM.name))) - resultSet.next should be(true) - resultSet.getString(1) should be(TableTypes.TABLE.name) - resultSet.getString("TABLE_TYPE") should be(TableTypes.TABLE.name) - resultSet.getString("table_type") should be(TableTypes.TABLE.name) - resultSet.next should be(true) - resultSet.getString(1) should be(TableTypes.STREAM.name) - resultSet.getString("TABLE_TYPE") should be(TableTypes.STREAM.name) - resultSet.getString("table_type") should be(TableTypes.STREAM.name) - assertThrows[SQLException] { - resultSet.getString("UNKNOWN") - } - resultSet.next should be(false) - resultSet.getWarnings should be(None.orNull) - resultSet.close - } - } - } - -} From 4349d0030f4a7053766a8a1553ed9dd3af6b161c Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Fri, 8 Feb 2019 17:55:17 -0600 Subject: [PATCH 27/47] Support for wasNull and maxRows properties --- .../mmolimar/ksql/jdbc/Exceptions.scala | 4 +++ .../ksql/jdbc/KsqlDatabaseMetaData.scala | 14 ++++---- .../mmolimar/ksql/jdbc/KsqlStatement.scala | 33 ++++++++++--------- .../ksql/jdbc/resultset/KsqlResultSet.scala | 13 ++++---- .../ksql/jdbc/resultset/ResultSet.scala | 17 ++++++++-- .../ksql/jdbc/KsqlStatementSpec.scala | 8 ++++- .../jdbc/resultset/KsqlResultSetSpec.scala | 20 +++++++---- 7 files changed, 69 insertions(+), 40 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala index ef46353..f1782f9 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala @@ -26,6 +26,10 @@ case class NotSupported(feature: String, override val cause: Throwable = None.or override val message = s"Feature not supported: $feature." } +case class InvalidValue(prop: String, value: String, override val cause: Throwable = None.orNull) extends KsqlException { + override val message = s"value '' is not valid for property: $prop." +} + case class KsqlQueryError(override val message: String = "Error executing query.", override val cause: Throwable = None.orNull) extends KsqlException diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala index d0cb042..e5a1538 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala @@ -63,7 +63,7 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def storesUpperCaseQuotedIdentifiers: Boolean = throw NotSupported("storesUpperCaseQuotedIdentifiers") override def getUDTs(catalog: String, schemaPattern: String, typeNamePattern: String, types: Array[Int]): ResultSet = - new IteratorResultSet(List.empty[HeaderField], Iterator.empty) + new IteratorResultSet(List.empty[HeaderField], 0, Iterator.empty) override def getAttributes(catalog: String, schemaPattern: String, typeNamePattern: String, attributeNamePattern: String): ResultSet = throw NotSupported("getAttributes") @@ -234,13 +234,13 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D tableNamePattern: String, columnNamePattern: String): ResultSet = throw NotSupported("getPseudoColumns") - override def getCatalogs: ResultSet = new IteratorResultSet(DatabaseMetadataHeaders.catalogs, Iterator.empty) + override def getCatalogs: ResultSet = new IteratorResultSet(DatabaseMetadataHeaders.catalogs, 0, Iterator.empty) override def getSuperTables(catalog: String, schemaPattern: String, tableNamePattern: String): ResultSet = { validateCatalogAndSchema(catalog, schemaPattern) - new IteratorResultSet(DatabaseMetadataHeaders.superTables, Iterator.empty) + new IteratorResultSet(DatabaseMetadataHeaders.superTables, 0, Iterator.empty) } override def getMaxColumnsInOrderBy: Int = throw NotSupported("getMaxColumnsInOrderBy") @@ -299,7 +299,7 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D }).toIterator } else Iterator.empty - new IteratorResultSet(DatabaseMetadataHeaders.tables, itTables ++ itStreams) + new IteratorResultSet(DatabaseMetadataHeaders.tables, 0, itTables ++ itStreams) } override def supportsMultipleTransactions: Boolean = throw NotSupported("supportsMultipleTransactions") @@ -322,7 +322,7 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def getExtraNameCharacters: String = throw NotSupported("getExtraNameCharacters") - override def getSchemas: ResultSet = new IteratorResultSet(DatabaseMetadataHeaders.schemas, Iterator.empty) + override def getSchemas: ResultSet = new IteratorResultSet(DatabaseMetadataHeaders.schemas, 0, Iterator.empty) override def getSchemas(catalog: String, schemaPattern: String): ResultSet = { validateCatalogAndSchema(catalog, schemaPattern) @@ -392,7 +392,7 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def supportsTransactionIsolationLevel(level: Int): Boolean = throw NotSupported("supportsTransactionIsolationLevel") - override def getTableTypes: ResultSet = new IteratorResultSet(DatabaseMetadataHeaders.tableTypes, + override def getTableTypes: ResultSet = new IteratorResultSet(DatabaseMetadataHeaders.tableTypes, 0, Iterator(Seq(TableTypes.TABLE.name), Seq(TableTypes.STREAM.name))) override def getMaxColumnsInTable: Int = throw NotSupported("getMaxColumnsInTable") @@ -446,7 +446,7 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D }).toIterator } - new IteratorResultSet(DatabaseMetadataHeaders.columns, tableSchemas) + new IteratorResultSet(DatabaseMetadataHeaders.columns, 0, tableSchemas) } override def supportsResultSetType(`type`: Int): Boolean = throw NotSupported("supportsResultSetType") diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala index f7ecabc..3d4e709 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala @@ -24,6 +24,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = import KsqlStatement._ private[this] var currentResultSet: Option[ResultSet] = None + private var maxRows = 0 override def setMaxFieldSize(max: Int): Unit = throw NotSupported("setMaxFieldSize") @@ -54,7 +55,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = override def addBatch(sql: String): Unit = throw NotSupported("addBatch") - override def getMaxRows: Int = throw NotSupported("getMaxRows") + override def getMaxRows: Int = maxRows private def executeKsqlRequest(sql: String): Unit = { currentResultSet = None @@ -122,7 +123,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = override def getResultSetType: Int = ResultSet.TYPE_FORWARD_ONLY - override def setMaxRows(max: Int): Unit = throw NotSupported("setMaxRows") + override def setMaxRows(max: Int): Unit = if (max < 0) throw InvalidValue("maxRows", max.toString) else maxRows = max override def getFetchSize: Int = throw NotSupported("getFetchSize") @@ -184,7 +185,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = } val columns = queryDesc.getFields.asScala.map(f => HeaderField(f.getName, mapType(f.getSchema.getType), 0)).toList - new StreamedResultSet(new KsqlResultSetMetaData(columns), new KsqlQueryStream(stream), timeout) + new StreamedResultSet(new KsqlResultSetMetaData(columns), new KsqlQueryStream(stream), maxRows, timeout) } private implicit def toResultSet(list: KsqlEntityList): ResultSet = { @@ -200,9 +201,9 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = e.getCommandStatus.getStatus.name, e.getCommandStatus.getMessage )) - new IteratorResultSet[String](commandStatusEntity, rows) + new IteratorResultSet[String](commandStatusEntity, maxRows, rows) } - case e: ExecutionPlan => new IteratorResultSet[String](executionPlanEntity, Iterator(Seq(e.getExecutionPlan))) + case e: ExecutionPlan => new IteratorResultSet[String](executionPlanEntity, maxRows, Iterator(Seq(e.getExecutionPlan))) case e: FunctionDescriptionList => { val rows = e.getFunctions.asScala.map(f => Seq( e.getAuthor, @@ -214,14 +215,14 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = f.getReturnType, f.getArguments.asScala.map(arg => s"${arg.getName}:${arg.getType}").mkString(", ") )).toIterator - new IteratorResultSet[String](functionDescriptionListEntity, rows) + new IteratorResultSet[String](functionDescriptionListEntity, maxRows, rows) } case e: FunctionNameList => { val rows = e.getFunctions.asScala.map(f => Seq( f.getName, f.getType.name )).toIterator - new IteratorResultSet[String](functionNameListEntity, rows) + new IteratorResultSet[String](functionNameListEntity, maxRows, rows) } case e: KafkaTopicsList => { val rows = e.getTopics.asScala.map(t => Seq( @@ -231,7 +232,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = t.getRegistered.booleanValue, t.getReplicaInfo.asScala.mkString(", ") )).toIterator - new IteratorResultSet[Any](kafkaTopicsListEntity, rows) + new IteratorResultSet[Any](kafkaTopicsListEntity, maxRows, rows) } case e: KsqlTopicsList => { val rows = e.getTopics.asScala.map(t => Seq( @@ -239,14 +240,14 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = t.getKafkaTopic, t.getFormat.name )).toIterator - new IteratorResultSet[String](ksqlTopicsListEntity, rows) + new IteratorResultSet[String](ksqlTopicsListEntity, maxRows, rows) } case e: PropertiesList => { val rows = e.getProperties.asScala.map(p => Seq( p._1, p._2.toString )).toIterator - new IteratorResultSet[String](propertiesListEntity, rows) + new IteratorResultSet[String](propertiesListEntity, maxRows, rows) } case e: Queries => { val rows = e.getQueries.asScala.map(q => Seq( @@ -254,7 +255,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = q.getQueryString, q.getSinks.asScala.mkString(", ") )).toIterator - new IteratorResultSet[String](queriesEntity, rows) + new IteratorResultSet[String](queriesEntity, maxRows, rows) } case e@(_: QueryDescriptionEntity | _: QueryDescriptionList) => { val descriptions: Seq[QueryDescription] = if (e.isInstanceOf[QueryDescriptionEntity]) { @@ -271,7 +272,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = d.getTopology, d.getExecutionPlan )).toIterator - new IteratorResultSet[String](queryDescriptionEntityList, rows) + new IteratorResultSet[String](queryDescriptionEntityList, maxRows, rows) } case e@(_: SourceDescriptionEntity | _: SourceDescriptionList) => { val descriptions: Seq[SourceDescription] = if (e.isInstanceOf[SourceDescriptionEntity]) { @@ -292,7 +293,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = d.getErrorStats, d.getTimestamp )).toIterator - new IteratorResultSet[Any](sourceDescriptionEntityList, rows) + new IteratorResultSet[Any](sourceDescriptionEntityList, maxRows, rows) } case e: StreamsList => { val rows = e.getStreams.asScala.map(s => Seq( @@ -300,7 +301,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = s.getTopic, s.getFormat )).toIterator - new IteratorResultSet[String](streamsListEntity, rows) + new IteratorResultSet[String](streamsListEntity, maxRows, rows) } case e: TablesList => { val rows = e.getTables.asScala.map(t => Seq( @@ -309,7 +310,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = t.getFormat, t.getIsWindowed )).toIterator - new IteratorResultSet[Any](tablesListEntity, rows) + new IteratorResultSet[Any](tablesListEntity, maxRows, rows) } case e: TopicDescription => { val rows = Iterator(Seq( @@ -318,7 +319,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = e.getFormat, e.getSchemaString )) - new IteratorResultSet[String](topicDescriptionEntity, rows) + new IteratorResultSet[String](topicDescriptionEntity, maxRows, rows) } }).getOrElse(throw KsqlCommandError(s"Cannot build result set for '${list.get(0).getStatementText}'.")) } diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala index 43b245e..3a00c3f 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala @@ -17,11 +17,12 @@ import scala.concurrent.{Await, Future, TimeoutException} import scala.util.{Failure, Success, Try} -class IteratorResultSet[T <: Any](private val metadata: ResultSetMetaData, - private[jdbc] val rows: Iterator[Seq[T]]) extends AbstractResultSet(metadata, rows) { +class IteratorResultSet[T <: Any](private val metadata: ResultSetMetaData, private val maxRows: Long, + private[jdbc] val rows: Iterator[Seq[T]]) + extends AbstractResultSet(metadata, maxRows, rows) { - def this(columns: List[HeaderField], rows: Iterator[Seq[T]]) = - this(new KsqlResultSetMetaData(columns), rows) + def this(columns: List[HeaderField], maxRows: Long, rows: Iterator[Seq[T]]) = + this(new KsqlResultSetMetaData(columns), maxRows, rows) override protected def getValue[V <: AnyRef](columnIndex: Int): V = currentRow.get(columnIndex - 1).asInstanceOf[V] @@ -42,8 +43,8 @@ private[jdbc] class KsqlQueryStream(stream: KsqlRestClient.QueryStream) extends } class StreamedResultSet(private val metadata: ResultSetMetaData, - private val stream: KsqlQueryStream, val timeout: Long = 0) - extends AbstractResultSet[StreamedRow](metadata, stream) { + private val stream: KsqlQueryStream, private val maxRows: Long, val timeout: Long = 0) + extends AbstractResultSet[StreamedRow](metadata, maxRows, stream) { private val emptyRow: StreamedRow = StreamedRow.row(new GenericRow) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala index 59c3141..d98a097 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala @@ -401,10 +401,13 @@ private[resultset] class ResultSetNotSupported extends ResultSet with WrapperNot } private[resultset] abstract class AbstractResultSet[T](private val metadata: ResultSetMetaData, + private val maxRows: Long, private val records: Iterator[T]) extends ResultSetNotSupported { private val indexByLabel: Map[String, Int] = (1 to metadata.getColumnCount) .map(index => (metadata.getColumnLabel(index).toUpperCase -> index)).toMap + private var lastColumnNull = true + private var rowCounter = 0 protected var currentRow: Option[T] = None @@ -419,7 +422,11 @@ private[resultset] abstract class AbstractResultSet[T](private val metadata: Res override final def next: Boolean = closed match { case true => throw ResultSetError("Result set is already closed.") - case false => nextResult + case false if maxRows != 0 && rowCounter >= maxRows => false + case _ => + val result = nextResult + rowCounter += 1 + result } override final def close: Unit = closed match { @@ -472,13 +479,17 @@ private[resultset] abstract class AbstractResultSet[T](private val metadata: Res override def getWarnings: SQLWarning = None.orNull + override def wasNull: Boolean = lastColumnNull + private def getColumn[T <: AnyRef](columnLabel: String)(implicit ev: ClassTag[T]): T = { getColumn[T](getColumnIndex(columnLabel)) } private def getColumn[T <: AnyRef](columnIndex: Int)(implicit ev: ClassTag[T]): T = { checkRow(columnIndex) - inferValue[T](columnIndex) + val result = inferValue[T](columnIndex) + lastColumnNull = Option(result).map(_ => false).getOrElse(true) + result } private def checkRow(columnIndex: Int) = { @@ -500,7 +511,7 @@ private[resultset] abstract class AbstractResultSet[T](private val metadata: Res import ImplicitClasses._ ev.runtimeClass match { case Any_ if ev.runtimeClass == value.getClass => value - case String_ => value.toString + case String_ => Option(value).map(_.toString).getOrElse(None.orNull) case JBoolean_ if value.isInstanceOf[String] => JBoolean.parseBoolean(value.asInstanceOf[String]) case JBoolean_ if value.isInstanceOf[Number] => value.asInstanceOf[Number].intValue != 0 case JShort_ if value.isInstanceOf[String] => JShort.parseShort(value.asInstanceOf[String]) diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala index 2d46746..e21166e 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala @@ -21,7 +21,7 @@ import scala.collection.JavaConverters._ class KsqlStatementSpec extends WordSpec with Matchers with MockFactory with OneInstancePerTest { val implementedMethods = Seq("execute", "executeQuery", "getResultSet", "getUpdateCount", "getResultSetType", - "getWarnings", "cancel") + "getWarnings", "getMaxRows", "cancel", "setMaxRows") "A KsqlStatement" when { @@ -145,7 +145,13 @@ class KsqlStatementSpec extends WordSpec with Matchers with MockFactory with One .once Option(statement.executeQuery("select * from test;")) should not be (None) + statement.getMaxRows should be(0) statement.getResultSet shouldNot be(None.orNull) + assertThrows[SQLException] { + statement.setMaxRows(-1) + } + statement.setMaxRows(1) + statement.getMaxRows should be(1) statement.getUpdateCount should be(-1) statement.getResultSetType should be(ResultSet.TYPE_FORWARD_ONLY) diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala index 748072c..e17fafd 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala @@ -12,17 +12,17 @@ import org.scalatest.{Matchers, OneInstancePerTest, WordSpec} import scala.collection.JavaConverters._ -class StreamedResultSetSpec extends WordSpec with Matchers with MockFactory with OneInstancePerTest { +class KsqlResultSetSpec extends WordSpec with Matchers with MockFactory with OneInstancePerTest { "A IteratorResultSet" when { val implementedMethods = Seq("next", "getString", "getBytes", "getByte", "getBytes", "getBoolean", "getShort", - "getInt", "getLong", "getFloat", "getDouble", "getMetaData", "close", "getWarnings") + "getInt", "getLong", "getFloat", "getDouble", "getMetaData", "close", "getWarnings", "wasNull") "validating specs" should { "throw not supported exception if not supported" in { - val resultSet = new IteratorResultSet[String](List.empty[HeaderField], Iterator.empty) + val resultSet = new IteratorResultSet[String](List.empty[HeaderField], 0, Iterator.empty) reflectMethods[IteratorResultSet[String]](implementedMethods, false, resultSet) .foreach(method => { assertThrows[SQLFeatureNotSupportedException] { @@ -33,9 +33,12 @@ class StreamedResultSetSpec extends WordSpec with Matchers with MockFactory with "work if implemented" in { - val resultSet = new IteratorResultSet(DatabaseMetadataHeaders.tableTypes, Iterator(Seq(TableTypes.TABLE.name), + val resultSet = new IteratorResultSet(DatabaseMetadataHeaders.tableTypes, 2, Iterator(Seq(TableTypes.TABLE.name), Seq(TableTypes.STREAM.name))) + + resultSet.wasNull should be(true) resultSet.next should be(true) + resultSet.getString(1) should be(TableTypes.TABLE.name) resultSet.getString("TABLE_TYPE") should be(TableTypes.TABLE.name) resultSet.getString("table_type") should be(TableTypes.TABLE.name) @@ -43,6 +46,7 @@ class StreamedResultSetSpec extends WordSpec with Matchers with MockFactory with resultSet.getString(1) should be(TableTypes.STREAM.name) resultSet.getString("TABLE_TYPE") should be(TableTypes.STREAM.name) resultSet.getString("table_type") should be(TableTypes.STREAM.name) + resultSet.wasNull should be(false) assertThrows[SQLException] { resultSet.getString("UNKNOWN") } @@ -55,8 +59,8 @@ class StreamedResultSetSpec extends WordSpec with Matchers with MockFactory with "A StreamedResultSet" when { val implementedMethods = Seq("isLast", "isAfterLast", "isBeforeFirst", "isFirst", "next", - "getConcurrency", "close", "getString", "getBytes", "getByte", "getBytes", "getBoolean", "getShort", - "getInt", "getLong", "getFloat", "getDouble", "getMetaData", "getResultSet", "getUpdateCount", "getWarnings") + "getConcurrency", "close", "getString", "getBytes", "getByte", "getBytes", "getBoolean", "getShort", "getInt", + "getLong", "getFloat", "getDouble", "getMetaData", "getResultSet", "getUpdateCount", "getWarnings", "wasNull") "validating specs" should { @@ -103,12 +107,13 @@ class StreamedResultSetSpec extends WordSpec with Matchers with MockFactory with (mockedQueryStream.close _).expects } - val resultSet = new StreamedResultSet(resultSetMetadata, mockedQueryStream) + val resultSet = new StreamedResultSet(resultSetMetadata, mockedQueryStream, 0) resultSet.getMetaData should be(resultSetMetadata) resultSet.isLast should be(false) resultSet.isAfterLast should be(false) resultSet.isBeforeFirst should be(false) resultSet.getConcurrency should be(ResultSet.CONCUR_READ_ONLY) + resultSet.wasNull should be(true) resultSet.isFirst should be(true) resultSet.next should be(true) @@ -136,6 +141,7 @@ class StreamedResultSetSpec extends WordSpec with Matchers with MockFactory with resultSet.getLong(index + 1) should be(e(6)) resultSet.getFloat(index + 1) should be(e(7)) resultSet.getDouble(index + 1) should be(e(8)) + resultSet.wasNull should be(false) } } From 7a440638abc088162aea0008e4166c149e5dcb0a Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sun, 10 Feb 2019 13:08:09 -0600 Subject: [PATCH 28/47] Added database metadata not supported class --- .../github/mmolimar/ksql/jdbc/Headers.scala | 12 + .../ksql/jdbc/KsqlDatabaseMetaData.scala | 269 +++++++++++------- .../mmolimar/ksql/jdbc/KsqlDriver.scala | 20 +- .../mmolimar/ksql/jdbc/KsqlStatement.scala | 1 + .../ksql/jdbc/KsqlDatabaseMetaDataSpec.scala | 33 ++- 5 files changed, 222 insertions(+), 113 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala index ecaa174..99ca901 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala @@ -84,6 +84,18 @@ object DatabaseMetadataHeaders { HeaderField("IS_GENERATEDCOLUMN", Types.VARCHAR, 3) ) + val procedures = List( + HeaderField("PROCEDURE_CAT", Types.CHAR, 255), + HeaderField("PROCEDURE_SCHEM", Types.CHAR, 255), + HeaderField("PROCEDURE_NAME", Types.CHAR, 255), + HeaderField("reserved1", Types.CHAR, 0), + HeaderField("reserved2", Types.CHAR, 0), + HeaderField("reserved3", Types.CHAR, 0), + HeaderField("REMARKS", Types.CHAR, 255), + HeaderField("PROCEDURE_TYPE", Types.SMALLINT, 6), + HeaderField("SPECIFIC_NAME", Types.CHAR, 255) + ) + def mapDataType(dataType: String): Int = dataType match { case "BOOL" | "BOOLEAN" => Types.BOOLEAN case "INT" | "INTEGER" => Types.INTEGER diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala index e5a1538..4a4112e 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala @@ -26,7 +26,7 @@ object TableTypes { } -class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends DatabaseMetaData with WrapperNotSupported { +class DatabaseMetaDataNotSupported extends DatabaseMetaData with WrapperNotSupported { override def supportsMultipleOpenResults: Boolean = throw NotSupported("supportsMultipleOpenResults") @@ -42,9 +42,9 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def insertsAreDetected(`type`: Int): Boolean = throw NotSupported("insertsAreDetected") - override def getDriverMajorVersion: Int = KsqlDriver.majorVersion + override def getDriverMajorVersion: Int = throw NotSupported("getDriverMajorVersion") - override def getDatabaseProductVersion: String = "5.1.0" + override def getDatabaseProductVersion: String = throw NotSupported("getDatabaseProductVersion") override def getIndexInfo(catalog: String, schema: String, table: String, unique: Boolean, approximate: Boolean): ResultSet = throw NotSupported("getIndexInfo") @@ -56,14 +56,14 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def isCatalogAtStart: Boolean = throw NotSupported("isCatalogAtStart") - override def getJDBCMinorVersion: Int = KsqlDriver.jdbcMinorVersion + override def getJDBCMinorVersion: Int = throw NotSupported("getJDBCMinorVersion") override def supportsMixedCaseQuotedIdentifiers: Boolean = throw NotSupported("supportsMixedCaseQuotedIdentifiers") override def storesUpperCaseQuotedIdentifiers: Boolean = throw NotSupported("storesUpperCaseQuotedIdentifiers") override def getUDTs(catalog: String, schemaPattern: String, typeNamePattern: String, types: Array[Int]): ResultSet = - new IteratorResultSet(List.empty[HeaderField], 0, Iterator.empty) + throw NotSupported("getUDTs") override def getAttributes(catalog: String, schemaPattern: String, typeNamePattern: String, attributeNamePattern: String): ResultSet = throw NotSupported("getAttributes") @@ -80,7 +80,7 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def supportsSchemasInDataManipulation: Boolean = throw NotSupported("supportsSchemasInDataManipulation") - override def getDatabaseMinorVersion: Int = 1 + override def getDatabaseMinorVersion: Int = throw NotSupported("getDatabaseMinorVersion") override def supportsSchemasInProcedureCalls: Boolean = throw NotSupported("supportsSchemasInProcedureCalls") @@ -92,7 +92,7 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def supportsCatalogsInDataManipulation: Boolean = throw NotSupported("supportsCatalogsInDataManipulation") - override def getDatabaseProductName: String = "KSQL" + override def getDatabaseProductName: String = throw NotSupported("getDatabaseProductName") override def supportsOpenCursorsAcrossCommit: Boolean = throw NotSupported("supportsOpenCursorsAcrossCommit") @@ -100,13 +100,13 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def supportsExtendedSQLGrammar: Boolean = throw NotSupported("supportsExtendedSQLGrammar") - override def getJDBCMajorVersion: Int = KsqlDriver.jdbcMajorVersion + override def getJDBCMajorVersion: Int = throw NotSupported("getJDBCMajorVersion") override def getUserName: String = throw NotSupported("getUserName") override def getMaxProcedureNameLength: Int = throw NotSupported("getMaxProcedureNameLength") - override def getDriverName: String = KsqlDriver.driverName + override def getDriverName: String = throw NotSupported("getDriverName") override def getMaxRowSize: Int = throw NotSupported("getMaxRowSize") @@ -156,7 +156,7 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D foreignCatalog: String, foreignSchema: String, foreignTable: String): ResultSet = throw NotSupported("getCrossReference") - override def getDatabaseMajorVersion: Int = 5 + override def getDatabaseMajorVersion: Int = throw NotSupported("getDatabaseMajorVersion") override def supportsColumnAliasing: Boolean = throw NotSupported("supportsColumnAliasing") @@ -195,7 +195,8 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def allProceduresAreCallable: Boolean = throw NotSupported("allProceduresAreCallable") - override def getImportedKeys(catalog: String, schema: String, table: String): ResultSet = throw NotSupported("getImportedKeys") + override def getImportedKeys(catalog: String, schema: String, table: String): ResultSet = + throw NotSupported("getImportedKeys") override def usesLocalFiles: Boolean = throw NotSupported("usesLocalFiles") @@ -221,10 +222,10 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def supportsCorrelatedSubqueries: Boolean = throw NotSupported("supportsCorrelatedSubqueries") - override def isReadOnly: Boolean = true + override def isReadOnly: Boolean = throw NotSupported("isReadOnly") - override def getProcedures(catalog: String, schemaPattern: String, - procedureNamePattern: String): ResultSet = throw NotSupported("getProcedures") + override def getProcedures(catalog: String, schemaPattern: String, procedureNamePattern: String): ResultSet = + throw NotSupported("getProcedures") override def supportsUnionAll: Boolean = throw NotSupported("supportsUnionAll") @@ -234,14 +235,10 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D tableNamePattern: String, columnNamePattern: String): ResultSet = throw NotSupported("getPseudoColumns") - override def getCatalogs: ResultSet = new IteratorResultSet(DatabaseMetadataHeaders.catalogs, 0, Iterator.empty) + override def getCatalogs: ResultSet = throw NotSupported("getCatalogs") override def getSuperTables(catalog: String, schemaPattern: String, - tableNamePattern: String): ResultSet = { - validateCatalogAndSchema(catalog, schemaPattern) - - new IteratorResultSet(DatabaseMetadataHeaders.superTables, 0, Iterator.empty) - } + tableNamePattern: String): ResultSet = throw NotSupported("getSuperTables") override def getMaxColumnsInOrderBy: Int = throw NotSupported("getMaxColumnsInOrderBy") @@ -267,40 +264,7 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def nullsAreSortedHigh: Boolean = throw NotSupported("nullsAreSortedHigh") override def getTables(catalog: String, schemaPattern: String, - tableNamePattern: String, types: Array[String]): ResultSet = { - - validateCatalogAndSchema(catalog, schemaPattern) - - types.foreach(t => if (!TableTypes.tableTypes.map(_.name).contains(t)) throw UnknownTableType(s"Unknown table type $t")) - val tablePattern = { - if (Option(tableNamePattern).getOrElse("").equals("")) ".*" else tableNamePattern - }.toUpperCase.r.pattern - - val itTables = if (types.contains(TableTypes.TABLE.name)) { - val tables = ksqlConnection.executeKsqlCommand("SHOW TABLES;") - if (tables.isErroneous) throw KsqlCommandError(s"Error showing tables: ${tables.getErrorMessage.getMessage}") - - tables.getResponse.asScala.flatMap(_.asInstanceOf[TablesList].getTables.asScala) - .filter(tb => tablePattern.matcher(tb.getName.toUpperCase).matches) - .map(tb => { - Seq("", "", tb.getName, TableTypes.TABLE.name, "Topic: " + tb.getTopic + ". Windowed: " + tb.getIsWindowed, - "", tb.getFormat, "", "", "") - }).toIterator - } else Iterator.empty - - val itStreams = if (types.contains(TableTypes.STREAM.name)) { - val streams = ksqlConnection.executeKsqlCommand("SHOW STREAMS;") - if (streams.isErroneous) throw KsqlCommandError(s"Error showing streams: ${streams.getErrorMessage.getMessage}") - - streams.getResponse.asScala.flatMap(_.asInstanceOf[StreamsList].getStreams.asScala) - .filter(tb => tablePattern.matcher(tb.getName.toUpperCase).matches) - .map(tb => { - Seq("", "", tb.getName, TableTypes.STREAM.name, "Topic: " + tb.getTopic, "", tb.getFormat, "", "", "") - }).toIterator - } else Iterator.empty - - new IteratorResultSet(DatabaseMetadataHeaders.tables, 0, itTables ++ itStreams) - } + tableNamePattern: String, types: Array[String]): ResultSet = throw NotSupported("getTables") override def supportsMultipleTransactions: Boolean = throw NotSupported("supportsMultipleTransactions") @@ -322,14 +286,11 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def getExtraNameCharacters: String = throw NotSupported("getExtraNameCharacters") - override def getSchemas: ResultSet = new IteratorResultSet(DatabaseMetadataHeaders.schemas, 0, Iterator.empty) + override def getSchemas: ResultSet = throw NotSupported("getSchemas") - override def getSchemas(catalog: String, schemaPattern: String): ResultSet = { - validateCatalogAndSchema(catalog, schemaPattern) - getSchemas - } + override def getSchemas(catalog: String, schemaPattern: String): ResultSet = throw NotSupported("getSchemas") - override def supportsMultipleResultSets: Boolean = false + override def supportsMultipleResultSets: Boolean = throw NotSupported("supportsMultipleResultSets") override def ownInsertsAreVisible(`type`: Int): Boolean = throw NotSupported("ownInsertsAreVisible") @@ -343,7 +304,7 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D table: String, scope: Int, nullable: Boolean): ResultSet = throw NotSupported("getBestRowIdentifier") - override def getDriverVersion: String = KsqlDriver.version + override def getDriverVersion: String = throw NotSupported("getDriverVersion") override def storesUpperCaseIdentifiers: Boolean = throw NotSupported("storesUpperCaseIdentifiers") @@ -351,14 +312,16 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def getMaxCatalogNameLength: Int = throw NotSupported("getMaxCatalogNameLength") - override def supportsDataManipulationTransactionsOnly: Boolean = throw NotSupported("supportsDataManipulationTransactionsOnly") + override def supportsDataManipulationTransactionsOnly: Boolean = + throw NotSupported("supportsDataManipulationTransactionsOnly") override def getSystemFunctions: String = throw NotSupported("getSystemFunctions") override def getColumnPrivileges(catalog: String, schema: String, - table: String, columnNamePattern: String): ResultSet = throw NotSupported("getColumnPrivileges") + table: String, columnNamePattern: String): ResultSet = + throw NotSupported("getColumnPrivileges") - override def getDriverMinorVersion: Int = KsqlDriver.minorVersion + override def getDriverMinorVersion: Int = throw NotSupported("getDriverMinorVersion") override def getMaxTableNameLength: Int = throw NotSupported("getMaxTableNameLength") @@ -370,7 +333,8 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def usesLocalFilePerTable: Boolean = throw NotSupported("usesLocalFilePerTable") - override def autoCommitFailureClosesAllResultSets: Boolean = throw NotSupported("autoCommitFailureClosesAllResultSets") + override def autoCommitFailureClosesAllResultSets: Boolean = + throw NotSupported("autoCommitFailureClosesAllResultSets") override def supportsCatalogsInIndexDefinitions: Boolean = throw NotSupported("supportsCatalogsInIndexDefinitions") @@ -380,7 +344,8 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def supportsStatementPooling: Boolean = throw NotSupported("supportsStatementPooling") - override def supportsCatalogsInPrivilegeDefinitions: Boolean = throw NotSupported("supportsCatalogsInPrivilegeDefinitions") + override def supportsCatalogsInPrivilegeDefinitions: Boolean = + throw NotSupported("supportsCatalogsInPrivilegeDefinitions") override def supportsStoredProcedures: Boolean = throw NotSupported("supportsStoredProcedures") @@ -390,19 +355,151 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def supportsSubqueriesInComparisons: Boolean = throw NotSupported("supportsSubqueriesInComparisons") - override def supportsTransactionIsolationLevel(level: Int): Boolean = throw NotSupported("supportsTransactionIsolationLevel") + override def supportsTransactionIsolationLevel(level: Int): Boolean = + throw NotSupported("supportsTransactionIsolationLevel") - override def getTableTypes: ResultSet = new IteratorResultSet(DatabaseMetadataHeaders.tableTypes, 0, - Iterator(Seq(TableTypes.TABLE.name), Seq(TableTypes.STREAM.name))) + override def getTableTypes: ResultSet = throw NotSupported("getTableTypes") override def getMaxColumnsInTable: Int = throw NotSupported("getMaxColumnsInTable") - override def getConnection: Connection = ksqlConnection + override def getConnection: Connection = throw NotSupported("getConnection") override def updatesAreDetected(`type`: Int): Boolean = throw NotSupported("updatesAreDetected") override def supportsPositionedDelete: Boolean = throw NotSupported("supportsPositionedDelete") + override def getColumns(catalog: String, schemaPattern: String, + tableNamePattern: String, columnNamePattern: String): ResultSet = + throw NotSupported("getColumns") + + override def supportsResultSetType(`type`: Int): Boolean = throw NotSupported("supportsResultSetType") + + override def supportsMinimumSQLGrammar: Boolean = throw NotSupported("supportsMinimumSQLGrammar") + + override def generatedKeyAlwaysReturned: Boolean = throw NotSupported("generatedKeyAlwaysReturned") + + override def supportsConvert: Boolean = throw NotSupported("supportsConvert") + + override def supportsConvert(fromType: Int, toType: Int): Boolean = throw NotSupported("supportsConvert") + + override def getExportedKeys(catalog: String, schema: String, table: String): ResultSet = + throw NotSupported("getExportedKeys") + + override def supportsOrderByUnrelated: Boolean = throw NotSupported("supportsOrderByUnrelated") + + override def getSQLStateType: Int = throw NotSupported("getSQLStateType") + + override def supportsOpenStatementsAcrossRollback: Boolean = throw NotSupported("supportsOpenStatementsAcrossRollback") + + override def getMaxColumnsInIndex: Int = throw NotSupported("getMaxColumnsInIndex") + + override def getTimeDateFunctions: String = throw NotSupported("getTimeDateFunctions") + + override def supportsSchemasInIndexDefinitions: Boolean = throw NotSupported("supportsSchemasInIndexDefinitions") + + override def supportsANSI92IntermediateSQL: Boolean = throw NotSupported("supportsANSI92IntermediateSQL") + + override def getCatalogSeparator: String = throw NotSupported("getCatalogSeparator") + + override def othersInsertsAreVisible(`type`: Int): Boolean = throw NotSupported("othersInsertsAreVisible") + + override def supportsSchemasInTableDefinitions: Boolean = throw NotSupported("supportsSchemasInTableDefinitions") + +} + +class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends DatabaseMetaDataNotSupported { + + override def getDriverMajorVersion: Int = KsqlDriver.driverMajorVersion + + override def getDriverMinorVersion: Int = KsqlDriver.driverMinorVersion + + override def getJDBCMajorVersion: Int = KsqlDriver.jdbcMajorVersion + + override def getJDBCMinorVersion: Int = KsqlDriver.jdbcMinorVersion + + override def getDriverVersion: String = KsqlDriver.driverVersion + + override def getDriverName: String = KsqlDriver.driverName + + override def getDatabaseProductName: String = KsqlDriver.ksqlName + + override def getDatabaseProductVersion: String = KsqlDriver.ksqlVersion + + override def getDatabaseMajorVersion: Int = KsqlDriver.driverMajorVersion + + override def getDatabaseMinorVersion: Int = KsqlDriver.driverMinorVersion + + override def getUDTs(catalog: String, schemaPattern: String, typeNamePattern: String, types: Array[Int]): ResultSet = + new IteratorResultSet(List.empty[HeaderField], 0, Iterator.empty) + + override def getSQLKeywords: String = Seq("CREATE STREAM", "DESCRIBE", "DESCRIBE FUNCTION", "EXPLAIN", + "DROP STREAM", "DROP TABLE", "PRINT", "CAST", "SHOW FUNCTIONS", "LIST FUNCTIONS", + "SHOW TOPICS", "SHOW FUNCTIONS", "SHOW STREAMS", "SHOW TABLES", "SHOW QUERIES", "SHOW PROPERTIES", + "TERMINATE").mkString(",") + + override def isReadOnly: Boolean = true + + override def getProcedures(catalog: String, schemaPattern: String, procedureNamePattern: String): ResultSet = { + new IteratorResultSet(DatabaseMetadataHeaders.procedures, 0, Iterator.empty) + } + + override def getCatalogs: ResultSet = new IteratorResultSet(DatabaseMetadataHeaders.catalogs, 0, Iterator.empty) + + override def getSuperTables(catalog: String, schemaPattern: String, + tableNamePattern: String): ResultSet = { + validateCatalogAndSchema(catalog, schemaPattern) + + new IteratorResultSet(DatabaseMetadataHeaders.superTables, 0, Iterator.empty) + } + + override def getTables(catalog: String, schemaPattern: String, + tableNamePattern: String, types: Array[String]): ResultSet = { + + validateCatalogAndSchema(catalog, schemaPattern) + + types.foreach(t => if (!TableTypes.tableTypes.map(_.name).contains(t)) throw UnknownTableType(s"Unknown table type $t")) + val tablePattern = { + if (Option(tableNamePattern).getOrElse("").equals("")) ".*" else tableNamePattern + }.toUpperCase.r.pattern + + val itTables = if (types.contains(TableTypes.TABLE.name)) { + val tables = ksqlConnection.executeKsqlCommand("SHOW TABLES;") + if (tables.isErroneous) throw KsqlCommandError(s"Error showing tables: ${tables.getErrorMessage.getMessage}") + + tables.getResponse.asScala.flatMap(_.asInstanceOf[TablesList].getTables.asScala) + .filter(tb => tablePattern.matcher(tb.getName.toUpperCase).matches) + .map(tb => { + Seq("", "", tb.getName, TableTypes.TABLE.name, "Topic: " + tb.getTopic + ". Windowed: " + tb.getIsWindowed, + "", tb.getFormat, "", "", "") + }).toIterator + } else Iterator.empty + + val itStreams = if (types.contains(TableTypes.STREAM.name)) { + val streams = ksqlConnection.executeKsqlCommand("SHOW STREAMS;") + if (streams.isErroneous) throw KsqlCommandError(s"Error showing streams: ${streams.getErrorMessage.getMessage}") + + streams.getResponse.asScala.flatMap(_.asInstanceOf[StreamsList].getStreams.asScala) + .filter(tb => tablePattern.matcher(tb.getName.toUpperCase).matches) + .map(tb => { + Seq("", "", tb.getName, TableTypes.STREAM.name, "Topic: " + tb.getTopic, "", tb.getFormat, "", "", "") + }).toIterator + } else Iterator.empty + + new IteratorResultSet(DatabaseMetadataHeaders.tables, 0, itTables ++ itStreams) + } + + override def getSchemas: ResultSet = new IteratorResultSet(DatabaseMetadataHeaders.schemas, 0, Iterator.empty) + + override def getSchemas(catalog: String, schemaPattern: String): ResultSet = { + validateCatalogAndSchema(catalog, schemaPattern) + getSchemas + } + + override def getTableTypes: ResultSet = new IteratorResultSet(DatabaseMetadataHeaders.tableTypes, 0, + Iterator(Seq(TableTypes.TABLE.name), Seq(TableTypes.STREAM.name))) + + override def getConnection: Connection = ksqlConnection + override def getColumns(catalog: String, schemaPattern: String, tableNamePattern: String, columnNamePattern: String): ResultSet = { validateCatalogAndSchema(catalog, schemaPattern) @@ -449,37 +546,9 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D new IteratorResultSet(DatabaseMetadataHeaders.columns, 0, tableSchemas) } - override def supportsResultSetType(`type`: Int): Boolean = throw NotSupported("supportsResultSetType") - - override def supportsMinimumSQLGrammar: Boolean = throw NotSupported("supportsMinimumSQLGrammar") + override def supportsCatalogsInTableDefinitions: Boolean = false - override def generatedKeyAlwaysReturned: Boolean = throw NotSupported("generatedKeyAlwaysReturned") - - override def supportsConvert: Boolean = throw NotSupported("supportsConvert") - - override def supportsConvert(fromType: Int, toType: Int): Boolean = throw NotSupported("supportsConvert") - - override def getExportedKeys(catalog: String, schema: String, table: String): ResultSet = throw NotSupported("getExportedKeys") - - override def supportsOrderByUnrelated: Boolean = throw NotSupported("supportsOrderByUnrelated") - - override def getSQLStateType: Int = throw NotSupported("getSQLStateType") - - override def supportsOpenStatementsAcrossRollback: Boolean = throw NotSupported("supportsOpenStatementsAcrossRollback") - - override def getMaxColumnsInIndex: Int = throw NotSupported("getMaxColumnsInIndex") - - override def getTimeDateFunctions: String = throw NotSupported("getTimeDateFunctions") - - override def supportsSchemasInIndexDefinitions: Boolean = throw NotSupported("supportsSchemasInIndexDefinitions") - - override def supportsANSI92IntermediateSQL: Boolean = throw NotSupported("supportsANSI92IntermediateSQL") - - override def getCatalogSeparator: String = throw NotSupported("getCatalogSeparator") - - override def othersInsertsAreVisible(`type`: Int): Boolean = throw NotSupported("othersInsertsAreVisible") - - override def supportsSchemasInTableDefinitions: Boolean = throw NotSupported("supportsSchemasInTableDefinitions") + override def supportsMultipleResultSets: Boolean = false private def validateCatalogAndSchema(catalog: String, schema: String) = { if (catalog != null && catalog != "") throw UnknownCatalog(s"Unknown catalog $catalog") diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala index 900fa6e..d7954d7 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala @@ -10,14 +10,22 @@ import scala.util.Try object KsqlDriver { - val driverName = "KSQL JDBC" - val version = "0.3" + val ksqlName = "KSQL" val ksqlPrefix = "jdbc:ksql://" - val majorVersion = 1 - val minorVersion = 0 + + val driverName = "KSQL JDBC driver" + val driverMajorVersion = 0 + val driverMinorVersion = 3 + val driverVersion = s"$driverMajorVersion.$driverMinorVersion" + val jdbcMajorVersion = 4 val jdbcMinorVersion = 1 + val ksqlMajorVersion = 5 + val ksqlMinorVersion = 1 + val ksqlMicroVersion = 0 + val ksqlVersion = s"$ksqlMajorVersion.$ksqlMinorVersion.$ksqlMicroVersion" + private val ksqlServerRegex = "([A-Za-z0-9._%+-]+):([0-9]{1,5})" private val ksqlPropsRegex = "(\\?([A-Za-z0-9._-]+=[A-Za-z0-9._-]+(&[A-Za-z0-9._-]+=[A-Za-z0-9._-]+)*)){0,1}" @@ -43,9 +51,9 @@ class KsqlDriver extends Driver { override def getPropertyInfo(url: String, info: Properties): scala.Array[DriverPropertyInfo] = scala.Array.empty - override def getMinorVersion: Int = KsqlDriver.minorVersion + override def getMinorVersion: Int = KsqlDriver.driverMinorVersion - override def getMajorVersion: Int = KsqlDriver.majorVersion + override def getMajorVersion: Int = KsqlDriver.driverMajorVersion override def getParentLogger: Logger = throw NotSupported("getParentLogger") diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala index 3d4e709..848e61f 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala @@ -211,6 +211,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = e.getName, e.getPath, e.getVersion, + e.getType.name, f.getDescription, f.getReturnType, f.getArguments.asScala.map(arg => s"${arg.getName}:${arg.getType}").mkString(", ") diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala index 9578e1d..b2db8b3 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala @@ -14,16 +14,15 @@ import org.scalatest.{Matchers, OneInstancePerTest, WordSpec} class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory with OneInstancePerTest { - val implementedMethods = Seq("getDatabaseProductName", "getDatabaseMajorVersion", "getDatabaseMinorVersion", - "getDatabaseProductVersion", "getDriverName", "getDriverVersion", "getDriverMajorVersion", - "getDriverMinorVersion", "getJDBCMajorVersion", "getJDBCMinorVersion", "getConnection", "getCatalogs", - "getTableTypes", "getTables", "getSchemas", "getSuperTables", "getUDTs", "getColumns", "isReadOnly", - "supportsMultipleResultSets") - "A KsqlDatabaseMetaData" when { + val implementedMethods = Seq("getDatabaseProductName", "getDatabaseMajorVersion", "getDatabaseMinorVersion", + "getDatabaseProductVersion", "getDriverName", "getDriverVersion", "getDriverMajorVersion", + "getDriverMinorVersion", "getJDBCMajorVersion", "getJDBCMinorVersion", "getConnection", "getCatalogs", + "getTableTypes", "getTables", "getSchemas", "getSuperTables", "getUDTs", "getColumns", "isReadOnly", + "supportsMultipleResultSets", "getSQLKeywords", "getProcedures", "supportsCatalogsInTableDefinitions") val mockResponse = mock[Response] - (mockResponse.getEntity _).expects.returns(mock[InputStream]) + (mockResponse.getEntity _).expects.returns(mock[InputStream]).noMoreThanOnce val mockKsqlRestClient = mock[MockableKsqlRestClient] @@ -120,7 +119,27 @@ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory w } metadata.supportsMultipleResultSets should be(false) + metadata.getSQLKeywords.split(",").length should be(17) + metadata.getProcedures(None.orNull, None.orNull, None.orNull).next should be(false) + metadata.supportsCatalogsInTableDefinitions should be(false) + + } + } + } + + "A DatabaseMetaDataNotSupported" when { + + "validating specs" should { + "throw not supported exception if not supported" in { + + val metadata = new DatabaseMetaDataNotSupported + reflectMethods[DatabaseMetaDataNotSupported](Seq.empty, false, metadata) + .foreach(method => { + assertThrows[SQLFeatureNotSupportedException] { + method() + } + }) } } } From 5ca59f8a568c5112ea53fa17c1b7222f0959689b Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sun, 10 Feb 2019 13:19:45 -0600 Subject: [PATCH 29/47] More support for JDBC spec --- .../com/github/mmolimar/ksql/jdbc/KsqlConnection.scala | 4 ++-- .../github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala | 2 ++ .../com/github/mmolimar/ksql/jdbc/KsqlConnectionSpec.scala | 6 +++++- .../mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala | 5 ++++- .../com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala | 4 ++-- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala index d598962..0e24ffb 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala @@ -122,7 +122,7 @@ class KsqlConnection(values: KsqlConnectionValues, properties: Properties) exten override def setTypeMap(map: util.Map[String, Class[_]]): Unit = throw NotSupported("setTypeMap") - override def getCatalog: String = throw NotSupported("getCatalog") + override def getCatalog: String = None.orNull override def createClob: Clob = throw NotSupported("createClob") @@ -140,7 +140,7 @@ class KsqlConnection(values: KsqlConnectionValues, properties: Properties) exten override def createArrayOf(typeName: String, elements: scala.Array[AnyRef]): Array = throw NotSupported("createArrayOf") - override def setCatalog(catalog: String): Unit = throw NotSupported("setCatalog") + override def setCatalog(catalog: String): Unit = {} override def close: Unit = ksqlClient.close diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala index 4a4112e..7ab45d7 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala @@ -550,6 +550,8 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def supportsMultipleResultSets: Boolean = false + override def supportsSchemasInDataManipulation: Boolean = false + private def validateCatalogAndSchema(catalog: String, schema: String) = { if (catalog != null && catalog != "") throw UnknownCatalog(s"Unknown catalog $catalog") if (schema != null && schema != "") throw UnknownSchema(s"Unknown schema $schema") diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlConnectionSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlConnectionSpec.scala index 451ed00..202aa43 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlConnectionSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlConnectionSpec.scala @@ -13,7 +13,8 @@ import org.scalatest.{Matchers, WordSpec} class KsqlConnectionSpec extends WordSpec with Matchers with MockFactory { val implementedMethods = Seq("createStatement", "getAutoCommit", "getTransactionIsolation", - "setClientInfo", "isReadOnly", "isValid", "close", "getMetaData", "getWarnings", "setAutoCommit") + "setClientInfo", "isReadOnly", "isValid", "close", "getMetaData", "getWarnings", "setAutoCommit", + "getCatalog", "setCatalog") "A KsqlConnection" when { @@ -63,6 +64,9 @@ class KsqlConnectionSpec extends WordSpec with Matchers with MockFactory { ksqlConnection.setAutoCommit(false) ksqlConnection.getAutoCommit should be(false) ksqlConnection.getWarnings should be(None.orNull) + ksqlConnection.getCatalog should be(None.orNull) + ksqlConnection.setCatalog("test") + ksqlConnection.getCatalog should be(None.orNull) (ksqlRestClient.close _).expects ksqlConnection.close diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala index b2db8b3..11f28a3 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala @@ -19,7 +19,8 @@ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory w "getDatabaseProductVersion", "getDriverName", "getDriverVersion", "getDriverMajorVersion", "getDriverMinorVersion", "getJDBCMajorVersion", "getJDBCMinorVersion", "getConnection", "getCatalogs", "getTableTypes", "getTables", "getSchemas", "getSuperTables", "getUDTs", "getColumns", "isReadOnly", - "supportsMultipleResultSets", "getSQLKeywords", "getProcedures", "supportsCatalogsInTableDefinitions") + "supportsMultipleResultSets", "getSQLKeywords", "getProcedures", "supportsCatalogsInTableDefinitions", + "supportsSchemasInDataManipulation") val mockResponse = mock[Response] (mockResponse.getEntity _).expects.returns(mock[InputStream]).noMoreThanOnce @@ -122,6 +123,8 @@ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory w metadata.getSQLKeywords.split(",").length should be(17) metadata.getProcedures(None.orNull, None.orNull, None.orNull).next should be(false) metadata.supportsCatalogsInTableDefinitions should be(false) + metadata.supportsSchemasInDataManipulation should be(false) + metadata.supportsSchemasInDataManipulation should be(false) } } diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala index 5583ae6..8d061a4 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala @@ -21,8 +21,8 @@ class KsqlDriverSpec extends WordSpec with Matchers with MockFactory { driver.jdbcCompliant should be(false) } "have a major and minor version" in { - driver.getMinorVersion should be(0) - driver.getMajorVersion should be(1) + driver.getMinorVersion should be(3) + driver.getMajorVersion should be(0) } "have no properties" in { driver.getPropertyInfo("", new Properties).length should be(0) From 3888d6cdf973017386b04c679c01dd0572ee5224 Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sun, 10 Feb 2019 14:36:32 -0600 Subject: [PATCH 30/47] Type info implementation for database metadata resulset --- .../github/mmolimar/ksql/jdbc/Headers.scala | 37 +++- .../ksql/jdbc/KsqlDatabaseMetaData.scala | 184 +++++++++++++++++- .../ksql/jdbc/KsqlDatabaseMetaDataSpec.scala | 15 +- 3 files changed, 219 insertions(+), 17 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala index 99ca901..21d56a7 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala @@ -85,15 +85,36 @@ object DatabaseMetadataHeaders { ) val procedures = List( - HeaderField("PROCEDURE_CAT", Types.CHAR, 255), - HeaderField("PROCEDURE_SCHEM", Types.CHAR, 255), - HeaderField("PROCEDURE_NAME", Types.CHAR, 255), - HeaderField("reserved1", Types.CHAR, 0), - HeaderField("reserved2", Types.CHAR, 0), - HeaderField("reserved3", Types.CHAR, 0), - HeaderField("REMARKS", Types.CHAR, 255), + HeaderField("PROCEDURE_CAT", Types.VARCHAR, 255), + HeaderField("PROCEDURE_SCHEM", Types.VARCHAR, 255), + HeaderField("PROCEDURE_NAME", Types.VARCHAR, 255), + HeaderField("reserved1", Types.VARCHAR, 0), + HeaderField("reserved2", Types.VARCHAR, 0), + HeaderField("reserved3", Types.VARCHAR, 0), + HeaderField("REMARKS", Types.VARCHAR, 255), HeaderField("PROCEDURE_TYPE", Types.SMALLINT, 6), - HeaderField("SPECIFIC_NAME", Types.CHAR, 255) + HeaderField("SPECIFIC_NAME", Types.VARCHAR, 255) + ) + + val typeInfo = List( + HeaderField("TYPE_NAME", Types.VARCHAR, 32), + HeaderField("DATA_TYPE", Types.INTEGER, 5), + HeaderField("PRECISION", Types.INTEGER, 10), + HeaderField("LITERAL_PREFIX", Types.VARCHAR, 4), + HeaderField("LITERAL_SUFFIX", Types.VARCHAR, 4), + HeaderField("CREATE_PARAMS", Types.VARCHAR, 32), + HeaderField("NULLABLE", Types.SMALLINT, 5), + HeaderField("CASE_SENSITIVE", Types.BOOLEAN, 3), + HeaderField("SEARCHABLE", Types.SMALLINT, 3), + HeaderField("UNSIGNED_ATTRIBUTE", Types.BOOLEAN, 3), + HeaderField("FIXED_PREC_SCALE", Types.BOOLEAN, 3), + HeaderField("AUTO_INCREMENT", Types.BOOLEAN, 3), + HeaderField("LOCAL_TYPE_NAME", Types.VARCHAR, 32), + HeaderField("MINIMUM_SCALE", Types.SMALLINT, 5), + HeaderField("MAXIMUM_SCALE", Types.SMALLINT, 5), + HeaderField("SQL_DATA_TYPE", Types.INTEGER, 10), + HeaderField("SQL_DATETIME_SUB", Types.INTEGER, 10), + HeaderField("NUM_PREC_RADIX", Types.INTEGER, 10) ) def mapDataType(dataType: String): Int = dataType match { diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala index 7ab45d7..af22a69 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala @@ -437,6 +437,10 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D "SHOW TOPICS", "SHOW FUNCTIONS", "SHOW STREAMS", "SHOW TABLES", "SHOW QUERIES", "SHOW PROPERTIES", "TERMINATE").mkString(",") + override def getMaxStatements: Int = 0 + + override def getMaxStatementLength: Int = 0 + override def isReadOnly: Boolean = true override def getProcedures(catalog: String, schemaPattern: String, procedureNamePattern: String): ResultSet = { @@ -445,6 +449,8 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def getCatalogs: ResultSet = new IteratorResultSet(DatabaseMetadataHeaders.catalogs, 0, Iterator.empty) + override def getConnection: Connection = ksqlConnection + override def getSuperTables(catalog: String, schemaPattern: String, tableNamePattern: String): ResultSet = { validateCatalogAndSchema(catalog, schemaPattern) @@ -498,7 +504,171 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def getTableTypes: ResultSet = new IteratorResultSet(DatabaseMetadataHeaders.tableTypes, 0, Iterator(Seq(TableTypes.TABLE.name), Seq(TableTypes.STREAM.name))) - override def getConnection: Connection = ksqlConnection + override def getTypeInfo: ResultSet = { + val booleanType = Seq( + "BOOLEAN", + Types.BOOLEAN, + 3L, + "", + "", + "", + DatabaseMetaData.typeNullable, + Boolean.box(false), + DatabaseMetaData.typeSearchable, + Boolean.box(false), + Boolean.box(false), + Boolean.box(false), + "BOOLEAN", + 0, + 0, + Types.BOOLEAN, + 0, + 10 + ) + val integerType = Seq( + "INTEGER", + Types.INTEGER, + 10L, + "", + "", + "", + DatabaseMetaData.typeNullable, + Boolean.box(false), + DatabaseMetaData.typeSearchable, + Boolean.box(true), + Boolean.box(false), + Boolean.box(false), + "INTEGER", + 0, + 0, + Types.INTEGER, + 0, + 10 + ) + val bitIntType = Seq( + "BIGINT", + Types.BIGINT, + 19L, + "", + "", + "", + DatabaseMetaData.typeNullable, + Boolean.box(false), + DatabaseMetaData.typeSearchable, + Boolean.box(true), + Boolean.box(false), + Boolean.box(false), + "BIGINT", + 0, + 0, + Types.BIGINT, + 0, + 10 + ) + val doubleType = Seq( + "DOUBLE", + Types.DOUBLE, + 22L, + "", + "", + "", + DatabaseMetaData.typeNullable, + Boolean.box(false), + DatabaseMetaData.typeSearchable, + Boolean.box(true), + Boolean.box(false), + Boolean.box(false), + "DOUBLE", + -308, + 308, + Types.DOUBLE, + 0, + 10 + ) + val varcharType = Seq( + "VARCHAR", + Types.VARCHAR, + 65535L, + "", + "", + "", + DatabaseMetaData.typeNullable, + Boolean.box(false), + DatabaseMetaData.typeSearchable, + Boolean.box(false), + Boolean.box(false), + Boolean.box(false), + "STRING", + 0, + 0, + Types.VARCHAR, + 0, + 10 + ) + val arrayType = Seq( + "ARRAY", + Types.ARRAY, + 65535L, + "", + "", + "", + DatabaseMetaData.typeNullable, + Boolean.box(false), + DatabaseMetaData.typeSearchable, + Boolean.box(false), + Boolean.box(false), + Boolean.box(false), + "ARRAY", + 0, + 0, + Types.ARRAY, + 0, + 10 + ) + val mapType = Seq( + "JAVA_OBJECT", + Types.JAVA_OBJECT, + 65535L, + "", + "", + "", + DatabaseMetaData.typeNullable, + Boolean.box(false), + DatabaseMetaData.typeSearchable, + Boolean.box(false), + Boolean.box(false), + Boolean.box(false), + "ARRAY", + 0, + 0, + Types.JAVA_OBJECT, + 0, + 10 + ) + val structType = Seq( + "STRUCT", + Types.STRUCT, + 65535L, + "", + "", + "", + DatabaseMetaData.typeNullable, + Boolean.box(false), + DatabaseMetaData.typeSearchable, + Boolean.box(false), + Boolean.box(false), + Boolean.box(false), + "STRUCT", + 0, + 0, + Types.STRUCT, + 0, + 10 + ) + val typeIterator: Iterator[Seq[Any]] = Iterator(booleanType, integerType, bitIntType, doubleType, varcharType, + arrayType, mapType, structType) + new IteratorResultSet(DatabaseMetadataHeaders.typeInfo, 0, typeIterator) + } override def getColumns(catalog: String, schemaPattern: String, tableNamePattern: String, columnNamePattern: String): ResultSet = { @@ -509,7 +679,7 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D if (Option(columnNamePattern).getOrElse("").equals("")) ".*" else columnNamePattern }.toUpperCase.r.pattern - var tableSchemas: Iterator[Seq[AnyRef]] = Iterator.empty + var columns: Iterator[Seq[AnyRef]] = Iterator.empty while (tables.next) { val tableName = tables.getString(3) val describe = ksqlConnection.executeKsqlCommand(s"DESCRIBE $tableName;") @@ -530,9 +700,9 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D "", "", Int.box(-1), Int.box(-1), Int.box(32), Int.box(17), "", "", "", "", Int.box(Types.VARCHAR), "YES", "YES")) } - tableSchemas ++= defaultFields + columns ++= defaultFields - tableSchemas ++= describe.getResponse.asScala.map(_.asInstanceOf[SourceDescriptionEntity]) + columns ++= describe.getResponse.asScala.map(_.asInstanceOf[SourceDescriptionEntity]) .map(_.getSourceDescription) .filter(sd => columnPattern.matcher(sd.getName.toUpperCase).matches) .map(sd => { @@ -543,15 +713,19 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D }).toIterator } - new IteratorResultSet(DatabaseMetadataHeaders.columns, 0, tableSchemas) + new IteratorResultSet(DatabaseMetadataHeaders.columns, 0, columns) } + override def supportsCatalogsInDataManipulation: Boolean = false + override def supportsCatalogsInTableDefinitions: Boolean = false override def supportsMultipleResultSets: Boolean = false override def supportsSchemasInDataManipulation: Boolean = false + override def supportsSchemasInTableDefinitions: Boolean = false + private def validateCatalogAndSchema(catalog: String, schema: String) = { if (catalog != null && catalog != "") throw UnknownCatalog(s"Unknown catalog $catalog") if (schema != null && schema != "") throw UnknownSchema(s"Unknown schema $schema") diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala index 11f28a3..be79330 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala @@ -18,9 +18,11 @@ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory w val implementedMethods = Seq("getDatabaseProductName", "getDatabaseMajorVersion", "getDatabaseMinorVersion", "getDatabaseProductVersion", "getDriverName", "getDriverVersion", "getDriverMajorVersion", "getDriverMinorVersion", "getJDBCMajorVersion", "getJDBCMinorVersion", "getConnection", "getCatalogs", - "getTableTypes", "getTables", "getSchemas", "getSuperTables", "getUDTs", "getColumns", "isReadOnly", - "supportsMultipleResultSets", "getSQLKeywords", "getProcedures", "supportsCatalogsInTableDefinitions", - "supportsSchemasInDataManipulation") + "getMaxStatements", "getMaxStatementLength", "getTableTypes", "getTables", "getTypeInfo", "getSchemas", + "getSuperTables", "getUDTs", "getColumns", "isReadOnly", "getSQLKeywords", "getProcedures", + "supportsCatalogsInDataManipulation", "supportsCatalogsInTableDefinitions", "supportsMultipleResultSets", + "supportsSchemasInDataManipulation", "supportsSchemasInTableDefinitions" + ) val mockResponse = mock[Response] (mockResponse.getEntity _).expects.returns(mock[InputStream]).noMoreThanOnce @@ -119,12 +121,17 @@ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory w metadata.getColumns("", "test", "test", "test") } + metadata.getTypeInfo.getMetaData.getColumnCount should be(18) metadata.supportsMultipleResultSets should be(false) metadata.getSQLKeywords.split(",").length should be(17) + metadata.getMaxStatements should be(0) + metadata.getMaxStatementLength should be(0) metadata.getProcedures(None.orNull, None.orNull, None.orNull).next should be(false) + metadata.supportsCatalogsInDataManipulation should be(false) metadata.supportsCatalogsInTableDefinitions should be(false) + metadata.supportsMultipleResultSets should be(false) metadata.supportsSchemasInDataManipulation should be(false) - metadata.supportsSchemasInDataManipulation should be(false) + metadata.supportsSchemasInTableDefinitions should be(false) } } From d8b4a1c3e62ddf4aff8c1214f2546525848f0737 Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sun, 10 Feb 2019 16:12:52 -0600 Subject: [PATCH 31/47] Extra info for database metadata resultset --- .../mmolimar/ksql/jdbc/KsqlConnection.scala | 14 +++++-- .../ksql/jdbc/KsqlDatabaseMetaData.scala | 16 ++++++++ .../ksql/jdbc/KsqlDatabaseMetaDataSpec.scala | 17 +++++++-- .../mmolimar/ksql/jdbc/KsqlDriverSpec.scala | 38 +++++++++++++------ 4 files changed, 67 insertions(+), 18 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala index 0e24ffb..13379bd 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala @@ -14,11 +14,18 @@ import scala.util.{Failure, Success, Try} case class KsqlConnectionValues(ksqlServer: String, port: Int, config: Map[String, String]) { - def getKsqlUrl: String = { + def ksqlUrl: String = { val protocol = if (isSecured) "https://" else "http://" protocol + ksqlServer + ":" + port } + def jdbcUrl: String = { + val suffix = if (config.isEmpty) "" else "?" + s"${KsqlDriver.ksqlPrefix}$ksqlServer:$port$suffix${ + config.map(c => s"${c._1}=${c._2}").mkString("&") + }" + } + def isSecured: Boolean = config.getOrElse("secured", "false").toBoolean def properties: Boolean = config.getOrElse("properties", "false").toBoolean @@ -27,7 +34,8 @@ case class KsqlConnectionValues(ksqlServer: String, port: Int, config: Map[Strin } -class KsqlConnection(values: KsqlConnectionValues, properties: Properties) extends Connection with WrapperNotSupported { +class KsqlConnection(private[jdbc] val values: KsqlConnectionValues, properties: Properties) + extends Connection with WrapperNotSupported { private val ksqlClient = init @@ -37,7 +45,7 @@ class KsqlConnection(values: KsqlConnectionValues, properties: Properties) exten } else { Collections.emptyMap[String, AnyRef] } - new KsqlRestClient(values.getKsqlUrl, props) + new KsqlRestClient(values.ksqlUrl, props) } private[jdbc] def validate: Unit = { diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala index af22a69..d7be021 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala @@ -441,6 +441,8 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def getMaxStatementLength: Int = 0 + override def getURL: String = ksqlConnection.values.jdbcUrl + override def isReadOnly: Boolean = true override def getProcedures(catalog: String, schemaPattern: String, procedureNamePattern: String): ResultSet = { @@ -716,16 +718,30 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D new IteratorResultSet(DatabaseMetadataHeaders.columns, 0, columns) } + override def supportsAlterTableWithAddColumn: Boolean = false + + override def supportsAlterTableWithDropColumn: Boolean = false + override def supportsCatalogsInDataManipulation: Boolean = false override def supportsCatalogsInTableDefinitions: Boolean = false + override def supportsCatalogsInProcedureCalls: Boolean = false + override def supportsMultipleResultSets: Boolean = false + override def supportsMultipleTransactions: Boolean = false + + override def supportsSavepoints: Boolean = false + override def supportsSchemasInDataManipulation: Boolean = false override def supportsSchemasInTableDefinitions: Boolean = false + override def supportsStoredFunctionsUsingCallSyntax: Boolean = true + + override def supportsStoredProcedures: Boolean = false + private def validateCatalogAndSchema(catalog: String, schema: String) = { if (catalog != null && catalog != "") throw UnknownCatalog(s"Unknown catalog $catalog") if (schema != null && schema != "") throw UnknownSchema(s"Unknown schema $schema") diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala index be79330..d4fcdf1 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala @@ -19,9 +19,12 @@ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory w "getDatabaseProductVersion", "getDriverName", "getDriverVersion", "getDriverMajorVersion", "getDriverMinorVersion", "getJDBCMajorVersion", "getJDBCMinorVersion", "getConnection", "getCatalogs", "getMaxStatements", "getMaxStatementLength", "getTableTypes", "getTables", "getTypeInfo", "getSchemas", - "getSuperTables", "getUDTs", "getColumns", "isReadOnly", "getSQLKeywords", "getProcedures", - "supportsCatalogsInDataManipulation", "supportsCatalogsInTableDefinitions", "supportsMultipleResultSets", - "supportsSchemasInDataManipulation", "supportsSchemasInTableDefinitions" + "getSuperTables", "getUDTs", "getColumns", "getURL", "isReadOnly", "getSQLKeywords", "getProcedures", + "supportsAlterTableWithAddColumn", "supportsAlterTableWithDropColumn", + "supportsCatalogsInDataManipulation", "supportsCatalogsInTableDefinitions", "supportsCatalogsInProcedureCalls", + "supportsMultipleResultSets", "supportsMultipleTransactions", "supportsSavepoints", + "supportsSchemasInDataManipulation", "supportsSchemasInTableDefinitions", + "supportsStoredFunctionsUsingCallSyntax", "supportsStoredProcedures" ) val mockResponse = mock[Response] @@ -121,17 +124,25 @@ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory w metadata.getColumns("", "test", "test", "test") } + metadata.getURL should be("jdbc:ksql://localhost:8080") metadata.getTypeInfo.getMetaData.getColumnCount should be(18) metadata.supportsMultipleResultSets should be(false) metadata.getSQLKeywords.split(",").length should be(17) metadata.getMaxStatements should be(0) metadata.getMaxStatementLength should be(0) metadata.getProcedures(None.orNull, None.orNull, None.orNull).next should be(false) + metadata.supportsAlterTableWithAddColumn should be(false) + metadata.supportsAlterTableWithDropColumn should be(false) metadata.supportsCatalogsInDataManipulation should be(false) metadata.supportsCatalogsInTableDefinitions should be(false) + metadata.supportsCatalogsInProcedureCalls should be(false) metadata.supportsMultipleResultSets should be(false) + metadata.supportsMultipleTransactions should be(false) metadata.supportsSchemasInDataManipulation should be(false) metadata.supportsSchemasInTableDefinitions should be(false) + metadata.supportsStoredFunctionsUsingCallSyntax should be(true) + metadata.supportsStoredProcedures should be(false) + metadata.supportsSavepoints should be(false) } } diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala index 8d061a4..d45e713 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala @@ -102,63 +102,76 @@ class KsqlDriverSpec extends WordSpec with Matchers with MockFactory { "return the URL parsed properly" in { val ksqlServer = "ksql-server" val ksqlPort = 8080 + val ksqlUrl = s"http://${ksqlServer}:${ksqlPort}" + val ksqlUrlSecured = s"https://${ksqlServer}:${ksqlPort}" - var connectionValues = KsqlDriver.parseUrl(s"jdbc:ksql://${ksqlServer}:${ksqlPort}") + var url = s"jdbc:ksql://${ksqlServer}:${ksqlPort}" + var connectionValues = KsqlDriver.parseUrl(url) connectionValues.ksqlServer should be(ksqlServer) connectionValues.port should be(ksqlPort) connectionValues.config.isEmpty should be(true) - connectionValues.getKsqlUrl should be(s"http://${ksqlServer}:${ksqlPort}") + connectionValues.ksqlUrl should be(ksqlUrl) + connectionValues.jdbcUrl should be(url) connectionValues.isSecured should be(false) connectionValues.properties should be(false) connectionValues.timeout should be(0) - connectionValues = KsqlDriver.parseUrl(s"jdbc:ksql://${ksqlServer}:${ksqlPort}?prop1=value1") + url = s"jdbc:ksql://${ksqlServer}:${ksqlPort}?prop1=value1" + connectionValues = KsqlDriver.parseUrl(url) connectionValues.ksqlServer should be(ksqlServer) connectionValues.port should be(ksqlPort) connectionValues.config.size should be(1) connectionValues.config.get("prop1").get should be("value1") - connectionValues.getKsqlUrl should be(s"http://${ksqlServer}:${ksqlPort}") + connectionValues.ksqlUrl should be(ksqlUrl) + connectionValues.jdbcUrl should be(url) connectionValues.isSecured should be(false) connectionValues.properties should be(false) connectionValues.timeout should be(0) - connectionValues = KsqlDriver.parseUrl(s"jdbc:ksql://${ksqlServer}:${ksqlPort}?prop1=value1&secured=true&prop2=value2") + url = s"jdbc:ksql://${ksqlServer}:${ksqlPort}?prop1=value1&secured=true&prop2=value2" + connectionValues = KsqlDriver.parseUrl(url) connectionValues.ksqlServer should be(ksqlServer) connectionValues.port should be(ksqlPort) connectionValues.config.size should be(3) connectionValues.config.get("prop1").get should be("value1") connectionValues.config.get("prop2").get should be("value2") connectionValues.config.get("secured").get should be("true") - connectionValues.getKsqlUrl should be(s"https://${ksqlServer}:${ksqlPort}") + connectionValues.ksqlUrl should be(ksqlUrlSecured) + connectionValues.jdbcUrl should be(url) connectionValues.isSecured should be(true) connectionValues.properties should be(false) connectionValues.timeout should be(0) - connectionValues = KsqlDriver.parseUrl(s"jdbc:ksql://${ksqlServer}:${ksqlPort}?prop1=value1&timeout=100&prop2=value2") + url = s"jdbc:ksql://${ksqlServer}:${ksqlPort}?prop1=value1&timeout=100&prop2=value2" + connectionValues = KsqlDriver.parseUrl(url) connectionValues.ksqlServer should be(ksqlServer) connectionValues.port should be(ksqlPort) connectionValues.config.size should be(3) connectionValues.config.get("prop1").get should be("value1") connectionValues.config.get("prop2").get should be("value2") connectionValues.config.get("timeout").get should be("100") - connectionValues.getKsqlUrl should be(s"http://${ksqlServer}:${ksqlPort}") + connectionValues.ksqlUrl should be(ksqlUrl) + connectionValues.jdbcUrl should be(url) connectionValues.isSecured should be(false) connectionValues.properties should be(false) connectionValues.timeout should be(100) - connectionValues = KsqlDriver.parseUrl(s"jdbc:ksql://${ksqlServer}:${ksqlPort}?prop1=value1&properties=true&prop2=value2") + url = s"jdbc:ksql://${ksqlServer}:${ksqlPort}?prop1=value1&properties=true&prop2=value2" + connectionValues = KsqlDriver.parseUrl(url) connectionValues.ksqlServer should be(ksqlServer) connectionValues.port should be(ksqlPort) connectionValues.config.size should be(3) connectionValues.config.get("prop1").get should be("value1") connectionValues.config.get("prop2").get should be("value2") connectionValues.config.get("properties").get should be("true") - connectionValues.getKsqlUrl should be(s"http://${ksqlServer}:${ksqlPort}") + connectionValues.ksqlUrl should be(ksqlUrl) + connectionValues.jdbcUrl should be(url) connectionValues.isSecured should be(false) connectionValues.properties should be(true) connectionValues.timeout should be(0) - connectionValues = KsqlDriver.parseUrl(s"jdbc:ksql://${ksqlServer}:${ksqlPort}?timeout=100&secured=true&properties=true&prop1=value1") + url = s"jdbc:ksql://${ksqlServer}:${ksqlPort}?timeout=100&secured=true&properties=true&prop1=value1" + connectionValues = KsqlDriver.parseUrl(url) connectionValues.ksqlServer should be(ksqlServer) connectionValues.port should be(ksqlPort) connectionValues.config.size should be(4) @@ -166,7 +179,8 @@ class KsqlDriverSpec extends WordSpec with Matchers with MockFactory { connectionValues.config.get("timeout").get should be("100") connectionValues.config.get("secured").get should be("true") connectionValues.config.get("properties").get should be("true") - connectionValues.getKsqlUrl should be(s"https://${ksqlServer}:${ksqlPort}") + connectionValues.ksqlUrl should be(ksqlUrlSecured) + connectionValues.jdbcUrl should be(url) connectionValues.isSecured should be(true) connectionValues.properties should be(true) connectionValues.timeout should be(100) From f2886e77a1cd40410b2503451b03c9a0ebab62ac Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Mon, 11 Feb 2019 00:25:21 -0600 Subject: [PATCH 32/47] Enable listing SQL functions in database metadata --- .../github/mmolimar/ksql/jdbc/Headers.scala | 10 -- .../ksql/jdbc/KsqlDatabaseMetaData.scala | 39 +++++++- .../github/mmolimar/ksql/jdbc/package.scala | 28 ++++++ .../ksql/jdbc/KsqlDatabaseMetaDataSpec.scala | 96 +++++++++++++------ .../ksql/jdbc/KsqlStatementSpec.scala | 2 +- 5 files changed, 131 insertions(+), 44 deletions(-) create mode 100644 src/main/scala/com/github/mmolimar/ksql/jdbc/package.scala diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala index 21d56a7..77742c0 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala @@ -16,16 +16,6 @@ object HeaderField { } } -private object implicits { - - implicit def toIndexedMap(headers: List[HeaderField]): Map[Int, HeaderField] = { - headers.zipWithIndex.map { case (header, index) => { - HeaderField(header.name, header.label, header.jdbcType, header.length, index + 1) - } - }.map(h => h.index -> h).toMap - } -} - object DatabaseMetadataHeaders { val tableTypes = List(HeaderField("TABLE_TYPE", Types.VARCHAR, 0)) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala index d7be021..f328ec8 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala @@ -3,10 +3,13 @@ package com.github.mmolimar.ksql.jdbc import java.sql.{Connection, DatabaseMetaData, ResultSet, RowIdLifetime, Types} import com.github.mmolimar.ksql.jdbc.Exceptions._ +import com.github.mmolimar.ksql.jdbc.implicits.ResultSetStream import com.github.mmolimar.ksql.jdbc.resultset.IteratorResultSet import io.confluent.ksql.rest.entity.{SourceDescriptionEntity, StreamsList, TablesList} import scala.collection.JavaConverters._ +import scala.collection.mutable + object TableTypes { @@ -429,6 +432,12 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def getDatabaseMinorVersion: Int = KsqlDriver.driverMinorVersion + override def getCatalogTerm: String = "TOPIC" + + override def getSchemaTerm: String = "" + + override def getProcedureTerm: String = "" + override def getUDTs(catalog: String, schemaPattern: String, typeNamePattern: String, types: Array[Int]): ResultSet = new IteratorResultSet(List.empty[HeaderField], 0, Iterator.empty) @@ -682,8 +691,8 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D }.toUpperCase.r.pattern var columns: Iterator[Seq[AnyRef]] = Iterator.empty - while (tables.next) { - val tableName = tables.getString(3) + tables.toStream.foreach { table => + val tableName = table.getString(3) val describe = ksqlConnection.executeKsqlCommand(s"DESCRIBE $tableName;") if (describe.isErroneous) throw KsqlCommandError(s"Error describing table $tableName: " + describe.getErrorMessage.getMessage) @@ -718,6 +727,14 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D new IteratorResultSet(DatabaseMetadataHeaders.columns, 0, columns) } + override def getNumericFunctions: String = availableFunctions(None, Set("INT", "BIGINT", "DOUBLE")).mkString(",") + + override def getStringFunctions: String = availableFunctions(None, Set("VARCHAR", "STRING")).mkString(",") + + override def getSystemFunctions: String = availableFunctions(Some("Confluent")).mkString(",") + + override def getTimeDateFunctions: String = availableFunctions(None, Set.empty).mkString(",") + override def supportsAlterTableWithAddColumn: Boolean = false override def supportsAlterTableWithDropColumn: Boolean = false @@ -742,6 +759,24 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def supportsStoredProcedures: Boolean = false + private def availableFunctions(author: Option[String] = None, fnTypes: Set[String] = Set(".*")): Set[String] = { + var functions = mutable.Set.empty[String] + + (ksqlConnection.createStatement.executeQuery("LIST FUNCTIONS")).toStream.foreach { fn => + val fnName = fn.getString("FUNCTION_NAME_FN_NAME") + + ksqlConnection.createStatement.executeQuery(s"DESCRIBE FUNCTION $fnName").toStream.foreach { fnDesc => + val fnAuthor = fnDesc.getString("FUNCTION_DESCRIPTION_AUTHOR").trim.toUpperCase + val returnType = fnDesc.getString("FUNCTION_DESCRIPTION_FN_RETURN_TYPE") + if (fnTypes.filter(returnType.matches(_)).nonEmpty && + (author.isEmpty || author.get.toUpperCase == fnAuthor.toUpperCase)) { + functions += fnName + } + } + } + functions.toSet + } + private def validateCatalogAndSchema(catalog: String, schema: String) = { if (catalog != null && catalog != "") throw UnknownCatalog(s"Unknown catalog $catalog") if (schema != null && schema != "") throw UnknownSchema(s"Unknown schema $schema") diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/package.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/package.scala new file mode 100644 index 0000000..e1b8d06 --- /dev/null +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/package.scala @@ -0,0 +1,28 @@ +package com.github.mmolimar.ksql + +import java.sql.ResultSet + +package object jdbc { + + object implicits { + + implicit class ResultSetStream(resultSet: ResultSet) { + + def toStream: Stream[ResultSet] = new Iterator[ResultSet] { + + def hasNext = resultSet.next + + def next = resultSet + + }.toStream + } + + implicit def toIndexedMap(headers: List[HeaderField]): Map[Int, HeaderField] = { + headers.zipWithIndex.map { case (header, index) => { + HeaderField(header.name, header.label, header.jdbcType, header.length, index + 1) + } + }.map(h => h.index -> h).toMap + } + } + +} diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala index d4fcdf1..5f7eb2c 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala @@ -6,11 +6,13 @@ import java.util.{Collections, Properties} import com.github.mmolimar.ksql.jdbc.utils.TestUtils._ import io.confluent.ksql.rest.client.{KsqlRestClient, RestResponse} -import io.confluent.ksql.rest.entity.{KsqlEntityList, KsqlErrorMessage} +import io.confluent.ksql.rest.entity._ import javax.ws.rs.core.Response import org.scalamock.scalatest.MockFactory import org.scalatest.{Matchers, OneInstancePerTest, WordSpec} +import scala.collection.JavaConverters._ + class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory with OneInstancePerTest { @@ -18,9 +20,10 @@ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory w val implementedMethods = Seq("getDatabaseProductName", "getDatabaseMajorVersion", "getDatabaseMinorVersion", "getDatabaseProductVersion", "getDriverName", "getDriverVersion", "getDriverMajorVersion", "getDriverMinorVersion", "getJDBCMajorVersion", "getJDBCMinorVersion", "getConnection", "getCatalogs", - "getMaxStatements", "getMaxStatementLength", "getTableTypes", "getTables", "getTypeInfo", "getSchemas", - "getSuperTables", "getUDTs", "getColumns", "getURL", "isReadOnly", "getSQLKeywords", "getProcedures", - "supportsAlterTableWithAddColumn", "supportsAlterTableWithDropColumn", + "getCatalogTerm", "getMaxStatements", "getMaxStatementLength", "getTableTypes", "getTables", "getTypeInfo", + "getSchemas", "getSuperTables", "getUDTs", "getColumns", "getURL", "isReadOnly", "getSQLKeywords", "getProcedures", + "getNumericFunctions", "getSchemaTerm", "getStringFunctions", "getSystemFunctions", "getTimeDateFunctions", + "getProcedureTerm", "supportsAlterTableWithAddColumn", "supportsAlterTableWithDropColumn", "supportsCatalogsInDataManipulation", "supportsCatalogsInTableDefinitions", "supportsCatalogsInProcedureCalls", "supportsMultipleResultSets", "supportsMultipleTransactions", "supportsSavepoints", "supportsSchemasInDataManipulation", "supportsSchemasInTableDefinitions", @@ -28,20 +31,19 @@ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory w ) val mockResponse = mock[Response] - (mockResponse.getEntity _).expects.returns(mock[InputStream]).noMoreThanOnce - - val mockKsqlRestClient = mock[MockableKsqlRestClient] + val mockedKsqlRestClient = mock[MockableKsqlRestClient] val values = KsqlConnectionValues("localhost", 8080, Map.empty[String, String]) val ksqlConnection = new KsqlConnection(values, new Properties) { - override def init: KsqlRestClient = mockKsqlRestClient + override def init: KsqlRestClient = mockedKsqlRestClient } val metadata = new KsqlDatabaseMetaData(ksqlConnection) "validating specs" should { "throw not supported exception if not supported" in { - (mockKsqlRestClient.makeQueryRequest _).expects(*) + (mockResponse.getEntity _).expects.returns(mock[InputStream]).once + (mockedKsqlRestClient.makeQueryRequest _).expects(*) .returns(RestResponse.successful[KsqlRestClient.QueryStream](mockQueryStream(mockResponse))) .anyNumberOfTimes @@ -54,39 +56,80 @@ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory w } "work if implemented" in { + val specialMethods = Set("getTables", "getColumns", "getNumericFunctions", "getStringFunctions", + "getSystemFunctions", "getTimeDateFunctions") val methods = implementedMethods - .filter(!_.equals("getTables")) - .filter(!_.equals("getColumns")) + .filterNot(specialMethods.contains(_)) reflectMethods[KsqlDatabaseMetaData](methods, true, metadata) .foreach(method => { method() }) - (mockKsqlRestClient.makeQueryRequest _).expects(*) - .returns(RestResponse.successful[KsqlRestClient.QueryStream](mockQueryStream(mockResponse))) - .anyNumberOfTimes - - (mockKsqlRestClient.makeKsqlRequest _).expects(*) - .returns(RestResponse.erroneous(new KsqlErrorMessage(-1, "error message", Collections.emptyList[String]))) - .once - assertThrows[SQLException] { metadata.getTables("", "", "", Array[String]("test")) } + + (mockedKsqlRestClient.makeKsqlRequest _).expects(*) + .returns(RestResponse.erroneous(new KsqlErrorMessage(-1, "error message", Collections.emptyList[String]))) + .once assertThrows[SQLException] { metadata.getTables("", "", "", Array[String](TableTypes.TABLE.name)) } - (mockKsqlRestClient.makeKsqlRequest _).expects(*) + (mockedKsqlRestClient.makeKsqlRequest _).expects(*) .returns(RestResponse.successful[KsqlEntityList](new KsqlEntityList)) - .anyNumberOfTimes - + .twice metadata.getTables("", "", "[a-z]*", Array[String](TableTypes.TABLE.name, TableTypes.STREAM.name)).next should be(false) + (mockedKsqlRestClient.makeKsqlRequest _).expects(*) + .returns(RestResponse.successful[KsqlEntityList](new KsqlEntityList)) + .twice + metadata.getColumns("", "", "", "").next should be(false) + + assertThrows[SQLException] { + metadata.getColumns("test", "", "test", "test") + } + assertThrows[SQLException] { + metadata.getColumns("", "test", "test", "test") + } + + val fnList = new FunctionNameList( + "LIST FUNCTIONS;", + List(new SimpleFunctionInfo("TESTFN", FunctionType.scalar)).asJava + ) + val entityListFn = new KsqlEntityList + entityListFn.add(fnList) + (mockedKsqlRestClient.makeKsqlRequest _).expects("LIST FUNCTIONS;") + .returns(RestResponse.successful[KsqlEntityList](entityListFn)) + .repeat(4) + + val descFn = new FunctionDescriptionList("DESCRIBE FUNCTION test;", + "TESTFN", "Description", "Confluent", "version", "path", + List( + new FunctionInfo(List(new ArgumentInfo("arg1", "INT", "Description")).asJava, "BIGINT", "Description"), + new FunctionInfo(List(new ArgumentInfo("arg1", "INT", "Description")).asJava, "STRING", "Description") + + ).asJava, + FunctionType.scalar + ) + val entityDescribeFn = new KsqlEntityList + entityDescribeFn.add(descFn) + (mockedKsqlRestClient.makeKsqlRequest _).expects("DESCRIBE FUNCTION TESTFN;") + .returns(RestResponse.successful[KsqlEntityList](entityDescribeFn)) + .repeat(4) + + metadata.getNumericFunctions should be("TESTFN") + metadata.getStringFunctions should be("TESTFN") + metadata.getSystemFunctions should be("TESTFN") + metadata.getTimeDateFunctions should be("") + Option(metadata.getConnection) should not be (None) metadata.getCatalogs.next should be(false) + metadata.getCatalogTerm should be("TOPIC") + metadata.getSchemaTerm should be ("") + metadata.getProcedureTerm should be ("") val tableTypes = metadata.getTableTypes tableTypes.next should be(true) @@ -116,17 +159,8 @@ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory w metadata.getSuperTables("", "test", "test") } - metadata.getColumns("", "", "", "").next should be(false) - assertThrows[SQLException] { - metadata.getColumns("test", "", "test", "test") - } - assertThrows[SQLException] { - metadata.getColumns("", "test", "test", "test") - } - metadata.getURL should be("jdbc:ksql://localhost:8080") metadata.getTypeInfo.getMetaData.getColumnCount should be(18) - metadata.supportsMultipleResultSets should be(false) metadata.getSQLKeywords.split(",").length should be(17) metadata.getMaxStatements should be(0) metadata.getMaxStatementLength should be(0) diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala index e21166e..e7542bf 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala @@ -181,7 +181,7 @@ class KsqlStatementSpec extends WordSpec with Matchers with MockFactory with One val functionDescriptionList = new FunctionDescriptionList("DESCRIBE FUNCTION test;", "TEST", "Description", "author", "version", "path", List( - new FunctionInfo(List(new ArgumentInfo("arg1", "INT", "Description")).asJava, "LONG", "Description") + new FunctionInfo(List(new ArgumentInfo("arg1", "INT", "Description")).asJava, "BIGINT", "Description") ).asJava, FunctionType.scalar ) From bd4835ddc899bac3622bd539dca6ba78a5b2999a Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Tue, 12 Feb 2019 22:10:56 -0600 Subject: [PATCH 33/47] Upgrade to KSQL 5.1.1 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 1775686..b6f8530 100644 --- a/build.sbt +++ b/build.sbt @@ -12,7 +12,7 @@ resolvers += "Confluent Maven Repo" at "http://packages.confluent.io/maven/" resolvers += "Confluent Snapshots Maven Repo" at "https://s3-us-west-2.amazonaws.com/confluent-snapshots/" resolvers += Resolver.mavenLocal -libraryDependencies += "io.confluent.ksql" % "ksql-rest-app" % "5.1.0" +libraryDependencies += "io.confluent.ksql" % "ksql-rest-app" % "5.1.1" libraryDependencies += "org.apache.kafka" %% "kafka" % "2.1.0" % "test" libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % "test" libraryDependencies += "org.scalamock" %% "scalamock-scalatest-support" % "3.6.0" % "test" From 330b2904491f16faa0c726e7333c932db7faf1bc Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Fri, 15 Feb 2019 22:11:15 -0600 Subject: [PATCH 34/47] Result set to print topic request --- .../github/mmolimar/ksql/jdbc/Headers.scala | 10 +++-- .../mmolimar/ksql/jdbc/KsqlStatement.scala | 34 +++++++++++------ .../ksql/jdbc/resultset/KsqlResultSet.scala | 31 ++++++++++++++-- .../resultset/KsqlResultSetMetaData.scala | 6 +-- .../resultset/KsqlResultSetMetaDataSpec.scala | 30 +++++++-------- .../jdbc/resultset/KsqlResultSetSpec.scala | 37 ++++++++++++++++++- 6 files changed, 108 insertions(+), 40 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala index 77742c0..53b81b7 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala @@ -131,7 +131,7 @@ object KsqlEntityHeaders { ) val executionPlanEntity = List( - HeaderField("EXECUTION_PLAN", Types.VARCHAR, 256) + HeaderField("EXECUTION_PLAN", Types.VARCHAR, 255) ) val functionDescriptionListEntity = List( @@ -181,8 +181,8 @@ object KsqlEntityHeaders { HeaderField("QUERY_DESCRIPTION_FIELDS", Types.VARCHAR, 128), HeaderField("QUERY_DESCRIPTION_SOURCES", Types.VARCHAR, 32), HeaderField("QUERY_DESCRIPTION_SINKS", Types.VARCHAR, 32), - HeaderField("QUERY_DESCRIPTION_TOPOLOGY", Types.VARCHAR, 256), - HeaderField("QUERY_DESCRIPTION_EXECUTION_PLAN", Types.VARCHAR, 256) + HeaderField("QUERY_DESCRIPTION_TOPOLOGY", Types.VARCHAR, 255), + HeaderField("QUERY_DESCRIPTION_EXECUTION_PLAN", Types.VARCHAR, 255) ) val queryDescriptionEntityList = queryDescriptionEntity @@ -222,4 +222,8 @@ object KsqlEntityHeaders { HeaderField("TOPIC_DESCRIPTION_SCHEMA_STRING", Types.BOOLEAN, 64) ) + val printTopic = List( + HeaderField("PRINT_TOPIC", Types.VARCHAR, 255) + ) + } diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala index 848e61f..de6acb3 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala @@ -1,5 +1,6 @@ package com.github.mmolimar.ksql.jdbc +import java.io.InputStream import java.sql.{Connection, ResultSet, SQLWarning, Statement, Types} import com.github.mmolimar.ksql.jdbc.Exceptions._ @@ -63,7 +64,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = val stmt = Try(ksqlParser.getStatements(fixedSql)) match { case Failure(e) => throw KsqlQueryError(s"Error parsing query '$fixedSql': ${e.getMessage}.", e) case Success(s) if s.size() != 1 => throw KsqlQueryError("You have to execute just one query at a time. " + - s"Number of queries sent: ${s.size}.") + s"Number of queries sent: '${s.size}'.") case Success(s) => s.get(0) } @@ -71,7 +72,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = case "SELECT" => ksqlClient.makeQueryRequest(fixedSql).asInstanceOf[RestResponse[AnyRef]] case "PRINT" => - ksqlClient.makeQueryRequest(fixedSql).asInstanceOf[RestResponse[AnyRef]] + ksqlClient.makePrintTopicRequest(fixedSql).asInstanceOf[RestResponse[AnyRef]] case _ => ksqlClient.makeKsqlRequest(fixedSql).asInstanceOf[RestResponse[AnyRef]] } @@ -98,6 +99,7 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = e.asInstanceOf[KsqlRestClient.QueryStream] } case e: KsqlEntityList => e.asInstanceOf[KsqlEntityList] + case e: InputStream => e.asInstanceOf[InputStream] } currentResultSet = Some(resultSet) } @@ -173,21 +175,29 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = private implicit def toResultSet(stream: KsqlRestClient.QueryStream) (implicit queryDesc: QueryDescription): ResultSet = { - def mapType(ksqlType: KsqlType) = ksqlType match { - case KsqlType.INTEGER => Types.INTEGER - case KsqlType.BIGINT => Types.BIGINT - case KsqlType.DOUBLE => Types.DOUBLE - case KsqlType.BOOLEAN => Types.BOOLEAN - case KsqlType.STRING => Types.VARCHAR - case KsqlType.MAP => Types.JAVA_OBJECT - case KsqlType.ARRAY => Types.ARRAY - case KsqlType.STRUCT => Types.STRUCT + def mapType(ksqlType: KsqlType): (Int, Int) = ksqlType match { + case KsqlType.INTEGER => (Types.INTEGER, 16) + case KsqlType.BIGINT => (Types.BIGINT, 32) + case KsqlType.DOUBLE => (Types.DOUBLE, 32) + case KsqlType.BOOLEAN => (Types.BOOLEAN, 8) + case KsqlType.STRING => (Types.VARCHAR, 128) + case KsqlType.MAP => (Types.JAVA_OBJECT, 255) + case KsqlType.ARRAY => (Types.ARRAY, 255) + case KsqlType.STRUCT => (Types.STRUCT, 255) } - val columns = queryDesc.getFields.asScala.map(f => HeaderField(f.getName, mapType(f.getSchema.getType), 0)).toList + val columns = queryDesc.getFields.asScala.map { f => + val (mappedType, length) = mapType(f.getSchema.getType) + HeaderField(f.getName, mappedType, length) + }.toList new StreamedResultSet(new KsqlResultSetMetaData(columns), new KsqlQueryStream(stream), maxRows, timeout) } + private implicit def toResultSet(stream: InputStream): ResultSet = { + new StreamedResultSet(new KsqlResultSetMetaData(KsqlEntityHeaders.printTopic), + new KsqlInputStream(stream), maxRows, timeout) + } + private implicit def toResultSet(list: KsqlEntityList): ResultSet = { import KsqlEntityHeaders._ diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala index 3a00c3f..508eb8f 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSet.scala @@ -1,8 +1,8 @@ package com.github.mmolimar.ksql.jdbc.resultset -import java.io.Closeable +import java.io.{Closeable, InputStream} import java.sql.{ResultSet, ResultSetMetaData} -import java.util.{Iterator => JIterator} +import java.util.{NoSuchElementException, Scanner, Iterator => JIterator} import com.github.mmolimar.ksql.jdbc.Exceptions._ import com.github.mmolimar.ksql.jdbc.{HeaderField, InvalidColumn} @@ -32,7 +32,9 @@ class IteratorResultSet[T <: Any](private val metadata: ResultSetMetaData, priva } -private[jdbc] class KsqlQueryStream(stream: KsqlRestClient.QueryStream) extends Closeable with JIterator[StreamedRow] { +trait KsqlStream extends Closeable with JIterator[StreamedRow] + +private[jdbc] class KsqlQueryStream(stream: KsqlRestClient.QueryStream) extends KsqlStream { override def close: Unit = stream.close @@ -42,8 +44,29 @@ private[jdbc] class KsqlQueryStream(stream: KsqlRestClient.QueryStream) extends } +private[jdbc] class KsqlInputStream(stream: InputStream) extends KsqlStream { + private var isClosed = false + private lazy val scanner = new Scanner(stream) + + override def close: Unit = { + isClosed = true + scanner.close + } + + override def hasNext: Boolean = { + if (isClosed) throw new IllegalStateException("Cannot call hasNext() when stream is closed.") + scanner.hasNextLine + } + + override def next: StreamedRow = { + if (!hasNext) throw new NoSuchElementException + StreamedRow.row(new GenericRow(scanner.nextLine)) + } + +} + class StreamedResultSet(private val metadata: ResultSetMetaData, - private val stream: KsqlQueryStream, private val maxRows: Long, val timeout: Long = 0) + private val stream: KsqlStream, private val maxRows: Long, val timeout: Long = 0) extends AbstractResultSet[StreamedRow](metadata, maxRows, stream) { private val emptyRow: StreamedRow = StreamedRow.row(new GenericRow) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaData.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaData.scala index 6d7fc22..073374a 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaData.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaData.scala @@ -31,11 +31,7 @@ class KsqlResultSetMetaData(private[jdbc] val columns: List[HeaderField]) extend override def getColumnCount: Int = columns.size - override def getColumnDisplaySize(column: Int): Int = getField(column).jdbcType match { - case Types.INTEGER | Types.BIGINT | Types.DOUBLE => 16 - case Types.BOOLEAN => 5 - case _ => 64 - } + override def getColumnDisplaySize(column: Int): Int = getField(column).length override def getColumnLabel(column: Int): String = getField(column).label diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaDataSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaDataSpec.scala index 9ba1907..9218e1c 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaDataSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaDataSpec.scala @@ -20,15 +20,15 @@ class KsqlResultSetMetaDataSpec extends WordSpec with Matchers with MockFactory val resultSet = new KsqlResultSetMetaData( List( - HeaderField("field1", Types.INTEGER, 16), + HeaderField("field1", Types.INTEGER, 8), HeaderField("field2", Types.BIGINT, 16), - HeaderField("field3", Types.DOUBLE, 16), - HeaderField("field4", Types.BOOLEAN, 16), - HeaderField("field5", Types.VARCHAR, 16), - HeaderField("field6", Types.JAVA_OBJECT, 16), - HeaderField("field7", Types.ARRAY, 16), - HeaderField("field8", Types.STRUCT, 16), - HeaderField("field9", -999, 16) + HeaderField("field3", Types.DOUBLE, 32), + HeaderField("field4", Types.BOOLEAN, 5), + HeaderField("field5", Types.VARCHAR, 128), + HeaderField("field6", Types.JAVA_OBJECT, 255), + HeaderField("field7", Types.ARRAY, 255), + HeaderField("field8", Types.STRUCT, 512), + HeaderField("field9", -999, 9) )) "throw not supported exception if not supported" in { @@ -50,7 +50,7 @@ class KsqlResultSetMetaDataSpec extends WordSpec with Matchers with MockFactory resultSet.getColumnClassName(1) should be("java.lang.Integer") resultSet.getColumnType(1) should be(java.sql.Types.INTEGER) resultSet.getColumnTypeName(1) should be(KsqlType.INTEGER.name) - resultSet.getColumnDisplaySize(1) should be(16) + resultSet.getColumnDisplaySize(1) should be(8) resultSet.getColumnClassName(2) should be("java.lang.Long") resultSet.getColumnType(2) should be(java.sql.Types.BIGINT) @@ -60,7 +60,7 @@ class KsqlResultSetMetaDataSpec extends WordSpec with Matchers with MockFactory resultSet.getColumnClassName(3) should be("java.lang.Double") resultSet.getColumnType(3) should be(java.sql.Types.DOUBLE) resultSet.getColumnTypeName(3) should be(KsqlType.DOUBLE.name) - resultSet.getColumnDisplaySize(3) should be(16) + resultSet.getColumnDisplaySize(3) should be(32) resultSet.getColumnClassName(4) should be("java.lang.Boolean") resultSet.getColumnType(4) should be(java.sql.Types.BOOLEAN) @@ -70,27 +70,27 @@ class KsqlResultSetMetaDataSpec extends WordSpec with Matchers with MockFactory resultSet.getColumnClassName(5) should be("java.lang.String") resultSet.getColumnType(5) should be(java.sql.Types.VARCHAR) resultSet.getColumnTypeName(5) should be(KsqlType.STRING.name) - resultSet.getColumnDisplaySize(5) should be(64) + resultSet.getColumnDisplaySize(5) should be(128) resultSet.getColumnClassName(6) should be("java.util.Map") resultSet.getColumnType(6) should be(java.sql.Types.JAVA_OBJECT) resultSet.getColumnTypeName(6) should be(KsqlType.MAP.name) - resultSet.getColumnDisplaySize(6) should be(64) + resultSet.getColumnDisplaySize(6) should be(255) resultSet.getColumnClassName(7) should be("java.sql.Array") resultSet.getColumnType(7) should be(java.sql.Types.ARRAY) resultSet.getColumnTypeName(7) should be(KsqlType.ARRAY.name) - resultSet.getColumnDisplaySize(7) should be(64) + resultSet.getColumnDisplaySize(7) should be(255) resultSet.getColumnClassName(8) should be("java.sql.Struct") resultSet.getColumnType(8) should be(java.sql.Types.STRUCT) resultSet.getColumnTypeName(8) should be(KsqlType.STRUCT.name) - resultSet.getColumnDisplaySize(8) should be(64) + resultSet.getColumnDisplaySize(8) should be(512) resultSet.getColumnClassName(9) should be("java.lang.String") resultSet.getColumnType(9) should be(-999) resultSet.getColumnTypeName(9) should be(KsqlType.STRING.name) - resultSet.getColumnDisplaySize(9) should be(64) + resultSet.getColumnDisplaySize(9) should be(9) resultSet.isCaseSensitive(2) should be(false) resultSet.isCaseSensitive(5) should be(true) diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala index e17fafd..0fca351 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala @@ -93,7 +93,7 @@ class KsqlResultSetSpec extends WordSpec with Matchers with MockFactory with One }) } - "work if implemented" in { + "work when reading from a query stream" in { val mockedQueryStream = mock[KsqlQueryStream] inSequence { @@ -185,6 +185,41 @@ class KsqlResultSetSpec extends WordSpec with Matchers with MockFactory with One resultSet.next } } + + "work when reading from an input stream" in { + + val mockedInputStream = mock[KsqlInputStream] + inSequence { + (mockedInputStream.hasNext _).expects.returns(true) + (mockedInputStream.hasNext _).expects.returns(true) + val columnValues = Seq[AnyRef]("test") + val row = StreamedRow.row(new GenericRow(columnValues.asJava)) + (mockedInputStream.next _).expects.returns(row) + (mockedInputStream.hasNext _).expects.returns(false) + (mockedInputStream.close _).expects + } + + val resultSet = new StreamedResultSet(resultSetMetadata, mockedInputStream, 0) + resultSet.getMetaData should be(resultSetMetadata) + resultSet.isLast should be(false) + resultSet.isAfterLast should be(false) + resultSet.isBeforeFirst should be(false) + resultSet.getConcurrency should be(ResultSet.CONCUR_READ_ONLY) + resultSet.wasNull should be(true) + + resultSet.isFirst should be(true) + resultSet.next should be(true) + + resultSet.getString(1) should be("test") + resultSet.next should be(false) + resultSet.isFirst should be(false) + resultSet.getWarnings should be(None.orNull) + resultSet.close + resultSet.close + assertThrows[SQLException] { + resultSet.next + } + } } } From 6296a0b171e28330934f5eb94e9ffbdc08af5d75 Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sat, 16 Feb 2019 00:41:47 -0600 Subject: [PATCH 35/47] Listing date and timestamp functions --- .../ksql/jdbc/KsqlDatabaseMetaData.scala | 27 ++++++++---- .../ksql/jdbc/KsqlDatabaseMetaDataSpec.scala | 41 +++++++++++++------ 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala index f328ec8..78d95d6 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala @@ -727,13 +727,23 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D new IteratorResultSet(DatabaseMetadataHeaders.columns, 0, columns) } - override def getNumericFunctions: String = availableFunctions(None, Set("INT", "BIGINT", "DOUBLE")).mkString(",") + override def getNumericFunctions: String = availableFunctions( + author = None, + types = Set("INT", "BIGINT", "DOUBLE") + ).mkString(",") - override def getStringFunctions: String = availableFunctions(None, Set("VARCHAR", "STRING")).mkString(",") + override def getStringFunctions: String = availableFunctions( + author = None, + types = Set("VARCHAR", "STRING") + ).mkString(",") - override def getSystemFunctions: String = availableFunctions(Some("Confluent")).mkString(",") + override def getSystemFunctions: String = availableFunctions(author = Some("Confluent")).mkString(",") - override def getTimeDateFunctions: String = availableFunctions(None, Set.empty).mkString(",") + override def getTimeDateFunctions: String = availableFunctions( + author = None, + names = Set(".*TIME.*", ".*DATE.*"), + types = Set.empty + ).mkString(",") override def supportsAlterTableWithAddColumn: Boolean = false @@ -759,16 +769,17 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def supportsStoredProcedures: Boolean = false - private def availableFunctions(author: Option[String] = None, fnTypes: Set[String] = Set(".*")): Set[String] = { + private def availableFunctions(author: Option[String] = None, names: Set[String] = Set.empty, + types: Set[String] = Set(".*")): Set[String] = { var functions = mutable.Set.empty[String] (ksqlConnection.createStatement.executeQuery("LIST FUNCTIONS")).toStream.foreach { fn => - val fnName = fn.getString("FUNCTION_NAME_FN_NAME") + val fnName = fn.getString("FUNCTION_NAME_FN_NAME").toUpperCase ksqlConnection.createStatement.executeQuery(s"DESCRIBE FUNCTION $fnName").toStream.foreach { fnDesc => val fnAuthor = fnDesc.getString("FUNCTION_DESCRIPTION_AUTHOR").trim.toUpperCase - val returnType = fnDesc.getString("FUNCTION_DESCRIPTION_FN_RETURN_TYPE") - if (fnTypes.filter(returnType.matches(_)).nonEmpty && + val fnReturnType = fnDesc.getString("FUNCTION_DESCRIPTION_FN_RETURN_TYPE") + if ((types.filter(fnReturnType.matches(_)).nonEmpty || names.filter(fnName.matches(_)).nonEmpty) && (author.isEmpty || author.get.toUpperCase == fnAuthor.toUpperCase)) { functions += fnName } diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala index 5f7eb2c..6920ca5 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala @@ -97,39 +97,54 @@ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory w val fnList = new FunctionNameList( "LIST FUNCTIONS;", - List(new SimpleFunctionInfo("TESTFN", FunctionType.scalar)).asJava + List( + new SimpleFunctionInfo("TESTFN", FunctionType.scalar), + new SimpleFunctionInfo("TESTDATEFN", FunctionType.scalar) + ).asJava ) val entityListFn = new KsqlEntityList entityListFn.add(fnList) - (mockedKsqlRestClient.makeKsqlRequest _).expects("LIST FUNCTIONS;") - .returns(RestResponse.successful[KsqlEntityList](entityListFn)) - .repeat(4) - val descFn = new FunctionDescriptionList("DESCRIBE FUNCTION test;", + val descFn1 = new FunctionDescriptionList("DESCRIBE FUNCTION testfn;", "TESTFN", "Description", "Confluent", "version", "path", List( new FunctionInfo(List(new ArgumentInfo("arg1", "INT", "Description")).asJava, "BIGINT", "Description"), new FunctionInfo(List(new ArgumentInfo("arg1", "INT", "Description")).asJava, "STRING", "Description") - ).asJava, FunctionType.scalar ) - val entityDescribeFn = new KsqlEntityList - entityDescribeFn.add(descFn) + val descFn2 = new FunctionDescriptionList("DESCRIBE FUNCTION testdatefn;", + "TESTDATEFN", "Description", "Unknown", "version", "path", + List( + new FunctionInfo(List(new ArgumentInfo("arg1", "INT", "Description")).asJava, "BIGINT", "Description") + ).asJava, + FunctionType.scalar + ) + val entityDescribeFn1 = new KsqlEntityList + entityDescribeFn1.add(descFn1) + val entityDescribeFn2 = new KsqlEntityList + entityDescribeFn2.add(descFn2) + + (mockedKsqlRestClient.makeKsqlRequest _).expects("LIST FUNCTIONS;") + .returns(RestResponse.successful[KsqlEntityList](entityListFn)) + .repeat(4) (mockedKsqlRestClient.makeKsqlRequest _).expects("DESCRIBE FUNCTION TESTFN;") - .returns(RestResponse.successful[KsqlEntityList](entityDescribeFn)) + .returns(RestResponse.successful[KsqlEntityList](entityDescribeFn1)) + .repeat(4) + (mockedKsqlRestClient.makeKsqlRequest _).expects("DESCRIBE FUNCTION TESTDATEFN;") + .returns(RestResponse.successful[KsqlEntityList](entityDescribeFn2)) .repeat(4) - metadata.getNumericFunctions should be("TESTFN") + metadata.getNumericFunctions should be("TESTDATEFN,TESTFN") metadata.getStringFunctions should be("TESTFN") metadata.getSystemFunctions should be("TESTFN") - metadata.getTimeDateFunctions should be("") + metadata.getTimeDateFunctions should be("TESTDATEFN") Option(metadata.getConnection) should not be (None) metadata.getCatalogs.next should be(false) metadata.getCatalogTerm should be("TOPIC") - metadata.getSchemaTerm should be ("") - metadata.getProcedureTerm should be ("") + metadata.getSchemaTerm should be("") + metadata.getProcedureTerm should be("") val tableTypes = metadata.getTableTypes tableTypes.next should be(true) From 5a6e7bc5c97ce72f451bfbc3805d2258a8353ddb Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Mon, 18 Feb 2019 23:06:07 -0600 Subject: [PATCH 36/47] Adding more support for JDBC spec in database metadata --- .../ksql/jdbc/KsqlDatabaseMetaData.scala | 84 +++++++++++++++++++ .../ksql/jdbc/KsqlDatabaseMetaDataSpec.scala | 73 +++++++++++++++- .../ksql/jdbc/KsqlStatementSpec.scala | 11 ++- 3 files changed, 163 insertions(+), 5 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala index 78d95d6..ce18d4b 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala @@ -745,6 +745,44 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D types = Set.empty ).mkString(",") + override def allProceduresAreCallable: Boolean = false + + override def allTablesAreSelectable: Boolean = false + + override def getMaxConnections: Int = 0 + + override def getIdentifierQuoteString: String = " " + + override def getSearchStringEscape: String = "%" + + override def getExtraNameCharacters: String = "#@" + + override def nullsAreSortedHigh: Boolean = false + + override def nullsAreSortedLow: Boolean = false + + override def nullsAreSortedAtStart: Boolean = false + + override def nullsAreSortedAtEnd: Boolean = false + + override def nullPlusNonNullIsNull: Boolean = true + + override def usesLocalFiles: Boolean = true + + override def usesLocalFilePerTable: Boolean = true + + override def storesUpperCaseIdentifiers: Boolean = false + + override def storesLowerCaseIdentifiers: Boolean = false + + override def storesMixedCaseIdentifiers: Boolean = true + + override def storesUpperCaseQuotedIdentifiers: Boolean = false + + override def storesLowerCaseQuotedIdentifiers: Boolean = false + + override def storesMixedCaseQuotedIdentifiers: Boolean = true + override def supportsAlterTableWithAddColumn: Boolean = false override def supportsAlterTableWithDropColumn: Boolean = false @@ -769,6 +807,52 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def supportsStoredProcedures: Boolean = false + override def supportsMixedCaseQuotedIdentifiers: Boolean = true + + override def supportsColumnAliasing: Boolean = true + + override def supportsMixedCaseIdentifiers: Boolean = true + + override def supportsConvert: Boolean = false + + override def supportsConvert(fromType: Int, toType: Int): Boolean = false + + override def supportsTableCorrelationNames: Boolean = true + + override def supportsDifferentTableCorrelationNames: Boolean = true + + override def supportsExpressionsInOrderBy: Boolean = true + + override def supportsGroupBy: Boolean = true + + override def supportsOrderByUnrelated: Boolean = false + + override def supportsGroupByUnrelated: Boolean = true + + override def supportsGroupByBeyondSelect: Boolean = true + + override def supportsLikeEscapeClause: Boolean = true + + override def supportsNonNullableColumns: Boolean = true + + override def supportsMinimumSQLGrammar: Boolean = true + + override def supportsCoreSQLGrammar: Boolean = false + + override def supportsExtendedSQLGrammar: Boolean = false + + override def supportsOuterJoins: Boolean = false + + override def supportsFullOuterJoins: Boolean = false + + override def supportsLimitedOuterJoins: Boolean = false + + override def supportsUnion: Boolean = false + + override def supportsUnionAll: Boolean = false + + override def supportsTransactions: Boolean = false + private def availableFunctions(author: Option[String] = None, names: Set[String] = Set.empty, types: Set[String] = Set(".*")): Set[String] = { var functions = mutable.Set.empty[String] diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala index 6920ca5..1db4741 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala @@ -17,17 +17,30 @@ import scala.collection.JavaConverters._ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory with OneInstancePerTest { "A KsqlDatabaseMetaData" when { - val implementedMethods = Seq("getDatabaseProductName", "getDatabaseMajorVersion", "getDatabaseMinorVersion", + val implementedMethods = Seq("allProceduresAreCallable", "allTablesAreSelectable", + "getMaxConnections", "getIdentifierQuoteString", "getIdentifierQuoteString", "getSearchStringEscape", + "getExtraNameCharacters", "getDatabaseProductName", "getDatabaseMajorVersion", "getDatabaseMinorVersion", "getDatabaseProductVersion", "getDriverName", "getDriverVersion", "getDriverMajorVersion", "getDriverMinorVersion", "getJDBCMajorVersion", "getJDBCMinorVersion", "getConnection", "getCatalogs", "getCatalogTerm", "getMaxStatements", "getMaxStatementLength", "getTableTypes", "getTables", "getTypeInfo", "getSchemas", "getSuperTables", "getUDTs", "getColumns", "getURL", "isReadOnly", "getSQLKeywords", "getProcedures", "getNumericFunctions", "getSchemaTerm", "getStringFunctions", "getSystemFunctions", "getTimeDateFunctions", - "getProcedureTerm", "supportsAlterTableWithAddColumn", "supportsAlterTableWithDropColumn", + "getProcedureTerm", + "nullsAreSortedHigh", "nullsAreSortedLow", "nullsAreSortedAtStart", "nullsAreSortedAtEnd", "nullPlusNonNullIsNull", + "usesLocalFiles", "usesLocalFilePerTable", + "supportsAlterTableWithAddColumn", "supportsAlterTableWithDropColumn", "supportsCatalogsInDataManipulation", "supportsCatalogsInTableDefinitions", "supportsCatalogsInProcedureCalls", "supportsMultipleResultSets", "supportsMultipleTransactions", "supportsSavepoints", "supportsSchemasInDataManipulation", "supportsSchemasInTableDefinitions", - "supportsStoredFunctionsUsingCallSyntax", "supportsStoredProcedures" + "supportsStoredFunctionsUsingCallSyntax", "supportsStoredProcedures", "storesUpperCaseIdentifiers", + "storesLowerCaseIdentifiers", "storesMixedCaseIdentifiers", "storesUpperCaseQuotedIdentifiers", + "storesLowerCaseQuotedIdentifiers", "storesMixedCaseQuotedIdentifiers", "supportsMixedCaseQuotedIdentifiers", + "supportsColumnAliasing", "supportsMixedCaseIdentifiers", "supportsConvert", "supportsTableCorrelationNames", + "supportsDifferentTableCorrelationNames", "supportsExpressionsInOrderBy", "supportsExtendedSQLGrammar", + "supportsGroupBy", "supportsOrderByUnrelated", "supportsGroupByUnrelated", "supportsGroupByBeyondSelect", + "supportsLikeEscapeClause", "supportsNonNullableColumns", "supportsMinimumSQLGrammar", "supportsCoreSQLGrammar", + "supportsExtendedSQLGrammar", "supportsOuterJoins", "supportsFullOuterJoins", + "supportsLimitedOuterJoins", "supportsUnion", "supportsUnionAll", "supportsTransactions" ) val mockResponse = mock[Response] @@ -173,6 +186,9 @@ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory w assertThrows[SQLException] { metadata.getSuperTables("", "test", "test") } + } + + "check JDBC specs" in { metadata.getURL should be("jdbc:ksql://localhost:8080") metadata.getTypeInfo.getMetaData.getColumnCount should be(18) @@ -180,6 +196,32 @@ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory w metadata.getMaxStatements should be(0) metadata.getMaxStatementLength should be(0) metadata.getProcedures(None.orNull, None.orNull, None.orNull).next should be(false) + metadata.getIdentifierQuoteString should be(" ") + metadata.getSearchStringEscape should be("%") + metadata.getExtraNameCharacters should be("#@") + metadata.getMaxConnections should be(0) + metadata.getIdentifierQuoteString should be(" ") + + metadata.allProceduresAreCallable should be(false) + metadata.allTablesAreSelectable should be(false) + + metadata.nullsAreSortedHigh should be(false) + metadata.nullsAreSortedLow should be(false) + metadata.nullsAreSortedAtStart should be(false) + metadata.nullsAreSortedAtEnd should be(false) + + metadata.usesLocalFiles should be(true) + metadata.usesLocalFilePerTable should be(true) + + metadata.nullPlusNonNullIsNull should be(true) + + metadata.storesUpperCaseIdentifiers should be(false) + metadata.storesLowerCaseIdentifiers should be(false) + metadata.storesMixedCaseIdentifiers should be(true) + metadata.storesUpperCaseQuotedIdentifiers should be(false) + metadata.storesLowerCaseQuotedIdentifiers should be(false) + metadata.storesMixedCaseQuotedIdentifiers should be(true) + metadata.supportsAlterTableWithAddColumn should be(false) metadata.supportsAlterTableWithDropColumn should be(false) metadata.supportsCatalogsInDataManipulation should be(false) @@ -192,7 +234,30 @@ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory w metadata.supportsStoredFunctionsUsingCallSyntax should be(true) metadata.supportsStoredProcedures should be(false) metadata.supportsSavepoints should be(false) - + metadata.supportsMixedCaseIdentifiers should be(true) + metadata.supportsMixedCaseQuotedIdentifiers should be(true) + metadata.supportsColumnAliasing should be(true) + metadata.supportsConvert should be(false) + metadata.supportsConvert(12, 15) should be(false) + metadata.supportsTableCorrelationNames should be(true) + metadata.supportsDifferentTableCorrelationNames should be(true) + metadata.supportsExpressionsInOrderBy should be(true) + metadata.supportsExtendedSQLGrammar should be(false) + metadata.supportsGroupBy should be(true) + metadata.supportsOrderByUnrelated should be(false) + metadata.supportsGroupByUnrelated should be(true) + metadata.supportsGroupByBeyondSelect should be(true) + metadata.supportsLikeEscapeClause should be(true) + metadata.supportsNonNullableColumns should be(true) + metadata.supportsMinimumSQLGrammar should be(true) + metadata.supportsCoreSQLGrammar should be(false) + metadata.supportsExtendedSQLGrammar should be(false) + metadata.supportsOuterJoins should be(false) + metadata.supportsFullOuterJoins should be(false) + metadata.supportsLimitedOuterJoins should be(false) + metadata.supportsTransactions should be(false) + metadata.supportsUnion should be(false) + metadata.supportsUnionAll should be(false) } } } diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala index e7542bf..1a2d471 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala @@ -1,6 +1,6 @@ package com.github.mmolimar.ksql.jdbc -import java.io.InputStream +import java.io.{ByteArrayInputStream, InputStream} import java.sql.{ResultSet, SQLException, SQLFeatureNotSupportedException} import com.github.mmolimar.ksql.jdbc.utils.TestUtils._ @@ -158,6 +158,15 @@ class KsqlStatementSpec extends WordSpec with Matchers with MockFactory with One statement.getWarnings should be(None.orNull) } + "work when printing topics" in { + (mockedKsqlRestClient.makePrintTopicRequest _).expects(*) + .returns(RestResponse.successful[InputStream](new ByteArrayInputStream("test".getBytes))) + .once + Option(statement.executeQuery("print 'test'")) should not be (None) + statement.getResultSet.next should be(true) + statement.getResultSet.getString(1) should be("test") + } + "work when executing KSQL commands" in { import KsqlEntityHeaders._ From 0f8465eff665bad3d090037b60b91651722657c0 Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Thu, 21 Feb 2019 23:15:56 -0600 Subject: [PATCH 37/47] Validate connection state with KSQL --- .../com/github/mmolimar/ksql/jdbc/Exceptions.scala | 8 ++++++-- .../github/mmolimar/ksql/jdbc/KsqlConnection.scala | 12 ++++++++---- .../mmolimar/ksql/jdbc/KsqlConnectionSpec.scala | 10 ++++++++-- .../github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala | 3 ++- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala index f1782f9..3ebd22e 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala @@ -4,9 +4,9 @@ import java.sql.{SQLException, SQLFeatureNotSupportedException} sealed trait KsqlException { - def message: String = "" + def message: String - def cause: Throwable = None.orNull + def cause: Throwable } @@ -18,6 +18,10 @@ case class CannotConnect(url: String, msg: String, override val cause: Throwable override def message = s"Cannot connect to this URL ${url}. Error message: ${msg}." } +case class NotConnected(url: String, override val cause: Throwable = None.orNull) extends KsqlException { + override def message = s"Not connected to database: ${url}." +} + case class InvalidProperty(name: String, override val cause: Throwable = None.orNull) extends KsqlException { override def message = s"Invalid property ${name}." } diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala index 13379bd..931ae56 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala @@ -38,6 +38,7 @@ class KsqlConnection(private[jdbc] val values: KsqlConnectionValues, properties: extends Connection with WrapperNotSupported { private val ksqlClient = init + private var connected: Option[Boolean] = None private[jdbc] def init: KsqlRestClient = { val props = if (values.properties) { @@ -53,7 +54,7 @@ class KsqlConnection(private[jdbc] val values: KsqlConnectionValues, properties: case Success(response) if response.isErroneous => throw CannotConnect(values.ksqlServer, response.getErrorMessage.getMessage) case Failure(e) => throw CannotConnect(values.ksqlServer, e.getMessage) - case _ => + case _ => connected = Some(true) } } @@ -150,7 +151,10 @@ class KsqlConnection(private[jdbc] val values: KsqlConnectionValues, properties: override def setCatalog(catalog: String): Unit = {} - override def close: Unit = ksqlClient.close + override def close: Unit = { + ksqlClient.close + connected = Some(false) + } override def getAutoCommit: Boolean = false @@ -176,7 +180,7 @@ class KsqlConnection(private[jdbc] val values: KsqlConnectionValues, properties: override def releaseSavepoint(savepoint: Savepoint): Unit = throw NotSupported("releaseSavepoint") - override def isClosed: Boolean = throw NotSupported("isClosed") + override def isClosed: Boolean = !connected.getOrElse(throw NotConnected(values.jdbcUrl)) override def createStruct(typeName: String, attributes: scala.Array[AnyRef]): Struct = throw NotSupported("createStruct") @@ -185,6 +189,6 @@ class KsqlConnection(private[jdbc] val values: KsqlConnectionValues, properties: override def setSchema(schema: String): Unit = throw NotSupported("setSchema") - override def commit: Unit = throw NotSupported("commit") + override def commit: Unit = {} } diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlConnectionSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlConnectionSpec.scala index 202aa43..2a1b8e1 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlConnectionSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlConnectionSpec.scala @@ -13,8 +13,8 @@ import org.scalatest.{Matchers, WordSpec} class KsqlConnectionSpec extends WordSpec with Matchers with MockFactory { val implementedMethods = Seq("createStatement", "getAutoCommit", "getTransactionIsolation", - "setClientInfo", "isReadOnly", "isValid", "close", "getMetaData", "getWarnings", "setAutoCommit", - "getCatalog", "setCatalog") + "setClientInfo", "isReadOnly", "isClosed", "isValid", "close", "commit", "getMetaData", "getWarnings", + "setAutoCommit", "getCatalog", "setCatalog") "A KsqlConnection" when { @@ -35,6 +35,10 @@ class KsqlConnectionSpec extends WordSpec with Matchers with MockFactory { } "work if implemented" in { + + assertThrows[SQLException] { + ksqlConnection.isClosed + } ksqlConnection.getTransactionIsolation should be(Connection.TRANSACTION_NONE) ksqlConnection.setClientInfo(new Properties) @@ -70,6 +74,8 @@ class KsqlConnectionSpec extends WordSpec with Matchers with MockFactory { (ksqlRestClient.close _).expects ksqlConnection.close + ksqlConnection.isClosed should be(true) + ksqlConnection.commit } } } diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala index d45e713..d9801c3 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala @@ -71,7 +71,8 @@ class KsqlDriverSpec extends WordSpec with Matchers with MockFactory { (mockKsqlRestClient.makeRootRequest _).expects() .returns(RestResponse.successful[ServerInfo](new ServerInfo("v1", "id1", "svc1"))) .once - driver.connect("jdbc:ksql://localhost:9999", new Properties) + val connection = driver.connect("jdbc:ksql://localhost:9999", new Properties) + connection.isClosed should be(false) } } From b0601dc6e7b49ccde317222ee50223358d2c8fbc Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sat, 23 Feb 2019 16:15:45 -0600 Subject: [PATCH 38/47] Upgrade to KSQL 5.1.2 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index b6f8530..e7795e8 100644 --- a/build.sbt +++ b/build.sbt @@ -12,7 +12,7 @@ resolvers += "Confluent Maven Repo" at "http://packages.confluent.io/maven/" resolvers += "Confluent Snapshots Maven Repo" at "https://s3-us-west-2.amazonaws.com/confluent-snapshots/" resolvers += Resolver.mavenLocal -libraryDependencies += "io.confluent.ksql" % "ksql-rest-app" % "5.1.1" +libraryDependencies += "io.confluent.ksql" % "ksql-rest-app" % "5.1.2" libraryDependencies += "org.apache.kafka" %% "kafka" % "2.1.0" % "test" libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % "test" libraryDependencies += "org.scalamock" %% "scalamock-scalatest-support" % "3.6.0" % "test" From b30703b80d25480279dc095c4da457f81a7440ed Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sat, 23 Feb 2019 18:10:22 -0600 Subject: [PATCH 39/47] Default classes to throw not supported exceptions --- .../github/mmolimar/ksql/jdbc/Headers.scala | 3 - .../mmolimar/ksql/jdbc/KsqlConnection.scala | 197 +++++++++++------- .../mmolimar/ksql/jdbc/KsqlDriver.scala | 2 +- .../mmolimar/ksql/jdbc/KsqlStatement.scala | 138 +++++++----- .../ksql/jdbc/resultset/ResultSet.scala | 14 +- 5 files changed, 209 insertions(+), 145 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala index 53b81b7..6ee74b2 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/Headers.scala @@ -11,9 +11,6 @@ object HeaderField { HeaderField(name, name.toUpperCase, jdbcType, length, -1) } - def apply(name: String, label: String, jdbcType: Int, length: Int): HeaderField = { - HeaderField(name, label, jdbcType, length, -1) - } } object DatabaseMetadataHeaders { diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala index 931ae56..4911045 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlConnection.scala @@ -34,8 +34,126 @@ case class KsqlConnectionValues(ksqlServer: String, port: Int, config: Map[Strin } +class ConnectionNotSupported extends Connection with WrapperNotSupported { + + override def commit: Unit = throw NotSupported("commit") + + override def getHoldability: Int = throw NotSupported("getHoldability") + + override def setCatalog(catalog: String): Unit = throw NotSupported("setCatalog") + + override def setHoldability(holdability: Int): Unit = throw NotSupported("setHoldability") + + override def prepareStatement(sql: String): PreparedStatement = throw NotSupported("prepareStatement") + + override def prepareStatement(sql: String, resultSetType: Int, resultSetConcurrency: Int): PreparedStatement = + throw NotSupported("prepareStatement") + + override def prepareStatement(sql: String, resultSetType: Int, resultSetConcurrency: Int, + resultSetHoldability: Int): PreparedStatement = throw NotSupported("prepareStatement") + + override def prepareStatement(sql: String, autoGeneratedKeys: Int): PreparedStatement = + throw NotSupported("prepareStatement") + + override def prepareStatement(sql: String, columnIndexes: scala.Array[Int]): PreparedStatement = + throw NotSupported("prepareStatement") + + override def prepareStatement(sql: String, columnNames: scala.Array[String]): PreparedStatement = + throw NotSupported("prepareStatement") + + override def createClob: Clob = throw NotSupported("createClob") + + override def setSchema(schema: String): Unit = throw NotSupported("setSchema") + + override def setClientInfo(name: String, value: String): Unit = throw NotSupported("setClientInfo") + + override def setClientInfo(properties: Properties): Unit = throw NotSupported("setClientInfo") + + override def createSQLXML: SQLXML = throw NotSupported("createSQLXML") + + override def getCatalog: String = throw NotSupported("getCatalog") + + override def createBlob: Blob = throw NotSupported("createBlob") + + override def createStatement: Statement = throw NotSupported("createStatement") + + override def createStatement(resultSetType: Int, resultSetConcurrency: Int): Statement = + throw NotSupported("createStatement") + + override def createStatement(resultSetType: Int, resultSetConcurrency: Int, resultSetHoldability: Int): Statement = + throw NotSupported("createStatement") + + override def abort(executor: Executor): Unit = throw NotSupported("abort") + + override def setAutoCommit(autoCommit: Boolean): Unit = throw NotSupported("setAutoCommit") + + override def getMetaData: DatabaseMetaData = throw NotSupported("getMetaData") + + override def setReadOnly(readOnly: Boolean): Unit = throw NotSupported("setReadOnly") + + override def prepareCall(sql: String): CallableStatement = throw NotSupported("prepareCall") + + override def prepareCall(sql: String, resultSetType: Int, resultSetConcurrency: Int): CallableStatement = + throw NotSupported("prepareCall") + + override def prepareCall(sql: String, resultSetType: Int, resultSetConcurrency: Int, + resultSetHoldability: Int): CallableStatement = throw NotSupported("prepareCall") + + override def setTransactionIsolation(level: Int): Unit = throw NotSupported("setTransactionIsolation") + + override def getWarnings: SQLWarning = throw NotSupported("getWarnings") + + override def releaseSavepoint(savepoint: Savepoint): Unit = throw NotSupported("releaseSavepoint") + + override def nativeSQL(sql: String): String = throw NotSupported("nativeSQL") + + override def isReadOnly: Boolean = throw NotSupported("isReadOnly") + + override def createArrayOf(typeName: String, elements: scala.Array[AnyRef]): Array = + throw NotSupported("createArrayOf") + + override def setSavepoint: Savepoint = throw NotSupported("setSavepoint") + + override def setSavepoint(name: String): Savepoint = throw NotSupported("setSavepoint") + + override def close: Unit = throw NotSupported("close") + + override def createNClob: NClob = throw NotSupported("createNClob") + + override def rollback: Unit = throw NotSupported("rollback") + + override def rollback(savepoint: Savepoint): Unit = throw NotSupported("rollback") + + override def setNetworkTimeout(executor: Executor, milliseconds: Int): Unit = throw NotSupported("setNetworkTimeout") + + override def setTypeMap(map: util.Map[String, Class[_]]): Unit = throw NotSupported("setTypeMap") + + override def isValid(timeout: Int): Boolean = throw NotSupported("isValid") + + override def getAutoCommit: Boolean = throw NotSupported("getAutoCommit") + + override def clearWarnings: Unit = throw NotSupported("clearWarnings") + + override def getSchema: String = throw NotSupported("getSchema") + + override def getNetworkTimeout: Int = throw NotSupported("getNetworkTimeout") + + override def isClosed: Boolean = throw NotSupported("isClosed") + + override def getTransactionIsolation: Int = throw NotSupported("getTransactionIsolation") + + override def createStruct(typeName: String, attributes: scala.Array[AnyRef]): Struct = throw NotSupported("createStruct") + + override def getClientInfo(name: String): String = throw NotSupported("getClientInfo") + + override def getClientInfo: Properties = throw NotSupported("getClientInfo") + + override def getTypeMap: util.Map[String, Class[_]] = throw NotSupported("getTypeMap") + +} + class KsqlConnection(private[jdbc] val values: KsqlConnectionValues, properties: Properties) - extends Connection with WrapperNotSupported { + extends ConnectionNotSupported { private val ksqlClient = init private var connected: Option[Boolean] = None @@ -62,40 +180,10 @@ class KsqlConnection(private[jdbc] val values: KsqlConnectionValues, properties: override def setAutoCommit(autoCommit: Boolean): Unit = {} - override def setHoldability(holdability: Int): Unit = throw NotSupported("setHoldability") - - override def clearWarnings: Unit = throw NotSupported("clearWarnings") - - override def getNetworkTimeout: Int = throw NotSupported("getNetworkTimeout") - - override def createBlob: Blob = throw NotSupported("createBlob") - - override def createSQLXML: SQLXML = throw NotSupported("createSQLXML") - - override def setSavepoint: Savepoint = throw NotSupported("setSavepoint") - - override def setSavepoint(name: String): Savepoint = throw NotSupported("setSavepoint") - - override def createNClob: NClob = throw NotSupported("createNClob") - override def getTransactionIsolation: Int = Connection.TRANSACTION_NONE - override def getClientInfo(name: String): String = throw NotSupported("getClientInfo") - - override def getClientInfo: Properties = throw NotSupported("getClientInfo") - - override def getSchema: String = throw NotSupported("getSchema") - - override def setNetworkTimeout(executor: Executor, milliseconds: Int): Unit = throw NotSupported("setNetworkTimeout") - override def getMetaData: DatabaseMetaData = new KsqlDatabaseMetaData(this) - override def getTypeMap: util.Map[String, Class[_]] = throw NotSupported("getTypeMap") - - override def rollback: Unit = throw NotSupported("rollback") - - override def rollback(savepoint: Savepoint): Unit = throw NotSupported("rollback") - override def createStatement: Statement = createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY) override def createStatement(resultSetType: Int, resultSetConcurrency: Int): Statement = { @@ -112,10 +200,6 @@ class KsqlConnection(private[jdbc] val values: KsqlConnectionValues, properties: new KsqlStatement(ksqlClient, values.timeout) } - override def getHoldability: Int = throw NotSupported("getHoldability") - - override def setReadOnly(readOnly: Boolean): Unit = throw NotSupported("setReadOnly") - override def setClientInfo(name: String, value: String): Unit = { val ksql = s"SET '${name.trim}'='${value.trim}';" if (ksqlClient.makeKsqlRequest(ksql).isErroneous) { @@ -129,26 +213,8 @@ class KsqlConnection(private[jdbc] val values: KsqlConnectionValues, properties: override def isReadOnly: Boolean = true - override def setTypeMap(map: util.Map[String, Class[_]]): Unit = throw NotSupported("setTypeMap") - override def getCatalog: String = None.orNull - override def createClob: Clob = throw NotSupported("createClob") - - override def nativeSQL(sql: String): String = throw NotSupported("nativeSQL") - - override def setTransactionIsolation(level: Int): Unit = throw NotSupported("setTransactionIsolation") - - override def prepareCall(sql: String): CallableStatement = throw NotSupported("prepareCall") - - override def prepareCall(sql: String, resultSetType: Int, - resultSetConcurrency: Int): CallableStatement = throw NotSupported("prepareCall") - - override def prepareCall(sql: String, resultSetType: Int, resultSetConcurrency: Int, - resultSetHoldability: Int): CallableStatement = throw NotSupported("prepareCall") - - override def createArrayOf(typeName: String, elements: scala.Array[AnyRef]): Array = throw NotSupported("createArrayOf") - override def setCatalog(catalog: String): Unit = {} override def close: Unit = { @@ -158,37 +224,12 @@ class KsqlConnection(private[jdbc] val values: KsqlConnectionValues, properties: override def getAutoCommit: Boolean = false - override def abort(executor: Executor): Unit = throw NotSupported("abort") - override def isValid(timeout: Int): Boolean = ksqlClient.makeStatusRequest.isSuccessful - override def prepareStatement(sql: String): PreparedStatement = throw NotSupported("prepareStatement") - - override def prepareStatement(sql: String, resultSetType: Int, - resultSetConcurrency: Int): PreparedStatement = throw NotSupported("prepareStatement") - - override def prepareStatement(sql: String, resultSetType: Int, resultSetConcurrency: Int, - resultSetHoldability: Int): PreparedStatement = throw NotSupported("prepareStatement") - - override def prepareStatement(sql: String, autoGeneratedKeys: Int): PreparedStatement = throw NotSupported("prepareStatement") - - override def prepareStatement(sql: String, columnIndexes: scala.Array[Int]): PreparedStatement = - throw NotSupported("prepareStatement") - - override def prepareStatement(sql: String, columnNames: scala.Array[String]): PreparedStatement = - throw NotSupported("prepareStatement") - - override def releaseSavepoint(savepoint: Savepoint): Unit = throw NotSupported("releaseSavepoint") - override def isClosed: Boolean = !connected.getOrElse(throw NotConnected(values.jdbcUrl)) - override def createStruct(typeName: String, attributes: scala.Array[AnyRef]): Struct = - throw NotSupported("createStruct") - override def getWarnings: SQLWarning = None.orNull - override def setSchema(schema: String): Unit = throw NotSupported("setSchema") - override def commit: Unit = {} } diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala index d7954d7..9e59ece 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala @@ -47,7 +47,7 @@ class KsqlDriver extends Driver { override def acceptsURL(url: String): Boolean = Option(url).exists(_.startsWith(KsqlDriver.ksqlPrefix)) - override def jdbcCompliant(): Boolean = false + override def jdbcCompliant: Boolean = false override def getPropertyInfo(url: String, info: Properties): scala.Array[DriverPropertyInfo] = scala.Array.empty diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala index de6acb3..ee8fcb7 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala @@ -19,26 +19,100 @@ private object KsqlStatement { } -class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = 0) - extends Statement with WrapperNotSupported { +class StatementNotSupported extends Statement with WrapperNotSupported { - import KsqlStatement._ + override def cancel: Unit = throw NotSupported("cancel") - private[this] var currentResultSet: Option[ResultSet] = None - private var maxRows = 0 + override def getResultSetHoldability: Int = throw NotSupported("getResultSetHoldability") - override def setMaxFieldSize(max: Int): Unit = throw NotSupported("setMaxFieldSize") + override def getMaxFieldSize: Int = throw NotSupported("getMaxFieldSize") + + override def getUpdateCount: Int = throw NotSupported("getUpdateCount") + + override def setPoolable(poolable: Boolean): Unit = throw NotSupported("setPoolable") + + override def getFetchSize: Int = throw NotSupported("getFetchSize") + + override def setQueryTimeout(seconds: Int): Unit = throw NotSupported("setQueryTimeout") + + override def setFetchDirection(direction: Int): Unit = throw NotSupported("setFetchDirection") + + override def setMaxRows(max: Int): Unit = throw NotSupported("setMaxRows") + + override def setCursorName(name: String): Unit = throw NotSupported("setCursorName") + + override def getFetchDirection: Int = throw NotSupported("getFetchDirection") + + override def getResultSetType: Int = throw NotSupported("getResultSetType") override def getMoreResults: Boolean = throw NotSupported("getMoreResults") override def getMoreResults(current: Int): Boolean = throw NotSupported("getMoreResults") - override def clearWarnings: Unit = throw NotSupported("clearWarnings") + override def addBatch(sql: String): Unit = throw NotSupported("addBatch") - override def getGeneratedKeys: ResultSet = throw NotSupported("getGeneratedKeys") + override def execute(sql: String): Boolean = throw NotSupported("execute") + + override def execute(sql: String, autoGeneratedKeys: Int): Boolean = throw NotSupported("execute") + + override def execute(sql: String, columnIndexes: Array[Int]): Boolean = throw NotSupported("execute") + + override def execute(sql: String, columnNames: Array[String]): Boolean = throw NotSupported("execute") + + override def executeQuery(sql: String): ResultSet = throw NotSupported("executeQuery") + + override def isCloseOnCompletion: Boolean = throw NotSupported("isCloseOnCompletion") + + override def getResultSet: ResultSet = throw NotSupported("getResultSet") + + override def getMaxRows: Int = throw NotSupported("getMaxRows") + + override def setEscapeProcessing(enable: Boolean): Unit = throw NotSupported("setEscapeProcessing") + + override def executeUpdate(sql: String): Int = throw NotSupported("executeUpdate") + + override def executeUpdate(sql: String, autoGeneratedKeys: Int): Int = throw NotSupported("executeUpdate") + + override def executeUpdate(sql: String, columnIndexes: Array[Int]): Int = throw NotSupported("executeUpdate") + + override def executeUpdate(sql: String, columnNames: Array[String]): Int = throw NotSupported("executeUpdate") + + override def getQueryTimeout: Int = throw NotSupported("getQueryTimeout") + + override def getWarnings: SQLWarning = throw NotSupported("getWarnings") + + override def getConnection: Connection = throw NotSupported("getConnection") + + override def setMaxFieldSize(max: Int): Unit = throw NotSupported("setMaxFieldSize") + + override def isPoolable: Boolean = throw NotSupported("isPoolable") + + override def clearBatch: Unit = throw NotSupported("clearBatch") + + override def close: Unit = throw NotSupported("close") override def closeOnCompletion: Unit = throw NotSupported("closeOnCompletion") + override def executeBatch: Array[Int] = throw NotSupported("executeBatch") + + override def getGeneratedKeys: ResultSet = throw NotSupported("getGeneratedKeys") + + override def setFetchSize(rows: Int): Unit = throw NotSupported("setFetchSize") + + override def clearWarnings: Unit = throw NotSupported("clearWarnings") + + override def getResultSetConcurrency: Int = throw NotSupported("getResultSetConcurrency") + + override def isClosed: Boolean = throw NotSupported("isClosed") +} + +class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = 0) extends StatementNotSupported { + + import KsqlStatement._ + + private[this] var currentResultSet: Option[ResultSet] = None + private var maxRows = 0 + override def cancel: Unit = { currentResultSet.map(_.close) currentResultSet = None @@ -46,16 +120,8 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = override def getResultSet: ResultSet = currentResultSet.getOrElse(throw ResultSetError("Result set not initialized.")) - override def isPoolable: Boolean = throw NotSupported("isPoolable") - - override def setPoolable(poolable: Boolean): Unit = throw NotSupported("setPoolable") - - override def setCursorName(name: String): Unit = throw NotSupported("setCursorName") - override def getUpdateCount: Int = -1 - override def addBatch(sql: String): Unit = throw NotSupported("addBatch") - override def getMaxRows: Int = maxRows private def executeKsqlRequest(sql: String): Unit = { @@ -127,48 +193,8 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = override def setMaxRows(max: Int): Unit = if (max < 0) throw InvalidValue("maxRows", max.toString) else maxRows = max - override def getFetchSize: Int = throw NotSupported("getFetchSize") - - override def getResultSetHoldability: Int = throw NotSupported("getResultSetHoldability") - - override def setFetchDirection(direction: Int): Unit = throw NotSupported("setFetchDirection") - - override def getFetchDirection: Int = throw NotSupported("getFetchDirection") - - override def getResultSetConcurrency: Int = throw NotSupported("getResultSetConcurrency") - - override def clearBatch: Unit = throw NotSupported("clearBatch") - - override def close: Unit = throw NotSupported("close") - - override def isClosed: Boolean = throw NotSupported("isClosed") - - override def executeUpdate(sql: String): Int = throw NotSupported("executeUpdate") - - override def executeUpdate(sql: String, autoGeneratedKeys: Int): Int = throw NotSupported("executeUpdate") - - override def executeUpdate(sql: String, columnIndexes: Array[Int]): Int = throw NotSupported("executeUpdate") - - override def executeUpdate(sql: String, columnNames: Array[String]): Int = throw NotSupported("executeUpdate") - - override def getQueryTimeout: Int = throw NotSupported("getQueryTimeout") - override def getWarnings: SQLWarning = None.orNull - override def setFetchSize(rows: Int): Unit = throw NotSupported("setFetchSize") - - override def setQueryTimeout(seconds: Int): Unit = throw NotSupported("setQueryTimeout") - - override def executeBatch: Array[Int] = throw NotSupported("executeBatch") - - override def setEscapeProcessing(enable: Boolean): Unit = throw NotSupported("setEscapeProcessing") - - override def getConnection: Connection = throw NotSupported("getConnection") - - override def getMaxFieldSize: Int = throw NotSupported("getMaxFieldSize") - - override def isCloseOnCompletion: Boolean = throw NotSupported("isCloseOnCompletion") - private def fixSql(sql: String): String = { Option(sql).filter(_.trim.nonEmpty).map(s => if (s.trim.last == ';') s else s + ";").getOrElse("") } diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala index d98a097..3fdd931 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/ResultSet.scala @@ -38,7 +38,7 @@ private[resultset] class ResultSetNotSupported extends ResultSet with WrapperNot override def updateNString(columnLabel: String, nString: String): Unit = throw NotSupported("updateNString") - override def clearWarnings(): Unit = throw NotSupported("clearWarnings") + override def clearWarnings: Unit = throw NotSupported("clearWarnings") override def updateTimestamp(columnIndex: Int, x: Timestamp): Unit = throw NotSupported("updateTimestamp") @@ -70,7 +70,7 @@ private[resultset] class ResultSetNotSupported extends ResultSet with WrapperNot override def getBinaryStream(columnLabel: String): InputStream = throw NotSupported("getBinaryStream") - override def beforeFirst(): Unit = throw NotSupported("beforeFirst") + override def beforeFirst: Unit = throw NotSupported("beforeFirst") override def updateNCharacterStream(columnIndex: Int, x: Reader, length: Long): Unit = throw NotSupported("updateNCharacterStream") @@ -96,7 +96,7 @@ private[resultset] class ResultSetNotSupported extends ResultSet with WrapperNot override def updateNClob(columnLabel: String, reader: Reader): Unit = throw NotSupported("updateNClob") - override def last(): Boolean = throw NotSupported("last") + override def last: Boolean = throw NotSupported("last") override def isLast: Boolean = throw NotSupported("isLast") @@ -138,9 +138,9 @@ private[resultset] class ResultSetNotSupported extends ResultSet with WrapperNot override def getURL(columnLabel: String): URL = throw NotSupported("getURL") - override def updateRow(): Unit = throw NotSupported("updateRow") + override def updateRow: Unit = throw NotSupported("updateRow") - override def insertRow(): Unit = throw NotSupported("insertRow") + override def insertRow: Unit = throw NotSupported("insertRow") override def getMetaData: ResultSetMetaData = throw NotSupported("getMetaData") @@ -166,9 +166,9 @@ private[resultset] class ResultSetNotSupported extends ResultSet with WrapperNot override def getRowId(columnLabel: String): RowId = throw NotSupported("getRowId") - override def moveToInsertRow(): Unit = throw NotSupported("moveToInsertRow") + override def moveToInsertRow: Unit = throw NotSupported("moveToInsertRow") - override def rowInserted(): Boolean = throw NotSupported("rowInserted") + override def rowInserted: Boolean = throw NotSupported("rowInserted") override def getFloat(columnIndex: Int): Float = throw NotSupported("getFloat") From 40b31cbcc51c0b095bcad0ef9a91fee22a368c76 Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sat, 23 Feb 2019 18:10:49 -0600 Subject: [PATCH 40/47] Upgrade to Kafka 2.1.1 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index e7795e8..d916269 100644 --- a/build.sbt +++ b/build.sbt @@ -13,7 +13,7 @@ resolvers += "Confluent Snapshots Maven Repo" at "https://s3-us-west-2.amazonaws resolvers += Resolver.mavenLocal libraryDependencies += "io.confluent.ksql" % "ksql-rest-app" % "5.1.2" -libraryDependencies += "org.apache.kafka" %% "kafka" % "2.1.0" % "test" +libraryDependencies += "org.apache.kafka" %% "kafka" % "2.1.1" % "test" libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % "test" libraryDependencies += "org.scalamock" %% "scalamock-scalatest-support" % "3.6.0" % "test" libraryDependencies += "javax.ws.rs" % "javax.ws.rs-api" % "2.1.1" artifacts (Artifact("javax.ws.rs-api", "jar", "jar")) From a7ddf9f61e040be726f96c6390a6c6291979c74f Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sat, 23 Feb 2019 18:15:23 -0600 Subject: [PATCH 41/47] Test refactor --- .../jdbc/embedded/EmbeddedKsqlEngine.scala | 2 +- .../ksql/jdbc/KsqlConnectionSpec.scala | 24 +++++++++++---- .../ksql/jdbc/KsqlDatabaseMetaDataSpec.scala | 30 ++----------------- .../ksql/jdbc/KsqlStatementSpec.scala | 24 ++++++++++++--- .../resultset/KsqlResultSetMetaDataSpec.scala | 7 ++--- .../jdbc/resultset/KsqlResultSetSpec.scala | 11 +++---- .../mmolimar/ksql/jdbc/utils/TestUtils.scala | 12 +++++--- 7 files changed, 57 insertions(+), 53 deletions(-) diff --git a/src/it/scala/com/github/mmolimar/ksql/jdbc/embedded/EmbeddedKsqlEngine.scala b/src/it/scala/com/github/mmolimar/ksql/jdbc/embedded/EmbeddedKsqlEngine.scala index 0e1e4b9..8ff400d 100644 --- a/src/it/scala/com/github/mmolimar/ksql/jdbc/embedded/EmbeddedKsqlEngine.scala +++ b/src/it/scala/com/github/mmolimar/ksql/jdbc/embedded/EmbeddedKsqlEngine.scala @@ -27,7 +27,7 @@ class EmbeddedKsqlEngine(brokerList: String, port: Int = TestUtils.getAvailableP import java.util.function.{Function => JFunction, Supplier => JSupplier} implicit def toJavaSupplier[A](f: Function0[A]) = new JSupplier[A] { - override def get(): A = f() + override def get: A = f() } implicit def toJavaFunction[A, B](f: Function1[A, B]) = new JFunction[A, B] { diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlConnectionSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlConnectionSpec.scala index 2a1b8e1..97943d6 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlConnectionSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlConnectionSpec.scala @@ -12,10 +12,6 @@ import org.scalatest.{Matchers, WordSpec} class KsqlConnectionSpec extends WordSpec with Matchers with MockFactory { - val implementedMethods = Seq("createStatement", "getAutoCommit", "getTransactionIsolation", - "setClientInfo", "isReadOnly", "isClosed", "isValid", "close", "commit", "getMetaData", "getWarnings", - "setAutoCommit", "getCatalog", "setCatalog") - "A KsqlConnection" when { "validating specs" should { @@ -26,7 +22,8 @@ class KsqlConnectionSpec extends WordSpec with Matchers with MockFactory { } "throw not supported exception if not supported" in { - reflectMethods[KsqlConnection](implementedMethods, false, ksqlConnection) + val methods = implementedMethods[KsqlConnection] + reflectMethods[KsqlConnection](methods, false, ksqlConnection) .foreach(method => { assertThrows[SQLFeatureNotSupportedException] { method() @@ -80,4 +77,21 @@ class KsqlConnectionSpec extends WordSpec with Matchers with MockFactory { } } + "A ConnectionNotSupported" when { + + "validating specs" should { + + "throw not supported exception if not supported" in { + + val resultSet = new ConnectionNotSupported + reflectMethods[ConnectionNotSupported](Seq.empty, false, resultSet) + .foreach(method => { + assertThrows[SQLFeatureNotSupportedException] { + method() + } + }) + } + } + } + } diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala index 1db4741..a3e2fe2 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala @@ -17,31 +17,6 @@ import scala.collection.JavaConverters._ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory with OneInstancePerTest { "A KsqlDatabaseMetaData" when { - val implementedMethods = Seq("allProceduresAreCallable", "allTablesAreSelectable", - "getMaxConnections", "getIdentifierQuoteString", "getIdentifierQuoteString", "getSearchStringEscape", - "getExtraNameCharacters", "getDatabaseProductName", "getDatabaseMajorVersion", "getDatabaseMinorVersion", - "getDatabaseProductVersion", "getDriverName", "getDriverVersion", "getDriverMajorVersion", - "getDriverMinorVersion", "getJDBCMajorVersion", "getJDBCMinorVersion", "getConnection", "getCatalogs", - "getCatalogTerm", "getMaxStatements", "getMaxStatementLength", "getTableTypes", "getTables", "getTypeInfo", - "getSchemas", "getSuperTables", "getUDTs", "getColumns", "getURL", "isReadOnly", "getSQLKeywords", "getProcedures", - "getNumericFunctions", "getSchemaTerm", "getStringFunctions", "getSystemFunctions", "getTimeDateFunctions", - "getProcedureTerm", - "nullsAreSortedHigh", "nullsAreSortedLow", "nullsAreSortedAtStart", "nullsAreSortedAtEnd", "nullPlusNonNullIsNull", - "usesLocalFiles", "usesLocalFilePerTable", - "supportsAlterTableWithAddColumn", "supportsAlterTableWithDropColumn", - "supportsCatalogsInDataManipulation", "supportsCatalogsInTableDefinitions", "supportsCatalogsInProcedureCalls", - "supportsMultipleResultSets", "supportsMultipleTransactions", "supportsSavepoints", - "supportsSchemasInDataManipulation", "supportsSchemasInTableDefinitions", - "supportsStoredFunctionsUsingCallSyntax", "supportsStoredProcedures", "storesUpperCaseIdentifiers", - "storesLowerCaseIdentifiers", "storesMixedCaseIdentifiers", "storesUpperCaseQuotedIdentifiers", - "storesLowerCaseQuotedIdentifiers", "storesMixedCaseQuotedIdentifiers", "supportsMixedCaseQuotedIdentifiers", - "supportsColumnAliasing", "supportsMixedCaseIdentifiers", "supportsConvert", "supportsTableCorrelationNames", - "supportsDifferentTableCorrelationNames", "supportsExpressionsInOrderBy", "supportsExtendedSQLGrammar", - "supportsGroupBy", "supportsOrderByUnrelated", "supportsGroupByUnrelated", "supportsGroupByBeyondSelect", - "supportsLikeEscapeClause", "supportsNonNullableColumns", "supportsMinimumSQLGrammar", "supportsCoreSQLGrammar", - "supportsExtendedSQLGrammar", "supportsOuterJoins", "supportsFullOuterJoins", - "supportsLimitedOuterJoins", "supportsUnion", "supportsUnionAll", "supportsTransactions" - ) val mockResponse = mock[Response] val mockedKsqlRestClient = mock[MockableKsqlRestClient] @@ -60,7 +35,8 @@ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory w .returns(RestResponse.successful[KsqlRestClient.QueryStream](mockQueryStream(mockResponse))) .anyNumberOfTimes - reflectMethods[KsqlDatabaseMetaData](implementedMethods, false, metadata) + val methods = implementedMethods[KsqlDatabaseMetaData] + reflectMethods[KsqlDatabaseMetaData](methods, false, metadata) .foreach(method => { assertThrows[SQLFeatureNotSupportedException] { method() @@ -71,7 +47,7 @@ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory w "work if implemented" in { val specialMethods = Set("getTables", "getColumns", "getNumericFunctions", "getStringFunctions", "getSystemFunctions", "getTimeDateFunctions") - val methods = implementedMethods + val methods = implementedMethods[KsqlDatabaseMetaData] .filterNot(specialMethods.contains(_)) reflectMethods[KsqlDatabaseMetaData](methods, true, metadata) diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala index 1a2d471..43ad9ec 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala @@ -20,9 +20,6 @@ import scala.collection.JavaConverters._ class KsqlStatementSpec extends WordSpec with Matchers with MockFactory with OneInstancePerTest { - val implementedMethods = Seq("execute", "executeQuery", "getResultSet", "getUpdateCount", "getResultSetType", - "getWarnings", "getMaxRows", "cancel", "setMaxRows") - "A KsqlStatement" when { val mockResponse = mock[Response] @@ -38,7 +35,8 @@ class KsqlStatementSpec extends WordSpec with Matchers with MockFactory with One .returns(RestResponse.successful[KsqlRestClient.QueryStream](mockQueryStream(mockResponse))) .noMoreThanOnce - reflectMethods[KsqlStatement](implementedMethods, false, statement) + val methods = implementedMethods[KsqlStatement] + reflectMethods[KsqlStatement](methods, false, statement) .foreach(method => { assertThrows[SQLFeatureNotSupportedException] { method() @@ -287,4 +285,22 @@ class KsqlStatementSpec extends WordSpec with Matchers with MockFactory with One } } } + + "A StatementNotSupported" when { + + "validating specs" should { + + "throw not supported exception if not supported" in { + + val resultSet = new StatementNotSupported + reflectMethods[StatementNotSupported](Seq.empty, false, resultSet) + .foreach(method => { + assertThrows[SQLFeatureNotSupportedException] { + method() + } + }) + } + } + } + } diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaDataSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaDataSpec.scala index 9218e1c..c9be697 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaDataSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaDataSpec.scala @@ -10,10 +10,6 @@ import org.scalatest.{Matchers, OneInstancePerTest, WordSpec} class KsqlResultSetMetaDataSpec extends WordSpec with Matchers with MockFactory with OneInstancePerTest { - val implementedMethods = Seq("getColumnLabel", "getColumnName", - "getColumnTypeName", "getColumnClassName", "isCaseSensitive", "getColumnType", - "getColumnCount", "getPrecision", "getScale", "getColumnDisplaySize", "isNullable") - "A KsqlResultSetMetaData" when { "validating specs" should { @@ -33,7 +29,8 @@ class KsqlResultSetMetaDataSpec extends WordSpec with Matchers with MockFactory "throw not supported exception if not supported" in { - reflectMethods[KsqlResultSetMetaData](implementedMethods, false, resultSet) + val methods = implementedMethods[KsqlResultSetMetaData] + reflectMethods[KsqlResultSetMetaData](methods, false, resultSet) .foreach(method => { assertThrows[SQLFeatureNotSupportedException] { method() diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala index 0fca351..9bb3987 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetSpec.scala @@ -15,15 +15,14 @@ import scala.collection.JavaConverters._ class KsqlResultSetSpec extends WordSpec with Matchers with MockFactory with OneInstancePerTest { "A IteratorResultSet" when { - val implementedMethods = Seq("next", "getString", "getBytes", "getByte", "getBytes", "getBoolean", "getShort", - "getInt", "getLong", "getFloat", "getDouble", "getMetaData", "close", "getWarnings", "wasNull") "validating specs" should { "throw not supported exception if not supported" in { val resultSet = new IteratorResultSet[String](List.empty[HeaderField], 0, Iterator.empty) - reflectMethods[IteratorResultSet[String]](implementedMethods, false, resultSet) + val methods = implementedMethods[IteratorResultSet[String]] ++ implementedMethods[AbstractResultSet[String]] + reflectMethods[IteratorResultSet[String]](methods, false, resultSet) .foreach(method => { assertThrows[SQLFeatureNotSupportedException] { method() @@ -58,9 +57,6 @@ class KsqlResultSetSpec extends WordSpec with Matchers with MockFactory with One } "A StreamedResultSet" when { - val implementedMethods = Seq("isLast", "isAfterLast", "isBeforeFirst", "isFirst", "next", - "getConcurrency", "close", "getString", "getBytes", "getByte", "getBytes", "getBoolean", "getShort", "getInt", - "getLong", "getFloat", "getDouble", "getMetaData", "getResultSet", "getUpdateCount", "getWarnings", "wasNull") "validating specs" should { @@ -80,7 +76,8 @@ class KsqlResultSetSpec extends WordSpec with Matchers with MockFactory with One "throw not supported exception if not supported" in { val resultSet = new StreamedResultSet(resultSetMetadata, mock[KsqlQueryStream], 0) - reflectMethods[StreamedResultSet](implementedMethods, false, resultSet) + val methods = implementedMethods[StreamedResultSet] ++ implementedMethods[AbstractResultSet[StreamedRow]] + reflectMethods[StreamedResultSet](methods, false, resultSet) .foreach(method => { assertThrows[SQLFeatureNotSupportedException] { try { diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/utils/TestUtils.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/utils/TestUtils.scala index 9bf7c71..6242b56 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/utils/TestUtils.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/utils/TestUtils.scala @@ -7,6 +7,8 @@ import java.nio.channels.ServerSocketChannel import java.util import java.util.{Properties, Random, UUID} +import _root_.io.confluent.ksql.rest.client.KsqlRestClient +import javax.ws.rs.client.Client import javax.ws.rs.core.Response import kafka.utils.Logging import kafka.zk.KafkaZkClient @@ -18,8 +20,6 @@ import org.apache.kafka.common.utils.Time import scala.reflect.runtime.universe._ import scala.reflect.{ClassTag, _} -import _root_.io.confluent.ksql.rest.client.KsqlRestClient -import javax.ws.rs.client.Client object TestUtils extends Logging { @@ -143,7 +143,11 @@ object TestUtils extends Logging { }).head.newInstance(mockResponse).asInstanceOf[KsqlRestClient.QueryStream] } - def reflectMethods[T <: AnyRef](implementedMethods: Seq[String], implemented: Boolean, + def implementedMethods[T <: AnyRef](implicit ct: ClassTag[T]): Seq[String] = { + ct.runtimeClass.getMethods.filter(_.getDeclaringClass == ct.runtimeClass).map(_.getName) + } + + def reflectMethods[T <: AnyRef](methods: Seq[String], implemented: Boolean, obj: T)(implicit tt: TypeTag[T], ct: ClassTag[T]): Seq[() => Any] = { val ksqlPackage = "com.github.mmolimar.ksql" @@ -154,7 +158,7 @@ object TestUtils extends Logging { declarations.flatten .filter(_.overrides.nonEmpty) - .filter(ms => implementedMethods.contains(ms.name.toString) == implemented) + .filter(ms => methods.contains(ms.name.toString) == implemented) .map(_.asMethod) .filter(!_.isProtected) .map(m => { From 3d4ec122ac2c02cb64a05f3b7466c2c720fc92aa Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sat, 23 Feb 2019 20:23:18 -0600 Subject: [PATCH 42/47] Support for closing statements --- .../mmolimar/ksql/jdbc/Exceptions.scala | 3 ++ .../mmolimar/ksql/jdbc/KsqlStatement.scala | 10 +++++ .../ksql/jdbc/KsqlStatementSpec.scala | 42 +++++++++++-------- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala index 3ebd22e..14fceb1 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/Exceptions.scala @@ -34,6 +34,9 @@ case class InvalidValue(prop: String, value: String, override val cause: Throwab override val message = s"value '' is not valid for property: $prop." } +case class AlreadyClosed(override val message: String = "Already closed.", + override val cause: Throwable = None.orNull) extends KsqlException + case class KsqlQueryError(override val message: String = "Error executing query.", override val cause: Throwable = None.orNull) extends KsqlException diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala index ee8fcb7..23f2b2e 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala @@ -112,12 +112,20 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = private[this] var currentResultSet: Option[ResultSet] = None private var maxRows = 0 + private var closed = false override def cancel: Unit = { currentResultSet.map(_.close) currentResultSet = None } + override def close: Unit = { + cancel + closed = true + } + + override def isClosed: Boolean = closed + override def getResultSet: ResultSet = currentResultSet.getOrElse(throw ResultSetError("Result set not initialized.")) override def getUpdateCount: Int = -1 @@ -125,6 +133,8 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = override def getMaxRows: Int = maxRows private def executeKsqlRequest(sql: String): Unit = { + if (closed) throw AlreadyClosed("Statement already closed.") + currentResultSet = None val fixedSql = fixSql(sql) val stmt = Try(ksqlParser.getStatements(fixedSql)) match { diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala index 43ad9ec..f1a4d7b 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala @@ -107,23 +107,6 @@ class KsqlStatementSpec extends WordSpec with Matchers with MockFactory with One Map.empty[String, AnyRef].asJava ) - assertThrows[SQLException] { - (mockedKsqlRestClient.makeQueryRequest _).expects(*) - .returns(RestResponse.successful[KsqlRestClient.QueryStream](mockQueryStream(mockResponse))) - .once - val multipleResults = new KsqlEntityList - multipleResults.add(new QueryDescriptionEntity("select * from test;", queryDesc)) - multipleResults.add(new QueryDescriptionEntity("select * from test;", queryDesc)) - (mockedKsqlRestClient.makeKsqlRequest _).expects(*) - .returns(RestResponse.successful[KsqlEntityList](multipleResults)) - .once - statement.execute("select * from test") - } - assertThrows[SQLException] { - statement.getResultSet - } - statement.cancel - val entityList = new KsqlEntityList entityList.add(new QueryDescriptionEntity("select * from test;", queryDesc)) @@ -154,6 +137,31 @@ class KsqlStatementSpec extends WordSpec with Matchers with MockFactory with One statement.getUpdateCount should be(-1) statement.getResultSetType should be(ResultSet.TYPE_FORWARD_ONLY) statement.getWarnings should be(None.orNull) + + assertThrows[SQLException] { + (mockedKsqlRestClient.makeQueryRequest _).expects(*) + .returns(RestResponse.successful[KsqlRestClient.QueryStream](mockQueryStream(mockResponse))) + .once + val multipleResults = new KsqlEntityList + multipleResults.add(new QueryDescriptionEntity("select * from test;", queryDesc)) + multipleResults.add(new QueryDescriptionEntity("select * from test;", queryDesc)) + (mockedKsqlRestClient.makeKsqlRequest _).expects(*) + .returns(RestResponse.successful[KsqlEntityList](multipleResults)) + .once + statement.execute("select * from test") + } + assertThrows[SQLException] { + statement.getResultSet + } + statement.cancel + + statement.isClosed should be(false) + statement.close + statement.close + statement.isClosed should be(true) + assertThrows[SQLException] { + statement.executeQuery("select * from test;") + } } "work when printing topics" in { From 1454a456f8e0b41ab7b3b2169471e69c4c00269d Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sun, 24 Feb 2019 10:14:28 -0600 Subject: [PATCH 43/47] Adding more integration tests --- .../ksql/jdbc/KsqlDriverIntegrationTest.scala | 205 +++++++++++++++--- 1 file changed, 174 insertions(+), 31 deletions(-) diff --git a/src/it/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverIntegrationTest.scala b/src/it/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverIntegrationTest.scala index 3d00d87..2e4b230 100644 --- a/src/it/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverIntegrationTest.scala +++ b/src/it/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverIntegrationTest.scala @@ -1,9 +1,10 @@ package com.github.mmolimar.ksql.jdbc -import java.sql.{Connection, DriverManager, SQLException, Types} +import java.sql.{Connection, DriverManager, ResultSet, SQLException, Types} import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean +import com.github.mmolimar.ksql.jdbc.KsqlEntityHeaders._ import com.github.mmolimar.ksql.jdbc.embedded.{EmbeddedKafkaCluster, EmbeddedKsqlEngine, EmbeddedZookeeperServer} import com.github.mmolimar.ksql.jdbc.utils.TestUtils import org.apache.kafka.clients.producer.ProducerRecord @@ -26,15 +27,48 @@ class KsqlDriverIntegrationTest extends WordSpec with Matchers with BeforeAndAft "A KsqlConnection" when { - "creating a TABLE" should { + "managing a TABLE" should { + + val maxRecords = 5 + val table = TestUtils.randomString() + + "create the table properly" in { + val resultSet = createTestTableOrStream(table) + resultSet.next should be(true) + resultSet.getString(commandStatusEntity(0).name) should be("TABLE") + resultSet.getString(commandStatusEntity(1).name) should be(table.toUpperCase) + resultSet.getString(commandStatusEntity(2).name) should be("CREATE") + resultSet.getString(commandStatusEntity(3).name) should be("SUCCESS") + resultSet.getString(commandStatusEntity(4).name) should be("Table created") + resultSet.next should be(false) + resultSet.close + } + + "list the table already created" in { + val resultSet = ksqlConnection.createStatement.executeQuery(s"SHOW TABLES") + resultSet.next should be(true) + resultSet.getString(tablesListEntity(0).name) should be(table.toUpperCase) + resultSet.getString(tablesListEntity(1).name) should be(topic) + resultSet.getString(tablesListEntity(2).name) should be("JSON") + resultSet.getBoolean(tablesListEntity(3).name) should be(false) + resultSet.next should be(false) + resultSet.close + } + + "be able to get the execution plan for a query in a table" in { + val resultSet = ksqlConnection.createStatement.executeQuery(s"EXPLAIN SELECT * FROM $table") + resultSet.next should be(true) + resultSet.getString(queryDescriptionEntity(1).name) should be("ROWTIME, ROWKEY, FIELD1, FIELD2, FIELD3") + resultSet.getString(queryDescriptionEntity(2).name) should be(table.toUpperCase) + resultSet.next should be(false) + resultSet.close + } "be able to query all fields in the table" in { var counter = 0 - val maxRecords = 5 - val table = TestUtils.randomString() - createTestTableOrStream(table) - - val resultSet = ksqlConnection.createStatement.executeQuery(s"SELECT * FROM $table LIMIT $maxRecords") + val statement = ksqlConnection.createStatement + statement.setMaxRows(maxRecords) + val resultSet = statement.executeQuery(s"SELECT * FROM $table") while (resultSet.next) { resultSet.getLong(1) should not be (-1) Option(resultSet.getString(2)) should not be (None) @@ -48,14 +82,20 @@ class KsqlDriverIntegrationTest extends WordSpec with Matchers with BeforeAndAft } counter should be(maxRecords) + resultSet.close + statement.close + + val metadata = resultSet.getMetaData + metadata.getColumnCount should be(5) + metadata.getColumnName(1) should be("ROWTIME") + metadata.getColumnName(2) should be("ROWKEY") + metadata.getColumnName(3) should be("FIELD1") + metadata.getColumnName(4) should be("FIELD2") + metadata.getColumnName(5) should be("FIELD3") } - "be able to query one field in the table" in { + "be able to query one field in the table and get its metadata" in { var counter = 0 - val maxRecords = 5 - val table = TestUtils.randomString() - createTestTableOrStream(table) - val resultSet = ksqlConnection.createStatement.executeQuery(s"SELECT FIELD3 FROM $table LIMIT $maxRecords") while (resultSet.next) { resultSet.getString(1) should be("lorem ipsum") @@ -66,12 +106,13 @@ class KsqlDriverIntegrationTest extends WordSpec with Matchers with BeforeAndAft } counter should be(maxRecords) + val metadata = resultSet.getMetaData + metadata.getColumnCount should be(1) + metadata.getColumnName(1) should be("FIELD3") + } "be able to get the metadata for this table" in { - val table = TestUtils.randomString() - createTestTableOrStream(table) - var resultSet = ksqlConnection.getMetaData.getTables("", "", table, TableTypes.tableTypes.map(_.name).toArray) while (resultSet.next) { resultSet.getString("TABLE_NAME") should be(table.toUpperCase) @@ -117,17 +158,73 @@ class KsqlDriverIntegrationTest extends WordSpec with Matchers with BeforeAndAft } } } + + "describe the table" in { + val resultSet = ksqlConnection.createStatement.executeQuery(s"DESCRIBE $table") + resultSet.next should be(true) + resultSet.getMetaData.getColumnCount should be(sourceDescriptionEntity.size) + resultSet.getString(sourceDescriptionEntity(1).name) should be(table.toUpperCase) + resultSet.getString(sourceDescriptionEntity(2).name) should be(topic) + resultSet.getString(sourceDescriptionEntity(3).name) should be("TABLE") + resultSet.getString(sourceDescriptionEntity(4).name) should be("JSON") + resultSet.next should be(false) + resultSet.close + } + + "drop the table" in { + val resultSet = ksqlConnection.createStatement.executeQuery(s"DROP TABLE $table") + resultSet.next should be(true) + resultSet.getString(commandStatusEntity(0).name) should be("TABLE") + resultSet.getString(commandStatusEntity(1).name) should be(table.toUpperCase) + resultSet.getString(commandStatusEntity(2).name) should be("DROP") + resultSet.getString(commandStatusEntity(3).name) should be("SUCCESS") + resultSet.getString(commandStatusEntity(4).name) should be(s"Source ${table.toUpperCase} was dropped. ") + resultSet.next should be(false) + resultSet.close + } } - "creating a STREAM" should { + "managing a STREAM" should { + + val maxRecords = 5 + val stream = TestUtils.randomString() + + "create the stream properly" in { + val resultSet = createTestTableOrStream(stream, true) + resultSet.next should be(true) + resultSet.getString(commandStatusEntity(0).name) should be("STREAM") + resultSet.getString(commandStatusEntity(1).name) should be(stream.toUpperCase) + resultSet.getString(commandStatusEntity(2).name) should be("CREATE") + resultSet.getString(commandStatusEntity(3).name) should be("SUCCESS") + resultSet.getString(commandStatusEntity(4).name) should be("Stream created") + resultSet.next should be(false) + resultSet.close + } + + "list the stream already created" in { + val resultSet = ksqlConnection.createStatement.executeQuery(s"SHOW STREAMS") + resultSet.next should be(true) + resultSet.getString(streamsListEntity(0).name) should be(stream.toUpperCase) + resultSet.getString(streamsListEntity(1).name) should be(topic) + resultSet.getString(streamsListEntity(2).name) should be("JSON") + resultSet.next should be(false) + resultSet.close + } + + "be able to get the execution plan for a query in a stream" in { + val resultSet = ksqlConnection.createStatement.executeQuery(s"EXPLAIN SELECT * FROM $stream") + resultSet.next should be(true) + resultSet.getString(queryDescriptionEntity(1).name) should be("ROWTIME, ROWKEY, FIELD1, FIELD2, FIELD3") + resultSet.getString(queryDescriptionEntity(2).name) should be(stream.toUpperCase) + resultSet.next should be(false) + resultSet.close + } "be able to query all fields in the stream" in { var counter = 0 - val maxRecords = 5 - val stream = TestUtils.randomString() - createTestTableOrStream(stream, true) - - val resultSet = ksqlConnection.createStatement.executeQuery(s"SELECT * FROM $stream LIMIT $maxRecords") + val statement = ksqlConnection.createStatement + statement.setMaxRows(maxRecords) + val resultSet = statement.executeQuery(s"SELECT * FROM $stream") while (resultSet.next) { resultSet.getLong(1) should not be (-1) Option(resultSet.getString(2)) should not be (None) @@ -141,14 +238,20 @@ class KsqlDriverIntegrationTest extends WordSpec with Matchers with BeforeAndAft } counter should be(maxRecords) + resultSet.close + statement.close + + val metadata = resultSet.getMetaData + metadata.getColumnCount should be(5) + metadata.getColumnName(1) should be("ROWTIME") + metadata.getColumnName(2) should be("ROWKEY") + metadata.getColumnName(3) should be("FIELD1") + metadata.getColumnName(4) should be("FIELD2") + metadata.getColumnName(5) should be("FIELD3") } "be able to query one field in the stream" in { var counter = 0 - val maxRecords = 5 - val stream = TestUtils.randomString() - createTestTableOrStream(stream, true) - val resultSet = ksqlConnection.createStatement.executeQuery(s"SELECT FIELD3 FROM $stream LIMIT $maxRecords") while (resultSet.next) { resultSet.getString(1) should be("lorem ipsum") @@ -159,12 +262,12 @@ class KsqlDriverIntegrationTest extends WordSpec with Matchers with BeforeAndAft } counter should be(maxRecords) + val metadata = resultSet.getMetaData + metadata.getColumnCount should be(1) + metadata.getColumnName(1) should be("FIELD3") } "be able to get the metadata for this stream" in { - val stream = TestUtils.randomString() - createTestTableOrStream(stream, true) - var resultSet = ksqlConnection.getMetaData.getTables("", "", stream, TableTypes.tableTypes.map(_.name).toArray) while (resultSet.next) { resultSet.getString("TABLE_NAME") should be(stream.toUpperCase) @@ -210,6 +313,46 @@ class KsqlDriverIntegrationTest extends WordSpec with Matchers with BeforeAndAft } } } + + "describe the stream" in { + val resultSet = ksqlConnection.createStatement.executeQuery(s"DESCRIBE $stream") + resultSet.next should be(true) + resultSet.getMetaData.getColumnCount should be(sourceDescriptionEntity.size) + resultSet.getString(sourceDescriptionEntity(1).name) should be(stream.toUpperCase) + resultSet.getString(sourceDescriptionEntity(2).name) should be(topic) + resultSet.getString(sourceDescriptionEntity(3).name) should be("STREAM") + resultSet.getString(sourceDescriptionEntity(4).name) should be("JSON") + resultSet.next should be(false) + resultSet.close + } + + "drop the stream" in { + val resultSet = ksqlConnection.createStatement.executeQuery(s"DROP STREAM $stream") + resultSet.next should be(true) + resultSet.getString(commandStatusEntity(0).name) should be("STREAM") + resultSet.getString(commandStatusEntity(1).name) should be(stream.toUpperCase) + resultSet.getString(commandStatusEntity(2).name) should be("DROP") + resultSet.getString(commandStatusEntity(3).name) should be("SUCCESS") + resultSet.getString(commandStatusEntity(4).name) should be(s"Source ${stream.toUpperCase} was dropped. ") + resultSet.next should be(false) + resultSet.close + } + } + + "printing a Kafka topic" should { + + "show the content of that topic" in { + val statement = ksqlConnection.createStatement + statement.setMaxRows(3) + val resultSet = statement.executeQuery(s"PRINT '$topic'") + resultSet.next should be(true) + resultSet.getString(printTopic(0).name) should be("Format:STRING") + resultSet.next should be(true) + resultSet.next should be(true) + resultSet.next should be(false) + resultSet.close + statement.close + } } } @@ -229,10 +372,10 @@ class KsqlDriverIntegrationTest extends WordSpec with Matchers with BeforeAndAft } - private def createTestTableOrStream(str: String, isStream: Boolean = false) = { - ksqlConnection.createStatement.execute(s"CREATE ${if (isStream) "STREAM" else "TABLE"} $str " + + private def createTestTableOrStream(str: String, isStream: Boolean = false): ResultSet = { + ksqlConnection.createStatement.executeQuery(s"CREATE ${if (isStream) "STREAM" else "TABLE"} $str " + s"(FIELD1 INT, FIELD2 DOUBLE, FIELD3 VARCHAR) " + - s"WITH (KAFKA_TOPIC='$topic', VALUE_FORMAT='JSON', KEY='FIELD1');") should be(true) + s"WITH (KAFKA_TOPIC='$topic', VALUE_FORMAT='JSON', KEY='FIELD1');") } override def beforeAll = { From 7f83237f9cdf14a8154640f68d80f10804c1a454 Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Sun, 24 Feb 2019 10:16:14 -0600 Subject: [PATCH 44/47] Updating documentation --- README.md | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 01f6754..5a52ba9 100644 --- a/README.md +++ b/README.md @@ -10,23 +10,14 @@ perform queries to Kafka and then, the engine translates those requests to Kafka ### Building from source ### -First of all, the KSQL lib has to be installed into your local repo. - -So, cloning the KSQL repo: - -``git clone https://github.com/confluentinc/ksql.git && cd ksql && git checkout v5.1.0`` - -and installing it: - -``mvn clean install -Dmaven.test.skip=true`` - -Once you've done that, just clone the ksql-jdbc-driver repo and package it: +Just clone the ksql-jdbc-driver repo and package it: ``git clone https://github.com/mmolimar/ksql-jdbc-driver.git && cd ksql-jdbc-driver`` ``sbt clean package`` -If you want to build a fat jar containing both classes and dependencies needed, type the following: +If you want to build a fat jar containing both classes and dependencies -for instance, to use it in a +JDBC client such as [SQuirrel SQL](http://squirrel-sql.sourceforge.net/) or whichever-, type the following: ``sbt clean assembly`` @@ -64,6 +55,8 @@ where: * **\**: are the custom client properties (optionals). Available properties: * ``secured``: sets if the KSQL connection is secured or not. It's a boolean (``true``|``false``) and its default value is ``false``. + * ``properties``: enables to set in KSQL extra properties from the JDBC URL. It's a boolean (``true``|``false``) + and its default value is ``false``. * ``timeout``: sets the max wait time between each message when receiving them. It's a long and its default value is ``0`` which means that is infinite. From 9a947d16d101fd4be095d879be5e66e97a05b0d1 Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Mon, 25 Feb 2019 23:21:47 -0600 Subject: [PATCH 45/47] Upgrade driver version --- build.sbt | 2 +- .../scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala | 6 +++--- .../com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build.sbt b/build.sbt index d916269..94ce25e 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ name := "ksql-jdbc-driver" -version := "0.3-SNAPSHOT" +version := "1.0-SNAPSHOT" initialize := { assert(Integer.parseInt(sys.props("java.specification.version").split("\\.")(1)) >= 8, "Java 8 or above required") diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala index 9e59ece..0f05026 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDriver.scala @@ -14,8 +14,8 @@ object KsqlDriver { val ksqlPrefix = "jdbc:ksql://" val driverName = "KSQL JDBC driver" - val driverMajorVersion = 0 - val driverMinorVersion = 3 + val driverMajorVersion = 1 + val driverMinorVersion = 0 val driverVersion = s"$driverMajorVersion.$driverMinorVersion" val jdbcMajorVersion = 4 @@ -23,7 +23,7 @@ object KsqlDriver { val ksqlMajorVersion = 5 val ksqlMinorVersion = 1 - val ksqlMicroVersion = 0 + val ksqlMicroVersion = 2 val ksqlVersion = s"$ksqlMajorVersion.$ksqlMinorVersion.$ksqlMicroVersion" private val ksqlServerRegex = "([A-Za-z0-9._%+-]+):([0-9]{1,5})" diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala index d9801c3..3a31f0e 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDriverSpec.scala @@ -21,8 +21,8 @@ class KsqlDriverSpec extends WordSpec with Matchers with MockFactory { driver.jdbcCompliant should be(false) } "have a major and minor version" in { - driver.getMinorVersion should be(3) - driver.getMajorVersion should be(0) + driver.getMinorVersion should be(0) + driver.getMajorVersion should be(1) } "have no properties" in { driver.getPropertyInfo("", new Properties).length should be(0) From ac98e9b455b5efe8210cf582ded4215552df5e82 Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Tue, 26 Feb 2019 00:05:16 -0600 Subject: [PATCH 46/47] New metadata to result sets --- .../ksql/jdbc/KsqlDatabaseMetaData.scala | 2 ++ .../mmolimar/ksql/jdbc/KsqlStatement.scala | 2 ++ .../jdbc/resultset/KsqlResultSetMetaData.scala | 18 ++++++++++++++++++ .../ksql/jdbc/KsqlDatabaseMetaDataSpec.scala | 3 ++- .../mmolimar/ksql/jdbc/KsqlStatementSpec.scala | 1 + .../resultset/KsqlResultSetMetaDataSpec.scala | 13 +++++++++++-- 6 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala index ce18d4b..429dc30 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaData.scala @@ -438,6 +438,8 @@ class KsqlDatabaseMetaData(private val ksqlConnection: KsqlConnection) extends D override def getProcedureTerm: String = "" + override def getResultSetHoldability: Int = ResultSet.HOLD_CURSORS_OVER_COMMIT + override def getUDTs(catalog: String, schemaPattern: String, typeNamePattern: String, types: Array[Int]): ResultSet = new IteratorResultSet(List.empty[HeaderField], 0, Iterator.empty) diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala index 23f2b2e..1306bcc 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/KsqlStatement.scala @@ -201,6 +201,8 @@ class KsqlStatement(private val ksqlClient: KsqlRestClient, val timeout: Long = override def getResultSetType: Int = ResultSet.TYPE_FORWARD_ONLY + override def getResultSetHoldability: Int = ResultSet.HOLD_CURSORS_OVER_COMMIT + override def setMaxRows(max: Int): Unit = if (max < 0) throw InvalidValue("maxRows", max.toString) else maxRows = max override def getWarnings: SQLWarning = None.orNull diff --git a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaData.scala b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaData.scala index 073374a..eaec618 100644 --- a/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaData.scala +++ b/src/main/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaData.scala @@ -70,4 +70,22 @@ class KsqlResultSetMetaData(private[jdbc] val columns: List[HeaderField]) extend override def isNullable(column: Int): Int = ResultSetMetaData.columnNullableUnknown + override def isAutoIncrement(column: Int): Boolean = false + + override def isCurrency(column: Int): Boolean = false + + override def isSearchable(column: Int): Boolean = true + + override def isReadOnly(column: Int): Boolean = true + + override def isWritable(column: Int): Boolean = !isReadOnly(column) + + override def isDefinitelyWritable(column: Int): Boolean = isWritable(column) + + override def isSigned(column: Int): Boolean = getField(column).jdbcType match { + case Types.TINYINT | Types.SMALLINT | Types.INTEGER | + Types.BIGINT | Types.FLOAT | Types.DOUBLE | Types.DECIMAL => true + case _ => false + } + } diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala index a3e2fe2..085adbf 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlDatabaseMetaDataSpec.scala @@ -1,7 +1,7 @@ package com.github.mmolimar.ksql.jdbc import java.io.InputStream -import java.sql.{SQLException, SQLFeatureNotSupportedException} +import java.sql.{ResultSet, SQLException, SQLFeatureNotSupportedException} import java.util.{Collections, Properties} import com.github.mmolimar.ksql.jdbc.utils.TestUtils._ @@ -134,6 +134,7 @@ class KsqlDatabaseMetaDataSpec extends WordSpec with Matchers with MockFactory w metadata.getCatalogTerm should be("TOPIC") metadata.getSchemaTerm should be("") metadata.getProcedureTerm should be("") + metadata.getResultSetHoldability should be(ResultSet.HOLD_CURSORS_OVER_COMMIT) val tableTypes = metadata.getTableTypes tableTypes.next should be(true) diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala index f1a4d7b..6af21a9 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/KsqlStatementSpec.scala @@ -136,6 +136,7 @@ class KsqlStatementSpec extends WordSpec with Matchers with MockFactory with One statement.getUpdateCount should be(-1) statement.getResultSetType should be(ResultSet.TYPE_FORWARD_ONLY) + statement.getResultSetHoldability should be(ResultSet.HOLD_CURSORS_OVER_COMMIT) statement.getWarnings should be(None.orNull) assertThrows[SQLException] { diff --git a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaDataSpec.scala b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaDataSpec.scala index c9be697..a85f25e 100644 --- a/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaDataSpec.scala +++ b/src/test/scala/com/github/mmolimar/ksql/jdbc/resultset/KsqlResultSetMetaDataSpec.scala @@ -89,15 +89,24 @@ class KsqlResultSetMetaDataSpec extends WordSpec with Matchers with MockFactory resultSet.getColumnTypeName(9) should be(KsqlType.STRING.name) resultSet.getColumnDisplaySize(9) should be(9) - resultSet.isCaseSensitive(2) should be(false) - resultSet.isCaseSensitive(5) should be(true) resultSet.getColumnType(3) should be(java.sql.Types.DOUBLE) resultSet.getColumnCount should be(9) resultSet.getPrecision(3) should be(-1) resultSet.getPrecision(2) should be(0) resultSet.getScale(3) should be(-1) resultSet.getScale(4) should be(0) + + resultSet.isCaseSensitive(2) should be(false) + resultSet.isCaseSensitive(5) should be(true) resultSet.isNullable(1) should be(ResultSetMetaData.columnNullableUnknown) + resultSet.isCurrency(5) should be(false) + resultSet.isAutoIncrement(5) should be(false) + resultSet.isSearchable(5) should be(true) + resultSet.isReadOnly(5) should be(true) + resultSet.isWritable(5) should be(false) + resultSet.isDefinitelyWritable(5) should be(false) + resultSet.isSigned(2) should be(true) + resultSet.isSigned(5) should be(false) } } } From 41788c82fc2e54faf1cd4722cd353b481b20bab2 Mon Sep 17 00:00:00 2001 From: Mario Molina Date: Wed, 27 Feb 2019 08:32:12 -0600 Subject: [PATCH 47/47] Release version 1.0 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 94ce25e..c6832fd 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ name := "ksql-jdbc-driver" -version := "1.0-SNAPSHOT" +version := "1.0" initialize := { assert(Integer.parseInt(sys.props("java.specification.version").split("\\.")(1)) >= 8, "Java 8 or above required")