Skip to content

Commit

Permalink
zio#732 Oracle interpretation of literal const extended, DUAL table, …
Browse files Browse the repository at this point in the history
…functional tests
  • Loading branch information
bogatyra committed Jul 31, 2022
1 parent dc2b1eb commit d89a961
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 33 deletions.
107 changes: 103 additions & 4 deletions oracle/src/main/scala/zio/sql/oracle/OracleRenderModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@ package zio.sql.oracle
import zio.schema.Schema
import zio.schema.DynamicValue
import zio.schema.StandardType

import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.OffsetTime
import java.time.ZonedDateTime
import zio.sql.driver.Renderer
import zio.sql.driver.Renderer.Extensions
import zio.Chunk

import scala.collection.mutable
import java.time.OffsetDateTime
import java.time.YearMonth
import java.time.Duration
import java.time.format.{DateTimeFormatter, DateTimeFormatterBuilder}
import java.time.temporal.ChronoField._

trait OracleRenderModule extends OracleSqlModule { self =>

Expand Down Expand Up @@ -43,6 +46,102 @@ trait OracleRenderModule extends OracleSqlModule { self =>
render.toString
}

private object DateFormats {
val fmtTime = new DateTimeFormatterBuilder()
.appendValue(HOUR_OF_DAY, 2)
.appendLiteral(':')
.appendValue(MINUTE_OF_HOUR, 2)
.appendLiteral(':')
.appendValue(SECOND_OF_MINUTE, 2)
.appendFraction(NANO_OF_SECOND, 9, 9, true)
.appendOffset("+HH:MM", "Z")
.toFormatter()

val fmtTimeOffset = new DateTimeFormatterBuilder()
.append(fmtTime)
.appendFraction(NANO_OF_SECOND, 9, 9, true)
.toFormatter()

val fmtDateTime = new DateTimeFormatterBuilder().parseCaseInsensitive
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral('T')
.append(fmtTime)
.toFormatter()

val fmtDateTimeOffset = new DateTimeFormatterBuilder().parseCaseInsensitive
.append(fmtDateTime)
.appendOffset("+HH:MM", "Z")
.toFormatter()
}

private def buildLit(lit: self.Expr.Literal[_])(builder: StringBuilder): Unit = {
import TypeTag._
val value = lit.value
lit.typeTag match {
case TInstant =>
val _ = builder.append( s"""TO_TIMESTAMP_TZ('${DateFormats.fmtDateTimeOffset.format(
value.asInstanceOf[Instant]
)}', 'SYYYY-MM-DD"T"HH24:MI:SS.FF9TZH:TZM')""")
case TLocalTime =>
val localTime = value.asInstanceOf[LocalTime]
val _ = builder.append(
s"INTERVAL '${localTime.getHour}:${localTime.getMinute}:${localTime.getSecond}.${localTime.getNano}' HOUR TO SECOND(9)"
)
case TLocalDate =>
val _ = builder.append(s"TO_DATE('${DateTimeFormatter.ISO_LOCAL_DATE.format(value.asInstanceOf[LocalDate])}', 'SYYYY-MM-DD')")
case TLocalDateTime =>
val _ = builder.append(s"""TO_TIMESTAMP('${DateFormats.fmtDateTime.format(value.asInstanceOf[LocalDateTime])}', 'SYYYY-MM-DD"T"HH24:MI:SS.FF9')""")
case TZonedDateTime =>
val _ = builder.append( s"""TO_TIMESTAMP_TZ('${DateFormats.fmtDateTimeOffset.format(
value.asInstanceOf[ZonedDateTime]
)}', 'SYYYY-MM-DD"T"HH24:MI:SS.FF9TZH:TZM')""")
case TOffsetTime =>
val _ = builder.append(
s"TO_TIMESTAMP_TZ('${DateFormats.fmtTimeOffset.format(value.asInstanceOf[OffsetTime])}', 'HH24:MI:SS.FF9TZH:TZM')"
)
case TOffsetDateTime =>
val _ = builder.append(
s"""TO_TIMESTAMP_TZ('${DateFormats.fmtDateTimeOffset.format(
value.asInstanceOf[OffsetDateTime]
)}', 'SYYYY-MM-DD"T"HH24:MI:SS.FF9TZH:TZM')"""
)

case TBoolean =>
val b = value.asInstanceOf[Boolean]
if (b) {
val _ = builder.append('1')
} else {
val _ = builder.append('0')
}
case TUUID =>
val _ = builder.append(s"'$value'")

case TBigDecimal =>
val _ = builder.append(value)
case TByte =>
val _ = builder.append(value)
case TDouble =>
val _ = builder.append(value)
case TFloat =>
val _ = builder.append(value)
case TInt =>
val _ = builder.append(value)
case TLong =>
val _ = builder.append(value)
case TShort =>
val _ = builder.append(value)

case TChar =>
val _ = builder.append(s"N'$value'")
case TString =>
val _ = builder.append(s"N'$value'")

case _ =>
val _ = builder.append(s"'$value'")
}
}


// TODO: to consider the refactoring and using the implicit `Renderer`, see `renderExpr` in `PostgresRenderModule`
private def buildExpr[A, B](expr: self.Expr[_, A, B], builder: StringBuilder): Unit = expr match {
case Expr.Subselect(subselect) =>
Expand Down Expand Up @@ -85,8 +184,8 @@ trait OracleRenderModule extends OracleSqlModule { self =>
val _ = builder.append("1 = 1")
case Expr.Literal(false) =>
val _ = builder.append("0 = 1")
case Expr.Literal(value) =>
val _ = builder.append(value.toString.singleQuoted)
case literal: Expr.Literal[_] =>
val _ = buildLit(literal)(builder)
case Expr.AggregationCall(param, aggregation) =>
builder.append(aggregation.name.name)
builder.append("(")
Expand Down Expand Up @@ -178,7 +277,7 @@ trait OracleRenderModule extends OracleSqlModule { self =>
}

/**
* Drops the initial Litaral(true) present at the start of every WHERE expressions by default
* Drops the initial Litaral(true) present at the start of every WHERE expressions by default
* and proceeds to the rest of Expr's.
*/
private def buildWhereExpr[A, B](expr: self.Expr[_, A, B], builder: mutable.StringBuilder): Unit = expr match {
Expand Down
5 changes: 5 additions & 0 deletions oracle/src/main/scala/zio/sql/oracle/OracleSqlModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ trait OracleSqlModule extends Sql { self =>
val Sind = FunctionDef[Double, Double](FunctionName("sind"))
}

object Dual {
val dual = (string("dummy")).table("dual")
val (dummy) = dual.columns
}

implicit val instantSchema =
Schema.primitive[Instant](zio.schema.StandardType.InstantType(DateTimeFormatter.ISO_OFFSET_DATE_TIME))

Expand Down
58 changes: 29 additions & 29 deletions oracle/src/test/scala/zio/sql/oracle/CommonFunctionDefSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import zio.test._
object CommonFunctionDefSpec extends OracleRunnableSpec with ShopSchema {
import FunctionDef.{ CharLength => _, _ }
import Customers._
import Dual._

private def collectAndCompare[R, E](
expected: Seq[String],
Expand Down Expand Up @@ -83,55 +84,54 @@ object CommonFunctionDefSpec extends OracleRunnableSpec with ShopSchema {
),
suite("Schema independent tests")(
test("ltrim") {
assertZIO(execute(select(Ltrim(" hello "))).runHead.some)(equalTo("hello "))
assertZIO(execute(select(Ltrim(" hello ")).from(dual)).runHead.some)(equalTo("hello "))
},
test("rtrim") {
assertZIO(execute(select(Rtrim(" hello "))).runHead.some)(equalTo(" hello"))
assertZIO(execute(select(Rtrim(" hello ")).from(dual)).runHead.some)(equalTo(" hello"))
},
test("abs") {
assertZIO(execute(select(Abs(-3.14159))).runHead.some)(equalTo(3.14159))
assertZIO(execute(select(Abs(-3.14159)).from(dual)).runHead.some)(equalTo(3.14159))
},
test("log") {
assertZIO(execute(select(Log(2.0, 32.0))).runHead.some)(equalTo(5.0))
assertZIO(execute(select(Log(2.0, 32.0)).from(dual)).runHead.some)(equalTo(5.0))
},
test("acos") {
assertZIO(execute(select(Acos(-1.0))).runHead.some)(equalTo(3.141592653589793))
assertZIO(execute(select(Acos(-1.0)).from(dual)).runHead.some)(equalTo(3.141592653589793))
},
test("asin") {
assertZIO(execute(select(Asin(0.5))).runHead.some)(equalTo(0.5235987755982989))
assertZIO(execute(select(Asin(0.5)).from(dual)).runHead.some)(equalTo(0.5235987755982989))
},
test("ln") {
assertZIO(execute(select(Ln(3.0))).runHead.some)(equalTo(1.0986122886681097))
assertZIO(execute(select(Ln(3.0)).from(dual)).runHead.some)(equalTo(1.0986122886681097))
},
test("atan") {
assertZIO(execute(select(Atan(10.0))).runHead.some)(equalTo(1.4711276743037347))
assertZIO(execute(select(Atan(10.0)).from(dual)).runHead.some)(equalTo(1.4711276743037347))
},
test("cos") {
assertZIO(execute(select(Cos(3.141592653589793))).runHead.some)(equalTo(-1.0))
assertZIO(execute(select(Cos(3.141592653589793)).from(dual)).runHead.some)(equalTo(-1.0))
},
test("exp") {
assertZIO(execute(select(Exp(1.0))).runHead.some)(equalTo(2.718281828459045))
assertZIO(execute(select(Exp(1.0)).from(dual)).runHead.some)(equalTo(2.718281828459045))
},
test("floor") {
assertZIO(execute(select(Floor(-3.14159))).runHead.some)(equalTo(-4.0))
assertZIO(execute(select(Floor(-3.14159)).from(dual)).runHead.some)(equalTo(-4.0))
},
test("ceil") {
assertZIO(execute(select(Ceil(53.7), Ceil(-53.7))).runHead.some)(equalTo((54.0, -53.0)))
assertZIO(execute(select(Ceil(53.7), Ceil(-53.7)).from(dual)).runHead.some)(equalTo((54.0, -53.0)))
},
test("sin") {
assertZIO(execute(select(Sin(1.0))).runHead.some)(equalTo(0.8414709848078965))
assertZIO(execute(select(Sin(1.0)).from(dual)).runHead.some)(equalTo(0.8414709848078965))
},
test("sqrt") {
val query = select(Sqrt(121.0))

val query = select(Sqrt(121.0)).from(dual)
val expected = 11.0

val testResult = execute(query)

assertZIO(testResult.runHead.some)(equalTo(expected))
},
test("round") {
val query = select(Round(10.8124, 2))
val query = select(Round(10.8124, 2)).from(dual)

val expected = 10.81

Expand All @@ -144,7 +144,7 @@ object CommonFunctionDefSpec extends OracleRunnableSpec with ShopSchema {
assertion.mapErrorCause(cause => Cause.stackless(cause.untraced))
},
test("sign positive") {
val query = select(Sign(3.0))
val query = select(Sign(3.0)).from(dual)

val expected = 1

Expand All @@ -157,7 +157,7 @@ object CommonFunctionDefSpec extends OracleRunnableSpec with ShopSchema {
assertion.mapErrorCause(cause => Cause.stackless(cause.untraced))
},
test("sign negative") {
val query = select(Sign(-3.0))
val query = select(Sign(-3.0)).from(dual)

val expected = -1

Expand All @@ -170,7 +170,7 @@ object CommonFunctionDefSpec extends OracleRunnableSpec with ShopSchema {
assertion.mapErrorCause(cause => Cause.stackless(cause.untraced))
},
test("sign zero") {
val query = select(Sign(0.0))
val query = select(Sign(0.0)).from(dual)

val expected = 0

Expand All @@ -183,7 +183,7 @@ object CommonFunctionDefSpec extends OracleRunnableSpec with ShopSchema {
assertion.mapErrorCause(cause => Cause.stackless(cause.untraced))
},
test("power") {
val query = select(Power(7.0, 3.0))
val query = select(Power(7.0, 3.0)).from(dual)

val expected = 343.000000000000000

Expand All @@ -196,7 +196,7 @@ object CommonFunctionDefSpec extends OracleRunnableSpec with ShopSchema {
assertion.mapErrorCause(cause => Cause.stackless(cause.untraced))
},
test("mod") {
val query = select(Mod(-15.0, -4.0))
val query = select(Mod(-15.0, -4.0)).from(dual)

val expected = -3.0

Expand All @@ -209,7 +209,7 @@ object CommonFunctionDefSpec extends OracleRunnableSpec with ShopSchema {
assertion.mapErrorCause(cause => Cause.stackless(cause.untraced))
},
test("octet_length") {
val query = select(OctetLength("josé"))
val query = select(OctetLength("josé")).from(dual)

val expected = 5

Expand All @@ -220,9 +220,9 @@ object CommonFunctionDefSpec extends OracleRunnableSpec with ShopSchema {
} yield assert(r.head)(equalTo(expected))

assertion.mapErrorCause(cause => Cause.stackless(cause.untraced))
},
} @@ TestAspect.ignore,
test("ascii") {
val query = select(Ascii("""x"""))
val query = select(Ascii("""x""")).from(dual)

val expected = 120

Expand All @@ -235,7 +235,7 @@ object CommonFunctionDefSpec extends OracleRunnableSpec with ShopSchema {
assertion.mapErrorCause(cause => Cause.stackless(cause.untraced))
},
test("upper") {
val query = (select(Upper("ronald"))).limit(1)
val query = (select(Upper("ronald")).from(dual)).limit(1)

val expected = "RONALD"

Expand All @@ -248,7 +248,7 @@ object CommonFunctionDefSpec extends OracleRunnableSpec with ShopSchema {
assertion.mapErrorCause(cause => Cause.stackless(cause.untraced))
},
test("width_bucket") {
val query = select(WidthBucket(5.35, 0.024, 10.06, 5))
val query = select(WidthBucket(5.35, 0.024, 10.06, 5)).from(dual)

val expected = 3

Expand All @@ -261,7 +261,7 @@ object CommonFunctionDefSpec extends OracleRunnableSpec with ShopSchema {
assertion.mapErrorCause(cause => Cause.stackless(cause.untraced))
},
test("tan") {
val query = select(Tan(0.7853981634))
val query = select(Tan(0.7853981634)).from(dual)

val expected = 1.0000000000051035

Expand All @@ -274,10 +274,10 @@ object CommonFunctionDefSpec extends OracleRunnableSpec with ShopSchema {
assertion.mapErrorCause(cause => Cause.stackless(cause.untraced))
},
test("trim") {
assertZIO(execute(select(Trim(" 1234 "))).runHead.some)(equalTo("1234"))
assertZIO(execute(select(Trim(" 1234 ")).from(dual)).runHead.some)(equalTo("1234"))
},
test("lower") {
assertZIO(execute(select(Lower("YES"))).runHead.some)(equalTo("yes"))
assertZIO(execute(select(Lower("YES")).from(dual)).runHead.some)(equalTo("yes"))
}
)
)
Expand Down

0 comments on commit d89a961

Please sign in to comment.