Skip to content

Commit

Permalink
fix interval units properly
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelcolvin committed Sep 12, 2024
1 parent 5b6b404 commit bccfd22
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 49 deletions.
59 changes: 10 additions & 49 deletions datafusion/sql/src/expr/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
// under the License.

use crate::planner::{ContextProvider, PlannerContext, SqlToRel};
use arrow::compute::kernels::cast_utils::parse_interval_month_day_nano;
use arrow::compute::kernels::cast_utils::{
parse_interval_month_day_nano_config, IntervalParseConfig, IntervalUnit,
};
use arrow::datatypes::DECIMAL128_MAX_PRECISION;
use arrow_schema::DataType;
use datafusion_common::{
Expand Down Expand Up @@ -318,60 +320,19 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> {
}
};

let value = if has_units(&value) {
// If the interval already contains a unit
// `INTERVAL '5 month' rather than `INTERVAL '5' month`
// skip the other unit
value
} else {
// leading_field really means the unit if specified
// for example, "month" in `INTERVAL '5' month`
match interval.leading_field.as_ref() {
Some(leading_field) => {
format!("{value} {leading_field}")
}
None => {
// default to seconds for the units
// `INTERVAL '5' is parsed as '5 seconds'
format!("{value} seconds")
}
}
// leading_field really means the unit if specified
// for example, "month" in `INTERVAL '5' month`
let value = match interval.leading_field.as_ref() {
Some(leading_field) => format!("{value} {leading_field}"),
None => value,
};

let val = parse_interval_month_day_nano(&value)?;
let config = IntervalParseConfig::new(IntervalUnit::Second);
let val = parse_interval_month_day_nano_config(&value, config)?;
Ok(lit(ScalarValue::IntervalMonthDayNano(Some(val))))
}
}

// TODO make interval parsing better in arrow-rs / expose `IntervalType`
fn has_units(val: &str) -> bool {
let val = val.to_lowercase();
val.ends_with("century")
|| val.ends_with("centuries")
|| val.ends_with("decade")
|| val.ends_with("decades")
|| val.ends_with("year")
|| val.ends_with("years")
|| val.ends_with("month")
|| val.ends_with("months")
|| val.ends_with("week")
|| val.ends_with("weeks")
|| val.ends_with("day")
|| val.ends_with("days")
|| val.ends_with("hour")
|| val.ends_with("hours")
|| val.ends_with("minute")
|| val.ends_with("minutes")
|| val.ends_with("second")
|| val.ends_with("seconds")
|| val.ends_with("millisecond")
|| val.ends_with("milliseconds")
|| val.ends_with("microsecond")
|| val.ends_with("microseconds")
|| val.ends_with("nanosecond")
|| val.ends_with("nanoseconds")
}

/// Try to decode bytes from hex literal string.
///
/// None will be returned if the input literal is hex-invalid.
Expand Down
25 changes: 25 additions & 0 deletions datafusion/sqllogictest/test_files/interval.slt
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,31 @@ select i - d from t;
query error DataFusion error: Error during planning: Cannot coerce arithmetic expression Interval\(MonthDayNano\) \- Timestamp\(Nanosecond, None\) to valid types
select i - ts from t;

# interval unit abreiviation and plurals
query ?
select interval '1s'
----
1.000000000 secs

query ?
select '1s'::interval
----
1.000000000 secs

query ?
select interval'1sec'
----
1.000000000 secs

query ?
select interval '1ms'
----
0.001000000 secs

query ?
select interval '1 y' + interval '1 year'
----
24 mons

# interval (scalar) + date / timestamp (array)
query D
Expand Down

0 comments on commit bccfd22

Please sign in to comment.