diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/CastTable.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/CastTable.kt index 8f994fa1c7..b7709866b5 100644 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/CastTable.kt +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/CastTable.kt @@ -30,10 +30,12 @@ import org.partiql.types.PType.TIMEZ import org.partiql.types.PType.TINYINT import org.partiql.types.PType.VARCHAR import org.partiql.types.PType.VARIANT -import org.partiql.value.datetime.DateTimeValue import java.math.BigDecimal import java.math.BigInteger import java.math.RoundingMode +import java.time.LocalDate +import java.time.LocalTime +import java.time.ZoneOffset /** * Represent the cast operation. This casts an input [Datum] to the target [PType] (returning a potentially new [Datum]). @@ -103,9 +105,9 @@ internal object CastTable { registerString() registerBag() registerList() - registerTimestamp() registerDate() registerTime() + registerTimestamp() registerVariant() } @@ -442,57 +444,41 @@ internal object CastTable { register(ARRAY, ARRAY) { x, _ -> x } } - /** - * TODO: Flush this out. - */ - private fun registerTimestamp() { - // WITHOUT TZ - register(TIMESTAMP, VARCHAR) { x, t -> Datum.varchar(x.timestamp.toString(), t.length) } - register(TIMESTAMP, CHAR) { x, t -> Datum.character(x.timestamp.toString(), t.length) } - register(TIMESTAMP, CLOB) { x, t -> Datum.clob(x.timestamp.toString().toByteArray(), t.length) } - register(TIMESTAMP, STRING) { x, _ -> Datum.string(x.timestamp.toString()) } - register(TIMESTAMP, TIMESTAMP) { x, _ -> Datum.timestamp(x.timestamp) } - register(TIMESTAMP, TIMESTAMPZ) { x, _ -> Datum.timestamp(x.timestamp) } - register(TIMESTAMP, TIME) { x, _ -> Datum.time(x.timestamp.toTime()) } - register(TIMESTAMP, DATE) { x, _ -> Datum.date(x.timestamp.toDate()) } - // WITH TZ - register(TIMESTAMPZ, VARCHAR) { x, t -> Datum.varchar(x.timestamp.toString(), t.length) } - register(TIMESTAMPZ, CHAR) { x, t -> Datum.character(x.timestamp.toString(), t.length) } - register(TIMESTAMPZ, CLOB) { x, t -> Datum.clob(x.timestamp.toString().toByteArray(), t.length) } - register(TIMESTAMPZ, STRING) { x, _ -> Datum.string(x.timestamp.toString()) } - register(TIMESTAMPZ, TIMESTAMP) { x, _ -> Datum.timestamp(x.timestamp) } - register(TIMESTAMPZ, TIMESTAMPZ) { x, _ -> Datum.timestamp(x.timestamp) } - register(TIMESTAMPZ, TIME) { x, _ -> Datum.time(x.timestamp.toTime()) } - register(TIMESTAMPZ, DATE) { x, _ -> Datum.date(x.timestamp.toDate()) } - } private fun registerDate() { - register(DATE, VARCHAR) { x, t -> Datum.varchar(x.date.toString(), t.length) } - register(DATE, CHAR) { x, t -> Datum.character(x.date.toString(), t.length) } - register(DATE, CLOB) { x, t -> Datum.clob(x.date.toString().toByteArray(), t.length) } - register(DATE, STRING) { x, _ -> Datum.string(x.date.toString()) } - register(DATE, DATE) { x, _ -> Datum.date(x.date) } - register(DATE, TIMESTAMP) { x, _ -> Datum.timestamp(DateTimeValue.timestamp(x.date, DateTimeValue.time(0, 0, 0))) } - register(DATE, TIMESTAMPZ) { x, _ -> Datum.timestamp(DateTimeValue.timestamp(x.date, DateTimeValue.time(0, 0, 0))) } + register(DATE, STRING) { x, _ -> Datum.string(x.localDate.toString()) } + register(DATE, DATE) { x, _ -> Datum.date(x.localDate) } + register(DATE, TIMESTAMP) { x, _ -> Datum.timestamp(x.localDate.atTime(LocalTime.MIN), 0) } + register(DATE, TIMESTAMPZ) { x, _ -> Datum.timestampz(x.localDate.atTime(LocalTime.MIN).atOffset(ZoneOffset.UTC), 0) } } private fun registerTime() { // WITHOUT TZ - register(TIME, VARCHAR) { x, t -> Datum.varchar(x.time.toString(), t.length) } - register(TIME, CHAR) { x, t -> Datum.character(x.time.toString(), t.length) } - register(TIME, CLOB) { x, t -> Datum.clob(x.time.toString().toByteArray(), t.length) } - register(TIME, STRING) { x, _ -> Datum.string(x.time.toString()) } - register(TIME, TIME) { x, _ -> Datum.time(x.time) } - register(TIME, TIMESTAMP) { x, _ -> Datum.timestamp(DateTimeValue.timestamp(DateTimeValue.date(1970, 1, 1), x.time)) } - register(TIME, TIMESTAMPZ) { x, _ -> Datum.timestamp(DateTimeValue.timestamp(DateTimeValue.date(1970, 1, 1), x.time)) } + register(TIME, STRING) { x, _ -> Datum.string(x.localTime.toString()) } + register(TIME, TIME) { x, t -> Datum.time(x.localTime, t.precision) } + register(TIME, TIMEZ) { x, t -> Datum.timez(x.localTime.atOffset(ZoneOffset.UTC), t.precision) } + register(TIME, TIMESTAMP) { x, t -> Datum.timestamp(x.localTime.atDate(LocalDate.now()), t.precision) } + register(TIME, TIMESTAMPZ) { x, t -> Datum.timestampz(x.localTime.atDate(LocalDate.now()).atOffset(ZoneOffset.UTC), t.precision) } + // WITH TZ + register(TIMEZ, STRING) { x, _ -> Datum.string(x.offsetTime.toString()) } + register(TIMEZ, TIME) { x, t -> Datum.time(x.localTime, t.precision) } + register(TIMEZ, TIMEZ) { x, t -> Datum.timez(x.offsetTime, t.precision) } + register(TIMEZ, TIMESTAMP) { x, t -> Datum.timestamp(x.localTime.atDate(LocalDate.now()), t.precision) } + register(TIMEZ, TIMESTAMPZ) { x, t -> Datum.timestampz(x.offsetTime.atDate(LocalDate.now()), t.precision) } + } + private fun registerTimestamp() { + // WITHOUT TZ + register(TIMESTAMP, STRING) { x, _ -> Datum.string(x.localDateTime.toString()) } + register(TIMESTAMP, TIMESTAMP) { x, t -> Datum.timestamp(x.localDateTime, t.precision) } + register(TIMESTAMP, TIMESTAMPZ) { x, t -> Datum.timestampz(x.localDateTime.atOffset(ZoneOffset.UTC), t.precision) } + register(TIMESTAMP, TIME) { x, t -> Datum.time(x.localTime, t.precision) } + register(TIMESTAMP, DATE) { x, _ -> Datum.date(x.localDate) } // WITH TZ - register(TIMEZ, VARCHAR) { x, t -> Datum.varchar(x.time.toString(), t.length) } - register(TIMEZ, CHAR) { x, t -> Datum.character(x.time.toString(), t.length) } - register(TIMEZ, CLOB) { x, t -> Datum.clob(x.time.toString().toByteArray(), t.length) } - register(TIMEZ, STRING) { x, _ -> Datum.string(x.time.toString()) } - register(TIMEZ, TIME) { x, _ -> Datum.time(x.time) } - register(TIMEZ, TIMESTAMP) { x, _ -> Datum.timestamp(DateTimeValue.timestamp(DateTimeValue.date(1970, 1, 1), x.time)) } - register(TIMEZ, TIMESTAMPZ) { x, _ -> Datum.timestamp(DateTimeValue.timestamp(DateTimeValue.date(1970, 1, 1), x.time)) } + register(TIMESTAMPZ, STRING) { x, _ -> Datum.string(x.localDateTime.toString()) } + register(TIMESTAMPZ, TIMESTAMP) { x, t -> Datum.timestamp(x.localDateTime, t.precision) } + register(TIMESTAMPZ, TIMESTAMPZ) { x, t -> Datum.timestampz(x.offsetDateTime, t.precision) } + register(TIMESTAMPZ, TIME) { x, t -> Datum.time(x.localTime, t.precision) } + register(TIMESTAMPZ, DATE) { x, _ -> Datum.date(x.localDate) } } private fun registerVariant() { diff --git a/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt b/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt index 21b9d41afa..7df3e74324 100644 --- a/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt +++ b/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt @@ -1949,7 +1949,7 @@ internal class PartiQLParserDefault : PartiQLParser { } val precision = ctx.LITERAL_INTEGER()?.let { val p = it.text.toBigInteger().toInt() - if (p < 0 || 9 < p) throw error(it.symbol, "Precision out of bounds") + if (p < 0 || 9 < p) throw error(it.symbol, "Precision out of bounds [0,9]") p } val type = when (ctx.ZONE()) { diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt index 6f04f2a863..9b28fb9ba1 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt @@ -89,17 +89,14 @@ import org.partiql.planner.internal.ir.rexOpVarLocal import org.partiql.planner.internal.ir.rexOpVarUnresolved import org.partiql.planner.internal.typer.CompilerType import org.partiql.planner.internal.typer.PlanTyper.Companion.toCType -import org.partiql.planner.internal.utils.DateTimeUtils +import org.partiql.planner.internal.util.DateTimeUtils import org.partiql.spi.catalog.Identifier import org.partiql.spi.value.Datum import org.partiql.types.PType -import org.partiql.value.datetime.DateTimeValue import java.math.BigDecimal import java.math.BigInteger import java.math.MathContext import java.math.RoundingMode -import java.time.LocalDate -import java.time.format.DateTimeFormatter import org.partiql.ast.SetQuantifier as AstSetQuantifier /** @@ -192,20 +189,28 @@ internal object RexConverter { val typedString = this.stringValue() when (type.code()) { DataType.DATE -> { - val value = LocalDate.parse(typedString, DateTimeFormatter.ISO_LOCAL_DATE) - val date = DateTimeValue.date(value.year, value.monthValue, value.dayOfMonth) + val date = DateTimeUtils.parseDate(typedString) return Datum.date(date) } - DataType.TIME, DataType.TIME_WITH_TIME_ZONE -> { - val time = DateTimeUtils.parseTimeLiteral(typedString) + DataType.TIME -> { + val time = DateTimeUtils.parseTime(typedString) val precision = type.precision ?: 6 return Datum.time(time, precision) } - DataType.TIMESTAMP, DataType.TIMESTAMP_WITH_TIME_ZONE -> { + DataType.TIME_WITH_TIME_ZONE -> { + val time = DateTimeUtils.parseTimez(typedString) + val precision = type.precision ?: 6 + return Datum.timez(time, precision) + } + DataType.TIMESTAMP -> { val timestamp = DateTimeUtils.parseTimestamp(typedString) val precision = type.precision ?: 6 - val value = timestamp.toPrecision(precision) - return Datum.timestamp(value) + return Datum.timestamp(timestamp, precision) + } + DataType.TIMESTAMP_WITH_TIME_ZONE -> { + val timestamp = DateTimeUtils.parseTimestampz(typedString) + val precision = type.precision ?: 6 + return Datum.timestampz(timestamp, precision) } else -> error("Unsupported typed literal string: $this") } @@ -240,7 +245,11 @@ internal object RexConverter { * @param ctx * @return */ - internal fun visitExprCoerce(node: Expr, ctx: Env, coercion: Rex.Op.Subquery.Coercion = Rex.Op.Subquery.Coercion.SCALAR): Rex { + internal fun visitExprCoerce( + node: Expr, + ctx: Env, + coercion: Rex.Op.Subquery.Coercion = Rex.Op.Subquery.Coercion.SCALAR, + ): Rex { val rex = node.accept(this, ctx) return when (isSqlSelect(node)) { true -> { @@ -291,7 +300,8 @@ internal object RexConverter { val args = when (symbol) { "<", ">", "<=", ">=", - "=", "<>", "!=" -> { + "=", "<>", "!=", + -> { when { // Example: [1, 2] < (SELECT a, b FROM t) isLiteralArray(lhs) && isSqlSelect(rhs) -> { @@ -553,9 +563,18 @@ internal object RexConverter { val selectRef = fromNode.type.schema.size - 1 val constructor = when (val op = curPathNavi.op) { - is Rex.Op.Path.Index -> rex(curPathNavi.type, rexOpPathIndex(rex(op.root.type, rexOpVarLocal(0, selectRef)), op.key)) - is Rex.Op.Path.Key -> rex(curPathNavi.type, rexOpPathKey(rex(op.root.type, rexOpVarLocal(0, selectRef)), op.key)) - is Rex.Op.Path.Symbol -> rex(curPathNavi.type, rexOpPathSymbol(rex(op.root.type, rexOpVarLocal(0, selectRef)), op.key)) + is Rex.Op.Path.Index -> rex( + curPathNavi.type, + rexOpPathIndex(rex(op.root.type, rexOpVarLocal(0, selectRef)), op.key) + ) + is Rex.Op.Path.Key -> rex( + curPathNavi.type, + rexOpPathKey(rex(op.root.type, rexOpVarLocal(0, selectRef)), op.key) + ) + is Rex.Op.Path.Symbol -> rex( + curPathNavi.type, + rexOpPathSymbol(rex(op.root.type, rexOpVarLocal(0, selectRef)), op.key) + ) is Rex.Op.Var.Local -> rex(curPathNavi.type, rexOpVarLocal(0, selectRef)) else -> throw IllegalStateException() } @@ -867,7 +886,12 @@ internal object RexConverter { DataType.BIT_VARYING -> call("is_bitVarying", arg0) // TODO define in parser // - DataType.NUMERIC -> call("is_numeric", targetType.precision.toRex(), targetType.scale.toRex(), arg0) - DataType.DEC, DataType.DECIMAL -> call("is_decimal", targetType.precision.toRex(), targetType.scale.toRex(), arg0) + DataType.DEC, DataType.DECIMAL -> call( + "is_decimal", + targetType.precision.toRex(), + targetType.scale.toRex(), + arg0 + ) DataType.BIGINT, DataType.INT8, DataType.INTEGER8 -> call("is_int64", arg0) DataType.INT4, DataType.INTEGER4, DataType.INTEGER -> call("is_int32", arg0) DataType.INT -> call("is_int", arg0) @@ -1109,9 +1133,24 @@ internal object RexConverter { // DataType.DATE -> PType.date() DataType.TIME -> assertGtEqZeroAndCreate(PType.TIME, "precision", type.precision ?: 0, PType::time) - DataType.TIME_WITH_TIME_ZONE -> assertGtEqZeroAndCreate(PType.TIMEZ, "precision", type.precision ?: 0, PType::timez) - DataType.TIMESTAMP -> assertGtEqZeroAndCreate(PType.TIMESTAMP, "precision", type.precision ?: 6, PType::timestamp) - DataType.TIMESTAMP_WITH_TIME_ZONE -> assertGtEqZeroAndCreate(PType.TIMESTAMPZ, "precision", type.precision ?: 6, PType::timestampz) + DataType.TIME_WITH_TIME_ZONE -> assertGtEqZeroAndCreate( + PType.TIMEZ, + "precision", + type.precision ?: 0, + PType::timez + ) + DataType.TIMESTAMP -> assertGtEqZeroAndCreate( + PType.TIMESTAMP, + "precision", + type.precision ?: 6, + PType::timestamp + ) + DataType.TIMESTAMP_WITH_TIME_ZONE -> assertGtEqZeroAndCreate( + PType.TIMESTAMPZ, + "precision", + type.precision ?: 6, + PType::timestampz + ) // DataType.INTERVAL -> error("INTERVAL is not supported yet.") // diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/util/DateTimeUtils.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/util/DateTimeUtils.kt new file mode 100644 index 0000000000..4c893f9e6e --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/util/DateTimeUtils.kt @@ -0,0 +1,113 @@ +package org.partiql.planner.internal.util + +import java.math.BigDecimal +import java.time.DateTimeException +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.OffsetDateTime +import java.time.OffsetTime +import java.time.ZoneOffset +import java.util.regex.Matcher +import java.util.regex.Pattern + +internal object DateTimeUtils { + + private val DATE_PATTERN: Pattern = Pattern.compile("(?\\d{4,})-(?\\d{2,})-(?\\d{2,})") + private val TIME_PATTERN: Pattern = Pattern.compile("(?\\d{2,}):(?\\d{2,}):(?\\d{2,})(?:\\.(?\\d+))?\\s*(?([+-]\\d\\d:\\d\\d)|(?[Zz]))?") + private val SQL_TIMESTAMP_DATE_TIME_DELIMITER = "\\s+".toRegex() + private val RFC8889_TIMESTAMP_DATE_TIME_DELIMITER = "[Tt]".toRegex() + private val TIMESTAMP_PATTERN = "(?$DATE_PATTERN)($SQL_TIMESTAMP_DATE_TIME_DELIMITER|$RFC8889_TIMESTAMP_DATE_TIME_DELIMITER)(?