Skip to content

Commit

Permalink
Updates Datum to use java.time for a sane datetime (#1691)
Browse files Browse the repository at this point in the history
  • Loading branch information
rchowell authored Jan 7, 2025
1 parent 9e031c4 commit a7eedee
Show file tree
Hide file tree
Showing 55 changed files with 1,117 additions and 948 deletions.
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 @@ -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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -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 -> {
Expand Down Expand Up @@ -291,7 +300,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 @@ -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()
}
Expand Down Expand Up @@ -867,7 +886,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 @@ -1109,9 +1133,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

0 comments on commit a7eedee

Please sign in to comment.