Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1789,7 +1789,46 @@ case class TimestampAddInterval(
override def toString: String = s"$left + $right"
override def sql: String = s"${left.sql} + ${right.sql}"
override def inputTypes: Seq[AbstractDataType] =
Seq(AnyTimestampType, TypeCollection(CalendarIntervalType, DayTimeIntervalType))
Seq(
TypeCollection(AnyTimestampType, AnyTimestampNanoType),
TypeCollection(CalendarIntervalType, DayTimeIntervalType))

override def checkInputDataTypes(): TypeCheckResult = {
val leftIsTimestamp = AnyTimestampType.acceptsType(left.dataType)
val leftIsTimestampNanos = left.dataType.isInstanceOf[AnyTimestampNanoType]

if (!leftIsTimestamp && !leftIsTimestampNanos) {
DataTypeMismatch(
errorSubClass = "UNEXPECTED_INPUT_TYPE",
messageParameters = Map(
"paramIndex" -> ordinalNumber(0),
"requiredType" -> toSQLType(TypeCollection(AnyTimestampType, AnyTimestampNanoType)),
"inputSql" -> toSQLExpr(left),
"inputType" -> toSQLType(left.dataType)))
} else {
right.dataType match {
case _: DayTimeIntervalType => TypeCheckSuccess
case CalendarIntervalType if leftIsTimestampNanos =>
DataTypeMismatch(
errorSubClass = "UNEXPECTED_INPUT_TYPE",
messageParameters = Map(
"paramIndex" -> ordinalNumber(1),
"requiredType" -> toSQLType(DayTimeIntervalType()),
"inputSql" -> toSQLExpr(right),
"inputType" -> toSQLType(right.dataType)))
case CalendarIntervalType => TypeCheckSuccess
case _ =>
DataTypeMismatch(
errorSubClass = "UNEXPECTED_INPUT_TYPE",
messageParameters = Map(
"paramIndex" -> ordinalNumber(1),
"requiredType" ->
toSQLType(TypeCollection(CalendarIntervalType, DayTimeIntervalType)),
"inputSql" -> toSQLExpr(right),
"inputType" -> toSQLType(right.dataType)))
}
}
}

override def dataType: DataType = start.dataType

Expand All @@ -1800,7 +1839,13 @@ case class TimestampAddInterval(

override def nullSafeEval(start: Any, interval: Any): Any = right.dataType match {
case _: DayTimeIntervalType =>
timestampAddDayTime(start.asInstanceOf[Long], interval.asInstanceOf[Long], zoneIdInEval)
left.dataType match {
case _: AnyTimestampNanoType =>
timestampNanosAddDayTime(
start.asInstanceOf[TimestampNanosVal], interval.asInstanceOf[Long], zoneIdInEval)
case _ =>
timestampAddDayTime(start.asInstanceOf[Long], interval.asInstanceOf[Long], zoneIdInEval)
}
case CalendarIntervalType =>
val i = interval.asInstanceOf[CalendarInterval]
timestampAddInterval(start.asInstanceOf[Long], i.months, i.days, i.microseconds, zoneIdInEval)
Expand All @@ -1811,7 +1856,12 @@ case class TimestampAddInterval(
val dtu = DateTimeUtils.getClass.getName.stripSuffix("$")
interval.dataType match {
case _: DayTimeIntervalType =>
defineCodeGen(ctx, ev, (sd, dt) => s"""$dtu.timestampAddDayTime($sd, $dt, $zid)""")
left.dataType match {
case _: AnyTimestampNanoType =>
defineCodeGen(ctx, ev, (sd, dt) => s"""$dtu.timestampNanosAddDayTime($sd, $dt, $zid)""")
case _ =>
defineCodeGen(ctx, ev, (sd, dt) => s"""$dtu.timestampAddDayTime($sd, $dt, $zid)""")
}
case CalendarIntervalType =>
defineCodeGen(ctx, ev, (sd, i) => {
s"""$dtu.timestampAddInterval($sd, $i.months, $i.days, $i.microseconds, $zid)"""
Expand Down
Comment thread
uros-b marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,18 @@ object DateTimeUtils extends SparkDateTimeUtils {
instantToMicros(resultTimestamp.toInstant)
}

/**
* Adds a day-time interval to a nanosecond-precision timestamp value while preserving
* the `nanosWithinMicro` remainder.
*/
def timestampNanosAddDayTime(
start: TimestampNanosVal,
dayTime: Long,
zoneId: ZoneId): TimestampNanosVal = {
val epochMicros = timestampAddDayTime(start.epochMicros, dayTime, zoneId)
TimestampNanosVal.fromParts(epochMicros, start.nanosWithinMicro)
}

/**
* Adds a full interval (months, days, microseconds) to a timestamp represented as the number of
* microseconds since 1970-01-01 00:00:00Z.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1896,6 +1896,77 @@ class DateExpressionsSuite extends SparkFunSuite with ExpressionEvalHelper {
}
}

test("SPARK-57501: add/subtract ANSI day-time interval on nanos timestamps") {
val interval = Duration.ofDays(2).plusMinutes(3).plus(456, ChronoUnit.MICROS)
val minusInterval = Duration.ofDays(-1).minusMinutes(4).minus(321, ChronoUnit.MICROS)

val ntzType = TimestampNTZNanosType(9)
val ntzStart = DateTimeUtils.localDateTimeToTimestampNanos(
LocalDateTime.parse("2020-01-02T03:04:05.123456789"), precision = 9)
val ntzExpectedAdd = DateTimeUtils.localDateTimeToTimestampNanos(
LocalDateTime.parse("2020-01-04T03:07:05.123912789"), precision = 9)
val ntzExpectedSub = DateTimeUtils.localDateTimeToTimestampNanos(
LocalDateTime.parse("2020-01-01T03:00:05.123135789"), precision = 9)

checkEvaluation(
TimestampAddInterval(Literal.create(ntzStart, ntzType), Literal(interval), Some("UTC")),
ntzExpectedAdd)
checkEvaluation(
TimestampAddInterval(
Literal.create(ntzStart, ntzType),
UnaryMinus(Literal(interval)),
Some("UTC")),
DateTimeUtils.localDateTimeToTimestampNanos(
LocalDateTime.parse("2019-12-31T03:01:05.123000789"), precision = 9))
checkEvaluation(
TimestampAddInterval(Literal.create(ntzStart, ntzType), Literal(minusInterval), Some("UTC")),
ntzExpectedSub)
assert(ntzExpectedAdd.nanosWithinMicro == ntzStart.nanosWithinMicro)
assert(ntzExpectedSub.nanosWithinMicro == ntzStart.nanosWithinMicro)

val ltzType = TimestampLTZNanosType(9)
val ltzStart = DateTimeUtils.instantToTimestampNanos(
Instant.parse("2020-01-02T03:04:05.123456789Z"), precision = 9)
val ltzExpectedAdd = DateTimeUtils.instantToTimestampNanos(
Instant.parse("2020-01-04T03:07:05.123912789Z"), precision = 9)
val ltzExpectedSub = DateTimeUtils.instantToTimestampNanos(
Instant.parse("2020-01-01T03:00:05.123135789Z"), precision = 9)

checkEvaluation(
TimestampAddInterval(Literal.create(ltzStart, ltzType), Literal(interval), Some("UTC")),
ltzExpectedAdd)
checkEvaluation(
TimestampAddInterval(
Literal.create(ltzStart, ltzType),
UnaryMinus(Literal(interval)),
Some("UTC")),
DateTimeUtils.instantToTimestampNanos(
Instant.parse("2019-12-31T03:01:05.123000789Z"), precision = 9))
checkEvaluation(
TimestampAddInterval(Literal.create(ltzStart, ltzType), Literal(minusInterval), Some("UTC")),
ltzExpectedSub)
assert(ltzExpectedAdd.nanosWithinMicro == ltzStart.nanosWithinMicro)
assert(ltzExpectedSub.nanosWithinMicro == ltzStart.nanosWithinMicro)

checkConsistencyBetweenInterpretedAndCodegen(
(ts: Expression, dt: Expression) => TimestampAddInterval(ts, dt, Some("UTC")),
ntzType, DayTimeIntervalType())
checkConsistencyBetweenInterpretedAndCodegen(
(ts: Expression, dt: Expression) => TimestampAddInterval(ts, dt, Some("UTC")),
ltzType, DayTimeIntervalType())

val calendarInterval = Literal(new CalendarInterval(1, 2, 3L))
val ntzCalendar = TimestampAddInterval(
Literal.create(ntzStart, ntzType), calendarInterval, Some("UTC"))
val ntzMismatch = ntzCalendar.checkInputDataTypes().asInstanceOf[DataTypeMismatch]
assert(ntzMismatch.errorSubClass == "UNEXPECTED_INPUT_TYPE")

val ltzCalendar = TimestampAddInterval(
Literal.create(ltzStart, ltzType), calendarInterval, Some("UTC"))
val ltzMismatch = ltzCalendar.checkInputDataTypes().asInstanceOf[DataTypeMismatch]
assert(ltzMismatch.errorSubClass == "UNEXPECTED_INPUT_TYPE")
}

test("SPARK-37552: convert a timestamp_ntz to another time zone") {
checkEvaluation(
ConvertTimezone(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,51 @@ class DateTimeUtilsSuite extends SparkFunSuite with Matchers with SQLHelper {
date(2019, 11, 3, 12, 0, 0, 123000, LA))
}

test("SPARK-57501: timestamp nanos add day-time interval preserves nanosWithinMicro") {
def nanos(epochMicros: Long, nanosWithinMicro: Int): TimestampNanosVal =
TimestampNanosVal.fromParts(epochMicros, nanosWithinMicro.toShort)

// The epoch-micros part follows the micro `timestampAddDayTime` (including the transition from
// Pacific Standard to Pacific Daylight Time) while the sub-microsecond remainder is carried
// through unchanged.
assert(timestampNanosAddDayTime(
nanos(date(2019, 3, 9, 12, 0, 0, 123000, LA), 789), MICROS_PER_DAY, LA) ===
nanos(date(2019, 3, 10, 12, 0, 0, 123000, LA), 789))

outstandingZoneIds.foreach { zid =>
// The sub-microsecond remainder is preserved for the boundary values 0, 1 and 999.
Seq(0, 1, 999).foreach { rem =>
// Zero interval is a no-op on both the epoch-micros and the remainder.
assert(timestampNanosAddDayTime(
nanos(date(2021, 3, 18, 19, 44, 1, 100000, zid), rem), 0, zid) ===
nanos(date(2021, 3, 18, 19, 44, 1, 100000, zid), rem))
// Subtracting whole days shifts only the epoch-micros part.
assert(timestampNanosAddDayTime(
nanos(date(2021, 1, 19, 0, 0, 0, 0, zid), rem), -18 * MICROS_PER_DAY, zid) ===
nanos(date(2021, 1, 1, 0, 0, 0, 0, zid), rem))
// A +1 microsecond carry from the day-time interval only moves epochMicros
// (123456 -> 123457), never the remainder.
assert(timestampNanosAddDayTime(
nanos(date(2019, 5, 9, 12, 0, 0, 123456, zid), rem), 2 * MICROS_PER_DAY + 1, zid) ===
nanos(date(2019, 5, 11, 12, 0, 0, 123457, zid), rem))
// Pre-epoch (negative epochMicros) value.
assert(timestampNanosAddDayTime(
nanos(date(1960, 1, 2, 3, 4, 5, 123456, zid), rem), MICROS_PER_DAY, zid) ===
nanos(date(1960, 1, 3, 3, 4, 5, 123456, zid), rem))
}
}

// Consistency with the micro helper: epochMicros matches `timestampAddDayTime` exactly and the
// remainder is independent of the interval amount.
outstandingZoneIds.foreach { zid =>
val start = nanos(date(2020, 1, 2, 3, 4, 5, 123456, zid), 789)
val dayTime = 3 * MICROS_PER_HOUR + 7
val result = timestampNanosAddDayTime(start, dayTime, zid)
assert(result.epochMicros === timestampAddDayTime(start.epochMicros, dayTime, zid))
assert(result.nanosWithinMicro === start.nanosWithinMicro)
}
}

test("SPARK-34903: subtract timestamps") {
DateTimeTestUtils.outstandingZoneIds.foreach { zid =>
Seq(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1721,7 +1721,7 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
"inputSql" : "\"INTERVAL '2-2' YEAR TO MONTH\"",
"inputType" : "\"INTERVAL YEAR TO MONTH\"",
"paramIndex" : "first",
"requiredType" : "\"(TIMESTAMP OR TIMESTAMP WITHOUT TIME ZONE)\"",
"requiredType" : "(\"(TIMESTAMP OR TIMESTAMP WITHOUT TIME ZONE)\" or \"(TIMESTAMP_LTZ(P) OR TIMESTAMP_NTZ(P) WITH P IN [7, 9])\")",
"sqlExpr" : "\"INTERVAL '2-2' YEAR TO MONTH + INTERVAL '3' DAY\""
},
"queryContext" : [ {
Expand All @@ -1745,7 +1745,7 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
"inputSql" : "\"INTERVAL '2-2' YEAR TO MONTH\"",
"inputType" : "\"INTERVAL YEAR TO MONTH\"",
"paramIndex" : "first",
"requiredType" : "\"(TIMESTAMP OR TIMESTAMP WITHOUT TIME ZONE)\"",
"requiredType" : "(\"(TIMESTAMP OR TIMESTAMP WITHOUT TIME ZONE)\" or \"(TIMESTAMP_LTZ(P) OR TIMESTAMP_NTZ(P) WITH P IN [7, 9])\")",
"sqlExpr" : "\"INTERVAL '2-2' YEAR TO MONTH + INTERVAL '3' DAY\""
},
"queryContext" : [ {
Expand All @@ -1769,7 +1769,7 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
"inputSql" : "\"INTERVAL '2-2' YEAR TO MONTH\"",
"inputType" : "\"INTERVAL YEAR TO MONTH\"",
"paramIndex" : "first",
"requiredType" : "\"(TIMESTAMP OR TIMESTAMP WITHOUT TIME ZONE)\"",
"requiredType" : "(\"(TIMESTAMP OR TIMESTAMP WITHOUT TIME ZONE)\" or \"(TIMESTAMP_LTZ(P) OR TIMESTAMP_NTZ(P) WITH P IN [7, 9])\")",
"sqlExpr" : "\"INTERVAL '2-2' YEAR TO MONTH + (- INTERVAL '3' DAY)\""
},
"queryContext" : [ {
Expand Down Expand Up @@ -1815,7 +1815,7 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
"inputSql" : "\"1\"",
"inputType" : "\"INT\"",
"paramIndex" : "first",
"requiredType" : "\"(TIMESTAMP OR TIMESTAMP WITHOUT TIME ZONE)\"",
"requiredType" : "(\"(TIMESTAMP OR TIMESTAMP WITHOUT TIME ZONE)\" or \"(TIMESTAMP_LTZ(P) OR TIMESTAMP_NTZ(P) WITH P IN [7, 9])\")",
"sqlExpr" : "\"1 + (- INTERVAL '02' SECOND)\""
},
"queryContext" : [ {
Expand Down Expand Up @@ -1861,7 +1861,7 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
"inputSql" : "\"1\"",
"inputType" : "\"INT\"",
"paramIndex" : "first",
"requiredType" : "\"(TIMESTAMP OR TIMESTAMP WITHOUT TIME ZONE)\"",
"requiredType" : "(\"(TIMESTAMP OR TIMESTAMP WITHOUT TIME ZONE)\" or \"(TIMESTAMP_LTZ(P) OR TIMESTAMP_NTZ(P) WITH P IN [7, 9])\")",
"sqlExpr" : "\"1 + INTERVAL '02' SECOND\""
},
"queryContext" : [ {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1721,7 +1721,7 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
"inputSql" : "\"INTERVAL '2-2' YEAR TO MONTH\"",
"inputType" : "\"INTERVAL YEAR TO MONTH\"",
"paramIndex" : "first",
"requiredType" : "\"(TIMESTAMP OR TIMESTAMP WITHOUT TIME ZONE)\"",
"requiredType" : "(\"(TIMESTAMP OR TIMESTAMP WITHOUT TIME ZONE)\" or \"(TIMESTAMP_LTZ(P) OR TIMESTAMP_NTZ(P) WITH P IN [7, 9])\")",
"sqlExpr" : "\"INTERVAL '2-2' YEAR TO MONTH + INTERVAL '3' DAY\""
},
"queryContext" : [ {
Expand All @@ -1745,7 +1745,7 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
"inputSql" : "\"INTERVAL '2-2' YEAR TO MONTH\"",
"inputType" : "\"INTERVAL YEAR TO MONTH\"",
"paramIndex" : "first",
"requiredType" : "\"(TIMESTAMP OR TIMESTAMP WITHOUT TIME ZONE)\"",
"requiredType" : "(\"(TIMESTAMP OR TIMESTAMP WITHOUT TIME ZONE)\" or \"(TIMESTAMP_LTZ(P) OR TIMESTAMP_NTZ(P) WITH P IN [7, 9])\")",
"sqlExpr" : "\"INTERVAL '2-2' YEAR TO MONTH + INTERVAL '3' DAY\""
},
"queryContext" : [ {
Expand All @@ -1769,7 +1769,7 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
"inputSql" : "\"INTERVAL '2-2' YEAR TO MONTH\"",
"inputType" : "\"INTERVAL YEAR TO MONTH\"",
"paramIndex" : "first",
"requiredType" : "\"(TIMESTAMP OR TIMESTAMP WITHOUT TIME ZONE)\"",
"requiredType" : "(\"(TIMESTAMP OR TIMESTAMP WITHOUT TIME ZONE)\" or \"(TIMESTAMP_LTZ(P) OR TIMESTAMP_NTZ(P) WITH P IN [7, 9])\")",
"sqlExpr" : "\"INTERVAL '2-2' YEAR TO MONTH + (- INTERVAL '3' DAY)\""
},
"queryContext" : [ {
Expand Down Expand Up @@ -1815,7 +1815,7 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
"inputSql" : "\"1\"",
"inputType" : "\"INT\"",
"paramIndex" : "first",
"requiredType" : "\"(TIMESTAMP OR TIMESTAMP WITHOUT TIME ZONE)\"",
"requiredType" : "(\"(TIMESTAMP OR TIMESTAMP WITHOUT TIME ZONE)\" or \"(TIMESTAMP_LTZ(P) OR TIMESTAMP_NTZ(P) WITH P IN [7, 9])\")",
"sqlExpr" : "\"1 + (- INTERVAL '02' SECOND)\""
},
"queryContext" : [ {
Expand Down Expand Up @@ -1861,7 +1861,7 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
"inputSql" : "\"1\"",
"inputType" : "\"INT\"",
"paramIndex" : "first",
"requiredType" : "\"(TIMESTAMP OR TIMESTAMP WITHOUT TIME ZONE)\"",
"requiredType" : "(\"(TIMESTAMP OR TIMESTAMP WITHOUT TIME ZONE)\" or \"(TIMESTAMP_LTZ(P) OR TIMESTAMP_NTZ(P) WITH P IN [7, 9])\")",
"sqlExpr" : "\"1 + INTERVAL '02' SECOND\""
},
"queryContext" : [ {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
"inputSql" : "\"INTERVAL '2' YEAR\"",
"inputType" : "\"INTERVAL YEAR\"",
"paramIndex" : "first",
"requiredType" : "\"(TIMESTAMP OR TIMESTAMP WITHOUT TIME ZONE)\"",
"requiredType" : "(\"(TIMESTAMP OR TIMESTAMP WITHOUT TIME ZONE)\" or \"(TIMESTAMP_LTZ(P) OR TIMESTAMP_NTZ(P) WITH P IN [7, 9])\")",
"sqlExpr" : "\"INTERVAL '2' YEAR + INTERVAL '02' SECOND\""
},
"queryContext" : [ {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -577,3 +577,73 @@ SELECT map(DATE '2020-01-01', 'v') :: map<timestamp_ltz(9), string>
SELECT named_struct('f', DATE '2020-01-01') :: struct<f: timestamp_ltz(9)>
-- !query analysis
[Analyzer test output redacted due to nondeterminism]


-- !query
SELECT TIMESTAMP_LTZ '2020-01-02 03:04:05.123456789 UTC' +
INTERVAL '2 00:03:00.000456' DAY TO SECOND
-- !query analysis
Project [cast(2020-01-01 19:04:05.123456789 + INTERVAL '2 00:03:00.000456' DAY TO SECOND as timestamp_ltz(9)) AS TIMESTAMP_LTZ '2020-01-01 19:04:05.123456789' + INTERVAL '2 00:03:00.000456' DAY TO SECOND#x]
+- OneRowRelation


-- !query
SELECT TIMESTAMP_LTZ '2020-01-02 03:04:05.123456789 UTC' -
INTERVAL '1 00:04:00.000321' DAY TO SECOND
-- !query analysis
Project [cast(2020-01-01 19:04:05.123456789 - INTERVAL '1 00:04:00.000321' DAY TO SECOND as timestamp_ltz(9)) AS TIMESTAMP_LTZ '2020-01-01 19:04:05.123456789' - INTERVAL '1 00:04:00.000321' DAY TO SECOND#x]
+- OneRowRelation


-- !query
SELECT TIMESTAMP_LTZ '1960-01-02 03:04:05.123456789 UTC' +
INTERVAL '0 00:00:00.000001' DAY TO SECOND
-- !query analysis
Project [cast(1960-01-01 19:04:05.123456789 + INTERVAL '0 00:00:00.000001' DAY TO SECOND as timestamp_ltz(9)) AS TIMESTAMP_LTZ '1960-01-01 19:04:05.123456789' + INTERVAL '0 00:00:00.000001' DAY TO SECOND#x]
+- OneRowRelation


-- !query
SELECT TIMESTAMP_LTZ '2020-01-02 03:04:05.123456789 UTC' + make_interval(0, 1, 0, 2, 0, 0, 0)
-- !query analysis
org.apache.spark.sql.catalyst.ExtendedAnalysisException
{
"errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
"sqlState" : "42K09",
"messageParameters" : {
"inputSql" : "\"make_interval(0, 1, 0, 2, 0, 0, 0)\"",
"inputType" : "\"INTERVAL\"",
"paramIndex" : "second",
"requiredType" : "\"INTERVAL DAY TO SECOND\"",
"sqlExpr" : "\"TIMESTAMP_LTZ '2020-01-01 19:04:05.123456789' + make_interval(0, 1, 0, 2, 0, 0, 0)\""
},
"queryContext" : [ {
"objectType" : "",
"objectName" : "",
"startIndex" : 8,
"stopIndex" : 93,
"fragment" : "TIMESTAMP_LTZ '2020-01-02 03:04:05.123456789 UTC' + make_interval(0, 1, 0, 2, 0, 0, 0)"
} ]
}


-- !query
SELECT TIMESTAMP_LTZ '2020-01-02 03:04:05.123456789 UTC' + INTERVAL '1' MONTH
-- !query analysis
org.apache.spark.sql.catalyst.ExtendedAnalysisException
{
"errorClass" : "DATATYPE_MISMATCH.BINARY_OP_DIFF_TYPES",
"sqlState" : "42K09",
"messageParameters" : {
"left" : "\"TIMESTAMP_LTZ(9)\"",
"right" : "\"INTERVAL MONTH\"",
"sqlExpr" : "\"(TIMESTAMP_LTZ '2020-01-01 19:04:05.123456789' + INTERVAL '1' MONTH)\""
},
"queryContext" : [ {
"objectType" : "",
"objectName" : "",
"startIndex" : 8,
"stopIndex" : 77,
"fragment" : "TIMESTAMP_LTZ '2020-01-02 03:04:05.123456789 UTC' + INTERVAL '1' MONTH"
} ]
}
Loading