Skip to main content

datafusion_functions_window/
lead_lag.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! `lead` and `lag` window function implementations
19
20use crate::utils::{get_scalar_value_from_args, get_signed_integer};
21use arrow::array::UInt64Builder;
22use arrow::compute::{interleave, take};
23use arrow::datatypes::FieldRef;
24use datafusion_common::arrow::array::ArrayRef;
25use datafusion_common::arrow::datatypes::DataType;
26use datafusion_common::arrow::datatypes::Field;
27use datafusion_common::{DataFusionError, Result, ScalarValue, arrow_datafusion_err};
28use datafusion_doc::window_doc_sections::DOC_SECTION_ANALYTICAL;
29use datafusion_expr::{
30    Documentation, LimitEffect, Literal, PartitionEvaluator, ReversedUDWF, Signature,
31    TypeSignature, Volatility, WindowUDFImpl,
32};
33use datafusion_functions_window_common::expr::ExpressionArgs;
34use datafusion_functions_window_common::field::WindowUDFFieldArgs;
35use datafusion_functions_window_common::partition::PartitionEvaluatorArgs;
36use datafusion_physical_expr::expressions;
37use datafusion_physical_expr_common::physical_expr::PhysicalExpr;
38use std::cmp::min;
39use std::collections::VecDeque;
40use std::hash::Hash;
41use std::ops::Range;
42use std::sync::{Arc, LazyLock};
43
44get_or_init_udwf!(
45    Lag,
46    lag,
47    lag_udwf,
48    "Returns the row value that precedes the current row by a specified \
49    offset within partition. If no such row exists, then returns the \
50    default value.",
51    WindowShift::lag
52);
53get_or_init_udwf!(
54    Lead,
55    lead,
56    lead_udwf,
57    "Returns the value from a row that follows the current row by a \
58    specified offset within the partition. If no such row exists, then \
59    returns the default value.",
60    WindowShift::lead
61);
62
63/// Create an expression to represent the `lag` window function
64///
65/// returns value evaluated at the row that is offset rows before the current row within the partition;
66/// if there is no such row, instead return default (which must be of the same type as value).
67/// Both offset and default are evaluated with respect to the current row.
68/// If omitted, offset defaults to 1 and default to null
69pub fn lag(
70    arg: datafusion_expr::Expr,
71    shift_offset: Option<i64>,
72    default_value: Option<ScalarValue>,
73) -> datafusion_expr::Expr {
74    let shift_offset_lit = shift_offset
75        .map(|v| v.lit())
76        .unwrap_or(ScalarValue::Null.lit());
77    let default_lit = default_value.unwrap_or(ScalarValue::Null).lit();
78
79    lag_udwf().call(vec![arg, shift_offset_lit, default_lit])
80}
81
82/// Create an expression to represent the `lead` window function
83///
84/// returns value evaluated at the row that is offset rows after the current row within the partition;
85/// if there is no such row, instead return default (which must be of the same type as value).
86/// Both offset and default are evaluated with respect to the current row.
87/// If omitted, offset defaults to 1 and default to null
88pub fn lead(
89    arg: datafusion_expr::Expr,
90    shift_offset: Option<i64>,
91    default_value: Option<ScalarValue>,
92) -> datafusion_expr::Expr {
93    let shift_offset_lit = shift_offset
94        .map(|v| v.lit())
95        .unwrap_or(ScalarValue::Null.lit());
96    let default_lit = default_value.unwrap_or(ScalarValue::Null).lit();
97
98    lead_udwf().call(vec![arg, shift_offset_lit, default_lit])
99}
100
101#[derive(Debug, PartialEq, Eq, Hash)]
102pub enum WindowShiftKind {
103    Lag,
104    Lead,
105}
106
107impl WindowShiftKind {
108    fn name(&self) -> &'static str {
109        match self {
110            WindowShiftKind::Lag => "lag",
111            WindowShiftKind::Lead => "lead",
112        }
113    }
114
115    /// In [`WindowShiftEvaluator`] a positive offset is used to signal
116    /// computation of `lag()`. So here we negate the input offset
117    /// value when computing `lead()`.
118    fn shift_offset(&self, value: Option<i64>) -> i64 {
119        match self {
120            WindowShiftKind::Lag => value.unwrap_or(1),
121            WindowShiftKind::Lead => value.map_or(-1, |v| v.wrapping_neg()),
122        }
123    }
124}
125
126/// window shift expression
127#[derive(Debug, PartialEq, Eq, Hash)]
128pub struct WindowShift {
129    signature: Signature,
130    kind: WindowShiftKind,
131}
132
133impl WindowShift {
134    fn new(kind: WindowShiftKind) -> Self {
135        Self {
136            signature: Signature::one_of(
137                vec![
138                    TypeSignature::Any(1),
139                    TypeSignature::Any(2),
140                    TypeSignature::Any(3),
141                ],
142                Volatility::Immutable,
143            )
144            .with_parameter_names(vec![
145                "expr".to_string(),
146                "offset".to_string(),
147                "default".to_string(),
148            ])
149            .expect("valid parameter names for lead/lag"),
150            kind,
151        }
152    }
153
154    pub fn lag() -> Self {
155        Self::new(WindowShiftKind::Lag)
156    }
157
158    pub fn lead() -> Self {
159        Self::new(WindowShiftKind::Lead)
160    }
161
162    pub fn kind(&self) -> &WindowShiftKind {
163        &self.kind
164    }
165}
166
167static LAG_DOCUMENTATION: LazyLock<Documentation> = LazyLock::new(|| {
168    Documentation::builder(DOC_SECTION_ANALYTICAL, "Returns value evaluated at the row that is offset rows before the \
169            current row within the partition; if there is no such row, instead return default \
170            (which must be of the same type as value).", "lag(expression, offset, default)")
171        .with_argument("expression", "Expression to operate on")
172        .with_argument("offset", "Integer. Specifies how many rows back \
173        the value of expression should be retrieved. Defaults to 1.")
174        .with_argument("default", "The default value if the offset is \
175        not within the partition. Must be of the same type as expression.")
176        .with_sql_example(r#"
177```sql
178-- Example usage of the lag window function:
179SELECT employee_id,
180    salary,
181    lag(salary, 1, 0) OVER (ORDER BY employee_id) AS prev_salary
182FROM employees;
183
184+-------------+--------+-------------+
185| employee_id | salary | prev_salary |
186+-------------+--------+-------------+
187| 1           | 30000  | 0           |
188| 2           | 50000  | 30000       |
189| 3           | 70000  | 50000       |
190| 4           | 60000  | 70000       |
191+-------------+--------+-------------+
192```
193"#)
194        .build()
195});
196
197fn get_lag_doc() -> &'static Documentation {
198    &LAG_DOCUMENTATION
199}
200
201static LEAD_DOCUMENTATION: LazyLock<Documentation> = LazyLock::new(|| {
202    Documentation::builder(DOC_SECTION_ANALYTICAL,
203            "Returns value evaluated at the row that is offset rows after the \
204            current row within the partition; if there is no such row, instead return default \
205            (which must be of the same type as value).",
206        "lead(expression, offset, default)")
207        .with_argument("expression", "Expression to operate on")
208        .with_argument("offset", "Integer. Specifies how many rows \
209        forward the value of expression should be retrieved. Defaults to 1.")
210        .with_argument("default", "The default value if the offset is \
211        not within the partition. Must be of the same type as expression.")
212        .with_sql_example(r#"
213```sql
214-- Example usage of lead window function:
215SELECT
216    employee_id,
217    department,
218    salary,
219    lead(salary, 1, 0) OVER (PARTITION BY department ORDER BY salary) AS next_salary
220FROM employees;
221
222+-------------+-------------+--------+--------------+
223| employee_id | department  | salary | next_salary  |
224+-------------+-------------+--------+--------------+
225| 1           | Sales       | 30000  | 50000        |
226| 2           | Sales       | 50000  | 70000        |
227| 3           | Sales       | 70000  | 0            |
228| 4           | Engineering | 40000  | 60000        |
229| 5           | Engineering | 60000  | 0            |
230+-------------+-------------+--------+--------------+
231```
232"#)
233        .build()
234});
235
236fn get_lead_doc() -> &'static Documentation {
237    &LEAD_DOCUMENTATION
238}
239
240impl WindowUDFImpl for WindowShift {
241    fn name(&self) -> &str {
242        self.kind.name()
243    }
244
245    fn signature(&self) -> &Signature {
246        &self.signature
247    }
248
249    /// Handles the case where `NULL` expression is passed as an
250    /// argument to `lead`/`lag`. The type is refined depending
251    /// on the default value argument.
252    ///
253    /// For more details see: <https://github.com/apache/datafusion/issues/12717>
254    fn expressions(&self, expr_args: ExpressionArgs) -> Vec<Arc<dyn PhysicalExpr>> {
255        parse_expr(expr_args.input_exprs(), expr_args.input_fields())
256            .into_iter()
257            .collect::<Vec<_>>()
258    }
259
260    fn partition_evaluator(
261        &self,
262        partition_evaluator_args: PartitionEvaluatorArgs,
263    ) -> Result<Box<dyn PartitionEvaluator>> {
264        let shift_offset =
265            get_scalar_value_from_args(partition_evaluator_args.input_exprs(), 1)?
266                .map(|v| get_signed_integer(&v))
267                .map_or(Ok(None), |v| v.map(Some))
268                .map(|n| self.kind.shift_offset(n))
269                .map(|offset| {
270                    if partition_evaluator_args.is_reversed() {
271                        offset.wrapping_neg()
272                    } else {
273                        offset
274                    }
275                })?;
276        let default_value = parse_default_value(
277            partition_evaluator_args.input_exprs(),
278            partition_evaluator_args.input_fields(),
279        )?;
280
281        Ok(Box::new(WindowShiftEvaluator {
282            shift_offset,
283            default_value,
284            ignore_nulls: partition_evaluator_args.ignore_nulls(),
285            non_null_offsets: VecDeque::new(),
286        }))
287    }
288
289    fn field(&self, field_args: WindowUDFFieldArgs) -> Result<FieldRef> {
290        let return_field = parse_expr_field(field_args.input_fields())?;
291
292        Ok(return_field
293            .as_ref()
294            .clone()
295            .with_name(field_args.name())
296            .into())
297    }
298
299    fn reverse_expr(&self) -> ReversedUDWF {
300        match self.kind {
301            WindowShiftKind::Lag => ReversedUDWF::Reversed(lag_udwf()),
302            WindowShiftKind::Lead => ReversedUDWF::Reversed(lead_udwf()),
303        }
304    }
305
306    fn documentation(&self) -> Option<&Documentation> {
307        match self.kind {
308            WindowShiftKind::Lag => Some(get_lag_doc()),
309            WindowShiftKind::Lead => Some(get_lead_doc()),
310        }
311    }
312
313    fn limit_effect(&self, args: &[Arc<dyn PhysicalExpr>]) -> LimitEffect {
314        if self.kind == WindowShiftKind::Lag {
315            return LimitEffect::None;
316        }
317        match args {
318            [_, expr, ..] => {
319                let Some(lit) = expr.downcast_ref::<expressions::Literal>() else {
320                    return LimitEffect::Unknown;
321                };
322                let ScalarValue::Int64(Some(amount)) = lit.value() else {
323                    return LimitEffect::Unknown; // we should only get int64 from the parser
324                };
325                LimitEffect::Relative((*amount).max(0) as usize)
326            }
327            [_] => LimitEffect::Relative(1), // default value
328            _ => LimitEffect::Unknown,       // invalid arguments
329        }
330    }
331}
332
333/// When `lead`/`lag` is evaluated on a `NULL` expression we attempt to
334/// refine it by matching it with the type of the default value.
335///
336/// For e.g. in `lead(NULL, 1, false)` the generic `ScalarValue::Null`
337/// is refined into `ScalarValue::Boolean(None)`. Only the type is
338/// refined, the expression value remains `NULL`.
339///
340/// When the window function is evaluated with `NULL` expression
341/// this guarantees that the type matches with that of the default
342/// value.
343///
344/// For more details see: <https://github.com/apache/datafusion/issues/12717>
345fn parse_expr(
346    input_exprs: &[Arc<dyn PhysicalExpr>],
347    input_fields: &[FieldRef],
348) -> Result<Arc<dyn PhysicalExpr>> {
349    assert!(!input_exprs.is_empty());
350    assert!(!input_fields.is_empty());
351
352    let expr = Arc::clone(input_exprs.first().unwrap());
353    let expr_field = input_fields.first().unwrap();
354
355    // Handles the most common case where NULL is unexpected
356    if !expr_field.data_type().is_null() {
357        return Ok(expr);
358    }
359
360    let default_value = get_scalar_value_from_args(input_exprs, 2)?;
361    default_value.map_or(Ok(expr), |value| {
362        ScalarValue::try_from(&value.data_type())
363            .map(|v| Arc::new(expressions::Literal::new(v)) as Arc<dyn PhysicalExpr>)
364    })
365}
366
367static NULL_FIELD: LazyLock<FieldRef> =
368    LazyLock::new(|| Field::new("value", DataType::Null, true).into());
369
370/// Returns the field of the default value(if provided) when the
371/// expression is `NULL`.
372///
373/// Otherwise, returns the expression field unchanged.
374fn parse_expr_field(input_fields: &[FieldRef]) -> Result<FieldRef> {
375    assert!(!input_fields.is_empty());
376    let expr_field = input_fields.first().unwrap_or(&NULL_FIELD);
377
378    // Handles the most common case where NULL is unexpected
379    if !expr_field.data_type().is_null() {
380        return Ok(expr_field.as_ref().clone().with_nullable(true).into());
381    }
382
383    let default_value_field = input_fields.get(2).unwrap_or(&NULL_FIELD);
384    Ok(default_value_field
385        .as_ref()
386        .clone()
387        .with_nullable(true)
388        .into())
389}
390
391/// Handles type coercion and null value refinement for default value
392/// argument depending on the data type of the input expression.
393fn parse_default_value(
394    input_exprs: &[Arc<dyn PhysicalExpr>],
395    input_types: &[FieldRef],
396) -> Result<ScalarValue> {
397    let expr_field = parse_expr_field(input_types)?;
398    let unparsed = get_scalar_value_from_args(input_exprs, 2)?;
399
400    unparsed
401        .filter(|v| !v.data_type().is_null())
402        .map(|v| v.cast_to(expr_field.data_type()))
403        .unwrap_or_else(|| ScalarValue::try_from(expr_field.data_type()))
404}
405
406#[derive(Debug)]
407struct WindowShiftEvaluator {
408    shift_offset: i64,
409    default_value: ScalarValue,
410    ignore_nulls: bool,
411    // VecDeque contains offset values that between non-null entries
412    non_null_offsets: VecDeque<usize>,
413}
414
415fn offset_magnitude(offset: i64) -> usize {
416    let offset = offset.unsigned_abs();
417    if offset > usize::MAX as u64 {
418        usize::MAX
419    } else {
420        offset as usize
421    }
422}
423
424enum ShiftIndexBuilder {
425    Take(UInt64Builder),
426    Interleave(Vec<(usize, usize)>),
427}
428
429impl ShiftIndexBuilder {
430    fn new(capacity: usize, default_is_null: bool) -> Self {
431        if default_is_null {
432            Self::Take(UInt64Builder::with_capacity(capacity))
433        } else {
434            Self::Interleave(Vec::with_capacity(capacity))
435        }
436    }
437
438    fn append_option(&mut self, index: Option<usize>) {
439        match self {
440            Self::Take(indices) => {
441                indices.append_option(index.map(|index| index as u64));
442            }
443            Self::Interleave(indices) => {
444                // `interleave` receives `[array, default]`.
445                indices.push(index.map_or((1, 0), |index| (0, index)));
446            }
447        }
448    }
449
450    fn finish(
451        self,
452        array: &ArrayRef,
453        default_value: &ScalarValue,
454    ) -> Result<ArrayRef, DataFusionError> {
455        match self {
456            Self::Take(mut indices) => {
457                let indices = indices.finish();
458                take(array.as_ref(), &indices, None)
459                    .map_err(|error| arrow_datafusion_err!(error))
460            }
461            Self::Interleave(indices) => {
462                let default = default_value.to_array_of_size(1)?;
463                interleave(&[array.as_ref(), default.as_ref()], &indices)
464                    .map_err(|error| arrow_datafusion_err!(error))
465            }
466        }
467    }
468}
469
470impl WindowShiftEvaluator {
471    fn is_lag(&self) -> bool {
472        // Mode is LAG, when shift_offset is positive
473        self.shift_offset > 0
474    }
475}
476
477// implement ignore null for evaluate_all
478fn evaluate_all_with_ignore_null(
479    array: &ArrayRef,
480    offset: i64,
481    default_value: &ScalarValue,
482    is_lag: bool,
483) -> Result<ArrayRef, DataFusionError> {
484    if offset == 0 {
485        return Ok(Arc::clone(array));
486    }
487
488    // Arrays without NULLs do not necessarily have a null bitmap.
489    let Some(nulls) = array.nulls() else {
490        return shift_with_default_value(array, offset, default_value);
491    };
492
493    let shift = offset_magnitude(offset);
494    if shift >= array.len() {
495        return default_value.to_array_of_size(array.len());
496    }
497
498    let mut indices = ShiftIndexBuilder::new(array.len(), default_value.is_null());
499    if is_lag {
500        let mut preceding = VecDeque::new();
501        for index in 0..array.len() {
502            let result_index = if preceding.len() == shift {
503                preceding.front().copied()
504            } else {
505                None
506            };
507            indices.append_option(result_index);
508
509            if nulls.is_valid(index) {
510                if preceding.len() == shift {
511                    preceding.pop_front();
512                }
513                preceding.push_back(index);
514            }
515        }
516    } else {
517        let mut following = VecDeque::new();
518        let mut next_index = 0;
519        for index in 0..array.len() {
520            while following.front().is_some_and(|next| *next <= index) {
521                following.pop_front();
522            }
523            next_index = next_index.max(index.saturating_add(1));
524            while following.len() < shift && next_index < array.len() {
525                if nulls.is_valid(next_index) {
526                    following.push_back(next_index);
527                }
528                next_index += 1;
529            }
530            indices.append_option(following.get(shift - 1).copied());
531        }
532    }
533
534    indices.finish(array, default_value)
535}
536// TODO: change the original arrow::compute::kernels::window::shift impl to support an optional default value
537fn shift_with_default_value(
538    array: &ArrayRef,
539    offset: i64,
540    default_value: &ScalarValue,
541) -> Result<ArrayRef> {
542    use datafusion_common::arrow::compute::concat;
543
544    let value_len = array.len() as i64;
545    if offset == 0 {
546        Ok(Arc::clone(array))
547    } else if offset == i64::MIN || offset.abs() >= value_len {
548        default_value.to_array_of_size(value_len as usize)
549    } else {
550        let slice_offset = (-offset).clamp(0, value_len) as usize;
551        let length = array.len() - offset.unsigned_abs() as usize;
552        let slice = array.slice(slice_offset, length);
553
554        // Generate array with remaining `null` items
555        let nulls = offset.unsigned_abs() as usize;
556        let default_values = default_value.to_array_of_size(nulls)?;
557
558        // Concatenate both arrays, add nulls after if shift > 0 else before
559        if offset > 0 {
560            concat(&[default_values.as_ref(), slice.as_ref()])
561                .map_err(|e| arrow_datafusion_err!(e))
562        } else {
563            concat(&[slice.as_ref(), default_values.as_ref()])
564                .map_err(|e| arrow_datafusion_err!(e))
565        }
566    }
567}
568
569impl PartitionEvaluator for WindowShiftEvaluator {
570    fn get_range(&self, idx: usize, n_rows: usize) -> Result<Range<usize>> {
571        let offset = offset_magnitude(self.shift_offset);
572
573        if self.is_lag() {
574            let start = if self.non_null_offsets.len() == offset {
575                // How many rows needed previous than the current row to get necessary lag result
576                let offset: usize = self.non_null_offsets.iter().sum();
577                idx.saturating_sub(offset)
578            } else if !self.ignore_nulls {
579                idx.saturating_sub(offset)
580            } else {
581                0
582            };
583            let end = idx + 1;
584            Ok(Range { start, end })
585        } else {
586            let end = if self.non_null_offsets.len() == offset {
587                // How many rows needed further than the current row to get necessary lead result
588                let offset: usize = self.non_null_offsets.iter().sum();
589                min(idx.saturating_add(offset).saturating_add(1), n_rows)
590            } else if !self.ignore_nulls {
591                min(idx.saturating_add(offset), n_rows)
592            } else {
593                n_rows
594            };
595            Ok(Range { start: idx, end })
596        }
597    }
598
599    fn is_causal(&self) -> bool {
600        // Lagging windows are causal by definition:
601        self.is_lag()
602    }
603
604    fn evaluate(
605        &mut self,
606        values: &[ArrayRef],
607        range: &Range<usize>,
608    ) -> Result<ScalarValue> {
609        let array = &values[0];
610        let len = array.len();
611
612        // LAG mode
613        let i = if self.is_lag() {
614            range
615                .end
616                .checked_sub(1)
617                .and_then(|end| (end as i64).checked_sub(self.shift_offset))
618                .and_then(|value| usize::try_from(value).ok())
619        } else {
620            // LEAD mode
621            (range.start as i64)
622                .checked_sub(self.shift_offset)
623                .and_then(|value| usize::try_from(value).ok())
624        };
625        let mut idx: Option<usize> = i.filter(|i| *i < len);
626
627        // LAG with IGNORE NULLS calculated as the current row index - offset, but only for non-NULL rows
628        // If current row index points to NULL value the row is NOT counted
629        if self.ignore_nulls && self.is_lag() {
630            // LAG when NULLS are ignored.
631            // Find the nonNULL row index that shifted by offset comparing to current row index
632            let shift_offset = offset_magnitude(self.shift_offset);
633            idx = if self.non_null_offsets.len() == shift_offset {
634                let total_offset: usize = self.non_null_offsets.iter().sum();
635                Some(range.end - 1 - total_offset)
636            } else {
637                None
638            };
639
640            // Keep track of offset values between non-null entries
641            if array.is_valid(range.end - 1) {
642                // Non-null add new offset
643                self.non_null_offsets.push_back(1);
644                if self.non_null_offsets.len() > shift_offset {
645                    // WE do not need to keep track of more than `lag number of offset` values.
646                    self.non_null_offsets.pop_front();
647                }
648            } else if !self.non_null_offsets.is_empty() {
649                // Entry is null, increment offset value of the last entry.
650                let end_idx = self.non_null_offsets.len() - 1;
651                self.non_null_offsets[end_idx] += 1;
652            }
653        } else if self.ignore_nulls && !self.is_lag() {
654            // LEAD when NULLS are ignored.
655            // Stores the necessary non-null entry number further than the current row.
656            let non_null_row_count = offset_magnitude(self.shift_offset);
657
658            if self.non_null_offsets.is_empty() {
659                // When empty, fill non_null offsets with the data further than the current row.
660                let mut offset_val = 1;
661                for idx in range.start + 1..range.end {
662                    if array.is_valid(idx) {
663                        self.non_null_offsets.push_back(offset_val);
664                        offset_val = 1;
665                    } else {
666                        offset_val += 1;
667                    }
668                    // It is enough to keep track of `non_null_row_count + 1` non-null offset.
669                    // further data is unnecessary for the result.
670                    if self.non_null_offsets.len() == non_null_row_count.saturating_add(1)
671                    {
672                        break;
673                    }
674                }
675            } else if range.end < len && array.is_valid(range.end) {
676                // Update `non_null_offsets` with the new end data.
677                if array.is_valid(range.end) {
678                    // When non-null, append a new offset.
679                    self.non_null_offsets.push_back(1);
680                } else {
681                    // When null, increment offset count of the last entry
682                    let last_idx = self.non_null_offsets.len() - 1;
683                    self.non_null_offsets[last_idx] += 1;
684                }
685            }
686
687            // Find the nonNULL row index that shifted by offset comparing to current row index
688            idx = if self.non_null_offsets.len() >= non_null_row_count {
689                let total_offset: usize =
690                    self.non_null_offsets.iter().take(non_null_row_count).sum();
691                Some(range.start + total_offset)
692            } else {
693                None
694            };
695            // Prune `self.non_null_offsets` from the start. so that at next iteration
696            // start of the `self.non_null_offsets` matches with current row.
697            if !self.non_null_offsets.is_empty() {
698                self.non_null_offsets[0] -= 1;
699                if self.non_null_offsets[0] == 0 {
700                    // When offset is 0. Remove it.
701                    self.non_null_offsets.pop_front();
702                }
703            }
704        }
705
706        // Set the default value if
707        // - index is out of window bounds
708        // OR
709        // - ignore nulls mode and current value is null and is within window bounds
710        // .unwrap() is safe here as there is a none check in front
711        #[expect(clippy::unnecessary_unwrap)]
712        if !(idx.is_none() || (self.ignore_nulls && array.is_null(idx.unwrap()))) {
713            ScalarValue::try_from_array(array, idx.unwrap())
714        } else {
715            Ok(self.default_value.clone())
716        }
717    }
718
719    fn evaluate_all(
720        &mut self,
721        values: &[ArrayRef],
722        _num_rows: usize,
723    ) -> Result<ArrayRef> {
724        // LEAD, LAG window functions take single column, values will have size 1
725        let value = &values[0];
726        if !self.ignore_nulls {
727            shift_with_default_value(value, self.shift_offset, &self.default_value)
728        } else {
729            evaluate_all_with_ignore_null(
730                value,
731                self.shift_offset,
732                &self.default_value,
733                self.is_lag(),
734            )
735        }
736    }
737
738    fn supports_bounded_execution(&self) -> bool {
739        true
740    }
741}
742
743#[cfg(test)]
744mod tests {
745    use super::*;
746    use arrow::array::*;
747    use arrow::datatypes::Int8Type;
748    use datafusion_common::cast::{as_dictionary_array, as_int32_array, as_string_array};
749    use datafusion_physical_expr::expressions::{Column, Literal};
750
751    fn test_i32_result(
752        expr: WindowShift,
753        partition_evaluator_args: PartitionEvaluatorArgs,
754        expected: Int32Array,
755    ) -> Result<()> {
756        let arr: ArrayRef = Arc::new(Int32Array::from(vec![1, -2, 3, -4, 5, -6, 7, 8]));
757        let values = vec![arr];
758        let num_rows = values.len();
759        let result = expr
760            .partition_evaluator(partition_evaluator_args)?
761            .evaluate_all(&values, num_rows)?;
762        let result = as_int32_array(&result)?;
763        assert_eq!(expected, *result);
764        Ok(())
765    }
766
767    #[test]
768    fn lead_lag_get_range() -> Result<()> {
769        // LAG(2)
770        let lag_fn = WindowShiftEvaluator {
771            shift_offset: 2,
772            default_value: ScalarValue::Null,
773            ignore_nulls: false,
774            non_null_offsets: Default::default(),
775        };
776        assert_eq!(lag_fn.get_range(6, 10)?, Range { start: 4, end: 7 });
777        assert_eq!(lag_fn.get_range(0, 10)?, Range { start: 0, end: 1 });
778
779        // LAG(2 ignore nulls)
780        let lag_fn = WindowShiftEvaluator {
781            shift_offset: 2,
782            default_value: ScalarValue::Null,
783            ignore_nulls: true,
784            // models data received [<Some>, <Some>, <Some>, NULL, <Some>, NULL, <current row>, ...]
785            non_null_offsets: vec![2, 2].into(), // [1, 1, 2, 2] actually, just last 2 is used
786        };
787        assert_eq!(lag_fn.get_range(6, 10)?, Range { start: 2, end: 7 });
788
789        // LEAD(2)
790        let lead_fn = WindowShiftEvaluator {
791            shift_offset: -2,
792            default_value: ScalarValue::Null,
793            ignore_nulls: false,
794            non_null_offsets: Default::default(),
795        };
796        assert_eq!(lead_fn.get_range(6, 10)?, Range { start: 6, end: 8 });
797        assert_eq!(lead_fn.get_range(9, 10)?, Range { start: 9, end: 10 });
798
799        // LEAD(2 ignore nulls)
800        let lead_fn = WindowShiftEvaluator {
801            shift_offset: -2,
802            default_value: ScalarValue::Null,
803            ignore_nulls: true,
804            // models data received [..., <current row>, NULL, <Some>, NULL, <Some>, ..]
805            non_null_offsets: vec![2, 2].into(),
806        };
807        assert_eq!(lead_fn.get_range(4, 10)?, Range { start: 4, end: 9 });
808
809        Ok(())
810    }
811
812    #[test]
813    fn test_lead_window_shift() -> Result<()> {
814        let expr = Arc::new(Column::new("c3", 0)) as Arc<dyn PhysicalExpr>;
815
816        test_i32_result(
817            WindowShift::lead(),
818            PartitionEvaluatorArgs::new(
819                &[expr],
820                &[Field::new("f", DataType::Int32, true).into()],
821                false,
822                false,
823            ),
824            [
825                Some(-2),
826                Some(3),
827                Some(-4),
828                Some(5),
829                Some(-6),
830                Some(7),
831                Some(8),
832                None,
833            ]
834            .iter()
835            .collect::<Int32Array>(),
836        )
837    }
838
839    #[test]
840    fn test_lag_window_shift() -> Result<()> {
841        let expr = Arc::new(Column::new("c3", 0)) as Arc<dyn PhysicalExpr>;
842
843        test_i32_result(
844            WindowShift::lag(),
845            PartitionEvaluatorArgs::new(
846                &[expr],
847                &[Field::new("f", DataType::Int32, true).into()],
848                false,
849                false,
850            ),
851            [
852                None,
853                Some(1),
854                Some(-2),
855                Some(3),
856                Some(-4),
857                Some(5),
858                Some(-6),
859                Some(7),
860            ]
861            .iter()
862            .collect::<Int32Array>(),
863        )
864    }
865
866    #[test]
867    fn test_lag_with_default() -> Result<()> {
868        let expr = Arc::new(Column::new("c3", 0)) as Arc<dyn PhysicalExpr>;
869        let shift_offset =
870            Arc::new(Literal::new(ScalarValue::Int32(Some(1)))) as Arc<dyn PhysicalExpr>;
871        let default_value = Arc::new(Literal::new(ScalarValue::Int32(Some(100))))
872            as Arc<dyn PhysicalExpr>;
873
874        let input_exprs = &[expr, shift_offset, default_value];
875        let input_fields = [DataType::Int32, DataType::Int32, DataType::Int32]
876            .into_iter()
877            .map(|d| Field::new("f", d, true))
878            .map(Arc::new)
879            .collect::<Vec<_>>();
880
881        test_i32_result(
882            WindowShift::lag(),
883            PartitionEvaluatorArgs::new(input_exprs, &input_fields, false, false),
884            [
885                Some(100),
886                Some(1),
887                Some(-2),
888                Some(3),
889                Some(-4),
890                Some(5),
891                Some(-6),
892                Some(7),
893            ]
894            .iter()
895            .collect::<Int32Array>(),
896        )
897    }
898
899    #[test]
900    fn test_evaluate_all_with_ignore_null() -> Result<()> {
901        let input: ArrayRef = Arc::new(Int32Array::from(vec![
902            None,
903            Some(10),
904            None,
905            Some(20),
906            Some(30),
907            None,
908        ]));
909
910        let cases = [
911            (
912                1,
913                ScalarValue::Int32(None),
914                Int32Array::from(vec![
915                    None,
916                    None,
917                    Some(10),
918                    Some(10),
919                    Some(20),
920                    Some(30),
921                ]),
922            ),
923            (
924                -1,
925                ScalarValue::Int32(None),
926                Int32Array::from(vec![
927                    Some(10),
928                    Some(20),
929                    Some(20),
930                    Some(30),
931                    None,
932                    None,
933                ]),
934            ),
935            (
936                2,
937                ScalarValue::Int32(Some(-1)),
938                Int32Array::from(vec![
939                    Some(-1),
940                    Some(-1),
941                    Some(-1),
942                    Some(-1),
943                    Some(10),
944                    Some(20),
945                ]),
946            ),
947            (
948                -2,
949                ScalarValue::Int32(Some(-1)),
950                Int32Array::from(vec![
951                    Some(20),
952                    Some(30),
953                    Some(30),
954                    Some(-1),
955                    Some(-1),
956                    Some(-1),
957                ]),
958            ),
959            (
960                0,
961                ScalarValue::Int32(Some(-1)),
962                Int32Array::from(vec![None, Some(10), None, Some(20), Some(30), None]),
963            ),
964        ];
965
966        for (offset, default_value, expected) in cases {
967            let actual = evaluate_all_with_ignore_null(
968                &input,
969                offset,
970                &default_value,
971                offset > 0,
972            )?;
973            assert_eq!(expected, *as_int32_array(&actual)?);
974        }
975        Ok(())
976    }
977
978    #[test]
979    fn test_ignore_nulls_dictionary_with_bounded_keys() -> Result<()> {
980        let keys =
981            Int8Array::from_iter(std::iter::once(None).chain((0_i8..=127).map(Some)));
982        let values =
983            StringArray::from_iter_values((0..128).map(|index| format!("value-{index}")));
984        let input: ArrayRef = Arc::new(DictionaryArray::<Int8Type>::try_new(
985            keys,
986            Arc::new(values),
987        )?);
988        let default_value = ScalarValue::Dictionary(
989            Box::new(DataType::Int8),
990            Box::new(ScalarValue::Utf8(Some("default".to_string()))),
991        );
992
993        let actual = evaluate_all_with_ignore_null(&input, 1, &default_value, true)?;
994        let actual = as_dictionary_array::<Int8Type>(actual.as_ref())?;
995        let values = as_string_array(actual.values().as_ref())?;
996
997        assert_eq!(actual.len(), 129);
998        assert_eq!(values.len(), 128);
999        for index in 0..2 {
1000            let key = actual.key(index).expect("non-null default");
1001            assert_eq!(values.value(key), "default");
1002        }
1003        for index in 2..actual.len() {
1004            let key = actual.key(index).expect("selected value");
1005            assert_eq!(values.value(key), format!("value-{}", index - 2));
1006        }
1007        Ok(())
1008    }
1009
1010    #[test]
1011    fn test_ignore_nulls_without_null_bitmap() -> Result<()> {
1012        let input = Int32Array::from(vec![1, 2, 3]);
1013        assert!(input.nulls().is_none());
1014        let input: ArrayRef = Arc::new(input);
1015
1016        for (offset, expected) in [
1017            (1, Int32Array::from(vec![None, Some(1), Some(2)])),
1018            (-1, Int32Array::from(vec![Some(2), Some(3), None])),
1019        ] {
1020            let actual = evaluate_all_with_ignore_null(
1021                &input,
1022                offset,
1023                &ScalarValue::Int32(None),
1024                offset > 0,
1025            )?;
1026            assert_eq!(expected, *as_int32_array(&actual)?);
1027        }
1028        Ok(())
1029    }
1030}