From f628c6f0065bfd82aa384e841b21b3befa239232 Mon Sep 17 00:00:00 2001 From: Alan Cai Date: Mon, 24 Jun 2024 17:16:04 -0700 Subject: [PATCH 1/2] Add temporary datetime literal printing --- .../scribe/sql/PartiQLValueTextWriter.kt | 375 ++++++++++++++++++ .../org/partiql/scribe/sql/SqlDialect.kt | 1 - 2 files changed, 375 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/org/partiql/scribe/sql/PartiQLValueTextWriter.kt diff --git a/src/main/kotlin/org/partiql/scribe/sql/PartiQLValueTextWriter.kt b/src/main/kotlin/org/partiql/scribe/sql/PartiQLValueTextWriter.kt new file mode 100644 index 0000000..7f03949 --- /dev/null +++ b/src/main/kotlin/org/partiql/scribe/sql/PartiQLValueTextWriter.kt @@ -0,0 +1,375 @@ +package org.partiql.scribe.sql + +/* + * Copyright Amazon.com, Inc. or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at: + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ + +import org.partiql.value.BagValue +import org.partiql.value.BoolValue +import org.partiql.value.CharValue +import org.partiql.value.CollectionValue +import org.partiql.value.DateValue +import org.partiql.value.DecimalValue +import org.partiql.value.Float32Value +import org.partiql.value.Float64Value +import org.partiql.value.Int16Value +import org.partiql.value.Int32Value +import org.partiql.value.Int64Value +import org.partiql.value.Int8Value +import org.partiql.value.IntValue +import org.partiql.value.ListValue +import org.partiql.value.MissingValue +import org.partiql.value.NullValue +import org.partiql.value.PartiQLValue +import org.partiql.value.PartiQLValueExperimental +import org.partiql.value.SexpValue +import org.partiql.value.StringValue +import org.partiql.value.StructValue +import org.partiql.value.SymbolValue +import org.partiql.value.TimeValue +import org.partiql.value.TimestampValue +import org.partiql.value.datetime.Date +import org.partiql.value.datetime.Time +import org.partiql.value.datetime.TimeZone +import org.partiql.value.datetime.Timestamp +import org.partiql.value.io.PartiQLValueWriter +import org.partiql.value.util.PartiQLValueBaseVisitor +import java.io.OutputStream +import java.io.PrintStream +import java.math.BigDecimal +import kotlin.math.abs + +/** + * [PartiQLValueWriter] which outputs PartiQL text. + * + * TODO: Copied from partiql-lang-kotlin's [org.partiql.value.io.PartiQLValueTextWriter]. Delete after + * https://github.com/partiql/partiql-lang-kotlin/issues/1491 is resolved and released as a new PartiQL Kotlin version. + * + * @property out PrintStream + * @property formatted Print with newlines and indents + * @property indent Indent prefix, default is 2-spaces + */ +@PartiQLValueExperimental +public class PartiQLValueTextWriter( + private val out: PrintStream, + private val formatted: Boolean = true, + private val indent: String = " ", +) : PartiQLValueWriter { + + override fun append(value: PartiQLValue): PartiQLValueWriter { + val format = if (formatted) Format(indent) else null + val v = value.accept(ToString, format) // value.toString(format) + out.append(v) + return this + } + + override fun close() { + out.close() + } + + /** + * Text format + * + * @param indent Index prefix + */ + private data class Format( + val indent: String = " ", + val prefix: String = "", + ) { + fun nest() = copy(prefix = prefix + indent) + } + + /** + * Not implemented on the value classes as the textual format is not inherent to the values. + */ + @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") + private object ToString : PartiQLValueBaseVisitor() { + + override fun defaultVisit(v: PartiQLValue, format: Format?) = defaultReturn(v, format) + + override fun defaultReturn(v: PartiQLValue, format: Format?): Nothing = + throw IllegalArgumentException("Cannot write value of type ${v.type}") + + private inline fun PartiQLValue.toString(format: Format?, block: PartiQLValue.() -> String) = buildString { + if (format != null) append(format.prefix) + annotate(this@toString, this) + append(block()) + } + + private fun annotate(v: PartiQLValue, sb: StringBuilder) { + val annotations = v.annotations + if (annotations.isNotEmpty()) { + // TODO handle escaping + sb.append(annotations.joinToString("::", postfix = "::")) + } + } + + override fun visitNull(v: NullValue, format: Format?) = v.toString(format) { "null" } + + override fun visitMissing(v: MissingValue, format: Format?) = v.toString(format) { "missing" } + + override fun visitBool(v: BoolValue, format: Format?) = v.toString(format) { + when (v.value) { + null -> "null" + true -> "true" + false -> "false" + } + } + + override fun visitInt8(v: Int8Value, format: Format?) = v.toString(format) { + when (val value = v.value) { + null -> "null" // null.int8 + else -> value.toString() + } + } + + override fun visitInt16(v: Int16Value, format: Format?) = v.toString(format) { + when (val value = v.value) { + null -> "null" // null.int16 + else -> value.toString() + } + } + + override fun visitInt32(v: Int32Value, format: Format?) = v.toString(format) { + when (val value = v.value) { + null -> "null" // null.int32 + else -> value.toString() + } + } + + override fun visitInt64(v: Int64Value, format: Format?) = v.toString(format) { + when (val value = v.value) { + null -> "null" // null.int64 + else -> value.toString() + } + } + + override fun visitInt(v: IntValue, format: Format?) = v.toString(format) { + when (val value = v.value) { + null -> "null" // null.int + else -> value.toString() + } + } + + override fun visitDecimal(v: DecimalValue, format: Format?) = v.toString(format) { + when (val value = v.value) { + null -> "null" // null.decimal + else -> value.toString() + } + } + + override fun visitFloat32(v: Float32Value, format: Format?) = v.toString(format) { + when (val value = v.value) { + null -> "null" // null.float32 + else -> value.toString() + } + } + + override fun visitFloat64(v: Float64Value, format: Format?) = v.toString(format) { + when (val value = v.value) { + null -> "null" // null.float64 + else -> value.toString() + } + } + + override fun visitChar(v: CharValue, format: Format?) = v.toString(format) { + when (val value = v.value) { + null -> "null" // null.char + else -> "'$value'" + } + } + + // TODO escapes + override fun visitString(v: StringValue, format: Format?) = v.toString(format) { + when (val value = v.value) { + null -> "null" // null.string + else -> "'$value'" + } + } + + // TODO escapes, find source in IonJava + override fun visitSymbol(v: SymbolValue, format: Format?) = v.toString(format) { + when (val value = v.value) { + null -> "null" // null.symbol + else -> value + } + } + + override fun visitDate(v: DateValue, format: Format?) = v.toString(format) { + when (val value = v.value) { + null -> "null" // null.date + else -> sqlString(value) + } + } + + private fun padZeros(v: Int, totalDigits: Int): String = String.format("%0${totalDigits}d", v) + + private fun sqlString(d: Date): String { + val yyyy = padZeros(d.year, 4) + val mm = padZeros(d.month, 2) + val dd = padZeros(d.day, 2) + return "DATE '$yyyy-$mm-$dd'" + } + + override fun visitTime(v: TimeValue, format: Format?) = v.toString(format) { + when (val value = v.value) { + null -> "null" // null.time + else -> sqlString(value) + } + } + + private fun sqlString(tz: TimeZone?): String { + return when (tz) { + null -> "" + is TimeZone.UnknownTimeZone -> "-00:00" + is TimeZone.UtcOffset -> { + val sign = if (tz.totalOffsetMinutes < 0) { + "-" + } else { + "+" + } + val hh = padZeros(abs(tz.tzHour), 2) + val mm = padZeros(abs(tz.tzMinute), 2) + "$sign$hh:$mm" + } + } + } + + private fun sqlString(t: Time): String { + val hh = padZeros(t.hour, 2) + val mm = padZeros(t.minute, 2) + val ss = padZeros(t.decimalSecond.toInt(), 2) + val frac = t.decimalSecond.remainder(BigDecimal.ONE).toString().substring(1) // drop leading 0 + val timeZone = sqlString(t.timeZone) + return "TIME '$hh:$mm:$ss$frac$timeZone'" + } + + override fun visitTimestamp(v: TimestampValue, format: Format?) = v.toString(format) { + when (val value = v.value) { + null -> "null" // null.timestamp + else -> sqlString(value) + } + } + + private fun sqlString(t: Timestamp): String { + val yyyy = padZeros(t.year, 4) + val mon = padZeros(t.month, 2) + val dd = padZeros(t.day, 2) + val hh = padZeros(t.hour, 2) + val min = padZeros(t.minute, 2) + val ss = padZeros(t.decimalSecond.toInt(), 2) + val frac = t.decimalSecond.remainder(BigDecimal.ONE).toString().substring(1) // drop leading 0 + val timeZone = sqlString(t.timeZone) + return "TIMESTAMP '$yyyy-$mon-$dd $hh:$min:$ss$frac$timeZone'" + } + + override fun visitBag(v: BagValue<*>, format: Format?) = collection(v, format, "<<" to ">>") + + override fun visitList(v: ListValue<*>, format: Format?) = collection(v, format, "[" to "]") + + override fun visitSexp(v: SexpValue<*>, format: Format?) = collection(v, format, "(" to ")", " ") + + override fun visitStruct(v: StructValue<*>, format: Format?): String = buildString { + if (v.isNull) { + return "null" + } + // null.struct + val entries = v.entries.toList() + // {} + if (entries.isEmpty() || format == null) { + format?.let { append(it.prefix) } + annotate(v, this) + append("{") + val items = entries.map { + val fk = it.first + val fv = it.second.accept(ToString, null) // it.toString() + "$fk:$fv" + } + append(items.joinToString(",")) + append("}") + return@buildString + } + // print formatted + append(format.prefix) + annotate(v, this) + appendLine("{") + entries.forEachIndexed { i, e -> + val fk = e.first + val fv = e.second.accept(ToString, format.nest()).trimStart() // e.toString(format) + val suffix = if (i == entries.size - 1) "" else "," + append(format.prefix + format.indent) + append("$fk: $fv") + appendLine(suffix) + } + append(format.prefix) + append("}") + } + + private fun collection( + v: CollectionValue<*>, + format: Format?, + terminals: Pair, + separator: CharSequence = ",", + ) = buildString { + // null.bag, null.list, null.sexp + if (v.isNull) { + return "null" + } + val elements = v.toList() + // skip empty + if (elements.isEmpty() || format == null) { + format?.let { append(it.prefix) } + annotate(v, this) + append(terminals.first) + val items = elements.map { + it.accept(ToString, null) // it.toString() + } + append(items.joinToString(separator)) + append(terminals.second) + return@buildString + } + // print formatted + append(format.prefix) + annotate(v, this) + appendLine(terminals.first) + elements.forEachIndexed { i, e -> + val content = e.accept(ToString, format.nest()) // e.toString(format) + val suffix = if (i == elements.size - 1) "" else "," + append(content) + appendLine(suffix) + } + append(format.prefix) + append(terminals.second) + } + } +} + +@OptIn(PartiQLValueExperimental::class) +public class PartiQLValueWriterBuilder private constructor() { + + private var formatted: Boolean = true + + public companion object { + @JvmStatic + public fun standard(): PartiQLValueWriterBuilder = PartiQLValueWriterBuilder() + } + + public fun build(outputStream: OutputStream): PartiQLValueWriter = + PartiQLValueTextWriter( + out = PrintStream(outputStream), + formatted = formatted, + ) + + public fun formatted(formatted: Boolean = true): PartiQLValueWriterBuilder = + this.apply { this.formatted = formatted } +} diff --git a/src/main/kotlin/org/partiql/scribe/sql/SqlDialect.kt b/src/main/kotlin/org/partiql/scribe/sql/SqlDialect.kt index 102bdc4..21d2b11 100644 --- a/src/main/kotlin/org/partiql/scribe/sql/SqlDialect.kt +++ b/src/main/kotlin/org/partiql/scribe/sql/SqlDialect.kt @@ -33,7 +33,6 @@ import org.partiql.ast.visitor.AstBaseVisitor import org.partiql.value.MissingValue import org.partiql.value.NullValue import org.partiql.value.PartiQLValueExperimental -import org.partiql.value.io.PartiQLValueTextWriter import java.io.ByteArrayOutputStream import java.io.PrintStream From 83913c63a3176867638e334744569d8398576e9e Mon Sep 17 00:00:00 2001 From: Alan Cai Date: Tue, 25 Jun 2024 16:40:19 -0700 Subject: [PATCH 2/2] Add datetime literal tests and custom logic for Redshift and Spark --- .../targets/redshift/RedshiftDialect.kt | 78 +++++++++++++++++++ .../scribe/targets/spark/SparkDialect.kt | 10 +++ src/test/resources/inputs/basics/datetime.sql | 48 ++++++++++++ .../resources/inputs/builtins/datetime.sql | 6 ++ .../outputs/partiql/basics/datetime.sql | 48 ++++++++++++ .../outputs/partiql/builtins/datetime.sql | 6 ++ .../outputs/redshift/basics/datetime.sql | 49 ++++++++++++ .../outputs/redshift/builtins/datetime.sql | 6 ++ .../outputs/spark/basics/datetime.sql | 48 ++++++++++++ .../outputs/spark/builtins/datetime.sql | 5 ++ .../outputs/trino/basics/datetime.sql | 48 ++++++++++++ .../outputs/trino/builtins/datetime.sql | 7 +- 12 files changed, 358 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/inputs/basics/datetime.sql create mode 100644 src/test/resources/outputs/partiql/basics/datetime.sql create mode 100644 src/test/resources/outputs/redshift/basics/datetime.sql create mode 100644 src/test/resources/outputs/spark/basics/datetime.sql create mode 100644 src/test/resources/outputs/spark/builtins/datetime.sql create mode 100644 src/test/resources/outputs/trino/basics/datetime.sql diff --git a/src/main/kotlin/org/partiql/scribe/targets/redshift/RedshiftDialect.kt b/src/main/kotlin/org/partiql/scribe/targets/redshift/RedshiftDialect.kt index 57c438d..8d8a73e 100644 --- a/src/main/kotlin/org/partiql/scribe/targets/redshift/RedshiftDialect.kt +++ b/src/main/kotlin/org/partiql/scribe/targets/redshift/RedshiftDialect.kt @@ -12,6 +12,13 @@ import org.partiql.scribe.sql.SqlDialect import org.partiql.scribe.sql.concat import org.partiql.value.PartiQLValueExperimental import org.partiql.value.StringValue +import org.partiql.value.TimeValue +import org.partiql.value.TimestampValue +import org.partiql.value.datetime.Time +import org.partiql.value.datetime.TimeZone +import org.partiql.value.datetime.Timestamp +import java.math.BigDecimal +import kotlin.math.abs /** * Redshift SQL dialect for PartiQL Scribe. @@ -77,6 +84,77 @@ public open class RedshiftDialect : SqlDialect() { override fun visitTypeString(node: Type.String, tail: SqlBlock): SqlBlock = tail concat "VARCHAR" + // Essentially the same as `SqlDialect`. For SQL time and timestamp literals, Redshift (like Postgresql) requires + // specifying if the time/timestamp has a timezone in the type name. Otherwise, the offset will be ignored. + // Postgresql reference -- https://www.postgresql.org/docs/16/datatype-datetime.html#DATATYPE-DATETIME-INPUT-TIME-STAMPS + @OptIn(PartiQLValueExperimental::class) + override fun visitExprLit(node: Expr.Lit, tail: SqlBlock): SqlBlock { + val value = when (val v = node.value) { + is TimeValue -> { + when (val t = v.value) { + null -> "null" // null.time + else -> sqlString(t) + } + } + is TimestampValue -> { + when (val t = v.value) { + null -> "null" // null.timestamp + else -> sqlString(t) + } + } + else -> return super.visitExprLit(node, tail) + } + return tail concat value + } + + private fun padZeros(v: Int, totalDigits: Int): String = String.format("%0${totalDigits}d", v) + + private fun sqlString(tz: TimeZone?): String { + return when (tz) { + null -> "" + is TimeZone.UnknownTimeZone -> "-00:00" // could consider giving an error for unknown time zone offset + is TimeZone.UtcOffset -> { + val sign = if (tz.totalOffsetMinutes < 0) { + "-" + } else { + "+" + } + val hh = padZeros(abs(tz.tzHour), 2) + val mm = padZeros(abs(tz.tzMinute), 2) + "$sign$hh:$mm" + } + } + } + + private fun sqlString(t: Time): String { + val hh = padZeros(t.hour, 2) + val mm = padZeros(t.minute, 2) + val ss = padZeros(t.decimalSecond.toInt(), 2) + val frac = t.decimalSecond.remainder(BigDecimal.ONE).toString().substring(1) // drop leading 0 + val timeZone = sqlString(t.timeZone) + val timeType = when (t.timeZone) { + null -> "TIME" + else -> "TIMETZ" // `TIMETZ` is shorter than `TIME WITH TIME ZONE` + } + return "$timeType '$hh:$mm:$ss$frac$timeZone'" + } + + private fun sqlString(t: Timestamp): String { + val yyyy = padZeros(t.year, 4) + val mon = padZeros(t.month, 2) + val dd = padZeros(t.day, 2) + val hh = padZeros(t.hour, 2) + val min = padZeros(t.minute, 2) + val ss = padZeros(t.decimalSecond.toInt(), 2) + val frac = t.decimalSecond.remainder(BigDecimal.ONE).toString().substring(1) // drop leading 0 + val timeZone = sqlString(t.timeZone) + val timestampType = when (t.timeZone) { + null -> "TIMESTAMP" + else -> "TIMESTAMPTZ" // `TIMESTAMPTZ` is shorter than `TIMESTAMP WITH TIME ZONE` + } + return "$timestampType '$yyyy-$mon-$dd $hh:$min:$ss$frac$timeZone'" + } + private fun list( start: String? = "(", end: String? = ")", diff --git a/src/main/kotlin/org/partiql/scribe/targets/spark/SparkDialect.kt b/src/main/kotlin/org/partiql/scribe/targets/spark/SparkDialect.kt index 01949e2..d3a1824 100644 --- a/src/main/kotlin/org/partiql/scribe/targets/spark/SparkDialect.kt +++ b/src/main/kotlin/org/partiql/scribe/targets/spark/SparkDialect.kt @@ -15,6 +15,7 @@ import org.partiql.scribe.sql.concat import org.partiql.scribe.sql.sql import org.partiql.value.PartiQLValueExperimental import org.partiql.value.StringValue +import org.partiql.value.TimeValue public open class SparkDialect : SqlDialect() { @@ -113,6 +114,15 @@ public open class SparkDialect : SqlDialect() { return tail concat list(start, end) { node.values } } + @OptIn(PartiQLValueExperimental::class) + override fun visitExprLit(node: Expr.Lit, tail: SqlBlock): SqlBlock { + // Error on time literals; Spark does not have a time type or literal + if (node.value is TimeValue) { + error("Attempting to print ${node.value}. Time type is not supported in Spark.") + } + return super.visitExprLit(node, tail) + } + // Spark, has no notion of case sensitivity // https://spark.apache.org/docs/latest/sql-ref-identifier.html private fun Identifier.Symbol.sql() = "`$symbol`" diff --git a/src/test/resources/inputs/basics/datetime.sql b/src/test/resources/inputs/basics/datetime.sql new file mode 100644 index 0000000..36d63a6 --- /dev/null +++ b/src/test/resources/inputs/basics/datetime.sql @@ -0,0 +1,48 @@ +-- DATE +--#[datetime-00] +SELECT DATE '0001-02-03' FROM T; + +--#[datetime-01] +SELECT DATE '2020-02-03' FROM T; + +-- TIME +--#[datetime-02] +SELECT TIME '01:02:03' FROM T; + +--#[datetime-03] +SELECT TIME '01:02:03.456' FROM T; + +--#[datetime-04] +SELECT TIME '01:02:03.456-00:00' FROM T; + +--#[datetime-05] +SELECT TIME '01:02:03.456+00:00' FROM T; + +--#[datetime-06] +SELECT TIME '01:02:03.456+00:30' FROM T; + +--#[datetime-07] +SELECT TIME '01:02:03.456-00:30' FROM T; + +-- TIMESTAMP +--#[datetime-08] +SELECT TIMESTAMP '0001-02-03 04:05:06.78' FROM T; + +--#[datetime-09] +SELECT TIMESTAMP '0001-02-03 04:05:06.78-00:00' FROM T; + +--#[datetime-10] +SELECT TIMESTAMP '0001-02-03 04:05:06.78+00:00' FROM T; + +--#[datetime-11] +SELECT TIMESTAMP '0001-02-03 04:05:06.78+00:30' FROM T; + +--#[datetime-12] +SELECT TIMESTAMP '0001-02-03 04:05:06.78-00:30' FROM T; + +-- Ion Timestamp +--#[datetime-13] +SELECT `2007-01-01T` FROM T; + +--#[datetime-14] +SELECT `2007-02-23T12:14:33.079-08:00` FROM T; diff --git a/src/test/resources/inputs/builtins/datetime.sql b/src/test/resources/inputs/builtins/datetime.sql index 24ed8b8..5b2ecc0 100644 --- a/src/test/resources/inputs/builtins/datetime.sql +++ b/src/test/resources/inputs/builtins/datetime.sql @@ -60,3 +60,9 @@ SELECT DATE_DIFF(MINUTE, timestamp_1, timestamp_2) FROM T; --#[datetime-20] SELECT DATE_DIFF(SECOND, timestamp_1, timestamp_2) FROM T; + +--#[datetime-21] +SELECT DATE_ADD(SECOND, 1, TIMESTAMP '2017-01-02 03:04:05.006') FROM T; + +--#[datetime-22] +SELECT DATE_DIFF(SECOND, TIMESTAMP '2017-01-02T03:04:05.006', TIMESTAMP '2017-01-02T03:04:20.006') FROM T; diff --git a/src/test/resources/outputs/partiql/basics/datetime.sql b/src/test/resources/outputs/partiql/basics/datetime.sql new file mode 100644 index 0000000..0c28238 --- /dev/null +++ b/src/test/resources/outputs/partiql/basics/datetime.sql @@ -0,0 +1,48 @@ +-- DATE +--#[datetime-00] +SELECT DATE '0001-02-03' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-01] +SELECT DATE '2020-02-03' AS "_1" FROM "default"."T" AS "T"; + +-- TIME +--#[datetime-02] +SELECT TIME '01:02:03' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-03] +SELECT TIME '01:02:03.456' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-04] +SELECT TIME '01:02:03.456-00:00' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-05] +SELECT TIME '01:02:03.456+00:00' AS "_1" FROM "default"."T" AS "T" + +--#[datetime-06] +SELECT TIME '01:02:03.456+00:30' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-07] +SELECT TIME '01:02:03.456-00:30' AS "_1" FROM "default"."T" AS "T"; + +-- TIMESTAMP +--#[datetime-08] +SELECT TIMESTAMP '0001-02-03 04:05:06.78' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-09] +SELECT TIMESTAMP '0001-02-03 04:05:06.78-00:00' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-10] +SELECT TIMESTAMP '0001-02-03 04:05:06.78+00:00' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-11] +SELECT TIMESTAMP '0001-02-03 04:05:06.78+00:30' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-12] +SELECT TIMESTAMP '0001-02-03 04:05:06.78-00:30' AS "_1" FROM "default"."T" AS "T"; + +-- Ion Timestamp -> map to SQL timestamp literal +--#[datetime-13] +SELECT TIMESTAMP '2007-01-01 00:00:00-00:00' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-14] +SELECT TIMESTAMP '2007-02-23 12:14:33.079-08:00' AS "_1" FROM "default"."T" AS "T"; diff --git a/src/test/resources/outputs/partiql/builtins/datetime.sql b/src/test/resources/outputs/partiql/builtins/datetime.sql index 88a9099..79ae3fc 100644 --- a/src/test/resources/outputs/partiql/builtins/datetime.sql +++ b/src/test/resources/outputs/partiql/builtins/datetime.sql @@ -60,3 +60,9 @@ SELECT DATE_DIFF(MINUTE, "T"['timestamp_1'], "T"['timestamp_2']) AS "_1" FROM "d --#[datetime-20] SELECT DATE_DIFF(SECOND, "T"['timestamp_1'], "T"['timestamp_2']) AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-21] +SELECT DATE_ADD(SECOND, 1, TIMESTAMP '2017-01-02 03:04:05.006') AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-22] +SELECT DATE_DIFF(SECOND, TIMESTAMP '2017-01-02 03:04:05.006', TIMESTAMP '2017-01-02 03:04:20.006') AS "_1" FROM "default"."T" AS "T"; diff --git a/src/test/resources/outputs/redshift/basics/datetime.sql b/src/test/resources/outputs/redshift/basics/datetime.sql new file mode 100644 index 0000000..2f4ac24 --- /dev/null +++ b/src/test/resources/outputs/redshift/basics/datetime.sql @@ -0,0 +1,49 @@ +-- DATE +--#[datetime-00] +SELECT DATE '0001-02-03' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-01] +SELECT DATE '2020-02-03' AS "_1" FROM "default"."T" AS "T"; + +-- TIME +--#[datetime-02] +SELECT TIME '01:02:03' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-03] +SELECT TIME '01:02:03.456' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-04] +SELECT TIMETZ '01:02:03.456-00:00' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-05] +SELECT TIMETZ '01:02:03.456+00:00' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-06] +SELECT TIMETZ '01:02:03.456+00:30' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-07] +SELECT TIMETZ '01:02:03.456-00:30' AS "_1" FROM "default"."T" AS "T"; + +-- TIMESTAMP +--#[datetime-08] +SELECT TIMESTAMP '0001-02-03 04:05:06.78' AS "_1" FROM "default"."T" AS "T"; + +-- Redshift does not have an unknown timezone offset; -00:00 gets mapped to +00:00. +--#[datetime-09] +SELECT TIMESTAMPTZ '0001-02-03 04:05:06.78-00:00' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-10] +SELECT TIMESTAMPTZ '0001-02-03 04:05:06.78+00:00' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-11] +SELECT TIMESTAMPTZ '0001-02-03 04:05:06.78+00:30' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-12] +SELECT TIMESTAMPTZ '0001-02-03 04:05:06.78-00:30' AS "_1" FROM "default"."T" AS "T"; + +-- Ion Timestamp -> map to SQL timestamp literal +--#[datetime-13] +SELECT TIMESTAMPTZ '2007-01-01 00:00:00-00:00' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-14] +SELECT TIMESTAMPTZ '2007-02-23 12:14:33.079-08:00' AS "_1" FROM "default"."T" AS "T"; diff --git a/src/test/resources/outputs/redshift/builtins/datetime.sql b/src/test/resources/outputs/redshift/builtins/datetime.sql index aba5b6b..9dca03a 100644 --- a/src/test/resources/outputs/redshift/builtins/datetime.sql +++ b/src/test/resources/outputs/redshift/builtins/datetime.sql @@ -38,3 +38,9 @@ SELECT DATEDIFF(MINUTE, "T"."timestamp_1", "T"."timestamp_2") AS "_1" FROM "defa --#[datetime-20] SELECT DATEDIFF(SECOND, "T"."timestamp_1", "T"."timestamp_2") AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-21] +SELECT DATEADD(SECOND, 1, TIMESTAMP '2017-01-02 03:04:05.006') AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-22] +SELECT DATEDIFF(SECOND, TIMESTAMP '2017-01-02 03:04:05.006', TIMESTAMP '2017-01-02 03:04:20.006') AS "_1" FROM "default"."T" AS "T"; diff --git a/src/test/resources/outputs/spark/basics/datetime.sql b/src/test/resources/outputs/spark/basics/datetime.sql new file mode 100644 index 0000000..03775af --- /dev/null +++ b/src/test/resources/outputs/spark/basics/datetime.sql @@ -0,0 +1,48 @@ +-- DATE +--#[datetime-00] +SELECT DATE '0001-02-03' AS `_1` FROM `default`.`T` AS `T`; + +--#[datetime-01] +SELECT DATE '2020-02-03' AS `_1` FROM `default`.`T` AS `T`; + +-- TIME -- Spark has no `TIME` data type. Give an error when attempting to construct a SQL time literal +-- --#[datetime-02] +-- SELECT TIME '01:02:03' AS `_1` FROM `default`.`T` AS `T`; +-- +-- --#[datetime-03] +-- SELECT TIME '01:02:03.456' AS `_1` FROM `default`.`T` AS `T`; +-- +-- --#[datetime-04] +-- SELECT TIME '01:02:03.456-00:00' AS `_1` FROM `default`.`T` AS `T`; +-- +-- --#[datetime-05] +-- SELECT TIME '01:02:03.456+00:00' AS `_1` FROM `default`.`T` AS `T`; +-- +-- --#[datetime-06] +-- SELECT TIME '01:02:03.456+00:30' AS `_1` FROM `default`.`T` AS `T`; +-- +-- --#[datetime-07] +-- SELECT TIME '01:02:03.456-00:30' AS `_1` FROM `default`.`T` AS `T`; + +-- TIMESTAMP +--#[datetime-08] +SELECT TIMESTAMP '0001-02-03 04:05:06.78' AS `_1` FROM `default`.`T` AS `T`; + +--#[datetime-09] +SELECT TIMESTAMP '0001-02-03 04:05:06.78-00:00' AS `_1` FROM `default`.`T` AS `T`; + +--#[datetime-10] +SELECT TIMESTAMP '0001-02-03 04:05:06.78+00:00' AS `_1` FROM `default`.`T` AS `T`; + +--#[datetime-11] +SELECT TIMESTAMP '0001-02-03 04:05:06.78+00:30' AS `_1` FROM `default`.`T` AS `T`; + +--#[datetime-12] +SELECT TIMESTAMP '0001-02-03 04:05:06.78-00:30' AS `_1` FROM `default`.`T` AS `T`; + +-- Ion Timestamp -> map to SQL timestamp literal +--#[datetime-13] +SELECT TIMESTAMP '2007-01-01 00:00:00-00:00' AS `_1` FROM `default`.`T` AS `T`; + +--#[datetime-14] +SELECT TIMESTAMP '2007-02-23 12:14:33.079-08:00' AS `_1` FROM `default`.`T` AS `T`; diff --git a/src/test/resources/outputs/spark/builtins/datetime.sql b/src/test/resources/outputs/spark/builtins/datetime.sql new file mode 100644 index 0000000..f708602 --- /dev/null +++ b/src/test/resources/outputs/spark/builtins/datetime.sql @@ -0,0 +1,5 @@ +--#[datetime-21] +SELECT TIMESTAMP '2017-01-02 03:04:05.006' + `make_interval`(0, 0, 0, 0, 0, 0, 1) AS `_1` FROM `default`.`T` AS `T`; + +--#[datetime-22] +SELECT `unix_timestamp`(TIMESTAMP '2017-01-02 03:04:20.006') - `unix_timestamp`(TIMESTAMP '2017-01-02 03:04:05.006') AS `_1` FROM `default`.`T` AS `T`; diff --git a/src/test/resources/outputs/trino/basics/datetime.sql b/src/test/resources/outputs/trino/basics/datetime.sql new file mode 100644 index 0000000..9247a22 --- /dev/null +++ b/src/test/resources/outputs/trino/basics/datetime.sql @@ -0,0 +1,48 @@ +-- DATE +--#[datetime-00] +SELECT DATE '0001-02-03' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-01] +SELECT DATE '2020-02-03' AS "_1" FROM "default"."T" AS "T"; + +-- TIME +--#[datetime-02] +SELECT TIME '01:02:03' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-03] +SELECT TIME '01:02:03.456' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-04] +SELECT TIME '01:02:03.456-00:00' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-05] +SELECT TIME '01:02:03.456+00:00' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-06] +SELECT TIME '01:02:03.456+00:30' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-07] +SELECT TIME '01:02:03.456-00:30' AS "_1" FROM "default"."T" AS "T"; + +-- TIMESTAMP +--#[datetime-08] +SELECT TIMESTAMP '0001-02-03 04:05:06.78' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-09] +SELECT TIMESTAMP '0001-02-03 04:05:06.78-00:00' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-10] +SELECT TIMESTAMP '0001-02-03 04:05:06.78+00:00' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-11] +SELECT TIMESTAMP '0001-02-03 04:05:06.78+00:30' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-12] +SELECT TIMESTAMP '0001-02-03 04:05:06.78-00:30' AS "_1" FROM "default"."T" AS "T"; + +-- Ion Timestamp -> map to SQL timestamp literal +--#[datetime-13] +SELECT TIMESTAMP '2007-01-01 00:00:00-00:00' AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-14] +SELECT TIMESTAMP '2007-02-23 12:14:33.079-08:00' AS "_1" FROM "default"."T" AS "T"; diff --git a/src/test/resources/outputs/trino/builtins/datetime.sql b/src/test/resources/outputs/trino/builtins/datetime.sql index e6e8db6..e581aee 100644 --- a/src/test/resources/outputs/trino/builtins/datetime.sql +++ b/src/test/resources/outputs/trino/builtins/datetime.sql @@ -19,7 +19,6 @@ SELECT date_add('month', 5, current_date) AS "_1" FROM "default"."T" AS "T"; --#[datetime-14] SELECT date_add('year', 5, current_date) AS "_1" FROM "default"."T" AS "T"; - --#[datetime-15] SELECT date_diff('year', "T"."timestamp_1", "T"."timestamp_2") AS "_1" FROM "default"."T" AS "T"; @@ -37,3 +36,9 @@ SELECT date_diff('minute', "T"."timestamp_1", "T"."timestamp_2") AS "_1" FROM "d --#[datetime-20] SELECT date_diff('second', "T"."timestamp_1", "T"."timestamp_2") AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-21] +SELECT date_add('second', 1, TIMESTAMP '2017-01-02 03:04:05.006') AS "_1" FROM "default"."T" AS "T"; + +--#[datetime-22] +SELECT date_diff('second', TIMESTAMP '2017-01-02 03:04:05.006', TIMESTAMP '2017-01-02 03:04:20.006') AS "_1" FROM "default"."T" AS "T";