Skip to content

Commit bba28d6

Browse files
committed
feat: Implement float*interval and interval/float
1 parent aae150a commit bba28d6

File tree

3 files changed

+399
-2
lines changed

3 files changed

+399
-2
lines changed

datafusion/core/tests/sql/expr.rs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,164 @@ async fn test_interval_expressions() -> Result<()> {
725725
Ok(())
726726
}
727727

728+
#[tokio::test]
729+
async fn test_interval_mul_div_float() -> Result<()> {
730+
macro_rules! test_mul {
731+
($L:expr, $R:expr, $EXPECTED:expr) => {
732+
test_expression!(format!("({}) * ({})", $L, $R), $EXPECTED);
733+
test_expression!(format!("({}) * ({})", $R, $L), $EXPECTED);
734+
};
735+
}
736+
737+
test_mul!(
738+
"0.5",
739+
"interval '1 month'",
740+
"0 years 0 mons 15 days 0 hours 0 mins 0.000000000 secs"
741+
);
742+
test_mul!(
743+
"1.0",
744+
"interval '1 month'",
745+
"0 years 1 mons 0 days 0 hours 0 mins 0.000000000 secs"
746+
);
747+
test_mul!(
748+
"1.5",
749+
"interval '1 month'",
750+
"0 years 1 mons 15 days 0 hours 0 mins 0.000000000 secs"
751+
);
752+
test_mul!(
753+
"2.0",
754+
"interval '1 month'",
755+
"0 years 2 mons 0 days 0 hours 0 mins 0.000000000 secs"
756+
);
757+
test_mul!(
758+
"-1.5",
759+
"interval '1 month'",
760+
"0 years -1 mons -15 days 0 hours 0 mins 0.000000000 secs"
761+
);
762+
test_expression!(
763+
"1.5 * (interval '1 month') / 1.5",
764+
"0 years 0 mons 30 days 0 hours 0 mins 0.000000000 secs"
765+
);
766+
767+
test_mul!(
768+
"0.5",
769+
"interval '1 day'",
770+
"0 years 0 mons 0 days 12 hours 0 mins 0.000000000 secs"
771+
);
772+
test_mul!(
773+
"1.0",
774+
"interval '1 day'",
775+
"0 years 0 mons 1 days 0 hours 0 mins 0.000000000 secs"
776+
);
777+
test_mul!(
778+
"1.5",
779+
"interval '1 day'",
780+
"0 years 0 mons 1 days 12 hours 0 mins 0.000000000 secs"
781+
);
782+
test_mul!(
783+
"2.0",
784+
"interval '1 day'",
785+
"0 years 0 mons 2 days 0 hours 0 mins 0.000000000 secs"
786+
);
787+
test_mul!(
788+
"-1.5",
789+
"interval '1 day'",
790+
"0 years 0 mons -1 days -12 hours 0 mins 0.000000000 secs"
791+
);
792+
test_expression!(
793+
"1.5 * (interval '1 day') / 1.5",
794+
"0 years 0 mons 0 days 24 hours 0 mins 0.000000000 secs"
795+
);
796+
797+
test_mul!(
798+
"0.5",
799+
"interval '1 second'",
800+
"0 years 0 mons 0 days 0 hours 0 mins 0.500000000 secs"
801+
);
802+
test_mul!(
803+
"1.0",
804+
"interval '1 second'",
805+
"0 years 0 mons 0 days 0 hours 0 mins 1.000000000 secs"
806+
);
807+
test_mul!(
808+
"1.5",
809+
"interval '1 second'",
810+
"0 years 0 mons 0 days 0 hours 0 mins 1.500000000 secs"
811+
);
812+
test_mul!(
813+
"2.0",
814+
"interval '1 second'",
815+
"0 years 0 mons 0 days 0 hours 0 mins 2.000000000 secs"
816+
);
817+
// This test prints as -1.-500000000 secs, that looks like a bug in printing
818+
// TODO fix it
819+
// test_mul!(
820+
// "-1.5",
821+
// "interval '1 second'",
822+
// "0 years 0 mons 0 days 0 hours 0 mins -1.500000000 secs"
823+
// );
824+
test_expression!(
825+
"1.5 * (interval '1 second') / 1.5",
826+
"0 years 0 mons 0 days 0 hours 0 mins 1.000000000 secs"
827+
);
828+
829+
// Carry-over cases
830+
test_mul!(
831+
"1.5",
832+
"interval '1 month 1 day'",
833+
"0 years 1 mons 16 days 12 hours 0 mins 0.000000000 secs"
834+
);
835+
test_mul!(
836+
"1.5",
837+
"interval '1 month 1 second'",
838+
"0 years 1 mons 15 days 0 hours 0 mins 1.500000000 secs"
839+
);
840+
test_mul!(
841+
"1.5",
842+
"interval '1 day 1 second'",
843+
"0 years 0 mons 1 days 12 hours 0 mins 1.500000000 secs"
844+
);
845+
846+
Ok(())
847+
}
848+
849+
#[tokio::test]
850+
async fn test_interval_mul_bad_float() -> Result<()> {
851+
macro_rules! test_expr_error {
852+
($SQL:expr, $EXPECTED:expr) => {
853+
let ctx = SessionContext::new();
854+
let sql = format!("SELECT {}", $SQL);
855+
let actual_err = plan_and_collect(&ctx, sql.as_str()).await.unwrap_err();
856+
assert_eq!(actual_err.to_string(), $EXPECTED);
857+
};
858+
}
859+
macro_rules! test_mul_error {
860+
($L:expr, $R:expr, $EXPECTED:expr) => {
861+
test_expr_error!(format!("({}) * ({})", $L, $R), $EXPECTED);
862+
test_expr_error!(format!("({}) * ({})", $R, $L), $EXPECTED);
863+
};
864+
}
865+
866+
// This behaviour was checked on PostgreSQL 15.7
867+
test_mul_error!(
868+
"cast('NaN' as double precision)",
869+
"interval '1 month'",
870+
"Execution error: interval out of range (float)"
871+
);
872+
test_mul_error!(
873+
"cast('inf' as double precision)",
874+
"interval '1 month'",
875+
"Execution error: interval out of range (float)"
876+
);
877+
test_mul_error!(
878+
"cast('-inf' as double precision)",
879+
"interval '1 month'",
880+
"Execution error: interval out of range (float)"
881+
);
882+
883+
Ok(())
884+
}
885+
728886
#[cfg(feature = "unicode_expressions")]
729887
#[tokio::test]
730888
async fn test_substring_expr() -> Result<()> {

datafusion/expr/src/binary_rule.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,20 @@ pub fn distinct_coercion(
747747
| (Interval(unit), UInt8)
748748
| (Null, Interval(unit))
749749
| (Interval(unit), Null) => Some(Interval(unit.clone())),
750+
// float*interval result is always represented as MonthDayNano, to avoid precision loss
751+
(Float64, Interval(_))
752+
| (Interval(_), Float64)
753+
| (Float32, Interval(_))
754+
| (Interval(_), Float32)
755+
| (Float16, Interval(_))
756+
| (Interval(_), Float16) => Some(Interval(MonthDayNano)),
757+
_ => None,
758+
},
759+
Operator::Divide => match (lhs_type, rhs_type) {
760+
// interval/float result is represented as MonthDayNano, to avoid precision loss
761+
(Interval(_), Float64) | (Interval(_), Float32) | (Interval(_), Float16) => {
762+
Some(Interval(MonthDayNano))
763+
}
750764
_ => None,
751765
},
752766
_ => None,

0 commit comments

Comments
 (0)