From a4520bc78cdbe2e0e9e1889766fa0513630d7a3a Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Mon, 27 Jul 2026 18:15:51 +0300 Subject: [PATCH 1/2] feat: add `strictly_order_preserving` for more expr Closes: - #23920 --- datafusion/functions-nested/src/make_array.rs | 6 ++++++ datafusion/functions-nested/src/repeat.rs | 7 +++++++ datafusion/functions/src/math/factorial.rs | 12 ++++++++++++ datafusion/functions/src/math/floor.rs | 5 +++++ datafusion/functions/src/string/repeat.rs | 7 +++++++ 5 files changed, 37 insertions(+) diff --git a/datafusion/functions-nested/src/make_array.rs b/datafusion/functions-nested/src/make_array.rs index 6f083ab70007b..b2d2bdad5f567 100644 --- a/datafusion/functions-nested/src/make_array.rs +++ b/datafusion/functions-nested/src/make_array.rs @@ -39,6 +39,7 @@ use datafusion_expr::{ }; use datafusion_macros::user_doc; use itertools::Itertools as _; +use datafusion_expr_common::sort_properties::ExprProperties; make_udf_expr_and_func!( MakeArray, @@ -113,6 +114,11 @@ impl ScalarUDFImpl for MakeArray { &self.aliases } + fn strictly_order_preserving(&self, _inputs: &[ExprProperties]) -> Result { + // Not strictly order preserving since null input does not correspond to null output + Ok(false) + } + fn coerce_types(&self, arg_types: &[DataType]) -> Result> { if arg_types.is_empty() { Ok(vec![]) diff --git a/datafusion/functions-nested/src/repeat.rs b/datafusion/functions-nested/src/repeat.rs index d7dff21141429..de59003c98a2a 100644 --- a/datafusion/functions-nested/src/repeat.rs +++ b/datafusion/functions-nested/src/repeat.rs @@ -40,6 +40,7 @@ use datafusion_expr_common::signature::{Coercion, TypeSignatureClass}; use datafusion_macros::user_doc; use std::mem::size_of; use std::sync::Arc; +use datafusion_expr_common::sort_properties::ExprProperties; const ARRAY_REPEAT_LENGTH_EXCEEDED: &str = "array_repeat: requested length exceeds maximum array size"; @@ -137,6 +138,12 @@ impl ScalarUDFImpl for ArrayRepeat { make_scalar_function(array_repeat_inner)(&args.args) } + fn strictly_order_preserving(&self, _inputs: &[ExprProperties]) -> Result { + // this is not strictly order preserving + // since array_repeat(NULL, 2) = [NULL, NULL] and not NULL + Ok(false) + } + fn aliases(&self) -> &[String] { &self.aliases } diff --git a/datafusion/functions/src/math/factorial.rs b/datafusion/functions/src/math/factorial.rs index f4e9b60dd3799..ba5b83dd71fee 100644 --- a/datafusion/functions/src/math/factorial.rs +++ b/datafusion/functions/src/math/factorial.rs @@ -28,6 +28,7 @@ use datafusion_expr::{ ColumnarValue, Documentation, ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility, }; +use datafusion_expr_common::sort_properties::{ExprProperties, SortProperties}; use datafusion_macros::user_doc; #[user_doc( @@ -116,6 +117,17 @@ impl ScalarUDFImpl for FactorialFunc { } } + fn output_ordering(&self, inputs: &[ExprProperties]) -> Result { + // Keep the same ordering as the input + Ok(inputs[0].sort_properties.clone()) + } + + fn strictly_order_preserving(&self, _inputs: &[ExprProperties]) -> Result { + // There is one-to-one mapping between input and output and nulls maps to nulls + // and because overflow will be resulted in an error and not wrapping or saturating we are ok + Ok(true) + } + fn documentation(&self) -> Option<&Documentation> { self.doc() } diff --git a/datafusion/functions/src/math/floor.rs b/datafusion/functions/src/math/floor.rs index 4ab6e0eb5effd..dfca5577132ef 100644 --- a/datafusion/functions/src/math/floor.rs +++ b/datafusion/functions/src/math/floor.rs @@ -234,6 +234,11 @@ impl ScalarUDFImpl for FloorFunc { Ok(input[0].sort_properties) } + fn strictly_order_preserving(&self, _inputs: &[ExprProperties]) -> Result { + // floor allow distinct inputs to collapse into equal outputs + Ok(false) + } + fn evaluate_bounds(&self, inputs: &[&Interval]) -> Result { let data_type = inputs[0].data_type(); Interval::make_unbounded(&data_type) diff --git a/datafusion/functions/src/string/repeat.rs b/datafusion/functions/src/string/repeat.rs index a53f1e2e4fc42..edd5e56be11f3 100644 --- a/datafusion/functions/src/string/repeat.rs +++ b/datafusion/functions/src/string/repeat.rs @@ -32,6 +32,7 @@ use datafusion_common::{ use datafusion_expr::{ColumnarValue, Documentation, Volatility}; use datafusion_expr::{ScalarFunctionArgs, ScalarUDFImpl, Signature}; use datafusion_expr_common::signature::{Coercion, TypeSignatureClass}; +use datafusion_expr_common::sort_properties::ExprProperties; use datafusion_macros::user_doc; #[user_doc( @@ -160,6 +161,12 @@ impl ScalarUDFImpl for RepeatFunc { fn documentation(&self) -> Option<&Documentation> { self.doc() } + + fn strictly_order_preserving(&self, _inputs: &[ExprProperties]) -> Result { + // repeat does not strictly preserve ordering since for the following 2 rows: + // "bc" and "bca" and repeat(2) the result will be "bcbc" and "bcabca" which now the order is different + Ok(false) + } } /// Computes repeat for a single string value with max size check From 70ea66aeb3bffd2182076fff7c36a794399301be Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Tue, 28 Jul 2026 12:59:30 +0300 Subject: [PATCH 2/2] lint and format --- datafusion/functions-nested/src/make_array.rs | 2 +- datafusion/functions-nested/src/repeat.rs | 2 +- datafusion/functions/src/math/factorial.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/datafusion/functions-nested/src/make_array.rs b/datafusion/functions-nested/src/make_array.rs index b2d2bdad5f567..f1928be8765ae 100644 --- a/datafusion/functions-nested/src/make_array.rs +++ b/datafusion/functions-nested/src/make_array.rs @@ -37,9 +37,9 @@ use datafusion_expr::{ ColumnarValue, Documentation, ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility, }; +use datafusion_expr_common::sort_properties::ExprProperties; use datafusion_macros::user_doc; use itertools::Itertools as _; -use datafusion_expr_common::sort_properties::ExprProperties; make_udf_expr_and_func!( MakeArray, diff --git a/datafusion/functions-nested/src/repeat.rs b/datafusion/functions-nested/src/repeat.rs index de59003c98a2a..a8e094f65a2d4 100644 --- a/datafusion/functions-nested/src/repeat.rs +++ b/datafusion/functions-nested/src/repeat.rs @@ -37,10 +37,10 @@ use datafusion_expr::{ Volatility, }; use datafusion_expr_common::signature::{Coercion, TypeSignatureClass}; +use datafusion_expr_common::sort_properties::ExprProperties; use datafusion_macros::user_doc; use std::mem::size_of; use std::sync::Arc; -use datafusion_expr_common::sort_properties::ExprProperties; const ARRAY_REPEAT_LENGTH_EXCEEDED: &str = "array_repeat: requested length exceeds maximum array size"; diff --git a/datafusion/functions/src/math/factorial.rs b/datafusion/functions/src/math/factorial.rs index ba5b83dd71fee..0d01406d351e0 100644 --- a/datafusion/functions/src/math/factorial.rs +++ b/datafusion/functions/src/math/factorial.rs @@ -119,7 +119,7 @@ impl ScalarUDFImpl for FactorialFunc { fn output_ordering(&self, inputs: &[ExprProperties]) -> Result { // Keep the same ordering as the input - Ok(inputs[0].sort_properties.clone()) + Ok(inputs[0].sort_properties) } fn strictly_order_preserving(&self, _inputs: &[ExprProperties]) -> Result {