Repository: duckdb/duckdb (language: python)

Issue:
Window range ending in `0 preceding` invalid result
### What happens?

Running a window function with upper limit `0 preceding` excludes the current row and produces identical results to when running with `1 preceding`.

### To Reproduce

```sql
select x, sum(x) over (order by x range between 5 preceding and 1 preceding) as y from (select unnest(range(10)) as x);
select x, sum(x) over (order by x range between 5 preceding and 0 preceding) as y from (select unnest(range(10)) as x);
```

![image](https://github.com/user-attachments/assets/bfc6c760-7178-4a52-bd4d-644e54c8aa0e)

These look identical. I would expect `out2` to include the current row's `x` in its sum.

### OS:

Ubuntu22.04

### DuckDB Version:

1.0.0

### DuckDB Client:

Python 3.10.12

### Full Name:

Jakob Stigenberg

### Affiliation:

Qubos

### What is the latest build you tested with? If possible, we recommend testing with the latest nightly build.

I have tested with a stable release

### Did you include all relevant data sets for reproducing the issue?

Not applicable - the reproduction does not require a data set

### Did you include all code required to reproduce the issue?

- [X] Yes, I have

### Did you include all relevant configuration (e.g., CPU architecture, Python version, Linux distribution) to reproduce the issue?

- [ ] Yes, I have


Discussion / hints:
Yes it looks like this is being clamped to 1 instead of 0. Until I fix this, the workaround is to use `CURRENT ROW` instead, but I can see that if this is being machine generated, that might not be an option.

Fix patch:
diff --git a/src/execution/window_executor.cpp b/src/execution/window_executor.cpp
index 35bc7d97add4..56397f7a7a5b 100644
--- a/src/execution/window_executor.cpp
+++ b/src/execution/window_executor.cpp
@@ -297,13 +297,13 @@ static idx_t FindTypedRangeBound(const WindowInputColumn &over, const idx_t orde
 
 	// Check that the value we are searching for is in range.
 	if (range == WindowBoundary::EXPR_PRECEDING_RANGE) {
-		//	Preceding but value past the end
-		const auto cur_val = over.GetCell<T>(order_end);
+		//	Preceding but value past the current value
+		const auto cur_val = over.GetCell<T>(order_end - 1);
 		if (comp(cur_val, val)) {
 			throw OutOfRangeException("Invalid RANGE PRECEDING value");
 		}
 	} else {
-		//	Following but value before beginning
+		//	Following but value before the current value
 		D_ASSERT(range == WindowBoundary::EXPR_FOLLOWING_RANGE);
 		const auto cur_val = over.GetCell<T>(order_begin);
 		if (comp(val, cur_val)) {
@@ -570,7 +570,7 @@ void WindowBoundariesState::Update(const idx_t row_idx, const WindowInputColumn
 		if (boundary_start.CellIsNull(chunk_idx)) {
 			window_start = peer_start;
 		} else {
-			prev.start = FindOrderedRangeBound<true>(range_collection, range_sense, valid_start, row_idx,
+			prev.start = FindOrderedRangeBound<true>(range_collection, range_sense, valid_start, row_idx + 1,
 			                                         start_boundary, boundary_start, chunk_idx, prev);
 			window_start = prev.start;
 		}
@@ -624,8 +624,8 @@ void WindowBoundariesState::Update(const idx_t row_idx, const WindowInputColumn
 		if (boundary_end.CellIsNull(chunk_idx)) {
 			window_end = peer_end;
 		} else {
-			prev.end = FindOrderedRangeBound<false>(range_collection, range_sense, valid_start, row_idx, end_boundary,
-			                                        boundary_end, chunk_idx, prev);
+			prev.end = FindOrderedRangeBound<false>(range_collection, range_sense, valid_start, row_idx + 1,
+			                                        end_boundary, boundary_end, chunk_idx, prev);
 			window_end = prev.end;
 		}
 		break;


Test patch:
diff --git a/test/sql/window/test_window_range.test b/test/sql/window/test_window_range.test
index ea7bc463acf6..ffb442ae6cf0 100644
--- a/test/sql/window/test_window_range.test
+++ b/test/sql/window/test_window_range.test
@@ -982,6 +982,27 @@ NULL
 NULL
 NULL
 
+# Zero preceding
+query II
+SELECT 
+	x, 
+	SUM(x) OVER (
+		ORDER BY x 
+		RANGE BETWEEN 5 PRECEDING AND 0 PRECEDING
+	) AS y 
+FROM generate_series(1, 10) tbl(x);
+----
+1	1
+2	3
+3	6
+4	10
+5	15
+6	21
+7	27
+8	33
+9	39
+10	45
+
 #==========================================================================
 
 statement ok