Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions datafusion/optimizer/src/analyzer/type_coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,8 @@ fn extract_window_frame_target_type(col_type: &DataType) -> Result<DataType> {
| DataType::LargeList(_)
| DataType::FixedSizeList(_, _)
| DataType::Boolean
| DataType::Time32(_)
| DataType::Time64(_)
)
{
Ok(col_type.clone())
Expand Down Expand Up @@ -1106,9 +1108,8 @@ fn coerce_window_frame(
let target_type = extract_window_frame_target_type(&col_type)?;
// A finite offset bound (e.g. `5 PRECEDING`) is computed as
// `current_value ± offset`, so it is only meaningful for target
// types that support arithmetic. Strings, binaries, booleans
// and lists are orderable -- which is all a free range frame
// needs -- but have no such arithmetic.
// types that support arithmetic. Other orderable target types can
// still use free range frames, whose bounds require comparison only.
let supports_offset_arithmetic =
target_type.is_numeric() || is_interval(&target_type);
if !supports_offset_arithmetic && !window_frame.free_range() {
Expand Down
70 changes: 70 additions & 0 deletions datafusion/sqllogictest/test_files/window.slt
Original file line number Diff line number Diff line change
Expand Up @@ -6948,6 +6948,76 @@ ORDER BY x
62 2
62 2

# RANGE window frame over a time ORDER BY key. Time is orderable, so free
# frames can locate peer groups by comparison even though finite offsets do not
# have suitable frame-bound arithmetic.
query DI
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

# A non-aggregate window function and descending order over nanosecond time.
query DI
SELECT x, RANK() OVER (ORDER BY x DESC)
FROM (VALUES (arrow_cast('01:00:00', 'Time64(Nanosecond)')),
(arrow_cast('02:00:00', 'Time64(Nanosecond)')),
(arrow_cast('02:00:00', 'Time64(Nanosecond)'))) t(x)
ORDER BY x
----
01:00:00 3
02:00:00 1
02:00:00 1

# Explicit free bounds use the same comparison-only support.
query DI
SELECT x, COUNT(*) OVER (ORDER BY x RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES (arrow_cast('01:00:00', 'Time32(Second)')),
(arrow_cast('02:00:00', 'Time32(Second)')),
(arrow_cast('02:00:00', 'Time32(Second)'))) t(x)
ORDER BY x
----
01:00:00 3
02:00:00 2
02:00:00 2

query DI
SELECT x, COUNT(*) OVER (ORDER BY x)
FROM (VALUES (arrow_cast('01:00:00.000', 'Time32(Millisecond)')),
(arrow_cast('02:00:00.000', 'Time32(Millisecond)')),
(arrow_cast('02:00:00.000', 'Time32(Millisecond)'))) t(x)
ORDER BY x
----
01:00:00 1
02:00:00 3
02:00:00 3

# A time ORDER BY key nested in a dictionary resolves through the same arm.
query ?I
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

# Finite offsets over time are rejected for both the start and end bound.
query error DataFusion error: type_coercion\ncaused by\nError during planning: RANGE with offset PRECEDING/FOLLOWING is not supported for ORDER BY type Time64\(µs\)
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 Time64\(µs\)
SELECT COUNT(*) OVER (ORDER BY x RANGE BETWEEN CURRENT ROW AND INTERVAL '1' HOUR FOLLOWING)
FROM (VALUES (arrow_cast('01:00:00', 'Time64(Microsecond)'))) t(x)

Comment on lines +7012 to +7020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @fornwall. Any reason why this should not be supported for Time? They both work with Postgres and DuckDB:

SELECT COUNT(*) OVER (
  ORDER BY x RANGE BETWEEN INTERVAL '1' HOUR PRECEDING AND CURRENT ROW)
FROM (
  VALUES
    (TIME '01:00:00'),
    (TIME '02:00:00'),
    (TIME '04:00:00'),
    (TIME '05:00:00')
) t(x);
 count
-------
     1
     2
     1
     2

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nuno-faria We should support that as well!

But it requires some work to handle the non-wrapping around midnight part (ordinary time +/- interval wraps), so I think that's better in a follow up PR. Does that sound ok?

# 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)
Expand Down
2 changes: 1 addition & 1 deletion dev/update_function_docs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ where **offset** is an non-negative integer.

RANGE and GROUPS modes require an ORDER BY clause (with RANGE the ORDER BY must specify exactly one column).

In RANGE mode an **offset** is measured in ORDER BY values rather than in rows, so the bound is computed by adding it to or subtracting it from the current row's ORDER BY value. That restricts `offset PRECEDING` and `offset FOLLOWING` to ORDER BY types supporting such arithmetic, namely the numeric, date, and timestamp types. Other orderable types, such as strings and binaries, can still be used with `UNBOUNDED PRECEDING`, `CURRENT ROW` and `UNBOUNDED FOLLOWING`, which are located by comparing ORDER BY values.
In RANGE mode an **offset** is measured in ORDER BY values rather than in rows, so the bound is computed by adding it to or subtracting it from the current row's ORDER BY value. That restricts `offset PRECEDING` and `offset FOLLOWING` to ORDER BY types supporting such arithmetic, namely the numeric, date, and timestamp types. Other orderable types, such as strings, binaries, and times, can still be used with `UNBOUNDED PRECEDING`, `CURRENT ROW` and `UNBOUNDED FOLLOWING`, which are located by comparing ORDER BY values.

## Filter clause for aggregate window functions

Expand Down
2 changes: 1 addition & 1 deletion docs/source/user-guide/sql/window_functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ where **offset** is an non-negative integer.

RANGE and GROUPS modes require an ORDER BY clause (with RANGE the ORDER BY must specify exactly one column).

In RANGE mode an **offset** is measured in ORDER BY values rather than in rows, so the bound is computed by adding it to or subtracting it from the current row's ORDER BY value. That restricts `offset PRECEDING` and `offset FOLLOWING` to ORDER BY types supporting such arithmetic, namely the numeric, date, and timestamp types. Other orderable types, such as strings and binaries, can still be used with `UNBOUNDED PRECEDING`, `CURRENT ROW` and `UNBOUNDED FOLLOWING`, which are located by comparing ORDER BY values.
In RANGE mode an **offset** is measured in ORDER BY values rather than in rows, so the bound is computed by adding it to or subtracting it from the current row's ORDER BY value. That restricts `offset PRECEDING` and `offset FOLLOWING` to ORDER BY types supporting such arithmetic, namely the numeric, date, and timestamp types. Other orderable types, such as strings, binaries, and times, can still be used with `UNBOUNDED PRECEDING`, `CURRENT ROW` and `UNBOUNDED FOLLOWING`, which are located by comparing ORDER BY values.

## Filter clause for aggregate window functions

Expand Down