Skip to content

Commit

Permalink
poc on timestamp
Browse files Browse the repository at this point in the history
  • Loading branch information
yliuuuu committed Jun 12, 2023
1 parent fa67bbd commit bb48545
Show file tree
Hide file tree
Showing 35 changed files with 1,655 additions and 768 deletions.
29 changes: 16 additions & 13 deletions partiql-lang/src/main/antlr/PartiQL.g4
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,8 @@ canLosslessCast
canCast
: CAN_CAST PAREN_LEFT expr AS type PAREN_RIGHT;

// TODO: Reconsider how we treat datetime fields (i.e., YEAR, MONTH, etc)
// when we need to support interval
extract
: EXTRACT PAREN_LEFT IDENTIFIER FROM rhs=expr PAREN_RIGHT;

Expand Down Expand Up @@ -721,28 +723,29 @@ pair
: lhs=expr COLON rhs=expr;

literal
: NULL # LiteralNull
| MISSING # LiteralMissing
| TRUE # LiteralTrue
| FALSE # LiteralFalse
| LITERAL_STRING # LiteralString
| LITERAL_INTEGER # LiteralInteger
| LITERAL_DECIMAL # LiteralDecimal
| ION_CLOSURE # LiteralIon
| DATE LITERAL_STRING # LiteralDate
| TIME ( PAREN_LEFT LITERAL_INTEGER PAREN_RIGHT )? (WITH TIME ZONE)? LITERAL_STRING # LiteralTime
: NULL # LiteralNull
| MISSING # LiteralMissing
| TRUE # LiteralTrue
| FALSE # LiteralFalse
| LITERAL_STRING # LiteralString
| LITERAL_INTEGER # LiteralInteger
| LITERAL_DECIMAL # LiteralDecimal
| ION_CLOSURE # LiteralIon
| DATE LITERAL_STRING # LiteralDate
| TIME ( PAREN_LEFT LITERAL_INTEGER PAREN_RIGHT )? (WITH TIME ZONE)? LITERAL_STRING # LiteralTime
| TIMESTAMP ( PAREN_LEFT LITERAL_INTEGER PAREN_RIGHT )? (WITH TIME ZONE)? LITERAL_STRING # LiteralTimestamp
;

type
: datatype=(
NULL | BOOL | BOOLEAN | SMALLINT | INTEGER2 | INT2 | INTEGER | INT | INTEGER4 | INT4
| INTEGER8 | INT8 | BIGINT | REAL | TIMESTAMP | CHAR | CHARACTER | MISSING
| INTEGER8 | INT8 | BIGINT | REAL | CHAR | CHARACTER | MISSING
| STRING | SYMBOL | BLOB | CLOB | DATE | STRUCT | TUPLE | LIST | SEXP | BAG | ANY
) # TypeAtomic
| datatype=DOUBLE PRECISION # TypeAtomic
| datatype=(CHARACTER|CHAR|FLOAT|VARCHAR) ( PAREN_LEFT arg0=LITERAL_INTEGER PAREN_RIGHT )? # TypeArgSingle
| CHARACTER VARYING ( PAREN_LEFT arg0=LITERAL_INTEGER PAREN_RIGHT )? # TypeVarChar
| datatype=(DECIMAL|DEC|NUMERIC) ( PAREN_LEFT arg0=LITERAL_INTEGER ( COMMA arg1=LITERAL_INTEGER )? PAREN_RIGHT )? # TypeArgDouble
| TIME ( PAREN_LEFT precision=LITERAL_INTEGER PAREN_RIGHT )? (WITH TIME ZONE)? # TypeTimeZone
| datatype=(TIME|TIMESTAMP) ( PAREN_LEFT precision=LITERAL_INTEGER PAREN_RIGHT )? (WITH TIME ZONE)? # TypeTimeZone
| symbolPrimitive # TypeCustom
;
;
1 change: 0 additions & 1 deletion partiql-lang/src/main/antlr/PartiQLTokens.g4
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,6 @@ WORK: 'WORK';
WRITE: 'WRITE';
ZONE: 'ZONE';


