You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by gi...@apache.org on 2026/08/09 02:12:50 UTC

(datafusion) 01/01: fix: support untyped NULL input for median (#24104)

This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-24104-6b6cc0939ef599235e0356402492b4ab2c738e55
in repository https://gitbox.apache.org/repos/asf/datafusion.git

commit cc61491adde99acc59f334c48398ae4ea0f8d3fd
Author: Ma Zhengxuan <49...@users.noreply.github.com>
AuthorDate: Sun Aug 9 10:12:26 2026 +0800

    fix: support untyped NULL input for median (#24104)
    
    ## Which issue does this PR close?
    
    - Closes #24077.
    
    ## Rationale for this change
    
    `median(NULL)` worked in DataFusion 53 but regressed in DataFusion 54.
    The untyped NULL argument remained `DataType::Null`, causing physical
    planning to create a median accumulator for an unsupported input type.
    
    ## What changes are included in this PR?
    
    - Keep the existing declarative signature for `median`.
    - Handle `DataType::Null` in physical execution using `NoopAccumulator`.
    - Use a Null state field for Null input.
    - Fall back to the generic groups accumulator adapter for Null input.
    - Add a SQL logic regression test for `median(NULL)`.
    
    ## Are these changes tested?
    
    Yes.
    
    - `cargo fmt --all -- --check`
    - `cargo test --profile=ci --test sqllogictests -- aggregate.slt`
    - `cargo test -p datafusion-functions-aggregate`
    - `cargo clippy -p datafusion-functions-aggregate --all-targets
    --all-features -- -D warnings`
    
    ## Are there any user-facing changes?
    
    Yes. `median(NULL)` now returns NULL with `DataType::Null` instead of
    failing during physical execution.
    
    ---------
    
    Co-authored-by: mazhengxuan <ma...@didiglobal.com>
---
 datafusion/functions-aggregate/src/median.rs     | 18 +++++++++++++++++-
 datafusion/sqllogictest/test_files/aggregate.slt |  5 +++++
 2 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/datafusion/functions-aggregate/src/median.rs b/datafusion/functions-aggregate/src/median.rs
index 81a3c076df..fb74da87c7 100644
--- a/datafusion/functions-aggregate/src/median.rs
+++ b/datafusion/functions-aggregate/src/median.rs
@@ -53,6 +53,7 @@ use datafusion_expr::{
 use datafusion_expr::{EmitTo, GroupsAccumulator};
 use datafusion_functions_aggregate_common::aggregate::groups_accumulator::accumulate::accumulate;
 use datafusion_functions_aggregate_common::aggregate::groups_accumulator::nulls::filtered_null_mask;
+use datafusion_functions_aggregate_common::noop_accumulator::NoopAccumulator;
 use datafusion_functions_aggregate_common::utils::{GenericDistinctBuffer, Hashable};
 use datafusion_macros::user_doc;
 use std::collections::HashMap;
@@ -138,6 +139,17 @@ impl AggregateUDFImpl for Median {
     }
 
     fn state_fields(&self, args: StateFieldsArgs) -> Result<Vec<FieldRef>> {
+        if args.input_fields[0].data_type().is_null() {
+            return Ok(vec![
+                Field::new(
+                    format_state_name(args.name, self.name()),
+                    DataType::Null,
+                    true,
+                )
+                .into(),
+            ]);
+        }
+
         //Intermediate state is a list of the elements we have collected so far
         let field = Field::new_list_field(args.input_fields[0].data_type().clone(), true);
         let state_name = if args.is_distinct {
@@ -174,6 +186,10 @@ impl AggregateUDFImpl for Median {
         }
 
         let dt = acc_args.expr_fields[0].data_type().clone();
+        if dt.is_null() {
+            return Ok(Box::new(NoopAccumulator::default()));
+        }
+
         downcast_integer! {
             dt => (helper, dt),
             DataType::Float16 => helper!(Float16Type, dt),
@@ -192,7 +208,7 @@ impl AggregateUDFImpl for Median {
     }
 
     fn groups_accumulator_supported(&self, args: AccumulatorArgs) -> bool {
-        !args.is_distinct
+        !args.is_distinct && !args.expr_fields[0].data_type().is_null()
     }
 
     fn create_groups_accumulator(
diff --git a/datafusion/sqllogictest/test_files/aggregate.slt b/datafusion/sqllogictest/test_files/aggregate.slt
index 26b8a78f39..1a3e3f5aa6 100644
--- a/datafusion/sqllogictest/test_files/aggregate.slt
+++ b/datafusion/sqllogictest/test_files/aggregate.slt
@@ -1200,6 +1200,11 @@ select approx_median(NULL), arrow_typeof(approx_median(NULL)) from median_table;
 ----
 NULL Null
 
+query ?T
+select median(NULL), arrow_typeof(median(NULL));
+----
+NULL Null
+
 # median decimal
 statement ok
 create table t(c decimal(10, 4)) as values (0.0001), (0.0002), (0.0003), (0.0004), (0.0005), (0.0006);


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org