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/07/27 14:49:51 UTC

(datafusion) branch main updated: FFI: forward ScalarUDF preserves_lex_ordering (#23069)

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

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


The following commit(s) were added to refs/heads/main by this push:
     new 9facaaabd0 FFI: forward ScalarUDF preserves_lex_ordering (#23069)
9facaaabd0 is described below

commit 9facaaabd001d0f82ecc6bcd4d09adcc6f731109
Author: Amogh Ramesh <ra...@gmail.com>
AuthorDate: Mon Jul 27 19:58:38 2026 +0530

    FFI: forward ScalarUDF preserves_lex_ordering (#23069)
    
    ## Which issue does this PR close?
    
    Part of #22330.
    
    ## Rationale for this change
    
    `ForeignScalarUDF` inherits the default `preserves_lex_ordering`, so
    producer overrides are lost across the FFI boundary.
    
    ## What changes are included in this PR?
    
    - Forward `preserves_lex_ordering` through `FFI_ScalarUDF`.
    - Reuse the existing placement UDF for unit and dynamic-library
    coverage.
    
    ## Are these changes tested?
    
    - `cargo test -p datafusion-ffi --features integration-tests`
    - `cargo clippy --all-targets --all-features -- -D warnings`
    
    ## Are there any user-facing changes?
    
    The FFI ABI changes. Foreign libraries must rebuild against the new
    DataFusion version.
    
    ---------
    
    Signed-off-by: Amogh Ramesh <ra...@gmail.com>
---
 datafusion/ffi/src/tests/udf_udaf_udwf.rs |  8 +++++
 datafusion/ffi/src/udf/mod.rs             | 57 +++++++++++++++++++++++++++++++
 datafusion/ffi/tests/ffi_udf.rs           | 10 ++++--
 3 files changed, 73 insertions(+), 2 deletions(-)

diff --git a/datafusion/ffi/src/tests/udf_udaf_udwf.rs b/datafusion/ffi/src/tests/udf_udaf_udwf.rs
index b393f5db3a..a84df52b8d 100644
--- a/datafusion/ffi/src/tests/udf_udaf_udwf.rs
+++ b/datafusion/ffi/src/tests/udf_udaf_udwf.rs
@@ -20,6 +20,7 @@ use std::sync::Arc;
 use arrow_schema::DataType;
 use datafusion_catalog::TableFunctionImpl;
 use datafusion_common::ScalarValue;
+use datafusion_expr::sort_properties::ExprProperties;
 use datafusion_expr::{
     AggregateUDF, ColumnarValue, ExpressionPlacement, ScalarFunctionArgs, ScalarUDF,
     ScalarUDFImpl, Signature, Volatility, WindowUDF,
@@ -152,6 +153,13 @@ impl ScalarUDFImpl for PlacementUDF {
             ExpressionPlacement::KeepInPlace
         }
     }
+
+    fn preserves_lex_ordering(
+        &self,
+        inputs: &[ExprProperties],
+    ) -> datafusion_common::Result<bool> {
+        Ok(inputs.iter().all(|input| input.preserves_lex_ordering))
+    }
 }
 
 pub(crate) extern "C" fn create_placement_func() -> FFI_ScalarUDF {
diff --git a/datafusion/ffi/src/udf/mod.rs b/datafusion/ffi/src/udf/mod.rs
index 4fc22e859f..8e96dd9013 100644
--- a/datafusion/ffi/src/udf/mod.rs
+++ b/datafusion/ffi/src/udf/mod.rs
@@ -26,6 +26,7 @@ use arrow::ffi::{FFI_ArrowSchema, from_ffi, to_ffi};
 use arrow_schema::FieldRef;
 use datafusion_common::config::ConfigOptions;
 use datafusion_common::{DataFusionError, Result, internal_err};
+use datafusion_expr::sort_properties::ExprProperties;
 use datafusion_expr::type_coercion::functions::fields_with_udf;
 use datafusion_expr::{
     ColumnarValue, ExpressionPlacement, ReturnFieldArgs, ScalarFunctionArgs, ScalarUDF,
@@ -41,6 +42,7 @@ use stabby::vec::Vec as SVec;
 use crate::arrow_wrappers::{WrappedArray, WrappedSchema};
 use crate::config::FFI_ConfigOptions;
 use crate::expr::columnar_value::FFI_ColumnarValue;
+use crate::expr::expr_properties::FFI_ExprProperties;
 use crate::placement::FFI_ExpressionPlacement;
 use crate::util::{
     FFI_Result, rvec_wrapped_to_vec_datatype, vec_datatype_to_rvec_wrapped,
@@ -115,6 +117,12 @@ pub struct FFI_ScalarUDF {
     /// the foreign interface. See [`crate::get_library_marker_id`] and
     /// the crate's `README.md` for more information.
     pub library_marker_id: extern "C" fn() -> usize,
+
+    /// FFI equivalent to [`ScalarUDFImpl::preserves_lex_ordering`].
+    pub preserves_lex_ordering: unsafe extern "C" fn(
+        udf: &Self,
+        inputs: SVec<FFI_ExprProperties>,
+    ) -> FFI_Result<bool>,
 }
 
 unsafe impl Send for FFI_ScalarUDF {}
@@ -178,6 +186,19 @@ unsafe extern "C" fn placement_fn_wrapper(
     udf.inner().placement(&args).into()
 }
 
+unsafe extern "C" fn preserves_lex_ordering_fn_wrapper(
+    udf: &FFI_ScalarUDF,
+    inputs: SVec<FFI_ExprProperties>,
+) -> FFI_Result<bool> {
+    let result = inputs
+        .into_iter()
+        .map(ExprProperties::try_from)
+        .collect::<Result<Vec<_>>>()
+        .and_then(|inputs| udf.inner().preserves_lex_ordering(&inputs));
+
+    sresult!(result)
+}
+
 unsafe extern "C" fn invoke_with_args_fn_wrapper(
     udf: &FFI_ScalarUDF,
     args: SVec<WrappedArray>,
@@ -276,6 +297,7 @@ impl From<Arc<ScalarUDF>> for FFI_ScalarUDF {
             release: release_fn_wrapper,
             private_data: Box::into_raw(private_data) as *mut c_void,
             library_marker_id: crate::get_library_marker_id,
+            preserves_lex_ordering: preserves_lex_ordering_fn_wrapper,
         }
     }
 }
@@ -460,6 +482,18 @@ impl ScalarUDFImpl for ForeignScalarUDF {
 
         result.into()
     }
+
+    fn preserves_lex_ordering(&self, inputs: &[ExprProperties]) -> Result<bool> {
+        inputs
+            .iter()
+            .map(FFI_ExprProperties::try_from)
+            .collect::<Result<SVec<_>>>()
+            .and_then(|inputs| {
+                let result =
+                    unsafe { (self.udf.preserves_lex_ordering)(&self.udf, inputs) };
+                df_result!(result)
+            })
+    }
 }
 
 #[cfg(test)]
@@ -500,6 +534,14 @@ mod tests {
                 ExpressionPlacement::KeepInPlace
             }
         }
+
+        fn preserves_lex_ordering(&self, inputs: &[ExprProperties]) -> Result<bool> {
+            if inputs.is_empty() {
+                return internal_err!("preserves_lex_ordering requires an input");
+            }
+
+            Ok(inputs.iter().all(|input| input.preserves_lex_ordering))
+        }
     }
 
     #[test]
@@ -572,6 +614,21 @@ mod tests {
         );
         assert_eq!(foreign_udf.placement(&[]), ExpressionPlacement::KeepInPlace);
 
+        let preserves = ExprProperties::new_unknown().with_preserves_lex_ordering(true);
+        let does_not_preserve = ExprProperties::new_unknown();
+
+        assert!(
+            foreign_udf
+                .preserves_lex_ordering(std::slice::from_ref(&preserves))
+                .unwrap()
+        );
+        assert!(
+            !foreign_udf
+                .preserves_lex_ordering(&[preserves, does_not_preserve])
+                .unwrap()
+        );
+        assert!(foreign_udf.preserves_lex_ordering(&[]).is_err());
+
         Ok(())
     }
 }
