Repository navigation
feat: route date and timestamp interval arithmetic through codegen dispatch #5864
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
base: main
Are you sure you want to change the base?
Changes from all commits
a45bb87
2043cc5
495026f
6cb6e36
a488c29
80cd0a7
9479dd2
f39ca87
99519e6
d0b39f9
0e30a02
abee6ba
e31e355
6336ab4
ecbca4c
68e9e26
af5dd91
8794c04
d8ecc3f
54c7609
755862f
ab8370e
9dd5705
02cc7bd
7da1370
50d6040
cfa6413
c3ab854
ff019db
8bb0d59
d8b2c33
c5c8911
f6e447c
370f728
b6019be
8f5236a
c41f341
afff629
405a3ed
78230ab
1c4aaa1
f164ec7
6a7880e
83e15e4
91c65dd
b8ad767
538736d
d49a5bb
73bd42e
28556a5
c6f6e53
67e4873
515b3a9
75ffd1b
66ab6ad
42af798
9ba272c
a86c458
ca31c79
105e624
3860d45
0d5c8b8
78b48f4
b132288
9ed6316
141c5c8
6fbe2ff
15cad62
af3ffee
1cf0caa
de7e0a1
03d4732
34d4775
2fe31b7
69eb8e0
b656839
e7ea933
e9e8a5b
895a557
3841b15
d1b0dda
22891cc
8858821
f5aba02
91f359a
bed0454
9e5bf58
68526d5
213af14
bf54eae
e8023e1
c60fa0e
c07de5f
efbcee8
7176010
036ba5c
05cb982
d707bfd
aca5fdc
f8eb236
ff5ced3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,7 +21,7 @@ package org.apache.comet.serde | |
|
|
||
| import java.util.Locale | ||
|
|
||
| import org.apache.spark.sql.catalyst.expressions.{AddMonths, Attribute, Cast, ConvertTimezone, DateAdd, DateDiff, DateFormatClass, DateFromUnixDate, DateSub, DayOfMonth, DayOfWeek, DayOfYear, Days, DivideDTInterval, Expression, FromUTCTimestamp, GetDateField, GetTimestamp, Hour, Hours, LastDay, Literal, MakeDate, MakeDTInterval, MakeInterval, MakeTimestamp, MakeYMInterval, MicrosToTimestamp, MillisToTimestamp, Minute, Month, MonthsBetween, MultiplyDTInterval, MultiplyYMInterval, NextDay, PreciseTimestampConversion, Quarter, Second, SecondsToTimestamp, TimestampAdd, TimestampDiff, ToUnixTimestamp, ToUTCTimestamp, TruncDate, TruncTimestamp, UnixDate, UnixMicros, UnixMillis, UnixSeconds, UnixTimestamp, WeekDay, WeekOfYear, Year} | ||
| import org.apache.spark.sql.catalyst.expressions.{AddMonths, Attribute, Cast, ConvertTimezone, DateAdd, DateAddInterval, DateAddYMInterval, DateDiff, DateFormatClass, DateFromUnixDate, DateSub, DayOfMonth, DayOfWeek, DayOfYear, Days, DivideDTInterval, Expression, FromUTCTimestamp, GetDateField, GetTimestamp, Hour, Hours, LastDay, Literal, MakeDate, MakeDTInterval, MakeInterval, MakeTimestamp, MakeYMInterval, MicrosToTimestamp, MillisToTimestamp, Minute, Month, MonthsBetween, MultiplyDTInterval, MultiplyYMInterval, NextDay, PreciseTimestampConversion, Quarter, Second, SecondsToTimestamp, SubtractDates, SubtractTimestamps, TimestampAdd, TimestampAddYMInterval, TimestampDiff, ToUnixTimestamp, ToUTCTimestamp, TruncDate, TruncTimestamp, UnixDate, UnixMicros, UnixMillis, UnixSeconds, UnixTimestamp, WeekDay, WeekOfYear, Year} | ||
| import org.apache.spark.sql.types.{CalendarIntervalType, DataType, DateType, DoubleType, FloatType, IntegerType, LongType, StringType, TimestampNTZType, TimestampType} | ||
| import org.apache.spark.unsafe.types.UTF8String | ||
|
|
||
|
|
@@ -945,6 +945,33 @@ object CometTimestampAdd extends CometCodegenDispatch[TimestampAdd] | |
|
|
||
| object CometTimestampDiff extends CometCodegenDispatch[TimestampDiff] | ||
|
|
||
| // No `timestamp + interval` serde here: Spark 4.1 renames `TimeAdd`, so the shims own it. | ||
| object CometDateAddInterval extends CometCodegenDispatch[DateAddInterval] | ||
|
|
||
| object CometDateAddYMInterval extends CometCodegenDispatch[DateAddYMInterval] | ||
|
|
||
| object CometTimestampAddYMInterval extends CometCodegenDispatch[TimestampAddYMInterval] | ||
|
|
||
| // subtractDates leaves microseconds at 0, so legacy mode needs no guard here. | ||
| object CometSubtractDates extends CometCodegenDispatch[SubtractDates] | ||
|
|
||
| object CometSubtractTimestamps extends CometCodegenDispatch[SubtractTimestamps] { | ||
| private val legacyIntervalReason = | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the same Worth a sentence on why the two take opposite approaches to the same bug, as well.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. The reason string links #5279, and the comment above the branch says why this one declines where |
||
| "In legacy interval mode (`spark.sql.legacy.interval.enabled=true`) the result is a" + | ||
| " `CalendarIntervalType`, and the JVM codegen dispatcher's calendar-interval output" + | ||
| " cannot carry a span past about 292 years (see" + | ||
| " https://github.com/apache/datafusion-comet/issues/5279), so the expression falls" + | ||
| " back to Spark" | ||
|
|
||
| override def getUnsupportedReasons(): Seq[String] = Seq(legacyIntervalReason) | ||
|
|
||
| // Unlike `make_interval`, `ts - ts` can exceed the #5279 span limit on ordinary data. | ||
| override def getSupportLevel(expr: SubtractTimestamps): SupportLevel = expr.dataType match { | ||
| case CalendarIntervalType => Unsupported(Some(legacyIntervalReason)) | ||
| case _ => Compatible() | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Spark's internal `PreciseTimestampConversion` reinterprets a value between the timestamp types | ||
| * (`TimestampType` / `TimestampNTZType`) and `LongType` without losing microsecond precision. It | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, | ||
| * software distributed under the License is distributed on an | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| * KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
|
|
||
| package org.apache.comet.serde | ||
|
|
||
| import org.apache.spark.sql.catalyst.expressions.TimestampAddInterval | ||
|
|
||
| /** `timestamp + interval` on Spark 4.1+, which earlier versions call `TimeAdd`. */ | ||
| object CometTimestampAddInterval extends CometCodegenDispatch[TimestampAddInterval] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| -- Licensed to the Apache Software Foundation (ASF) under one | ||
| -- or more contributor license agreements. See the NOTICE file | ||
| -- distributed with this work for additional information | ||
| -- regarding copyright ownership. The ASF licenses this file | ||
| -- to you under the Apache License, Version 2.0 (the | ||
| -- "License"); you may not use this file except in compliance | ||
| -- with the License. You may obtain a copy of the License at | ||
| -- | ||
| -- http://www.apache.org/licenses/LICENSE-2.0 | ||
| -- | ||
| -- Unless required by applicable law or agreed to in writing, | ||
| -- software distributed under the License is distributed on an | ||
| -- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| -- KIND, either express or implied. See the License for the | ||
| -- specific language governing permissions and limitations | ||
| -- under the License. | ||
|
|
||
| -- ANSI errors are in date_add_interval_ansi.sql. America/Los_Angeles is pinned so the 25-hour | ||
| -- row crosses DST. | ||
| -- Config: spark.sql.session.timeZone=America/Los_Angeles | ||
| -- Config: spark.comet.exec.scalaUDF.codegen.enabled=true | ||
| -- Config: spark.comet.shuffle.mode=native | ||
|
|
||
| statement | ||
| CREATE TABLE test_date_add_interval(d date, y int, m int, dd int, h int, k int) USING parquet | ||
|
|
||
| statement | ||
| INSERT INTO test_date_add_interval VALUES | ||
| (date'2024-01-31', 0, 1, 0, 0, 1), | ||
| (date'2024-01-31', 1, 1, 1, 0, 1), | ||
| (date'2024-02-29', 1, 0, 0, 0, 2), | ||
| (date'2024-03-31', 0, -1, 0, 0, 2), | ||
| (date'2024-12-31', 0, 0, 1, 0, 3), | ||
| (date'2024-03-09', 0, 0, 1, 0, 3), | ||
| (date'2024-03-09', 0, 0, 0, 25, 4), | ||
| (date'1970-01-01', -1, -1, -1, -1, 4), | ||
| (date'2024-06-15', NULL, 1, 1, 0, 5), | ||
| (date'2024-06-15', 0, NULL, 1, 0, 5), | ||
| (date'2024-06-15', 0, 0, NULL, 0, 6), | ||
| (date'2024-06-15', 0, 0, 0, NULL, 6), | ||
| (NULL, 1, 1, 1, 0, 7) | ||
|
|
||
| -- month arithmetic clamps to the end of the shorter month before the day part is added | ||
| query expect_dispatch(dateaddinterval) | ||
| SELECT d, y, m, dd, d + make_interval(y, m, 0, dd) FROM test_date_add_interval | ||
|
|
||
| query | ||
| SELECT make_interval(y, m, 0, dd) + d FROM test_date_add_interval | ||
|
|
||
| -- with ANSI off the hour part is applied on the timestamp and truncated away again | ||
| query | ||
| SELECT d, h, d + make_interval(y, m, 0, dd, h) FROM test_date_add_interval | ||
|
|
||
| -- make_interval, as the parser rejects literals mixing year-month and day-time units | ||
| query | ||
| SELECT | ||
| d + make_interval(1, 0, 0, 1), | ||
| d + make_interval(-1, 0, 0, -1), | ||
| d + make_interval(0, 1, 0, 1), | ||
| d + make_interval(0, 1, 0, 1, 12), | ||
| d - make_interval(1, 0, 0, 1), | ||
| d - make_interval(0, 1, 0, 1) | ||
| FROM test_date_add_interval | ||
|
|
||
| query | ||
| SELECT | ||
| date'2024-01-31' + make_interval(0, 1, 0, 1), | ||
| date'2024-02-29' + make_interval(1, 0, 0, 1), | ||
| date'2024-01-31' - make_interval(0, 1, 0, 1) | ||
|
|
||
| query | ||
| SELECT k, d + make_interval(y, m, 0, dd) AS r | ||
| FROM test_date_add_interval | ||
| DISTRIBUTE BY k |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| -- Licensed to the Apache Software Foundation (ASF) under one | ||
| -- or more contributor license agreements. See the NOTICE file | ||
| -- distributed with this work for additional information | ||
| -- regarding copyright ownership. The ASF licenses this file | ||
| -- to you under the Apache License, Version 2.0 (the | ||
| -- "License"); you may not use this file except in compliance | ||
| -- with the License. You may obtain a copy of the License at | ||
| -- | ||
| -- http://www.apache.org/licenses/LICENSE-2.0 | ||
| -- | ||
| -- Unless required by applicable law or agreed to in writing, | ||
| -- software distributed under the License is distributed on an | ||
| -- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| -- KIND, either express or implied. See the License for the | ||
| -- specific language governing permissions and limitations | ||
| -- under the License. | ||
|
|
||
| -- With ANSI on, day-granular intervals stay on the date path and any time part is rejected. | ||
| -- Config: spark.sql.ansi.enabled=true | ||
| -- Config: spark.comet.exec.scalaUDF.codegen.enabled=true | ||
| -- MinSparkVersion: 4.0 | ||
|
|
||
| statement | ||
| CREATE TABLE test_date_add_interval_ansi(d date, m int, dd int) USING parquet | ||
|
|
||
| statement | ||
| INSERT INTO test_date_add_interval_ansi VALUES | ||
| (date'2024-01-31', 1, 1), | ||
| (date'2024-02-29', 12, 0), | ||
| (date'2024-03-31', -1, -1), | ||
| (date'2024-06-15', NULL, 1), | ||
| (NULL, 1, 1) | ||
|
|
||
| -- the NULL month row builds a NULL interval in the kernel and yields NULL, not an error; a NULL | ||
| -- literal would be folded | ||
| query expect_dispatch(dateaddinterval) | ||
| SELECT d, m, dd, d + make_interval(0, m, 0, dd), d - make_interval(0, 1, 0, 1) | ||
| FROM test_date_add_interval_ansi | ||
|
|
||
| query expect_error(INVALID_INTERVAL_WITH_MICROSECONDS_ADDITION) | ||
| SELECT d + make_interval(0, 1, 0, 1, 12) FROM test_date_add_interval_ansi |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| -- Licensed to the Apache Software Foundation (ASF) under one | ||
| -- or more contributor license agreements. See the NOTICE file | ||
| -- distributed with this work for additional information | ||
| -- regarding copyright ownership. The ASF licenses this file | ||
| -- to you under the Apache License, Version 2.0 (the | ||
| -- "License"); you may not use this file except in compliance | ||
| -- with the License. You may obtain a copy of the License at | ||
| -- | ||
| -- http://www.apache.org/licenses/LICENSE-2.0 | ||
| -- | ||
| -- Unless required by applicable law or agreed to in writing, | ||
| -- software distributed under the License is distributed on an | ||
| -- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| -- KIND, either express or implied. See the License for the | ||
| -- specific language governing permissions and limitations | ||
| -- under the License. | ||
|
|
||
| -- With ANSI on, day-granular intervals stay on the date path and any time part is rejected. | ||
| -- Config: spark.sql.ansi.enabled=true | ||
| -- Config: spark.comet.exec.scalaUDF.codegen.enabled=true | ||
| -- MaxSparkVersion: 3.5 | ||
|
|
||
| statement | ||
| CREATE TABLE test_date_add_interval_ansi(d date, m int, dd int) USING parquet | ||
|
|
||
| statement | ||
| INSERT INTO test_date_add_interval_ansi VALUES | ||
| (date'2024-01-31', 1, 1), | ||
| (date'2024-02-29', 12, 0), | ||
| (date'2024-03-31', -1, -1), | ||
| (date'2024-06-15', NULL, 1), | ||
| (NULL, 1, 1) | ||
|
|
||
| -- the NULL month row builds a NULL interval in the kernel and yields NULL, not an error; a NULL | ||
| -- literal would be folded | ||
| query expect_dispatch(dateaddinterval) | ||
| SELECT d, m, dd, d + make_interval(0, m, 0, dd), d - make_interval(0, 1, 0, 1) | ||
| FROM test_date_add_interval_ansi | ||
|
|
||
| query expect_error(Cannot add hours, minutes or seconds, milliseconds, microseconds to a date) | ||
| SELECT d + make_interval(0, 1, 0, 1, 12) FROM test_date_add_interval_ansi |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| -- Licensed to the Apache Software Foundation (ASF) under one | ||
| -- or more contributor license agreements. See the NOTICE file | ||
| -- distributed with this work for additional information | ||
| -- regarding copyright ownership. The ASF licenses this file | ||
| -- to you under the Apache License, Version 2.0 (the | ||
| -- "License"); you may not use this file except in compliance | ||
| -- with the License. You may obtain a copy of the License at | ||
| -- | ||
| -- http://www.apache.org/licenses/LICENSE-2.0 | ||
| -- | ||
| -- Unless required by applicable law or agreed to in writing, | ||
| -- software distributed under the License is distributed on an | ||
| -- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| -- KIND, either express or implied. See the License for the | ||
| -- specific language governing permissions and limitations | ||
| -- under the License. | ||
|
|
||
| -- Month arithmetic clamps to the last day of the target month (the 01-31 and 02-29 rows). | ||
| -- Config: spark.comet.exec.scalaUDF.codegen.enabled=true | ||
| -- Config: spark.comet.shuffle.mode=native | ||
|
|
||
| statement | ||
| CREATE TABLE test_date_add_ym_interval(d date, y int, m int, k int) USING parquet | ||
|
|
||
| statement | ||
| INSERT INTO test_date_add_ym_interval VALUES | ||
| (date'2024-01-31', 0, 1, 1), | ||
| (date'2024-01-31', 0, 13, 1), | ||
| (date'2024-02-29', 1, 0, 2), | ||
| (date'2024-02-29', 4, 0, 2), | ||
| (date'2024-03-31', 0, -1, 3), | ||
| (date'2024-12-31', 0, 2, 3), | ||
| (date'1970-01-01', -1, -1, 4), | ||
| (date'2024-06-15', NULL, 1, 5), | ||
| (date'2024-06-15', 1, NULL, 5), | ||
| (NULL, 1, 1, 6) | ||
|
|
||
| query expect_dispatch(dateaddyminterval) | ||
| SELECT d, y, m, d + make_ym_interval(y, m), make_ym_interval(y, m) + d | ||
| FROM test_date_add_ym_interval | ||
|
|
||
| query | ||
| SELECT | ||
| d + INTERVAL '1' YEAR, | ||
| d + INTERVAL '1' MONTH, | ||
| d + INTERVAL '1-2' YEAR TO MONTH, | ||
| d + INTERVAL '1 year 1 month', | ||
| d + INTERVAL '-1' MONTH, | ||
| d - INTERVAL '1' MONTH, | ||
| d - INTERVAL '1' YEAR | ||
| FROM test_date_add_ym_interval | ||
|
|
||
| query | ||
| SELECT | ||
| date'2024-01-31' + INTERVAL '1' MONTH, | ||
| date'2024-02-29' + INTERVAL '1' YEAR, | ||
| date'2024-03-31' - INTERVAL '1' MONTH | ||
|
|
||
| query | ||
| SELECT k, d + make_ym_interval(y, m) AS r | ||
| FROM test_date_add_ym_interval | ||
| DISTRIBUTE BY k |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reading this right above the
CometSubtractTimestampsguard, the obvious question is why this one does not need the same branch when its legacy result is also aCalendarIntervalType. The answer is insubtract_dates.sqlbut not here, and it is the invariant whoever adds the next calendar-interval-producing serde will need.Worth a line? Something like "Legacy mode returns a
CalendarIntervalType, butDateTimeUtils.subtractDatesalways sets microseconds to 0, so the dispatcher'smultiplyExactcannot overflow and both modes dispatch." I checked that on 3.4.3, 3.5.8, 4.0.1 and 4.1.3 and it holds on all four.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added in 495026f. The comment above
CometSubtractDatesnow saysDateTimeUtils.subtractDatesalways writes microseconds 0 into the calendar interval, so the dispatcher'smultiplyExactcannot overflow and both modes dispatch, which is the invariant the next calendar-interval serde needs.