Skip to content

Commit f204495

Browse files
Emit CurrentRow for a zero window frame distance, read a zero offset_expr as CurrentRow, and read RelCommon from UpdateRel
1 parent 89ff963 commit f204495

3 files changed

Lines changed: 99 additions & 15 deletions

File tree

‎datafusion/substrait/src/logical_plan/consumer/expr/window_function.rs‎

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ fn from_substrait_bound(
146146
#[expect(deprecated)]
147147
let offset =
148148
bound_offset(bound.offset, bound.offset_expr.as_deref())?;
149+
let Some(offset) = offset else {
150+
return Ok(WindowFrameBound::CurrentRow);
151+
};
149152
if offset <= 0 {
150153
return plan_err!("Preceding bound must be positive");
151154
}
@@ -157,6 +160,9 @@ fn from_substrait_bound(
157160
#[expect(deprecated)]
158161
let offset =
159162
bound_offset(bound.offset, bound.offset_expr.as_deref())?;
163+
let Some(offset) = offset else {
164+
return Ok(WindowFrameBound::CurrentRow);
165+
};
160166
if offset <= 0 {
161167
return plan_err!("Following bound must be positive");
162168
}
@@ -187,23 +193,72 @@ fn from_substrait_bound(
187193
/// Reads the distance of a window frame bound.
188194
///
189195
/// The specification requires a consumer to use `offset_expr` when it is set and
190-
/// to ignore `offset`. DataFusion frame bounds hold a literal, so an expression
191-
/// that is not an int64 literal cannot be represented.
196+
/// to ignore `offset`, and defines a zero `offset_expr` as equivalent to
197+
/// CurrentRow, which `None` reports here. DataFusion frame bounds hold a
198+
/// literal, so an expression that is not an int64 literal cannot be
199+
/// represented.
192200
fn bound_offset(
193201
offset: i64,
194202
offset_expr: Option<&Expression>,
195-
) -> datafusion::common::Result<i64> {
203+
) -> datafusion::common::Result<Option<i64>> {
196204
match offset_expr {
197205
Some(Expression {
198206
rex_type:
199207
Some(RexType::Literal(Literal {
200208
literal_type: Some(LiteralType::I64(value)),
201209
..
202210
})),
203-
}) => Ok(*value),
211+
}) => Ok((*value != 0).then_some(*value)),
204212
Some(_) => not_impl_err!(
205213
"Window frame bound offsets other than int64 literals are not supported"
206214
),
207-
None => Ok(offset),
215+
None => Ok(Some(offset)),
216+
}
217+
}
218+
219+
#[cfg(test)]
220+
mod tests {
221+
use super::*;
222+
223+
fn i64_literal(value: i64) -> Expression {
224+
Expression {
225+
rex_type: Some(RexType::Literal(Literal {
226+
literal_type: Some(LiteralType::I64(value)),
227+
..Default::default()
228+
})),
229+
}
230+
}
231+
232+
/// A zero `offset_expr` is defined as equivalent to CurrentRow, so it is
233+
/// read as such rather than rejected as a non-positive distance.
234+
#[test]
235+
fn zero_offset_expression_reads_as_current_row() {
236+
#[expect(deprecated)]
237+
let bound = Bound {
238+
kind: Some(BoundKind::Preceding(Box::new(SubstraitBound::Preceding {
239+
offset: 0,
240+
offset_expr: Some(Box::new(i64_literal(0))),
241+
}))),
242+
};
243+
assert_eq!(
244+
from_substrait_bound(Some(&bound), true).unwrap(),
245+
WindowFrameBound::CurrentRow
246+
);
247+
}
248+
249+
/// When `offset_expr` is set the consumer must use it and ignore `offset`.
250+
#[test]
251+
fn offset_expression_wins_over_the_deprecated_offset() {
252+
#[expect(deprecated)]
253+
let bound = Bound {
254+
kind: Some(BoundKind::Preceding(Box::new(SubstraitBound::Preceding {
255+
offset: 7,
256+
offset_expr: Some(Box::new(i64_literal(3))),
257+
}))),
258+
};
259+
assert_eq!(
260+
from_substrait_bound(Some(&bound), true).unwrap(),
261+
WindowFrameBound::Preceding(ScalarValue::UInt64(Some(3)))
262+
);
208263
}
209264
}

‎datafusion/substrait/src/logical_plan/consumer/rel/mod.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ fn retrieve_rel_common(rel: &Rel) -> Option<&RelCommon> {
157157
RelType::Window(w) => w.common.as_ref(),
158158
RelType::Exchange(e) => e.common.as_ref(),
159159
RelType::Expand(e) => e.common.as_ref(),
160-
RelType::Update(_) => None,
160+
RelType::Update(u) => u.common.as_ref(),
161161
},
162162
}
163163
}