/**
* window related
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,12 @@ enum class ErrorCode(
"expected time string to be of the format HH:MM:SS[.dddd...][+|-HH:MM]"
),

PARSE_INVALID_TIMESTAMP_STRING(
ErrorCategory.PARSER,
LOC_TOKEN,
"expected timestamp string to be of the format YYYY-MM-DD HH:MM:SS[.dddd...][+|-HH:MM]"
),

@Deprecated("This ErrorCode is subject to removal.") // To be removed before 1.0
@Suppress("UNUSED")
PARSE_EMPTY_SELECT(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import org.partiql.lang.types.StaticTypeUtils.staticTypeFromExprValue
import org.partiql.lang.types.TypedOpParameter
import org.partiql.lang.types.UnknownArguments
import org.partiql.lang.types.toTypedOpParameter
import org.partiql.lang.util.DateTimeUtil
import org.partiql.lang.util.bigDecimalOf
import org.partiql.lang.util.checkThreadInterrupted
import org.partiql.lang.util.codePointSequence
Expand Down Expand Up @@ -397,7 +398,6 @@ internal class EvaluatingCompiler(

private fun compileAstExpr(expr: PartiqlAst.Expr): ThunkEnv {
val metas = expr.metas

return when (expr) {
is PartiqlAst.Expr.Lit -> compileLit(expr, metas)
is PartiqlAst.Expr.Missing -> compileMissing(metas)
Expand All @@ -411,6 +411,7 @@ internal class EvaluatingCompiler(
is PartiqlAst.Expr.Parameter -> compileParameter(expr, metas)
is PartiqlAst.Expr.Date -> compileDate(expr, metas)
is PartiqlAst.Expr.LitTime -> compileLitTime(expr, metas)
is PartiqlAst.Expr.Timestamp -> compileTimestamp(expr, metas)

// arithmetic operations
is PartiqlAst.Expr.Plus -> compilePlus(expr, metas)
Expand Down Expand Up @@ -1448,8 +1449,9 @@ internal class EvaluatingCompiler(

// Short-circuit timestamp -> date roundtrip if precision isn't [Timestamp.Precision.DAY] or
// [Timestamp.Precision.MONTH] or [Timestamp.Precision.YEAR]
// TODO: FIX ME
ExprValueType.TIMESTAMP -> when (typedOpParameter.staticType) {
StaticType.DATE -> when (sourceValue.timestampValue().precision) {
StaticType.DATE -> when (sourceValue.timestampValue().ionTimestamp.precision) {
Timestamp.Precision.DAY, Timestamp.Precision.MONTH, Timestamp.Precision.YEAR -> roundTrip()
else -> ExprValue.newBoolean(false)
}
Expand Down Expand Up @@ -3047,6 +3049,40 @@ internal class EvaluatingCompiler(
)
}

private fun compileTimestamp(expr: PartiqlAst.Expr.Timestamp, metas: Map<String, Any>): ThunkEnv =
thunkFactory.thunkEnv(metas) {
val timestampValue = DateTimeUtil.Timestamp.of(
year = expr.value.year.value.toInt(),
month = expr.value.month.value.toInt(),
day = expr.value.day.value.toInt(),
hour = expr.value.hour.value.toInt(),
minute = expr.value.minute.value.toInt(),
second = expr.value.second.decimalValue.bigDecimalValue(),
tz_minutes = when (expr.value.tzSign?.text) {
null -> null
"+" -> expr.value.tzHour!!.value.toInt() * 60 + expr.value.tzMinutes!!.value.toInt()
// special case, if -00:00 then null
else -> {
val offset = -(expr.value.tzHour!!.value.toInt() * 60 + expr.value.tzMinutes!!.value.toInt())
println(offset)
if (offset == 0) null else offset
}
},
hasOffset = expr.value.hasTimeZone.value,
precision = expr.value.precision?.value?.toInt()
)
// TODO: Revisit This
if (!expr.value.hasTimeZone.value) {
ExprValue.newTimestamp(
timestampValue.adjustToSessionOffset(compileOptions.defaultTimezoneOffset)
)
} else {
ExprValue.newTimestamp(
timestampValue
)
}
}

/** A special wrapper for `UNPIVOT` values as a BAG. */
private class UnpivotedExprValue(private val values: Iterable<ExprValue>) : BaseExprValue() {
override val type = ExprValueType.BAG
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import org.partiql.lang.errors.ErrorCode
import org.partiql.lang.eval.time.NANOS_PER_SECOND
import org.partiql.lang.eval.time.Time
import org.partiql.lang.graph.Graph
import org.partiql.lang.util.DateTimeUtil
import org.partiql.lang.util.bytesValue
import org.partiql.lang.util.propertyValueMapOf
import java.math.BigDecimal
Expand Down Expand Up @@ -140,9 +141,9 @@ interface ExprValue : Iterable<ExprValue>, Faceted {
override fun dateValue(): LocalDate = value
}

private class TimestampExprValue(val value: Timestamp) : ScalarExprValue() {
private class TimestampExprValue(val value: DateTimeUtil.Timestamp) : ScalarExprValue() {
override val type: ExprValueType = ExprValueType.TIMESTAMP
override fun timestampValue(): Timestamp = value
override fun timestampValue(): DateTimeUtil.Timestamp = value
}

private class TimeExprValue(val value: Time) : ScalarExprValue() {
Expand Down Expand Up @@ -279,7 +280,7 @@ interface ExprValue : Iterable<ExprValue>, Faceted {

/** Returns a PartiQL `TIMESTAMP` [ExprValue] instance representing the specified [Timestamp]. */
@JvmStatic
fun newTimestamp(value: Timestamp): ExprValue =
fun newTimestamp(value: DateTimeUtil.Timestamp): ExprValue =
TimestampExprValue(value)

/** Returns a PartiQL `TIME` [ExprValue] instance representing the specified [Time]. */
Expand Down Expand Up @@ -390,7 +391,7 @@ interface ExprValue : Iterable<ExprValue>, Faceted {
val timestampValue = value.timestampValue()
newDate(timestampValue.year, timestampValue.month, timestampValue.day)
} // DATE
value is IonTimestamp -> newTimestamp(value.timestampValue()) // TIMESTAMP
value is IonTimestamp -> newTimestamp(DateTimeUtil.Timestamp.of(value.timestampValue(), true, null)) // TIMESTAMP
value is IonStruct && value.hasTypeAnnotation(TIME_ANNOTATION) -> {
val hourValue = (value["hour"] as IonInt).intValue()
val minuteValue = (value["minute"] as IonInt).intValue()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.amazon.ion.IonType
import com.amazon.ion.IonValue
import com.amazon.ion.Timestamp
import com.amazon.ion.system.IonSystemBuilder
import com.amazon.ionelement.api.ionTimestamp
import org.partiql.lang.ast.SourceLocationMeta
import org.partiql.lang.errors.ErrorCode
import org.partiql.lang.errors.Property
Expand All @@ -32,6 +33,7 @@ import org.partiql.lang.syntax.DATE_TIME_PART_KEYWORDS
import org.partiql.lang.syntax.DateTimePart
import org.partiql.lang.types.StaticTypeUtils.getRuntimeType
import org.partiql.lang.util.ConfigurableExprValueFormatter
import org.partiql.lang.util.DateTimeUtil
import org.partiql.lang.util.bigDecimalOf
import org.partiql.lang.util.coerce
import org.partiql.lang.util.compareTo
Expand Down Expand Up @@ -135,7 +137,7 @@ fun ExprValue.dateValue(): LocalDate =
fun ExprValue.timeValue(): Time =
scalar.timeValue() ?: errNoContext("Expected time: $this", errorCode = ErrorCode.EVALUATOR_UNEXPECTED_VALUE_TYPE, internal = false)

fun ExprValue.timestampValue(): Timestamp =
fun ExprValue.timestampValue(): DateTimeUtil.Timestamp =
scalar.timestampValue() ?: errNoContext("Expected timestamp: $this", errorCode = ErrorCode.EVALUATOR_UNEXPECTED_VALUE_TYPE, internal = false)

fun ExprValue.stringValue(): String =
Expand Down Expand Up @@ -401,6 +403,7 @@ fun ExprValue.cast(
else -> castFailedErr("Invalid type for numeric conversion: $type (this code should be unreachable)", internal = true)
}

// TODO: Revisit the casting behavior
@Suppress("DEPRECATION") // TypedOpBehavior.LEGACY is deprecated.
fun String.exprValue(type: SingleType) = when (type) {
is StringType -> when (typedOpBehavior) {
Expand Down Expand Up @@ -503,17 +506,35 @@ fun ExprValue.cast(
castFailedErr("can't convert string value to DECIMAL", internal = false, cause = e)
}
}
// TODO : Revisit Cast Behavior
// Shall we support cast from Date Type / Time Type?
// what about timestamp with precision <> timestamp with precision.
is TimestampType -> when {
// For now just support text
type.isText -> try {
return ExprValue.newTimestamp(Timestamp.valueOf(stringValue()))
println("source type is text, value is ${this.stringValue()}")
val datetime = DateTimeUtil.Timestamp.of(
Timestamp.valueOf(stringValue()),
targetType.withTimeZone,
targetType.precision
)
println("target type is $targetType")
println("datetime value is $datetime")
return ExprValue.newTimestamp(
DateTimeUtil.Timestamp.of(
Timestamp.valueOf(stringValue()),
targetType.withTimeZone,
targetType.precision
)
)
} catch (e: IllegalArgumentException) {
castFailedErr("can't convert string value to TIMESTAMP", internal = false, cause = e)
}
}
is DateType -> when {
type == ExprValueType.TIMESTAMP -> {
val ts = timestampValue()
return ExprValue.newDate(LocalDate.of(ts.year, ts.month, ts.day))
return ExprValue.newDate(LocalDate.of(ts.ionTimestamp.year, ts.ionTimestamp.month, ts.ionTimestamp.day))
}
type.isText -> try {
// validate that the date string follows the format YYYY-MM-DD
Expand Down Expand Up @@ -554,19 +575,19 @@ fun ExprValue.cast(
type == ExprValueType.TIMESTAMP -> {
val ts = timestampValue()
val timeZoneOffset = when (targetType.withTimeZone) {
true -> ts.localOffset ?: castFailedErr(
true -> ts.ionTimestamp.localOffset ?: castFailedErr(
"Can't convert timestamp value with unknown local offset (i.e. -00:00) to TIME WITH TIME ZONE.",
internal = false
)
else -> null
}
return ExprValue.newTime(
Time.of(
ts.hour,
ts.minute,
ts.second,
(ts.decimalSecond.remainder(BigDecimal.ONE).multiply(NANOS_PER_SECOND.toBigDecimal())).toInt(),
precision ?: ts.decimalSecond.scale(),
ts.ionTimestamp.hour,
ts.ionTimestamp.minute,
ts.ionTimestamp.second,
(ts.ionTimestamp.decimalSecond.remainder(BigDecimal.ONE).multiply(NANOS_PER_SECOND.toBigDecimal())).toInt(),
precision ?: ts.ionTimestamp.decimalSecond.scale(),
timeZoneOffset
)
)
Expand Down Expand Up @@ -613,7 +634,8 @@ fun ExprValue.cast(
type == ExprValueType.DATE -> return dateValue().toString().exprValue(targetType)
type == ExprValueType.TIME -> return timeValue().toString().exprValue(targetType)
type == ExprValueType.BOOL -> return booleanValue().toString().exprValue(targetType)
type == ExprValueType.TIMESTAMP -> return timestampValue().toString().exprValue(targetType)
// TODO : revisit Cast Behavior
type == ExprValueType.TIMESTAMP -> return timestampValue().ionTimestamp.toString().exprValue(targetType)
}
is ClobType -> when {
type.isLob -> return ExprValue.newClob(bytesValue())
Expand Down Expand Up @@ -752,7 +774,7 @@ fun ExprValue.toIonValue(ion: IonSystem): IonValue =
addTypeAnnotation(DATE_ANNOTATION)
}
}
ExprValueType.TIMESTAMP -> ion.newTimestamp(timestampValue())
ExprValueType.TIMESTAMP -> timestampValue().toIonValue(ion)
ExprValueType.TIME -> timeValue().toIonValue(ion)
ExprValueType.SYMBOL -> ion.newSymbol(stringValue())
ExprValueType.STRING -> ion.newString(stringValue())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,13 @@ import com.amazon.ion.IonReader
import com.amazon.ion.IonSystem
import com.amazon.ion.IonType
import com.amazon.ion.IonValue
import com.amazon.ion.Timestamp
import org.partiql.lang.errors.ErrorCode
import org.partiql.lang.eval.time.Time
import org.partiql.lang.util.DateTimeUtil.Timestamp
import org.partiql.lang.util.propertyValueMapOf
import java.math.BigDecimal
import java.math.BigInteger
import java.time.LocalDate
import kotlin.collections.asSequence

@Deprecated("Please use static constructor methods defined in [ExprValue] instead")
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ enum class ExprValueType(
is PartiqlAst.Type.DoublePrecisionType -> FLOAT
is PartiqlAst.Type.DecimalType -> DECIMAL
is PartiqlAst.Type.NumericType -> DECIMAL
is PartiqlAst.Type.TimestampType -> TIMESTAMP
is PartiqlAst.Type.TimestampType,
is PartiqlAst.Type.TimestampWithTimeZoneType -> TIMESTAMP
is PartiqlAst.Type.CharacterType -> STRING
is PartiqlAst.Type.CharacterVaryingType -> STRING
is PartiqlAst.Type.StringType -> STRING
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,7 @@ enum class NaturalExprValueComparators(private val order: Order, private val nul
handle(lType == ExprValueType.TIMESTAMP, rType == ExprValueType.TIMESTAMP) {
val lVal = left.timestampValue()
val rVal = right.timestampValue()

return lVal.compareTo(rVal)
return lVal.naturalOrderCompareTo(rVal)
}
) { return it }

Expand Down
3 changes: 2 additions & 1 deletion partiql-lang/src/main/kotlin/org/partiql/lang/eval/Scalar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package org.partiql.lang.eval

import com.amazon.ion.Timestamp
import org.partiql.lang.eval.time.Time
import org.partiql.lang.util.DateTimeUtil
import java.time.LocalDate

/**
Expand Down Expand Up @@ -43,7 +44,7 @@ interface Scalar {
* Returns this value as a [Timestamp] or `null` if not applicable.
* This operation is only applicable for [ExprValueType.TIMESTAMP]
*/
fun timestampValue(): Timestamp? = null
fun timestampValue(): DateTimeUtil.Timestamp? = null

/**
* Returns this value as a [LocalDate] or `null` if not applicable.
Expand Down
Loading

0 comments on commit bb48545

Please sign in to comment.