diff --git a/datafusion/ffi/tests/ffi_udf.rs b/datafusion/ffi/tests/ffi_udf.rs
index 617cbc196b..d9e7263ccd 100644
--- a/datafusion/ffi/tests/ffi_udf.rs
+++ b/datafusion/ffi/tests/ffi_udf.rs
@@ -26,6 +26,7 @@ mod tests {
     use datafusion::prelude::{SessionContext, col};
     use datafusion_execution::config::SessionConfig;
     use datafusion_expr::lit;
+    use datafusion_expr::sort_properties::ExprProperties;
     use datafusion_ffi::tests::create_record_batch;
     use datafusion_ffi::tests::utils::get_module;
     use std::sync::Arc;
@@ -90,8 +91,7 @@ mod tests {
         Ok(())
     }
 
-    /// This test validates that a producer's `placement` override survives the
-    /// FFI boundary instead of collapsing to the default `KeepInPlace`.
+    /// Checks planning-property overrides across the FFI boundary.
     #[tokio::test]
     async fn test_scalar_udf_placement() -> Result<()> {
         let module = get_module()?;
@@ -112,6 +112,12 @@ mod tests {
             ExpressionPlacement::KeepInPlace
         );
 
+        let preserves = ExprProperties::new_unknown().with_preserves_lex_ordering(true);
+        let does_not_preserve = ExprProperties::new_unknown();
+
+        assert!(foreign_func.preserves_lex_ordering(std::slice::from_ref(&preserves))?);
+        assert!(!foreign_func.preserves_lex_ordering(&[preserves, does_not_preserve])?);
+
         Ok(())
     }
 


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