diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3b333b2..675ff60 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -37,13 +37,13 @@ jobs: run: sbt ++2.12.11 compile test:compile - name: Build for Scala 2.13 run: sbt ++2.13.2 compile test:compile - - name: Code coverage report (Scala 2.12) + - name: Code coverage report (Scala 2.13) run: sbt coverage test coverageReport - - name: Archive code coverage report (Scala 2.12) + - name: Archive code coverage report (Scala 2.13) uses: actions/upload-artifact@v1 with: name: code-coverage-report - path: target/scala-2.12/scoverage-report + path: target/scala-2.13/scoverage-report - name: Shutdown dockerized services run: ./services.sh down - name: Push docker images diff --git a/build.sbt b/build.sbt index 89bdd47..f7558f0 100644 --- a/build.sbt +++ b/build.sbt @@ -1,3 +1,5 @@ +import sbt.CrossVersion + /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -16,13 +18,13 @@ */ val circeForScala211Version = "0.11.1" // Only for Scala v2.11 -val circeLatestVersion = "0.12.1" // for Scala v2.12+ +val circeLatestVersion = "0.13.0" // for Scala v2.12+ val mdedetrichVersion = "0.5.0" val scalacticVersion = "3.1.1" val scalatestVersion = "3.1.1" -val typesafeConfigVersion = "1.4.0" +val typesafeConfigVersion = "1.3.3" val typesafeLoggingVersion = "3.9.2" -val akkaHttpVersion = "10.1.11" +val akkaHttpVersion = "10.1.12" val sealerateVersion = "0.0.6" val logbackVersion = "1.2.3" val collectionCompatVersion = "2.1.6" @@ -87,7 +89,7 @@ lazy val commonSettings: Seq[Setting[_]] = Seq( ) ), crossScalaVersions in ThisBuild := Seq("2.11.12", "2.12.11", "2.13.2"), - scalaVersion in ThisBuild := "2.12.11", + scalaVersion in ThisBuild := "2.13.2", scalacOptions ++= Seq(Opts.compile.deprecation, "-Xlint", "-feature"), scalacOptions ++= unusedWarnings(scalaVersion.value), publishArtifact in Test := false, @@ -115,6 +117,9 @@ lazy val root = (project in file(".")) "ch.qos.logback" % "logback-classic" % logbackVersion % Provided, "org.scalactic" %% "scalactic" % scalacticVersion % Test, "org.scalatest" %% "scalatest" % scalatestVersion % Test - ) + ).map(_ exclude ("org.scala-lang", "scala-library")) + ) + .settings( + libraryDependencies ++= scalaVersionSpecificDependencies(scalaVersion.value) + .map(_ exclude ("org.scala-lang", "scala-library")) ) - .settings(libraryDependencies ++= scalaVersionSpecificDependencies(scalaVersion.value)) diff --git a/src/main/scala-2.11/ing/wbaa/druid/client/CirceDecoders.scala b/src/main/scala-2.11/ing/wbaa/druid/client/CirceDecoders.scala index 8e18da5..3826606 100644 --- a/src/main/scala-2.11/ing/wbaa/druid/client/CirceDecoders.scala +++ b/src/main/scala-2.11/ing/wbaa/druid/client/CirceDecoders.scala @@ -19,4 +19,11 @@ package ing.wbaa.druid.client import io.circe.java8.time._ -trait CirceDecoders extends JavaTimeDecoders +trait CirceDecoders extends JavaTimeDecoders { + + protected def mapRightProjection[L, R, R1](either: Either[L,R])(f: R => R1): Either[L,R1] = either match { + case Right(value) => Right(f(value)) + case _ => either.asInstanceOf[Either[L, R1]] + } + +} diff --git a/src/main/scala-2.11/ing/wbaa/druid/sql/SQLQueryFactory.scala b/src/main/scala-2.11/ing/wbaa/druid/sql/SQLQueryFactory.scala new file mode 100644 index 0000000..e16a78e --- /dev/null +++ b/src/main/scala-2.11/ing/wbaa/druid/sql/SQLQueryFactory.scala @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ing.wbaa.druid.sql + +import ing.wbaa.druid.{DruidConfig, SQLQuery, SQLQueryParameter} + +trait SQLQueryFactory { + + protected def createSQLQuery( + sc: StringContext, + parameters: Seq[SQLQueryParameter], + context: Map[String, String], + config: DruidConfig + ): SQLQuery = { + sc.checkLengths(parameters) + val query = sc.parts.map(StringContext.treatEscapes).mkString("?") + SQLQuery(query, context, parameters)(config) + } + +} diff --git a/src/main/scala-2.12/ing/wbaa/druid/client/CirceDecoders.scala b/src/main/scala-2.12/ing/wbaa/druid/client/CirceDecoders.scala index e15fe35..09320c0 100644 --- a/src/main/scala-2.12/ing/wbaa/druid/client/CirceDecoders.scala +++ b/src/main/scala-2.12/ing/wbaa/druid/client/CirceDecoders.scala @@ -17,4 +17,11 @@ package ing.wbaa.druid.client -trait CirceDecoders +trait CirceDecoders { + + protected def mapRightProjection[L, R, R1](either: Either[L,R])(f: R => R1): Either[L,R1] = either match { + case Right(value) => Right(f(value)) + case _ => either.asInstanceOf[Either[L, R1]] + } + +} diff --git a/src/main/scala-2.12/ing/wbaa/druid/sql/SQLQueryFactory.scala b/src/main/scala-2.12/ing/wbaa/druid/sql/SQLQueryFactory.scala new file mode 100644 index 0000000..c715069 --- /dev/null +++ b/src/main/scala-2.12/ing/wbaa/druid/sql/SQLQueryFactory.scala @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ing.wbaa.druid.sql + +import ing.wbaa.druid.{DruidConfig, SQLQuery, SQLQueryParameter} + +trait SQLQueryFactory { + + protected def createSQLQuery( + sc: StringContext, + parameters: Seq[SQLQueryParameter], + context: Map[String, String], + config: DruidConfig + ): SQLQuery = { + sc.checkLengths(parameters) + val query = sc.parts.map(StringContext.treatEscapes).mkString("?") + SQLQuery(query, context, parameters)(config) + } +} diff --git a/src/main/scala-2.13/ing/wbaa/druid/client/CirceDecoders.scala b/src/main/scala-2.13/ing/wbaa/druid/client/CirceDecoders.scala index e15fe35..1a58750 100644 --- a/src/main/scala-2.13/ing/wbaa/druid/client/CirceDecoders.scala +++ b/src/main/scala-2.13/ing/wbaa/druid/client/CirceDecoders.scala @@ -17,4 +17,8 @@ package ing.wbaa.druid.client -trait CirceDecoders +trait CirceDecoders { + + protected def mapRightProjection[L, R, R1](either: Either[L,R])(f: R => R1): Either[L,R1] = either.map(f(_)) + +} diff --git a/src/main/scala-2.13/ing/wbaa/druid/sql/SQLQueryFactory.scala b/src/main/scala-2.13/ing/wbaa/druid/sql/SQLQueryFactory.scala new file mode 100644 index 0000000..2a9b912 --- /dev/null +++ b/src/main/scala-2.13/ing/wbaa/druid/sql/SQLQueryFactory.scala @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ing.wbaa.druid.sql + +import ing.wbaa.druid.{DruidConfig, SQLQuery, SQLQueryParameter} + +trait SQLQueryFactory { + + protected def createSQLQuery( + sc: StringContext, + parameters: Seq[SQLQueryParameter], + context: Map[String, String], + config: DruidConfig + ): SQLQuery = { + StringContext.checkLengths(parameters, sc.parts) + val query = sc.parts.map(StringContext.processEscapes).mkString("?") + SQLQuery(query, context, parameters)(config) + } + +} diff --git a/src/main/scala/ing/wbaa/druid/DruidQuery.scala b/src/main/scala/ing/wbaa/druid/DruidQuery.scala index 5dc374e..86ff214 100644 --- a/src/main/scala/ing/wbaa/druid/DruidQuery.scala +++ b/src/main/scala/ing/wbaa/druid/DruidQuery.scala @@ -339,8 +339,7 @@ case class SQLQuery private[druid] (query: String, val resultFormat = "object" def execute()( - implicit config: DruidConfig = DruidConfig.DefaultConfig, - ec: ExecutionContext = config.client.actorSystem.dispatcher + implicit config: DruidConfig = DruidConfig.DefaultConfig ): Future[DruidSQLResults] = config.client.doQuery[DruidSQLResults](this) diff --git a/src/main/scala/ing/wbaa/druid/SQL.scala b/src/main/scala/ing/wbaa/druid/SQL.scala index 78a9a17..e5169d2 100644 --- a/src/main/scala/ing/wbaa/druid/SQL.scala +++ b/src/main/scala/ing/wbaa/druid/SQL.scala @@ -17,69 +17,17 @@ package ing.wbaa.druid -import java.sql.Timestamp -import java.time.{ Instant, LocalDate, LocalDateTime } +import ing.wbaa.druid.sql.SQLQueryFactory +import ing.wbaa.druid.sql.ParameterConversions -import scala.language.implicitConversions - -object SQL { - - implicit def char2Param(v: Char): SQLQueryParameter = - SQLQueryParameter(SQLQueryParameterType.Char, v.toString) - - implicit def string2Param(v: String): SQLQueryParameter = - SQLQueryParameter(SQLQueryParameterType.Varchar, v) - - implicit def byte2Param(v: Byte): SQLQueryParameter = - SQLQueryParameter(SQLQueryParameterType.Tinyint, v.toString) - - implicit def short2Param(v: Short): SQLQueryParameter = - SQLQueryParameter(SQLQueryParameterType.Smallint, v.toString) - - implicit def int2Param(v: Int): SQLQueryParameter = - SQLQueryParameter(SQLQueryParameterType.Integer, v.toString) - - implicit def long2Param(v: Long): SQLQueryParameter = - SQLQueryParameter(SQLQueryParameterType.Bigint, v.toString) - - implicit def float2Param(v: Float): SQLQueryParameter = - SQLQueryParameter(SQLQueryParameterType.Float, v.toString) - - implicit def double2Param(v: Double): SQLQueryParameter = - SQLQueryParameter(SQLQueryParameterType.Double, v.toString) - - implicit def boolean2Param(v: Boolean): SQLQueryParameter = - SQLQueryParameter(SQLQueryParameterType.Boolean, v.toString) - - implicit def localDate2Param(v: LocalDate)(implicit config: DruidConfig = - DruidConfig.DefaultConfig): SQLQueryParameter = - SQLQueryParameter(SQLQueryParameterType.Date, v.format(config.FormatterDate)) - - implicit def localDateTime2Param( - v: LocalDateTime - )(implicit config: DruidConfig = DruidConfig.DefaultConfig): SQLQueryParameter = - SQLQueryParameter(SQLQueryParameterType.Timestamp, v.format(config.FormatterDateTime)) - - implicit def timestamp2Param(v: Timestamp)(implicit config: DruidConfig = - DruidConfig.DefaultConfig): SQLQueryParameter = - SQLQueryParameter(SQLQueryParameterType.Timestamp, config.FormatterDateTime.format(v.toInstant)) - - implicit def instant2Param( - v: Instant - )(implicit config: DruidConfig = DruidConfig.DefaultConfig): SQLQueryParameter = - SQLQueryParameter(SQLQueryParameterType.Timestamp, config.FormatterDateTime.format(v)) +object SQL extends SQLQueryFactory with ParameterConversions { implicit class StringToSQL(val sc: StringContext) extends AnyVal { def dsql(parameters: SQLQueryParameter*)( implicit context: Map[String, String] = Map.empty, config: DruidConfig = DruidConfig.DefaultConfig - ): SQLQuery = { - sc.checkLengths(parameters) - val query = sc.parts.map(StringContext.treatEscapes).mkString("?") - SQLQuery(query, context, parameters)(config) - } + ): SQLQuery = createSQLQuery(sc, parameters, context, config) } - } diff --git a/src/main/scala/ing/wbaa/druid/client/DruidClient.scala b/src/main/scala/ing/wbaa/druid/client/DruidClient.scala index f3373b0..e606f44 100644 --- a/src/main/scala/ing/wbaa/druid/client/DruidClient.scala +++ b/src/main/scala/ing/wbaa/druid/client/DruidClient.scala @@ -147,19 +147,19 @@ trait DruidResponseHandler extends CirceDecoders { ) } } else { + body .map(_.data.decodeString("UTF-8")) .map { json => queryType match { case QueryType.Scan => - decode[List[DruidScanResults]](json).right - .map(results => DruidScanResponse(results)) + mapRightProjection(decode[List[DruidScanResults]](json))(DruidScanResponse) case QueryType.SQL => - decode[List[Json]](json).right - .map(results => DruidSQLResults(results)) + mapRightProjection(decode[List[Json]](json))(DruidSQLResults) case _ => - decode[List[DruidResult]](json).right - .map(results => DruidResponseTimeseriesImpl(results, queryType)) + mapRightProjection(decode[List[DruidResult]](json))( + results => DruidResponseTimeseriesImpl(results, queryType) + ) } } .map { diff --git a/src/main/scala/ing/wbaa/druid/dql/expressions/Filtering.scala b/src/main/scala/ing/wbaa/druid/dql/expressions/Filtering.scala index c07dd62..bc7b49d 100644 --- a/src/main/scala/ing/wbaa/druid/dql/expressions/Filtering.scala +++ b/src/main/scala/ing/wbaa/druid/dql/expressions/Filtering.scala @@ -80,7 +80,7 @@ class EqLong(dim: Dim, value: Long) extends FilteringExpression { SelectFilter(dim.name, Option(value.toString), dim.extractionFnOpt) override protected[dql] def createHaving: Having = if (dim.extractionFnOpt.isDefined) FilterHaving(this.createFilter) - else EqualToHaving(dim.name, value) + else EqualToHaving(dim.name, value.toDouble) } trait FilterOnlyOperator extends FilteringExpression { diff --git a/src/main/scala/ing/wbaa/druid/sql/ParameterConversions.scala b/src/main/scala/ing/wbaa/druid/sql/ParameterConversions.scala new file mode 100644 index 0000000..995635d --- /dev/null +++ b/src/main/scala/ing/wbaa/druid/sql/ParameterConversions.scala @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ing.wbaa.druid.sql + +import java.sql.Timestamp +import java.time.{ Instant, LocalDate, LocalDateTime } + +import ing.wbaa.druid.{ DruidConfig, SQLQueryParameter, SQLQueryParameterType } +import scala.language.implicitConversions + +trait ParameterConversions { + implicit def char2Param(v: Char): SQLQueryParameter = + SQLQueryParameter(SQLQueryParameterType.Char, v.toString) + + implicit def string2Param(v: String): SQLQueryParameter = + SQLQueryParameter(SQLQueryParameterType.Varchar, v) + + implicit def byte2Param(v: Byte): SQLQueryParameter = + SQLQueryParameter(SQLQueryParameterType.Tinyint, v.toString) + + implicit def short2Param(v: Short): SQLQueryParameter = + SQLQueryParameter(SQLQueryParameterType.Smallint, v.toString) + + implicit def int2Param(v: Int): SQLQueryParameter = + SQLQueryParameter(SQLQueryParameterType.Integer, v.toString) + + implicit def long2Param(v: Long): SQLQueryParameter = + SQLQueryParameter(SQLQueryParameterType.Bigint, v.toString) + + implicit def float2Param(v: Float): SQLQueryParameter = + SQLQueryParameter(SQLQueryParameterType.Float, v.toString) + + implicit def double2Param(v: Double): SQLQueryParameter = + SQLQueryParameter(SQLQueryParameterType.Double, v.toString) + + implicit def boolean2Param(v: Boolean): SQLQueryParameter = + SQLQueryParameter(SQLQueryParameterType.Boolean, v.toString) + + implicit def localDate2Param(v: LocalDate)(implicit config: DruidConfig = + DruidConfig.DefaultConfig): SQLQueryParameter = + SQLQueryParameter(SQLQueryParameterType.Date, v.format(config.FormatterDate)) + + implicit def localDateTime2Param( + v: LocalDateTime + )(implicit config: DruidConfig = DruidConfig.DefaultConfig): SQLQueryParameter = + SQLQueryParameter(SQLQueryParameterType.Timestamp, v.format(config.FormatterDateTime)) + + implicit def timestamp2Param(v: Timestamp)(implicit config: DruidConfig = + DruidConfig.DefaultConfig): SQLQueryParameter = + SQLQueryParameter(SQLQueryParameterType.Timestamp, config.FormatterDateTime.format(v.toInstant)) + + implicit def instant2Param( + v: Instant + )(implicit config: DruidConfig = DruidConfig.DefaultConfig): SQLQueryParameter = + SQLQueryParameter(SQLQueryParameterType.Timestamp, config.FormatterDateTime.format(v)) +} diff --git a/src/test/scala/ing/wbaa/druid/DruidClientSpec.scala b/src/test/scala/ing/wbaa/druid/DruidClientSpec.scala index f995f6b..1e6ea9c 100644 --- a/src/test/scala/ing/wbaa/druid/DruidClientSpec.scala +++ b/src/test/scala/ing/wbaa/druid/DruidClientSpec.scala @@ -23,7 +23,6 @@ import akka.http.scaladsl.model.headers.RawHeader import akka.http.scaladsl.model.{ HttpProtocols, StatusCodes } import ing.wbaa.druid.client.{ DruidHttpClient, HttpStatusException } import ing.wbaa.druid.definitions.{ CountAggregation, GranularityType } -import org.scalatest._ import org.scalatest.concurrent._ import scala.concurrent.duration._