diff --git a/oracle/src/main/scala/zio/sql/oracle/OracleRenderModule.scala b/oracle/src/main/scala/zio/sql/oracle/OracleRenderModule.scala index c1e22b34d..321100bc4 100644 --- a/oracle/src/main/scala/zio/sql/oracle/OracleRenderModule.scala +++ b/oracle/src/main/scala/zio/sql/oracle/OracleRenderModule.scala @@ -3,6 +3,7 @@ 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 @@ -10,12 +11,14 @@ 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 => @@ -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) => @@ -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("(") @@ -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 { diff --git a/oracle/src/main/scala/zio/sql/oracle/OracleSqlModule.scala b/oracle/src/main/scala/zio/sql/oracle/OracleSqlModule.scala index e6b17ff45..c22ae32fc 100644 --- a/oracle/src/main/scala/zio/sql/oracle/OracleSqlModule.scala +++ b/oracle/src/main/scala/zio/sql/oracle/OracleSqlModule.scala @@ -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)) diff --git a/oracle/src/test/scala/zio/sql/oracle/CommonFunctionDefSpec.scala b/oracle/src/test/scala/zio/sql/oracle/CommonFunctionDefSpec.scala index 9006926d7..01e187909 100644 --- a/oracle/src/test/scala/zio/sql/oracle/CommonFunctionDefSpec.scala +++ b/oracle/src/test/scala/zio/sql/oracle/CommonFunctionDefSpec.scala @@ -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], @@ -83,47 +84,46 @@ 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) @@ -131,7 +131,7 @@ object CommonFunctionDefSpec extends OracleRunnableSpec with ShopSchema { 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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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" @@ -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 @@ -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 @@ -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")) } ) )