Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates Datum to use java.time for a sane datetime #1691

Merged
merged 12 commits into from
Jan 7, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -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]).
Expand Down Expand Up @@ -103,9 +105,9 @@ internal object CastTable {
registerString()
registerBag()
registerList()
registerTimestamp()
registerDate()
registerTime()
registerTimestamp()
registerVariant()
}

Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1950,7 +1950,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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,17 +90,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

/**
Expand Down Expand Up @@ -193,20 +190,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")
}
Expand Down Expand Up @@ -241,7 +246,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 -> {
Expand Down Expand Up @@ -293,7 +302,8 @@ internal object RexConverter {
val args = when (symbol) {
"<", ">",
"<=", ">=",
"=", "<>", "!=" -> {
"=", "<>", "!=",
-> {
when {
// Example: [1, 2] < (SELECT a, b FROM t)
isLiteralArray(lhs) && isSqlSelect(rhs) -> {
Expand Down Expand Up @@ -555,9 +565,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()
}
Expand Down Expand Up @@ -869,7 +888,12 @@ internal object RexConverter {
DataType.BIT_VARYING -> call("is_bitVarying", arg0) // TODO define in parser
// <numeric types> - <exact numeric types>
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)
Expand Down Expand Up @@ -1111,9 +1135,24 @@ internal object RexConverter {
// <datetime type>
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
)
// <interval type>
DataType.INTERVAL -> error("INTERVAL is not supported yet.")
// <container type>
Expand Down
Loading
Loading