Skip to content

Commit 7bd7747

Browse files
samuelcolvinalamb
andauthored
fix interval units parsing (#12448)
* fix interval units properly * fix remaining tests :fingers-crossed: * fix logical conflict --------- Co-authored-by: Andrew Lamb <[email protected]>
1 parent aac10a4 commit 7bd7747

File tree

4 files changed

+55
-55
lines changed

4 files changed

+55
-55
lines changed

datafusion/sql/src/expr/value.rs

+10-49
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
// under the License.
1717

1818
use crate::planner::{ContextProvider, PlannerContext, SqlToRel};
19-
use arrow::compute::kernels::cast_utils::parse_interval_month_day_nano;
19+
use arrow::compute::kernels::cast_utils::{
20+
parse_interval_month_day_nano_config, IntervalParseConfig, IntervalUnit,
21+
};
2022
use arrow::datatypes::DECIMAL128_MAX_PRECISION;
2123
use arrow_schema::DataType;
2224
use datafusion_common::{
@@ -232,27 +234,15 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> {
232234

233235
let value = interval_literal(*interval.value, negative)?;
234236

235-
let value = if has_units(&value) {
236-
// If the interval already contains a unit
237-
// `INTERVAL '5 month' rather than `INTERVAL '5' month`
238-
// skip the other unit
239-
value
240-
} else {
241-
// leading_field really means the unit if specified
242-
// for example, "month" in `INTERVAL '5' month`
243-
match interval.leading_field.as_ref() {
244-
Some(leading_field) => {
245-
format!("{value} {leading_field}")
246-
}
247-
None => {
248-
// default to seconds for the units
249-
// `INTERVAL '5' is parsed as '5 seconds'
250-
format!("{value} seconds")
251-
}
252-
}
237+
// leading_field really means the unit if specified
238+
// for example, "month" in `INTERVAL '5' month`
239+
let value = match interval.leading_field.as_ref() {
240+
Some(leading_field) => format!("{value} {leading_field}"),
241+
None => value,
253242
};
254243

255-
let val = parse_interval_month_day_nano(&value)?;
244+
let config = IntervalParseConfig::new(IntervalUnit::Second);
245+
let val = parse_interval_month_day_nano_config(&value, config)?;
256246
Ok(lit(ScalarValue::IntervalMonthDayNano(Some(val))))
257247
}
258248
}
@@ -292,35 +282,6 @@ fn interval_literal(interval_value: SQLExpr, negative: bool) -> Result<String> {
292282
}
293283
}
294284

295-
// TODO make interval parsing better in arrow-rs / expose `IntervalType`
296-
fn has_units(val: &str) -> bool {
297-
let val = val.to_lowercase();
298-
val.ends_with("century")
299-
|| val.ends_with("centuries")
300-
|| val.ends_with("decade")
301-
|| val.ends_with("decades")
302-
|| val.ends_with("year")
303-
|| val.ends_with("years")
304-
|| val.ends_with("month")
305-
|| val.ends_with("months")
306-
|| val.ends_with("week")
307-
|| val.ends_with("weeks")
308-
|| val.ends_with("day")
309-
|| val.ends_with("days")
310-
|| val.ends_with("hour")
311-
|| val.ends_with("hours")
312-
|| val.ends_with("minute")
313-
|| val.ends_with("minutes")
314-
|| val.ends_with("second")
315-
|| val.ends_with("seconds")
316-
|| val.ends_with("millisecond")
317-
|| val.ends_with("milliseconds")
318-
|| val.ends_with("microsecond")
319-
|| val.ends_with("microseconds")
320-
|| val.ends_with("nanosecond")
321-
|| val.ends_with("nanoseconds")
322-
}
323-
324285
/// Try to decode bytes from hex literal string.
325286
///
326287
/// None will be returned if the input literal is hex-invalid.

datafusion/sql/src/statement.rs

+4
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> {
253253
order_by,
254254
partition_by,
255255
cluster_by,
256+
clustered_by,
256257
options,
257258
strict,
258259
copy_grants,
@@ -346,6 +347,9 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> {
346347
if cluster_by.is_some() {
347348
return not_impl_err!("Cluster by not supported")?;
348349
}
350+
if clustered_by.is_some() {
351+
return not_impl_err!("Clustered by not supported")?;
352+
}
349353
if options.is_some() {
350354
return not_impl_err!("Options not supported")?;
351355
}

datafusion/sqllogictest/test_files/expr.slt

-6
Original file line numberDiff line numberDiff line change
@@ -229,12 +229,6 @@ SELECT interval '5 day'
229229
----
230230
5 days
231231

232-
# Hour is ignored, this matches PostgreSQL
233-
query ?
234-
SELECT interval '5 day' hour
235-
----
236-
5 days
237-
238232
query ?
239233
SELECT interval '5 day 4 hours 3 minutes 2 seconds 100 milliseconds'
240234
----

datafusion/sqllogictest/test_files/interval.slt

+41
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,31 @@ select i - d from t;
486486
query error DataFusion error: Error during planning: Cannot coerce arithmetic expression Interval\(MonthDayNano\) \- Timestamp\(Nanosecond, None\) to valid types
487487
select i - ts from t;
488488

489+
# interval unit abreiviation and plurals
490+
query ?
491+
select interval '1s'
492+
----
493+
1.000000000 secs
494+
495+
query ?
496+
select '1s'::interval
497+
----
498+
1.000000000 secs
499+
500+
query ?
501+
select interval'1sec'
502+
----
503+
1.000000000 secs
504+
505+
query ?
506+
select interval '1ms'
507+
----
508+
0.001000000 secs
509+
510+
query ?
511+
select interval '1 y' + interval '1 year'
512+
----
513+
24 mons
489514

490515
# interval (scalar) + date / timestamp (array)
491516
query D
@@ -502,6 +527,22 @@ select '1 month'::interval + ts from t;
502527
2000-02-01T12:11:10
503528
2000-03-01T00:00:00
504529

530+
# trailing extra unit, this matches PostgreSQL
531+
query ?
532+
select interval '5 day 1' hour
533+
----
534+
5 days 1 hours
535+
536+
# trailing extra unit, this matches PostgreSQL
537+
query ?
538+
select interval '5 day 0' hour
539+
----
540+
5 days
541+
542+
# This is interpreted as "0 hours" with PostgreSQL, should be fixed with
543+
query error DataFusion error: Arrow error: Parser error: Invalid input syntax for type interval: "5 day HOUR"
544+
SELECT interval '5 day' hour
545+
505546
# expected error interval (scalar) - date / timestamp (array)
506547
query error DataFusion error: Error during planning: Cannot coerce arithmetic expression Interval\(MonthDayNano\) \- Date32 to valid types
507548
select '1 month'::interval - d from t;

0 commit comments

Comments
 (0)