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

kontinuation pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/sedona-db.git


The following commit(s) were added to refs/heads/main by this push:
     new 402dcb31 fix(rust/sedona-raster-functions): RasterExecutor always call 
the closure only once for scalar raster input (#559)
402dcb31 is described below

commit 402dcb318f010b7db943f28e5bd496a5d96f91a1
Author: Kristin Cowalcijk <[email protected]>
AuthorDate: Sun Feb 1 16:31:52 2026 +0800

    fix(rust/sedona-raster-functions): RasterExecutor always call the closure 
only once for scalar raster input (#559)
    
    ## Summary
    - Fix RasterExecutor::execute_raster_void() to broadcast scalar rasters 
across num_iterations() when other args are arrays.
    - Add regression tests for scalar raster + array coordinate inputs in 
rs_worldcoordinate and rs_rastercoordinate.
    
    ## Why
    DataFusion can invoke these scalar UDFs with a scalar raster and array 
coordinate arguments. Previously the executor only invoked the per-row closure 
once for scalar rasters, producing output arrays of length 1 instead of the 
expected batch length.
    
    ## Testing
    - cargo test -p sedona-raster-functions
---
 rust/sedona-raster-functions/src/executor.rs       | 22 ++++--
 rust/sedona-raster-functions/src/rs_envelope.rs    |  2 +-
 .../sedona-raster-functions/src/rs_geotransform.rs |  2 +-
 .../src/rs_rastercoordinate.rs                     | 91 +++++++++++++++++++++-
 .../src/rs_worldcoordinate.rs                      | 90 ++++++++++++++++++++-
 5 files changed, 194 insertions(+), 13 deletions(-)

diff --git a/rust/sedona-raster-functions/src/executor.rs 
b/rust/sedona-raster-functions/src/executor.rs
index e7bbbe9d..4f995c48 100644
--- a/rust/sedona-raster-functions/src/executor.rs
+++ b/rust/sedona-raster-functions/src/executor.rs
@@ -59,7 +59,7 @@ impl<'a, 'b> RasterExecutor<'a, 'b> {
     /// 4. Calling the provided function with each raster
     pub fn execute_raster_void<F>(&self, mut func: F) -> Result<()>
     where
-        F: FnMut(usize, Option<RasterRefImpl<'_>>) -> Result<()>,
+        F: FnMut(usize, Option<&RasterRefImpl<'_>>) -> Result<()>,
     {
         if self.arg_types[0] != RASTER {
             return sedona_internal_err!("First argument must be a raster 
type");
@@ -85,7 +85,7 @@ impl<'a, 'b> RasterExecutor<'a, 'b> {
                         continue;
                     }
                     let raster = raster_array.get(i)?;
-                    func(i, Some(raster))?;
+                    func(i, Some(&raster))?;
                 }
 
                 Ok(())
@@ -93,14 +93,22 @@ impl<'a, 'b> RasterExecutor<'a, 'b> {
             ColumnarValue::Scalar(scalar_value) => match scalar_value {
                 ScalarValue::Struct(arc_struct) => {
                     let raster_array = 
RasterStructArray::new(arc_struct.as_ref());
-                    if raster_array.is_null(0) {
-                        func(0, None)
+                    let raster_opt = if raster_array.is_null(0) {
+                        None
                     } else {
-                        let raster = raster_array.get(0)?;
-                        func(0, Some(raster))
+                        Some(raster_array.get(0)?)
+                    };
+                    for i in 0..self.num_iterations {
+                        func(i, raster_opt.as_ref())?;
                     }
+                    Ok(())
+                }
+                ScalarValue::Null => {
+                    for i in 0..self.num_iterations {
+                        func(i, None)?;
+                    }
+                    Ok(())
                 }
-                ScalarValue::Null => func(0, None),
                 _ => sedona_internal_err!("Expected Struct scalar for raster"),
             },
         }
diff --git a/rust/sedona-raster-functions/src/rs_envelope.rs 
b/rust/sedona-raster-functions/src/rs_envelope.rs
index 1ce7d157..8ecb60cb 100644
--- a/rust/sedona-raster-functions/src/rs_envelope.rs
+++ b/rust/sedona-raster-functions/src/rs_envelope.rs
@@ -82,7 +82,7 @@ impl SedonaScalarKernel for RsEnvelope {
         executor.execute_raster_void(|_i, raster_opt| {
             match raster_opt {
                 Some(raster) => {
-                    create_envelope_wkb(&raster, &mut builder)?;
+                    create_envelope_wkb(raster, &mut builder)?;
                     builder.append_value([]);
                 }
                 None => builder.append_null(),
diff --git a/rust/sedona-raster-functions/src/rs_geotransform.rs 
b/rust/sedona-raster-functions/src/rs_geotransform.rs
index 559cb3d8..0d98ee2b 100644
--- a/rust/sedona-raster-functions/src/rs_geotransform.rs
+++ b/rust/sedona-raster-functions/src/rs_geotransform.rs
@@ -251,7 +251,7 @@ impl SedonaScalarKernel for RsGeoTransform {
                     let metadata = raster.metadata();
                     match self.param {
                         GeoTransformParam::Rotation => {
-                            let rotation = rotation(&raster);
+                            let rotation = rotation(raster);
                             builder.append_value(rotation);
                         }
                         GeoTransformParam::ScaleX => 
builder.append_value(metadata.scale_x()),
diff --git a/rust/sedona-raster-functions/src/rs_rastercoordinate.rs 
b/rust/sedona-raster-functions/src/rs_rastercoordinate.rs
index d3bb59d2..bdfcf48f 100644
--- a/rust/sedona-raster-functions/src/rs_rastercoordinate.rs
+++ b/rust/sedona-raster-functions/src/rs_rastercoordinate.rs
@@ -155,7 +155,7 @@ impl SedonaScalarKernel for RsCoordinateMapper {
 
             match (raster_opt, x_opt, y_opt) {
                 (Some(raster), Some(x), Some(y)) => {
-                    let (raster_x, raster_y) = to_raster_coordinate(&raster, 
x, y)?;
+                    let (raster_x, raster_y) = to_raster_coordinate(raster, x, 
y)?;
                     match self.coord {
                         Coord::X => builder.append_value(raster_x),
                         Coord::Y => builder.append_value(raster_y),
@@ -216,7 +216,7 @@ impl SedonaScalarKernel for RsCoordinatePoint {
 
             match (raster_opt, x_opt, y_opt) {
                 (Some(raster), Some(world_x), Some(world_y)) => {
-                    let (raster_x, raster_y) = to_raster_coordinate(&raster, 
world_x, world_y)?;
+                    let (raster_x, raster_y) = to_raster_coordinate(raster, 
world_x, world_y)?;
                     item[5..13].copy_from_slice(&(raster_x as 
f64).to_le_bytes());
                     item[13..21].copy_from_slice(&(raster_y as 
f64).to_le_bytes());
                     builder.append_value(item);
@@ -234,6 +234,7 @@ impl SedonaScalarKernel for RsCoordinatePoint {
 mod tests {
     use super::*;
     use arrow_schema::DataType;
+    use datafusion_common::ScalarValue;
     use datafusion_expr::ScalarUDF;
     use rstest::rstest;
     use sedona_schema::datatypes::{RASTER, WKB_GEOMETRY};
@@ -368,4 +369,90 @@ mod tests {
             .unwrap();
         assert_array_equal(&result, &expected);
     }
+
+    #[rstest]
+    fn udf_invoke_xy_with_scalar_raster_array_coords(#[values(Coord::Y, 
Coord::X)] coord: Coord) {
+        let udf = match coord {
+            Coord::X => rs_worldtorastercoordx_udf(),
+            Coord::Y => rs_worldtorastercoordy_udf(),
+        };
+        let tester = ScalarUdfTester::new(
+            udf.into(),
+            vec![
+                RASTER,
+                SedonaType::Arrow(DataType::Float64),
+                SedonaType::Arrow(DataType::Float64),
+            ],
+        );
+
+        let rasters = generate_test_rasters(2, Some(0)).unwrap();
+        let scalar_raster = ScalarValue::try_from_array(&rasters, 1).unwrap();
+
+        let world_x = Arc::new(arrow_array::Float64Array::from(vec![2.0, 2.5, 
3.25]));
+        let world_y = Arc::new(arrow_array::Float64Array::from(vec![3.0, 2.5, 
1.75]));
+
+        let expected_coords = match coord {
+            Coord::X => vec![Some(0_i64), Some(4_i64), Some(10_i64)],
+            Coord::Y => vec![Some(0_i64), Some(3_i64), Some(8_i64)],
+        };
+
+        let result = tester
+            .invoke(vec![
+                ColumnarValue::Scalar(scalar_raster),
+                ColumnarValue::Array(world_x.clone()),
+                ColumnarValue::Array(world_y.clone()),
+            ])
+            .unwrap();
+
+        let array = match result {
+            ColumnarValue::Array(array) => array,
+            ColumnarValue::Scalar(_) => panic!("Expected array result"),
+        };
+
+        let expected: Arc<dyn arrow_array::Array> =
+            Arc::new(arrow_array::Int64Array::from(expected_coords));
+        assert_array_equal(&array, &expected);
+    }
+
+    #[test]
+    fn udf_invoke_pt_with_scalar_raster_array_coords() {
+        let udf = rs_worldtorastercoord_udf();
+        let tester = ScalarUdfTester::new(
+            udf.into(),
+            vec![
+                RASTER,
+                SedonaType::Arrow(DataType::Float64),
+                SedonaType::Arrow(DataType::Float64),
+            ],
+        );
+
+        let rasters = generate_test_rasters(2, Some(0)).unwrap();
+        let scalar_raster = ScalarValue::try_from_array(&rasters, 1).unwrap();
+
+        let world_x = Arc::new(arrow_array::Float64Array::from(vec![2.0, 2.5, 
3.25]));
+        let world_y = Arc::new(arrow_array::Float64Array::from(vec![3.0, 2.5, 
1.75]));
+
+        let result = tester
+            .invoke(vec![
+                ColumnarValue::Scalar(scalar_raster),
+                ColumnarValue::Array(world_x.clone()),
+                ColumnarValue::Array(world_y.clone()),
+            ])
+            .unwrap();
+
+        let array = match result {
+            ColumnarValue::Array(array) => array,
+            ColumnarValue::Scalar(_) => panic!("Expected array result"),
+        };
+
+        let expected = create_array(
+            &[
+                Some("POINT (0 0)"),
+                Some("POINT (4 3)"),
+                Some("POINT (10 8)"),
+            ],
+            &WKB_GEOMETRY,
+        );
+        assert_array_equal(&array, &expected);
+    }
 }
diff --git a/rust/sedona-raster-functions/src/rs_worldcoordinate.rs 
b/rust/sedona-raster-functions/src/rs_worldcoordinate.rs
index 4bd264c6..617e4fcd 100644
--- a/rust/sedona-raster-functions/src/rs_worldcoordinate.rs
+++ b/rust/sedona-raster-functions/src/rs_worldcoordinate.rs
@@ -153,7 +153,7 @@ impl SedonaScalarKernel for RsCoordinateMapper {
 
             match (raster_opt, x_opt, y_opt) {
                 (Some(raster), Some(x), Some(y)) => {
-                    let (world_x, world_y) = to_world_coordinate(&raster, x, 
y);
+                    let (world_x, world_y) = to_world_coordinate(raster, x, y);
                     match self.coord {
                         Coord::X => builder.append_value(world_x),
                         Coord::Y => builder.append_value(world_y),
@@ -214,7 +214,7 @@ impl SedonaScalarKernel for RsCoordinatePoint {
 
             match (raster_opt, x_opt, y_opt) {
                 (Some(raster), Some(x), Some(y)) => {
-                    let (world_x, world_y) = to_world_coordinate(&raster, x, 
y);
+                    let (world_x, world_y) = to_world_coordinate(raster, x, y);
                     item[5..13].copy_from_slice(&world_x.to_le_bytes());
                     item[13..21].copy_from_slice(&world_y.to_le_bytes());
                     builder.append_value(item);
@@ -232,6 +232,7 @@ impl SedonaScalarKernel for RsCoordinatePoint {
 mod tests {
     use super::*;
     use arrow_array::Array;
+    use datafusion_common::ScalarValue;
     use datafusion_expr::ScalarUDF;
     use rstest::rstest;
     use sedona_schema::datatypes::{RASTER, WKB_GEOMETRY};
@@ -357,4 +358,89 @@ mod tests {
             }
         }
     }
+
+    #[rstest]
+    fn udf_invoke_xy_with_scalar_raster_array_coords(#[values(Coord::Y, 
Coord::X)] coord: Coord) {
+        let udf = match coord {
+            Coord::X => rs_rastertoworldcoordx_udf(),
+            Coord::Y => rs_rastertoworldcoordy_udf(),
+        };
+        let tester = ScalarUdfTester::new(
+            udf.into(),
+            vec![
+                RASTER,
+                SedonaType::Arrow(DataType::Int32),
+                SedonaType::Arrow(DataType::Int32),
+            ],
+        );
+
+        let rasters = generate_test_rasters(2, None).unwrap();
+        let scalar_raster = ScalarValue::try_from_array(&rasters, 1).unwrap();
+
+        let x_vals = vec![0_i32, 1_i32];
+        let y_vals = vec![0_i32, 1_i32];
+        let x_coords: Arc<dyn Array> = 
Arc::new(arrow_array::Int32Array::from(x_vals.clone()));
+        let y_coords: Arc<dyn Array> = 
Arc::new(arrow_array::Int32Array::from(y_vals.clone()));
+
+        let result = tester
+            .invoke(vec![
+                ColumnarValue::Scalar(scalar_raster),
+                ColumnarValue::Array(x_coords),
+                ColumnarValue::Array(y_coords),
+            ])
+            .unwrap();
+
+        let array = match result {
+            ColumnarValue::Array(array) => array,
+            ColumnarValue::Scalar(_) => panic!("Expected array result"),
+        };
+
+        let expected_values = match coord {
+            Coord::X => vec![Some(2.0), Some(2.13)],
+            Coord::Y => vec![Some(3.0), Some(2.84)],
+        };
+        let expected: Arc<dyn arrow_array::Array> =
+            Arc::new(arrow_array::Float64Array::from(expected_values));
+        assert_array_equal(&array, &expected);
+    }
+
+    #[test]
+    fn udf_invoke_pt_with_scalar_raster_array_coords() {
+        let udf = rs_rastertoworldcoord_udf();
+        let tester = ScalarUdfTester::new(
+            udf.into(),
+            vec![
+                RASTER,
+                SedonaType::Arrow(DataType::Int32),
+                SedonaType::Arrow(DataType::Int32),
+            ],
+        );
+
+        let rasters = generate_test_rasters(2, None).unwrap();
+        let scalar_raster = ScalarValue::try_from_array(&rasters, 1).unwrap();
+
+        let x_vals = vec![0_i32, 1_i32];
+        let y_vals = vec![0_i32, 1_i32];
+        let x_coords: Arc<dyn Array> = 
Arc::new(arrow_array::Int32Array::from(x_vals.clone()));
+        let y_coords: Arc<dyn Array> = 
Arc::new(arrow_array::Int32Array::from(y_vals.clone()));
+
+        let result = tester
+            .invoke(vec![
+                ColumnarValue::Scalar(scalar_raster),
+                ColumnarValue::Array(x_coords),
+                ColumnarValue::Array(y_coords),
+            ])
+            .unwrap();
+
+        let array = match result {
+            ColumnarValue::Array(array) => array,
+            ColumnarValue::Scalar(_) => panic!("Expected array result"),
+        };
+
+        let expected = create_array(
+            &[Some("POINT (2 3)"), Some("POINT (2.13 2.84)")],
+            &WKB_GEOMETRY,
+        );
+        assert_array_equal(&array, &expected);
+    }
 }

Reply via email to