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 563114ea feat(rust/sedona-raster-functions): add RS_BandPixelType, 
RS_BandNoDataValue UDFs (#612)
563114ea is described below

commit 563114eaf1feac21467c15dbe5c2b20ac10208bd
Author: Kristin Cowalcijk <[email protected]>
AuthorDate: Wed Mar 4 12:47:01 2026 +0800

    feat(rust/sedona-raster-functions): add RS_BandPixelType, 
RS_BandNoDataValue UDFs (#612)
    
    ## Summary
    
    - Add `RS_BandPixelType` and `RS_BandNoDataValue` raster UDFs to 
`sedona-raster-functions`
    - Each function supports per-band queries with an optional band index 
parameter (defaults to band 1)
    - Includes unit tests and benchmark entries in `native-raster-functions.rs`
---
 docs/reference/sql/rs_bandnodatavalue.qmd          |  42 ++
 docs/reference/sql/rs_bandpixeltype.qmd            |  40 ++
 .../benches/native-raster-functions.rs             |  18 +
 rust/sedona-raster-functions/src/lib.rs            |   2 +-
 rust/sedona-raster-functions/src/raster_utils.rs   |  58 ---
 rust/sedona-raster-functions/src/register.rs       |   2 +
 .../src/rs_band_accessors.rs                       | 450 +++++++++++++++++++++
 rust/sedona-raster-functions/src/rs_bandpath.rs    |  16 +-
 8 files changed, 560 insertions(+), 68 deletions(-)

diff --git a/docs/reference/sql/rs_bandnodatavalue.qmd 
b/docs/reference/sql/rs_bandnodatavalue.qmd
new file mode 100644
index 00000000..05cc8de3
--- /dev/null
+++ b/docs/reference/sql/rs_bandnodatavalue.qmd
@@ -0,0 +1,42 @@
+---
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+title: RS_BandNoDataValue
+description: >
+  Returns the nodata value of the specified band as a double. Returns null if 
the
+  band has no nodata value defined.
+kernels:
+  - returns: float64
+    args: [raster]
+  - returns: float64
+    args:
+    - raster
+    - name: band
+      type: int
+      description: Band index (1-based). Defaults to 1 if not specified.
+---
+
+## Examples
+
+```sql
+SELECT RS_BandNoDataValue(RS_Example());
+```
+
+```sql
+SELECT RS_BandNoDataValue(RS_Example(), 1);
+```
diff --git a/docs/reference/sql/rs_bandpixeltype.qmd 
b/docs/reference/sql/rs_bandpixeltype.qmd
new file mode 100644
index 00000000..97349179
--- /dev/null
+++ b/docs/reference/sql/rs_bandpixeltype.qmd
@@ -0,0 +1,40 @@
+---
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+title: RS_BandPixelType
+description: Returns the pixel data type of the specified band as a string.
+kernels:
+  - returns: utf8
+    args: [raster]
+  - returns: utf8
+    args:
+    - raster
+    - name: band
+      type: int
+      description: Band index (1-based). Defaults to 1 if not specified.
+---
+
+## Examples
+
+```sql
+SELECT RS_BandPixelType(RS_Example());
+```
+
+```sql
+SELECT RS_BandPixelType(RS_Example(), 1);
+```
diff --git a/rust/sedona-raster-functions/benches/native-raster-functions.rs 
b/rust/sedona-raster-functions/benches/native-raster-functions.rs
index 37f32236..5afda2b0 100644
--- a/rust/sedona-raster-functions/benches/native-raster-functions.rs
+++ b/rust/sedona-raster-functions/benches/native-raster-functions.rs
@@ -20,6 +20,15 @@ use sedona_testing::benchmark_util::{benchmark, 
BenchmarkArgSpec::*, BenchmarkAr
 fn criterion_benchmark(c: &mut Criterion) {
     let f = sedona_raster_functions::register::default_function_set();
 
+    // RS_BandNoDataValue
+    benchmark::scalar(
+        c,
+        &f,
+        "native-raster",
+        "rs_bandnodatavalue",
+        BenchmarkArgs::Array(Raster(64, 64)),
+    );
+    // RS_BandPath
     benchmark::scalar(
         c,
         &f,
@@ -34,6 +43,15 @@ fn criterion_benchmark(c: &mut Criterion) {
         "rs_bandpath",
         BenchmarkArgs::ArrayScalar(Raster(64, 64), Int32(1, 2)),
     );
+    // RS_BandPixelType
+    benchmark::scalar(
+        c,
+        &f,
+        "native-raster",
+        "rs_bandpixeltype",
+        BenchmarkArgs::Array(Raster(64, 64)),
+    );
+
     benchmark::scalar(c, &f, "native-raster", "rs_convexhull", Raster(64, 64));
     benchmark::scalar(c, &f, "native-raster", "rs_crs", Raster(64, 64));
     benchmark::scalar(c, &f, "native-raster", "rs_envelope", Raster(64, 64));
diff --git a/rust/sedona-raster-functions/src/lib.rs 
b/rust/sedona-raster-functions/src/lib.rs
index 79b14983..f8f22714 100644
--- a/rust/sedona-raster-functions/src/lib.rs
+++ b/rust/sedona-raster-functions/src/lib.rs
@@ -16,8 +16,8 @@
 // under the License.
 
 mod executor;
-pub mod raster_utils;
 pub mod register;
+pub mod rs_band_accessors;
 pub mod rs_bandpath;
 pub mod rs_convexhull;
 pub mod rs_envelope;
diff --git a/rust/sedona-raster-functions/src/raster_utils.rs 
b/rust/sedona-raster-functions/src/raster_utils.rs
deleted file mode 100644
index 1a0b44bd..00000000
--- a/rust/sedona-raster-functions/src/raster_utils.rs
+++ /dev/null
@@ -1,58 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-use datafusion_common::error::Result;
-use datafusion_common::exec_err;
-
-/// Validate that a 1-based band index is within `[1, num_bands]`.
-pub fn validate_band_index(band_index: i32, num_bands: usize) -> Result<()> {
-    if band_index < 1 || band_index as usize > num_bands {
-        return exec_err!(
-            "Provided band index {} is not in the range [1, {}]",
-            band_index,
-            num_bands
-        );
-    }
-    Ok(())
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_validate_band_index_valid() {
-        assert!(validate_band_index(1, 3).is_ok());
-        assert!(validate_band_index(2, 3).is_ok());
-        assert!(validate_band_index(3, 3).is_ok());
-    }
-
-    #[test]
-    fn test_validate_band_index_zero() {
-        assert!(validate_band_index(0, 3).is_err());
-    }
-
-    #[test]
-    fn test_validate_band_index_negative() {
-        assert!(validate_band_index(-1, 3).is_err());
-    }
-
-    #[test]
-    fn test_validate_band_index_out_of_range() {
-        assert!(validate_band_index(4, 3).is_err());
-    }
-}
diff --git a/rust/sedona-raster-functions/src/register.rs 
b/rust/sedona-raster-functions/src/register.rs
index a4d38a26..378b40ab 100644
--- a/rust/sedona-raster-functions/src/register.rs
+++ b/rust/sedona-raster-functions/src/register.rs
@@ -38,6 +38,8 @@ pub fn default_function_set() -> FunctionSet {
 
     register_scalar_udfs!(
         function_set,
+        crate::rs_band_accessors::rs_bandpixeltype_udf,
+        crate::rs_band_accessors::rs_bandnodatavalue_udf,
         crate::rs_bandpath::rs_bandpath_udf,
         crate::rs_convexhull::rs_convexhull_udf,
         crate::rs_envelope::rs_envelope_udf,
diff --git a/rust/sedona-raster-functions/src/rs_band_accessors.rs 
b/rust/sedona-raster-functions/src/rs_band_accessors.rs
new file mode 100644
index 00000000..a2288363
--- /dev/null
+++ b/rust/sedona-raster-functions/src/rs_band_accessors.rs
@@ -0,0 +1,450 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::sync::Arc;
+use std::vec;
+
+use crate::executor::RasterExecutor;
+use arrow_array::builder::{Float64Builder, StringBuilder};
+use arrow_schema::DataType;
+use datafusion_common::cast::as_int32_array;
+use datafusion_common::error::Result;
+use datafusion_expr::{ColumnarValue, Volatility};
+use sedona_expr::scalar_udf::{SedonaScalarKernel, SedonaScalarUDF};
+use sedona_raster::traits::RasterRef;
+use sedona_schema::{datatypes::SedonaType, matchers::ArgMatcher};
+
+// ===========================================================================
+// RS_BandPixelType
+// ===========================================================================
+
+/// RS_BandPixelType() scalar UDF implementation
+///
+/// Returns the pixel data type of the specified band as a string.
+/// Accepts an optional band_index parameter (1-based, default is 1).
+pub fn rs_bandpixeltype_udf() -> SedonaScalarUDF {
+    SedonaScalarUDF::new(
+        "rs_bandpixeltype",
+        vec![
+            Arc::new(RsBandPixelType {}),
+            Arc::new(RsBandPixelTypeWithBand {}),
+        ],
+        Volatility::Immutable,
+    )
+}
+
+#[derive(Debug)]
+struct RsBandPixelType {}
+
+impl SedonaScalarKernel for RsBandPixelType {
+    fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+        let matcher = ArgMatcher::new(
+            vec![ArgMatcher::is_raster()],
+            SedonaType::Arrow(DataType::Utf8),
+        );
+        matcher.match_args(args)
+    }
+
+    fn invoke_batch(
+        &self,
+        arg_types: &[SedonaType],
+        args: &[ColumnarValue],
+    ) -> Result<ColumnarValue> {
+        let executor = RasterExecutor::new(arg_types, args);
+        let mut builder =
+            StringBuilder::with_capacity(executor.num_iterations(), 
executor.num_iterations() * 20);
+
+        executor
+            .execute_raster_void(|_i, raster_opt| get_pixel_type(raster_opt, 
1, &mut builder))?;
+
+        executor.finish(Arc::new(builder.finish()))
+    }
+}
+
+#[derive(Debug)]
+struct RsBandPixelTypeWithBand {}
+
+impl SedonaScalarKernel for RsBandPixelTypeWithBand {
+    fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+        let matcher = ArgMatcher::new(
+            vec![ArgMatcher::is_raster(), ArgMatcher::is_integer()],
+            SedonaType::Arrow(DataType::Utf8),
+        );
+        matcher.match_args(args)
+    }
+
+    fn invoke_batch(
+        &self,
+        arg_types: &[SedonaType],
+        args: &[ColumnarValue],
+    ) -> Result<ColumnarValue> {
+        let executor = RasterExecutor::new(arg_types, args);
+        let band_index_array = args[1].clone().cast_to(&DataType::Int32, 
None)?;
+        let band_index_array = 
band_index_array.into_array(executor.num_iterations())?;
+        let band_index_array = as_int32_array(&band_index_array)?;
+
+        let mut builder =
+            StringBuilder::with_capacity(executor.num_iterations(), 
executor.num_iterations() * 20);
+        let mut band_index_iter = band_index_array.iter();
+        executor.execute_raster_void(|_, raster_opt| {
+            let band_index = band_index_iter.next().unwrap().unwrap_or(1);
+            get_pixel_type(raster_opt, band_index, &mut builder)
+        })?;
+
+        executor.finish(Arc::new(builder.finish()))
+    }
+}
+
+fn get_pixel_type(
+    raster_opt: Option<&sedona_raster::array::RasterRefImpl<'_>>,
+    band_index: i32,
+    builder: &mut StringBuilder,
+) -> Result<()> {
+    match raster_opt {
+        None => {
+            builder.append_null();
+            Ok(())
+        }
+        Some(raster) => {
+            let num_bands = raster.bands().len();
+            if band_index < 1 || band_index > num_bands as i32 {
+                builder.append_null();
+                return Ok(());
+            }
+            let band = raster.bands().band(band_index as usize)?;
+            let dt = band.metadata().data_type()?;
+            builder.append_value(dt.pixel_type_name());
+            Ok(())
+        }
+    }
+}
+
+// ===========================================================================
+// RS_BandNoDataValue
+// ===========================================================================
+
+/// RS_BandNoDataValue() scalar UDF implementation
+///
+/// Returns the nodata value of the specified band as a Float64.
+/// Returns null if the band has no nodata value defined.
+/// Accepts an optional band_index parameter (1-based, default is 1).
+pub fn rs_bandnodatavalue_udf() -> SedonaScalarUDF {
+    SedonaScalarUDF::new(
+        "rs_bandnodatavalue",
+        vec![
+            Arc::new(RsBandNoDataValue {}),
+            Arc::new(RsBandNoDataValueWithBand {}),
+        ],
+        Volatility::Immutable,
+    )
+}
+
+#[derive(Debug)]
+struct RsBandNoDataValue {}
+
+impl SedonaScalarKernel for RsBandNoDataValue {
+    fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+        let matcher = ArgMatcher::new(
+            vec![ArgMatcher::is_raster()],
+            SedonaType::Arrow(DataType::Float64),
+        );
+        matcher.match_args(args)
+    }
+
+    fn invoke_batch(
+        &self,
+        arg_types: &[SedonaType],
+        args: &[ColumnarValue],
+    ) -> Result<ColumnarValue> {
+        let executor = RasterExecutor::new(arg_types, args);
+        let mut builder = 
Float64Builder::with_capacity(executor.num_iterations());
+
+        executor
+            .execute_raster_void(|_i, raster_opt| get_nodata_value(raster_opt, 
1, &mut builder))?;
+
+        executor.finish(Arc::new(builder.finish()))
+    }
+}
+
+#[derive(Debug)]
+struct RsBandNoDataValueWithBand {}
+
+impl SedonaScalarKernel for RsBandNoDataValueWithBand {
+    fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+        let matcher = ArgMatcher::new(
+            vec![ArgMatcher::is_raster(), ArgMatcher::is_integer()],
+            SedonaType::Arrow(DataType::Float64),
+        );
+        matcher.match_args(args)
+    }
+
+    fn invoke_batch(
+        &self,
+        arg_types: &[SedonaType],
+        args: &[ColumnarValue],
+    ) -> Result<ColumnarValue> {
+        let executor = RasterExecutor::new(arg_types, args);
+        let band_index_array = args[1].clone().cast_to(&DataType::Int32, 
None)?;
+        let band_index_array = 
band_index_array.into_array(executor.num_iterations())?;
+        let band_index_array = as_int32_array(&band_index_array)?;
+
+        let mut builder = 
Float64Builder::with_capacity(executor.num_iterations());
+        let mut band_index_iter = band_index_array.iter();
+        executor.execute_raster_void(|_, raster_opt| {
+            let band_index = band_index_iter.next().unwrap().unwrap_or(1);
+            get_nodata_value(raster_opt, band_index, &mut builder)
+        })?;
+
+        executor.finish(Arc::new(builder.finish()))
+    }
+}
+
+fn get_nodata_value(
+    raster_opt: Option<&sedona_raster::array::RasterRefImpl<'_>>,
+    band_index: i32,
+    builder: &mut Float64Builder,
+) -> Result<()> {
+    match raster_opt {
+        None => {
+            builder.append_null();
+            Ok(())
+        }
+        Some(raster) => {
+            let num_bands = raster.bands().len();
+            if band_index < 1 || band_index > num_bands as i32 {
+                builder.append_null();
+                return Ok(());
+            }
+            let band = raster.bands().band(band_index as usize)?;
+            let band_meta = band.metadata();
+            match band_meta.nodata_value_as_f64()? {
+                None => builder.append_null(),
+                Some(val) => builder.append_value(val),
+            }
+            Ok(())
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use arrow_array::{Array, Float64Array, Int32Array, Int64Array, 
StringArray, StructArray};
+    use datafusion_expr::ScalarUDF;
+    use sedona_raster::builder::RasterBuilder;
+    use sedona_raster::traits::{BandMetadata, RasterMetadata};
+    use sedona_schema::datatypes::RASTER;
+    use sedona_schema::raster::{BandDataType, StorageType};
+    use sedona_testing::compare::assert_array_equal;
+    use sedona_testing::rasters::generate_test_rasters;
+    use sedona_testing::testers::ScalarUdfTester;
+
+    /// Build a single-row raster StructArray with custom metadata and band 
metadata.
+    fn build_custom_raster(
+        meta: &RasterMetadata,
+        band_meta: &BandMetadata,
+        data: &[u8],
+        crs: Option<&str>,
+    ) -> StructArray {
+        let mut builder = RasterBuilder::new(1);
+        builder.start_raster(meta, crs).expect("start raster");
+        builder
+            .start_band(BandMetadata {
+                datatype: band_meta.datatype,
+                nodata_value: band_meta.nodata_value.clone(),
+                storage_type: band_meta.storage_type,
+                outdb_url: band_meta.outdb_url.clone(),
+                outdb_band_id: band_meta.outdb_band_id,
+            })
+            .expect("start band");
+        builder.band_data_writer().append_value(data);
+        builder.finish_band().expect("finish band");
+        builder.finish_raster().expect("finish raster");
+        builder.finish().expect("finish")
+    }
+
+    // -----------------------------------------------------------------------
+    // RS_BandPixelType tests
+    // -----------------------------------------------------------------------
+
+    #[test]
+    fn udf_bandpixeltype_metadata() {
+        let udf: ScalarUDF = rs_bandpixeltype_udf().into();
+        assert_eq!(udf.name(), "rs_bandpixeltype");
+    }
+
+    #[test]
+    fn udf_bandpixeltype_default_band() {
+        let udf: ScalarUDF = rs_bandpixeltype_udf().into();
+        let tester = ScalarUdfTester::new(udf, vec![RASTER]);
+        tester.assert_return_type(DataType::Utf8);
+
+        // generate_test_rasters creates UInt16 bands
+        let rasters = generate_test_rasters(3, Some(1)).unwrap();
+        let result = tester.invoke_array(Arc::new(rasters)).unwrap();
+        let string_array = result
+            .as_any()
+            .downcast_ref::<StringArray>()
+            .expect("Expected StringArray");
+
+        assert_eq!(string_array.value(0), "UNSIGNED_16BITS");
+        assert!(string_array.is_null(1));
+        assert_eq!(string_array.value(2), "UNSIGNED_16BITS");
+    }
+
+    #[test]
+    fn udf_bandpixeltype_with_band() {
+        let udf: ScalarUDF = rs_bandpixeltype_udf().into();
+        let tester = ScalarUdfTester::new(udf, vec![RASTER, 
SedonaType::Arrow(DataType::Int32)]);
+
+        let rasters = generate_test_rasters(3, Some(1)).unwrap();
+        let band_indices = Int32Array::from(vec![1, 1, 1]);
+        let result = tester
+            .invoke_arrays(vec![Arc::new(rasters), Arc::new(band_indices)])
+            .unwrap();
+        let string_array = result
+            .as_any()
+            .downcast_ref::<StringArray>()
+            .expect("Expected StringArray");
+
+        assert_eq!(string_array.value(0), "UNSIGNED_16BITS");
+        assert!(string_array.is_null(1));
+        assert_eq!(string_array.value(2), "UNSIGNED_16BITS");
+    }
+
+    #[test]
+    fn udf_bandpixeltype_with_int64_band() {
+        let udf: ScalarUDF = rs_bandpixeltype_udf().into();
+        let tester = ScalarUdfTester::new(udf, vec![RASTER, 
SedonaType::Arrow(DataType::Int64)]);
+
+        let rasters = generate_test_rasters(3, Some(1)).unwrap();
+        let band_indices = Int64Array::from(vec![1i64, 1, 1]);
+        let result = tester
+            .invoke_arrays(vec![Arc::new(rasters), Arc::new(band_indices)])
+            .unwrap();
+        let string_array = result
+            .as_any()
+            .downcast_ref::<StringArray>()
+            .expect("Expected StringArray");
+
+        assert_eq!(string_array.value(0), "UNSIGNED_16BITS");
+        assert!(string_array.is_null(1));
+        assert_eq!(string_array.value(2), "UNSIGNED_16BITS");
+    }
+
+    #[test]
+    fn udf_bandpixeltype_non_existing_band() {
+        let udf: ScalarUDF = rs_bandpixeltype_udf().into();
+        let tester = ScalarUdfTester::new(udf, vec![RASTER, 
SedonaType::Arrow(DataType::Int32)]);
+
+        let rasters = generate_test_rasters(1, None).unwrap();
+        let band_indices = Int32Array::from(vec![5]); // out of range
+        let result = tester
+            .invoke_arrays(vec![Arc::new(rasters), Arc::new(band_indices)])
+            .unwrap();
+        assert!(result.is_null(0));
+    }
+
+    // -----------------------------------------------------------------------
+    // RS_BandNoDataValue tests
+    // -----------------------------------------------------------------------
+
+    #[test]
+    fn udf_bandnodatavalue_metadata() {
+        let udf: ScalarUDF = rs_bandnodatavalue_udf().into();
+        assert_eq!(udf.name(), "rs_bandnodatavalue");
+    }
+
+    #[test]
+    fn udf_bandnodatavalue_default_band() {
+        let udf: ScalarUDF = rs_bandnodatavalue_udf().into();
+        let tester = ScalarUdfTester::new(udf, vec![RASTER]);
+        tester.assert_return_type(DataType::Float64);
+
+        // generate_test_rasters creates bands with nodata = [0, 0] (UInt16 = 
0)
+        let rasters = generate_test_rasters(3, Some(1)).unwrap();
+        let expected: Arc<dyn arrow_array::Array> =
+            Arc::new(Float64Array::from(vec![Some(0.0), None, Some(0.0)]));
+        let result = tester.invoke_array(Arc::new(rasters)).unwrap();
+        assert_array_equal(&result, &expected);
+    }
+
+    #[test]
+    fn udf_bandnodatavalue_with_int64_band() {
+        let udf: ScalarUDF = rs_bandnodatavalue_udf().into();
+        let tester = ScalarUdfTester::new(udf, vec![RASTER, 
SedonaType::Arrow(DataType::Int64)]);
+
+        let rasters = generate_test_rasters(3, Some(1)).unwrap();
+        let band_indices = Int64Array::from(vec![1i64, 1, 1]);
+        let expected: Arc<dyn arrow_array::Array> =
+            Arc::new(Float64Array::from(vec![Some(0.0), None, Some(0.0)]));
+        let result = tester
+            .invoke_arrays(vec![Arc::new(rasters), Arc::new(band_indices)])
+            .unwrap();
+        assert_array_equal(&result, &expected);
+    }
+
+    #[test]
+    fn udf_bandnodatavalue_no_nodata() {
+        // Create a raster without nodata
+        let meta = RasterMetadata {
+            width: 2,
+            height: 2,
+            upperleft_x: 0.0,
+            upperleft_y: 0.0,
+            scale_x: 1.0,
+            scale_y: -1.0,
+            skew_x: 0.0,
+            skew_y: 0.0,
+        };
+        let band_meta = BandMetadata {
+            datatype: BandDataType::UInt8,
+            nodata_value: None,
+            storage_type: StorageType::InDb,
+            outdb_url: None,
+            outdb_band_id: None,
+        };
+        let data = vec![1u8, 2, 3, 4];
+        let rasters = build_custom_raster(&meta, &band_meta, &data, 
Some("OGC:CRS84"));
+
+        let udf: ScalarUDF = rs_bandnodatavalue_udf().into();
+        let tester = ScalarUdfTester::new(udf, vec![RASTER]);
+        let result = tester.invoke_array(Arc::new(rasters)).unwrap();
+        let float_array = result
+            .as_any()
+            .downcast_ref::<Float64Array>()
+            .expect("Expected Float64Array");
+        assert!(float_array.is_null(0));
+    }
+
+    #[test]
+    fn udf_bandnodatavalue_non_existing_band() {
+        let udf: ScalarUDF = rs_bandnodatavalue_udf().into();
+        let tester = ScalarUdfTester::new(udf, vec![RASTER, 
SedonaType::Arrow(DataType::Int32)]);
+        tester.assert_return_type(DataType::Float64);
+
+        // generate_test_rasters creates bands with nodata = [0, 0] (UInt16 = 
0)
+        let rasters = generate_test_rasters(3, Some(1)).unwrap();
+        let bands = Int32Array::from(vec![0, 1, 2]); // out of range band index
+        let result = tester
+            .invoke_array_array(Arc::new(rasters), Arc::new(bands))
+            .unwrap();
+        assert!(result.is_null(0));
+        assert!(result.is_null(1));
+        assert!(result.is_null(2));
+    }
+}
diff --git a/rust/sedona-raster-functions/src/rs_bandpath.rs 
b/rust/sedona-raster-functions/src/rs_bandpath.rs
index 16d9b79b..df222a56 100644
--- a/rust/sedona-raster-functions/src/rs_bandpath.rs
+++ b/rust/sedona-raster-functions/src/rs_bandpath.rs
@@ -18,8 +18,8 @@ use std::{sync::Arc, vec};
 
 use crate::executor::RasterExecutor;
 use arrow_array::builder::StringBuilder;
-use arrow_array::{cast::AsArray, types::Int32Type, Array};
 use arrow_schema::DataType;
+use datafusion_common::cast::as_int32_array;
 use datafusion_common::error::Result;
 use datafusion_expr::{ColumnarValue, Volatility};
 use sedona_expr::scalar_udf::{SedonaScalarKernel, SedonaScalarUDF};
@@ -97,19 +97,17 @@ impl SedonaScalarKernel for RsBandPathWithBandIndex {
         let executor = RasterExecutor::new(arg_types, args);
 
         // Expand the band_index parameter to an array
-        let band_index_array = 
args[1].clone().into_array(executor.num_iterations())?;
-        let band_index_array = band_index_array.as_primitive::<Int32Type>();
+        let band_index_array = args[1].clone().cast_to(&DataType::Int32, 
None)?;
+        let band_index_array = 
band_index_array.into_array(executor.num_iterations())?;
+        let band_index_array = as_int32_array(&band_index_array)?;
 
         let preallocate_bytes = PREALLOC_SIZE_PER_PATH * 
executor.num_iterations();
         let mut builder =
             StringBuilder::with_capacity(executor.num_iterations(), 
preallocate_bytes);
 
-        executor.execute_raster_void(|i, raster_opt| {
-            let band_index = if band_index_array.is_null(i) {
-                1 // Default to band 1 if null
-            } else {
-                band_index_array.value(i)
-            };
+        let mut band_index_iter = band_index_array.iter();
+        executor.execute_raster_void(|_, raster_opt| {
+            let band_index = band_index_iter.next().unwrap().unwrap_or(1);
             get_band_path(raster_opt, band_index, &mut builder)
         })?;
 

Reply via email to