‎datafusion/substrait/src/logical_plan/producer/expr/window_function.rs‎

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -154,28 +154,39 @@ fn to_substrait_bound(bound: &WindowFrameBound) -> datafusion::common::Result<Bo
154154
}),
155155
WindowFrameBound::Preceding(s) => {
156156
let offset = to_substrait_bound_offset(s)?;
157+
if offset == 0 {
158+
// A zero distance is equivalent to CurrentRow, and `offset`
159+
// cannot represent zero, so the specification asks producers to
160+
// emit CurrentRow instead of a zero bound.
161+
return Ok(Bound {
162+
kind: Some(BoundKind::CurrentRow(SubstraitBound::CurrentRow {})),
163+
});
164+
}
157165
#[expect(deprecated)]
158166
Ok(Bound {
159167
kind: Some(BoundKind::Preceding(Box::new(SubstraitBound::Preceding {
160-
// `offset` carries the int64-literal equivalent for consumers
161-
// that do not read `offset_expr` yet. A zero distance is
162-
// equivalent to CurrentRow and `offset` cannot represent it,
163-
// so the specification asks producers not to write a zero
164-
// `offset_expr`.
168+
// `offset` carries the int64-literal equivalent for
169+
// consumers that do not read `offset_expr` yet.
165170
offset,
166-
offset_expr: (offset != 0)
167-
.then(|| Box::new(bound_offset_expr(offset))),
171+
offset_expr: Some(Box::new(bound_offset_expr(offset))),
168172
}))),
169173
})
170174
}
171175
WindowFrameBound::Following(s) => {
172176
let offset = to_substrait_bound_offset(s)?;
177+
if offset == 0 {
178+
// A zero distance is equivalent to CurrentRow, and `offset`
179+
// cannot represent zero, so the specification asks producers to
180+
// emit CurrentRow instead of a zero bound.
181+
return Ok(Bound {
182+
kind: Some(BoundKind::CurrentRow(SubstraitBound::CurrentRow {})),
183+
});
184+
}
173185
#[expect(deprecated)]
174186
Ok(Bound {
175187
kind: Some(BoundKind::Following(Box::new(SubstraitBound::Following {
176188
offset,
177-
offset_expr: (offset != 0)
178-
.then(|| Box::new(bound_offset_expr(offset))),
189+
offset_expr: Some(Box::new(bound_offset_expr(offset))),
179190
}))),
180191
})
181192
}
@@ -216,6 +227,24 @@ mod tests {
216227
use super::*;
217228
use datafusion::common::assert_contains;
218229

230+
#[test]
231+
fn zero_distance_bounds_become_current_row() {
232+
// `offset` cannot represent zero and the specification asks producers to
233+
// emit CurrentRow rather than a zero bound, so neither field is written.
234+
let frame = WindowFrame::new_bounds(
235+
WindowFrameUnits::Rows,
236+
WindowFrameBound::Preceding(ScalarValue::UInt64(Some(0))),
237+
WindowFrameBound::Following(ScalarValue::UInt64(Some(0))),
238+
);
239+
let (lower, upper) = to_substrait_bounds(&frame).unwrap();
240+
for bound in [lower, upper] {
241+
assert_eq!(
242+
bound.kind,
243+
Some(BoundKind::CurrentRow(SubstraitBound::CurrentRow {}))
244+
);
245+
}
246+
}
247+
219248
#[test]
220249
#[expect(deprecated)]
221250
fn window_frame_offsets() {

0 commit comments

Comments
 (0)