From 52adcd10370c6693b6787860f3122237ce23f173 Mon Sep 17 00:00:00 2001 From: water <672684719@qq.com> Date: Fri, 21 Aug 2026 15:21:14 +0800 Subject: [PATCH 1/3] fix: Support RANGE window frames over Time32/Time64 ORDER BY keys --- datafusion/optimizer/src/analyzer/type_coercion.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/datafusion/optimizer/src/analyzer/type_coercion.rs b/datafusion/optimizer/src/analyzer/type_coercion.rs index feadc0370bfd5..65d84ac1c6385 100644 --- a/datafusion/optimizer/src/analyzer/type_coercion.rs +++ b/datafusion/optimizer/src/analyzer/type_coercion.rs @@ -1076,6 +1076,8 @@ fn extract_window_frame_target_type(col_type: &DataType) -> Result { | DataType::LargeList(_) | DataType::FixedSizeList(_, _) | DataType::Boolean + | DataType::Time32(_) + | DataType::Time64(_) ) { Ok(col_type.clone()) From ac5ab6e4adf20838bfc92973eb94170eae5335be Mon Sep 17 00:00:00 2001 From: water <672684719@qq.com> Date: Fri, 21 Aug 2026 15:22:18 +0800 Subject: [PATCH 2/3] test: RANGE window frames over Time32/Time64 ORDER BY keys --- datafusion/sqllogictest/test_files/window.slt | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/datafusion/sqllogictest/test_files/window.slt b/datafusion/sqllogictest/test_files/window.slt index fe8bbdc345166..a888a4849027b 100644 --- a/datafusion/sqllogictest/test_files/window.slt +++ b/datafusion/sqllogictest/test_files/window.slt @@ -7014,3 +7014,86 @@ ORDER BY x false 3 true 2 true 2 + +# RANGE window frame over a Time64 ORDER BY key. The default frame for an +# ORDER BY without an explicit frame is RANGE BETWEEN UNBOUNDED PRECEDING AND +# CURRENT ROW, which used to fail with +# "Internal error: Cannot run range queries on datatype: Time64(Microsecond)". +# Time is orderable, so peer/range comparison is well defined just like Utf8. +query TI +SELECT x, COUNT(*) OVER (ORDER BY x) +FROM (VALUES (arrow_cast('01:00:00', 'Time64(Microsecond)')), + (arrow_cast('02:00:00', 'Time64(Microsecond)')), + (arrow_cast('02:00:00', 'Time64(Microsecond)'))) t(x) +ORDER BY x +---- +01:00:00 1 +02:00:00 3 +02:00:00 3 + +query TI +SELECT x, COUNT(*) OVER (ORDER BY x DESC RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) +FROM (VALUES (arrow_cast('03:00:00', 'Time32(Second)')), + (arrow_cast('04:00:00', 'Time32(Second)')), + (arrow_cast('04:00:00', 'Time32(Second)'))) t(x) +ORDER BY x +---- +03:00:00 3 +04:00:00 2 +04:00:00 2 + +query TI +SELECT x, COUNT(*) OVER (ORDER BY x RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) +FROM (VALUES (arrow_cast('01:00:00', 'Time32(Millisecond)')), + (arrow_cast('02:00:00', 'Time32(Millisecond)')), + (arrow_cast('02:00:00', 'Time32(Millisecond)'))) t(x) +ORDER BY x +---- +01:00:00 3 +02:00:00 2 +02:00:00 2 + +# A non-aggregate window function over a Time64 ORDER BY key. +query TI +SELECT x, RANK() OVER (ORDER BY x) +FROM (VALUES (arrow_cast('01:00:00', 'Time64(Microsecond)')), + (arrow_cast('02:00:00', 'Time64(Microsecond)')), + (arrow_cast('02:00:00', 'Time64(Microsecond)'))) t(x) +ORDER BY x +---- +01:00:00 1 +02:00:00 2 +02:00:00 2 + +# A Time64 order key nested in a dictionary resolves through the same arm. +query TI +SELECT x, COUNT(*) OVER (ORDER BY x) +FROM (VALUES (arrow_cast(arrow_cast('01:00:00', 'Time64(Microsecond)'), 'Dictionary(Int32, Time64(Microsecond))')), + (arrow_cast(arrow_cast('02:00:00', 'Time64(Microsecond)'), 'Dictionary(Int32, Time64(Microsecond))')), + (arrow_cast(arrow_cast('02:00:00', 'Time64(Microsecond)'), 'Dictionary(Int32, Time64(Microsecond)))'))) t(x) +ORDER BY x +---- +01:00:00 1 +02:00:00 3 +02:00:00 3 + +# Unsupported RANGE ORDER BY types still propagate the type-coercion error. +query error DataFusion error: type_coercion\ncaused by\nInternal error: Cannot run range queries on datatype: Struct\("c0": Int64\) +SELECT COUNT(*) OVER (ORDER BY x) +FROM (VALUES (struct(1))) t(x) + +# A finite offset such as `1 HOUR PRECEDING` cannot be supported for time order +# keys because time/interval arithmetic wraps around the 24-hour clock. It must +# be a planning error, not an internal error. +query error DataFusion error: type_coercion\ncaused by\nError during planning: RANGE with offset PRECEDING/FOLLOWING is not supported for ORDER BY type Time64\(Microsecond\) +SELECT COUNT(*) OVER (ORDER BY x RANGE BETWEEN INTERVAL '1' HOUR PRECEDING AND CURRENT ROW) +FROM (VALUES (arrow_cast('01:00:00', 'Time64(Microsecond)')) ) t(x) + +query error DataFusion error: type_coercion\ncaused by\nError during planning: RANGE with offset PRECEDING/FOLLOWING is not supported for ORDER BY type Time32\(Second\) +SELECT COUNT(*) OVER (ORDER BY x RANGE BETWEEN CURRENT ROW AND INTERVAL '15' MINUTE FOLLOWING) +FROM (VALUES (arrow_cast('01:00:00', 'Time32(Second)')) ) t(x) + +# Dictionary-wrapped time is unwrapped before checking offset arithmetic. +query error DataFusion error: type_coercion\ncaused by\nError during planning: RANGE with offset PRECEDING/FOLLOWING is not supported for ORDER BY type Time64\(Microsecond\) +SELECT COUNT(*) OVER (ORDER BY x RANGE BETWEEN INTERVAL '1' HOUR PRECEDING AND CURRENT ROW) +FROM (VALUES (arrow_cast(arrow_cast('01:00:00', 'Time64(Microsecond)'), 'Dictionary(Int32, Time64(Microsecond))'))) t(x) From c758992d85a70039a22d252c375d6667cac5574c Mon Sep 17 00:00:00 2001 From: water <672684719@qq.com> Date: Fri, 21 Aug 2026 15:22:54 +0800 Subject: [PATCH 3/3] test: fix dictionary-wrapped time cast syntax --- datafusion/sqllogictest/test_files/window.slt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datafusion/sqllogictest/test_files/window.slt b/datafusion/sqllogictest/test_files/window.slt index a888a4849027b..d0756eabfebf2 100644 --- a/datafusion/sqllogictest/test_files/window.slt +++ b/datafusion/sqllogictest/test_files/window.slt @@ -7070,7 +7070,7 @@ query TI SELECT x, COUNT(*) OVER (ORDER BY x) FROM (VALUES (arrow_cast(arrow_cast('01:00:00', 'Time64(Microsecond)'), 'Dictionary(Int32, Time64(Microsecond))')), (arrow_cast(arrow_cast('02:00:00', 'Time64(Microsecond)'), 'Dictionary(Int32, Time64(Microsecond))')), - (arrow_cast(arrow_cast('02:00:00', 'Time64(Microsecond)'), 'Dictionary(Int32, Time64(Microsecond)))'))) t(x) + (arrow_cast(arrow_cast('02:00:00', 'Time64(Microsecond)'), 'Dictionary(Int32, Time64(Microsecond))'))) t(x) ORDER BY x ---- 01:00:00 1