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

paleolimbot 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 dcc8be17 refactor(c/sedona-s2geography): Move s2geography UDFs to 
extension ABI (#683)
dcc8be17 is described below

commit dcc8be17708b703586da16b3c3a1f924d0f100b5
Author: Dewey Dunnington <[email protected]>
AuthorDate: Tue Mar 10 13:19:09 2026 -0500

    refactor(c/sedona-s2geography): Move s2geography UDFs to extension ABI 
(#683)
    
    Co-authored-by: Copilot <[email protected]>
---
 Cargo.lock                                         |   2 +
 Cargo.toml                                         |   1 +
 c/sedona-extension/src/lib.rs                      |   2 +-
 c/sedona-s2geography/CMakeLists.txt                |  21 +-
 c/sedona-s2geography/Cargo.toml                    |   4 +-
 .../benches/s2geography-functions.rs               |   2 +-
 c/sedona-s2geography/build.rs                      |  20 +-
 c/sedona-s2geography/s2geography                   |   2 +-
 c/sedona-s2geography/src/error.rs                  |  70 ---
 c/sedona-s2geography/src/geography_glue.cc         |  89 ++--
 c/sedona-s2geography/src/geography_glue.h          |  36 +-
 c/sedona-s2geography/src/geography_glue_bindgen.rs |  73 +--
 c/sedona-s2geography/src/lib.rs                    |   2 -
 c/sedona-s2geography/src/register.rs               |  45 +-
 c/sedona-s2geography/src/s2geography.rs            | 273 ++--------
 c/sedona-s2geography/src/scalar_kernel.rs          | 555 ---------------------
 c/sedona-s2geography/tests/mod.rs                  | 312 ++++++++++++
 c/sedona-tg/Cargo.toml                             |   2 +-
 rust/sedona/src/context.rs                         |   2 +-
 19 files changed, 421 insertions(+), 1092 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 389366b4..d664ff5c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5597,7 +5597,9 @@ dependencies = [
  "regex",
  "rstest",
  "sedona",
+ "sedona-common",
  "sedona-expr",
+ "sedona-extension",
  "sedona-functions",
  "sedona-schema",
  "sedona-testing",
diff --git a/Cargo.toml b/Cargo.toml
index 42eb0679..2e579ce2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -149,6 +149,7 @@ sedona-spatial-join = { version = "0.4.0", path = 
"rust/sedona-spatial-join" }
 sedona-testing = { version = "0.4.0", path = "rust/sedona-testing" }
 
 # C wrapper crates
+sedona-extension = { version = "0.4.0", path = "c/sedona-extension" }
 sedona-geoarrow-c = { version = "0.4.0", path = "c/sedona-geoarrow-c" }
 sedona-geos = { version = "0.4.0", path = "c/sedona-geos" }
 sedona-gdal = { version = "0.4.0", path = "c/sedona-gdal", default-features = 
false }
diff --git a/c/sedona-extension/src/lib.rs b/c/sedona-extension/src/lib.rs
index adc0a921..073ed939 100644
--- a/c/sedona-extension/src/lib.rs
+++ b/c/sedona-extension/src/lib.rs
@@ -15,5 +15,5 @@
 // specific language governing permissions and limitations
 // under the License.
 
-pub(crate) mod extension;
+pub mod extension;
 pub mod scalar_kernel;
diff --git a/c/sedona-s2geography/CMakeLists.txt 
b/c/sedona-s2geography/CMakeLists.txt
index 06cd0635..a805d22e 100644
--- a/c/sedona-s2geography/CMakeLists.txt
+++ b/c/sedona-s2geography/CMakeLists.txt
@@ -35,21 +35,6 @@ if(POLICY CMP0135)
   cmake_policy(SET CMP0135 NEW)
 endif()
 
-set(NANOARROW_NAMESPACE SedonaGeography)
-fetchcontent_declare(nanoarrow
-                     URL 
https://github.com/apache/arrow-nanoarrow/archive/8c4e869cc8e4920737a513bc3012780050016bc5.zip
-                     URL_HASH 
SHA256=1c5c44ab3b67199c873cc8d0c130c27ee94d6c7d91f9a4e269bd26f5646faf6b
-)
-
-set(GEOARROW_NAMESPACE SedonaGeography)
-fetchcontent_declare(geoarrow
-                     URL 
https://github.com/geoarrow/geoarrow-c/archive/9a4ceeebb6ce4272450df5ff4a56c22cb3111cef.zip
-                     URL_HASH 
SHA256=479066b3c89dcb86a7b2af04ef8e57ea50254f151f4d6e46fb5e241fce86feb1
-)
-
-fetchcontent_makeavailable(nanoarrow)
-fetchcontent_makeavailable(geoarrow)
-
 # S2's CMake is pretty awful so we just build the library ourselves. We pin the
 # version of s2geometry for predictability (although it is also available on
 # vcpkg and homebrew).
@@ -221,11 +206,9 @@ target_link_libraries(geography_glue
                       s2
                       OpenSSL::SSL
                       OpenSSL::Crypto
-                      ${S2_EXTRA_OPENSSL_LIBS}
-                      geoarrow::geoarrow
-                      nanoarrow::nanoarrow)
+                      ${S2_EXTRA_OPENSSL_LIBS})
 
-install(TARGETS geography_glue nanoarrow_static
+install(TARGETS geography_glue
         EXPORT "geoarrow"
         RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
         ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
diff --git a/c/sedona-s2geography/Cargo.toml b/c/sedona-s2geography/Cargo.toml
index db073194..4524923e 100644
--- a/c/sedona-s2geography/Cargo.toml
+++ b/c/sedona-s2geography/Cargo.toml
@@ -36,7 +36,7 @@ regex = { workspace = true }
 criterion = { workspace = true }
 rstest = { workspace = true }
 sedona = { workspace = true }
-sedona-testing = { workspace = true }
+sedona-testing = { workspace = true, features = ["criterion"] }
 
 [dependencies]
 arrow-schema = { workspace = true }
@@ -44,7 +44,9 @@ arrow-array = { workspace = true, features = ["ffi"] }
 datafusion-common = { workspace = true }
 datafusion-expr = { workspace = true }
 errno = { version = "0.3" }
+sedona-common = { workspace = true }
 sedona-expr = { workspace = true }
+sedona-extension = { workspace = true }
 sedona-functions = { workspace = true }
 sedona-schema = { workspace = true }
 thiserror = { workspace = true }
diff --git a/c/sedona-s2geography/benches/s2geography-functions.rs 
b/c/sedona-s2geography/benches/s2geography-functions.rs
index a03fe972..084817ea 100644
--- a/c/sedona-s2geography/benches/s2geography-functions.rs
+++ b/c/sedona-s2geography/benches/s2geography-functions.rs
@@ -27,7 +27,7 @@ use sedona_testing::benchmark_util::{benchmark, 
BenchmarkArgSpec::*, BenchmarkAr
 
 fn criterion_benchmark(c: &mut Criterion) {
     let mut f = FunctionSet::new();
-    for (name, kernel) in sedona_s2geography::register::scalar_kernels() {
+    for (name, kernel) in 
sedona_s2geography::register::scalar_kernels().unwrap() {
         f.add_scalar_udf_impl(name, kernel).unwrap();
     }
 
diff --git a/c/sedona-s2geography/build.rs b/c/sedona-s2geography/build.rs
index b43cf0f4..095be516 100644
--- a/c/sedona-s2geography/build.rs
+++ b/c/sedona-s2geography/build.rs
@@ -31,18 +31,12 @@ fn main() {
 
     // Link the libraries that are easy to enumerate by hand and whose location
     // we control in CMakeLists.txt.
-    let mut lib_dirs = [
-        "geography_glue",
-        "s2geography",
-        "s2",
-        "geoarrow",
-        "nanoarrow_static",
-    ]
-    .map(|lib| find_lib_dir(&dst, lib))
-    .into_iter()
-    .collect::<HashSet<_>>()
-    .into_iter()
-    .collect::<Vec<_>>();
+    let mut lib_dirs = ["geography_glue", "s2geography", "s2"]
+        .map(|lib| find_lib_dir(&dst, lib))
+        .into_iter()
+        .collect::<HashSet<_>>()
+        .into_iter()
+        .collect::<Vec<_>>();
 
     lib_dirs.sort();
     for lib_dir in lib_dirs {
@@ -52,8 +46,6 @@ fn main() {
     println!("cargo:rustc-link-lib=static=geography_glue");
     println!("cargo:rustc-link-lib=static=s2geography");
     println!("cargo:rustc-link-lib=static=s2");
-    println!("cargo:rustc-link-lib=static=geoarrow");
-    println!("cargo:rustc-link-lib=static=nanoarrow_static");
 
     // Parse the output we wrote from CMake that is the linker flags
     // that CMake thinks we need for Abseil and OpenSSL.
diff --git a/c/sedona-s2geography/s2geography b/c/sedona-s2geography/s2geography
index c4d4e5f7..86054bff 160000
--- a/c/sedona-s2geography/s2geography
+++ b/c/sedona-s2geography/s2geography
@@ -1 +1 @@
-Subproject commit c4d4e5f7416dc203d3cb0485d56f5d72e9ccb6dd
+Subproject commit 86054bfff0aa0d891950c4c2b39d1a930c166627
diff --git a/c/sedona-s2geography/src/error.rs 
b/c/sedona-s2geography/src/error.rs
deleted file mode 100644
index cfd53e75..00000000
--- a/c/sedona-s2geography/src/error.rs
+++ /dev/null
@@ -1,70 +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 std::{fmt::Display, num::TryFromIntError};
-
-use arrow_schema::ArrowError;
-use datafusion_common::DataFusionError;
-use errno::Errno;
-
-use thiserror::Error;
-
-#[derive(Error, Debug)]
-pub enum S2GeographyError {
-    Internal(String),
-    Arrow(ArrowError),
-    Code(i32),
-    Message(i32, String),
-    External(Box<dyn std::error::Error + Send + Sync>),
-}
-
-impl Display for S2GeographyError {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            S2GeographyError::Internal(message) => write!(f, "{message}"),
-            S2GeographyError::Arrow(error) => {
-                write!(f, "{error}")
-            }
-            S2GeographyError::Code(code) => {
-                write!(f, "{}", Errno(*code))
-            }
-            S2GeographyError::Message(code, message) => {
-                write!(f, "{}: {}", Errno(*code), message)
-            }
-            S2GeographyError::External(error) => {
-                write!(f, "{error}")
-            }
-        }
-    }
-}
-
-impl From<ArrowError> for S2GeographyError {
-    fn from(value: ArrowError) -> Self {
-        S2GeographyError::Arrow(value)
-    }
-}
-
-impl From<S2GeographyError> for DataFusionError {
-    fn from(value: S2GeographyError) -> Self {
-        DataFusionError::External(Box::new(value))
-    }
-}
-
-impl From<TryFromIntError> for S2GeographyError {
-    fn from(value: TryFromIntError) -> Self {
-        S2GeographyError::External(Box::new(value))
-    }
-}
diff --git a/c/sedona-s2geography/src/geography_glue.cc 
b/c/sedona-s2geography/src/geography_glue.cc
index 13f489cd..3cfef7e0 100644
--- a/c/sedona-s2geography/src/geography_glue.cc
+++ b/c/sedona-s2geography/src/geography_glue.cc
@@ -17,24 +17,19 @@
 
 #include "geography_glue.h"
 
+#include <cerrno>
 #include <cstdint>
 
 #include "absl/base/config.h"
 
-#include <geoarrow/geoarrow.h>
-#include <nanoarrow/nanoarrow.h>
 #include <openssl/opensslv.h>
 #include <s2geography.h>
-#include <s2geography/arrow_udf/arrow_udf.h>
+#include <s2geography/sedona_udf/sedona_extension.h>
 
 #include <s2/s2earth.h>
 
 using namespace s2geography;
 
-const char* SedonaGeographyGlueNanoarrowVersion(void) { return 
ArrowNanoarrowVersion(); }
-
-const char* SedonaGeographyGlueGeoArrowVersion(void) { return 
GeoArrowVersion(); }
-
 const char* SedonaGeographyGlueOpenSSLVersion(void) {
   static std::string version = std::string() + 
std::to_string(OPENSSL_VERSION_MAJOR) +
                                "." + std::to_string(OPENSSL_VERSION_MINOR) + 
"." +
@@ -77,58 +72,34 @@ uint64_t SedonaGeographyGlueLngLatToCellId(double lng, 
double lat) {
   }
 }
 
-struct UdfExporter {
-  static void Export(std::unique_ptr<s2geography::arrow_udf::ArrowUDF> udf,
-                     struct SedonaGeographyArrowUdf* out) {
-    out->private_data = udf.release();
-    out->init = &CInit;
-    out->execute = &CExecute;
-    out->get_last_error = &CLastError;
-    out->release = &CRelease;
-  }
-
-  static int CInit(struct SedonaGeographyArrowUdf* self, struct ArrowSchema* 
arg_schema,
-                   const char* options, struct ArrowSchema* out) {
-    auto udf = 
reinterpret_cast<s2geography::arrow_udf::ArrowUDF*>(self->private_data);
-    return udf->Init(arg_schema, options, out);
-  }
-  static int CExecute(struct SedonaGeographyArrowUdf* self, struct 
ArrowArray** args,
-                      int64_t n_args, struct ArrowArray* out) {
-    auto udf = 
reinterpret_cast<s2geography::arrow_udf::ArrowUDF*>(self->private_data);
-    return udf->Execute(args, n_args, out);
-  }
-  static const char* CLastError(struct SedonaGeographyArrowUdf* self) {
-    auto udf = 
reinterpret_cast<s2geography::arrow_udf::ArrowUDF*>(self->private_data);
-    return udf->GetLastError();
-  }
-
-  static void CRelease(struct SedonaGeographyArrowUdf* self) {
-    auto udf = 
reinterpret_cast<s2geography::arrow_udf::ArrowUDF*>(self->private_data);
-    delete udf;
-    self->private_data = nullptr;
-  }
-};
+size_t SedonaGeographyGlueNumKernels(void) { return 18; }
 
-#define INIT_UDF_IMPL(name)                                                \
-  void SedonaGeographyInitUdf##name(struct SedonaGeographyArrowUdf* out) { \
-    return UdfExporter::Export(s2geography::arrow_udf::name(), out);       \
+int SedonaGeographyGlueInitKernels(void* kernels_array, size_t 
kerenels_size_bytes) {
+  if (kerenels_size_bytes !=
+      (sizeof(SedonaCScalarKernel) * SedonaGeographyGlueNumKernels())) {
+    return EINVAL;
   }
 
-INIT_UDF_IMPL(Area);
-INIT_UDF_IMPL(Centroid);
-INIT_UDF_IMPL(ClosestPoint);
-INIT_UDF_IMPL(Contains);
-INIT_UDF_IMPL(ConvexHull);
-INIT_UDF_IMPL(Difference);
-INIT_UDF_IMPL(Distance);
-INIT_UDF_IMPL(Equals);
-INIT_UDF_IMPL(Intersection);
-INIT_UDF_IMPL(Intersects);
-INIT_UDF_IMPL(Length);
-INIT_UDF_IMPL(LineInterpolatePoint);
-INIT_UDF_IMPL(LineLocatePoint);
-INIT_UDF_IMPL(MaxDistance);
-INIT_UDF_IMPL(Perimeter);
-INIT_UDF_IMPL(ShortestLine);
-INIT_UDF_IMPL(SymDifference);
-INIT_UDF_IMPL(Union);
+  auto* kernel_ptr = reinterpret_cast<struct 
SedonaCScalarKernel*>(kernels_array);
+
+  s2geography::sedona_udf::AreaKernel(kernel_ptr++);
+  s2geography::sedona_udf::CentroidKernel(kernel_ptr++);
+  s2geography::sedona_udf::ClosestPointKernel(kernel_ptr++);
+  s2geography::sedona_udf::ContainsKernel(kernel_ptr++);
+  s2geography::sedona_udf::ConvexHullKernel(kernel_ptr++);
+  s2geography::sedona_udf::DifferenceKernel(kernel_ptr++);
+  s2geography::sedona_udf::DistanceKernel(kernel_ptr++);
+  s2geography::sedona_udf::EqualsKernel(kernel_ptr++);
+  s2geography::sedona_udf::IntersectionKernel(kernel_ptr++);
+  s2geography::sedona_udf::IntersectsKernel(kernel_ptr++);
+  s2geography::sedona_udf::LengthKernel(kernel_ptr++);
+  s2geography::sedona_udf::LineInterpolatePointKernel(kernel_ptr++);
+  s2geography::sedona_udf::LineLocatePointKernel(kernel_ptr++);
+  s2geography::sedona_udf::MaxDistanceKernel(kernel_ptr++);
+  s2geography::sedona_udf::PerimeterKernel(kernel_ptr++);
+  s2geography::sedona_udf::ShortestLineKernel(kernel_ptr++);
+  s2geography::sedona_udf::SymDifferenceKernel(kernel_ptr++);
+  s2geography::sedona_udf::UnionKernel(kernel_ptr++);
+
+  return 0;
+}
diff --git a/c/sedona-s2geography/src/geography_glue.h 
b/c/sedona-s2geography/src/geography_glue.h
index 1d37ca32..04ff532b 100644
--- a/c/sedona-s2geography/src/geography_glue.h
+++ b/c/sedona-s2geography/src/geography_glue.h
@@ -15,6 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
+#include <stddef.h>
 #include <stdint.h>
 
 #ifdef __cplusplus
@@ -45,40 +46,9 @@ double SedonaGeographyGlueTestLinkage(void);
 
 uint64_t SedonaGeographyGlueLngLatToCellId(double lng, double lat);
 
-struct SedonaGeographyArrowUdf {
-  int (*init)(struct SedonaGeographyArrowUdf* self, struct ArrowSchema* 
arg_schema,
-              const char* options, struct ArrowSchema* out);
-  int (*execute)(struct SedonaGeographyArrowUdf* self, struct ArrowArray** 
args,
-                 int64_t n_args, struct ArrowArray* out);
-  const char* (*get_last_error)(struct SedonaGeographyArrowUdf* self);
-  void (*release)(struct SedonaGeographyArrowUdf* self);
+size_t SedonaGeographyGlueNumKernels(void);
 
-  void* private_data;
-};
-
-#define DECLARE_UDF_IMPL(name) \
-  void SedonaGeographyInitUdf##name(struct SedonaGeographyArrowUdf* out)
-
-DECLARE_UDF_IMPL(Area);
-DECLARE_UDF_IMPL(Centroid);
-DECLARE_UDF_IMPL(ClosestPoint);
-DECLARE_UDF_IMPL(Contains);
-DECLARE_UDF_IMPL(ConvexHull);
-DECLARE_UDF_IMPL(Difference);
-DECLARE_UDF_IMPL(Distance);
-DECLARE_UDF_IMPL(Equals);
-DECLARE_UDF_IMPL(Intersection);
-DECLARE_UDF_IMPL(Intersects);
-DECLARE_UDF_IMPL(Length);
-DECLARE_UDF_IMPL(LineInterpolatePoint);
-DECLARE_UDF_IMPL(LineLocatePoint);
-DECLARE_UDF_IMPL(MaxDistance);
-DECLARE_UDF_IMPL(Perimeter);
-DECLARE_UDF_IMPL(ShortestLine);
-DECLARE_UDF_IMPL(SymDifference);
-DECLARE_UDF_IMPL(Union);
-
-#undef DECLARE_UDF_IMPL
+int SedonaGeographyGlueInitKernels(void* kernels_array, size_t 
kernels_size_bytes);
 
 #ifdef __cplusplus
 }
diff --git a/c/sedona-s2geography/src/geography_glue_bindgen.rs 
b/c/sedona-s2geography/src/geography_glue_bindgen.rs
index 45e09b7b..06bb4aa5 100644
--- a/c/sedona-s2geography/src/geography_glue_bindgen.rs
+++ b/c/sedona-s2geography/src/geography_glue_bindgen.rs
@@ -17,78 +17,15 @@
 
 use std::os::raw::{c_char, c_int, c_void};
 
-#[repr(C)]
-pub struct ArrowSchema {
-    _private: [u8; 0],
-}
-
-#[repr(C)]
-pub struct ArrowArray {
-    _private: [u8; 0],
-}
-
-#[repr(C)]
-#[derive(Debug, Copy, Clone)]
-pub struct SedonaGeographyArrowUdf {
-    pub init: Option<
-        unsafe extern "C" fn(
-            self_: *mut SedonaGeographyArrowUdf,
-            arg_schema: *mut ArrowSchema,
-            options: *const c_char,
-            out: *mut ArrowSchema,
-        ) -> c_int,
-    >,
-    pub execute: Option<
-        unsafe extern "C" fn(
-            self_: *mut SedonaGeographyArrowUdf,
-            args: *mut *mut ArrowArray,
-            n_args: i64,
-            out: *mut ArrowArray,
-        ) -> c_int,
-    >,
-    pub get_last_error:
-        Option<unsafe extern "C" fn(self_: *mut SedonaGeographyArrowUdf) -> 
*const c_char>,
-    pub release: Option<unsafe extern "C" fn(self_: *mut 
SedonaGeographyArrowUdf)>,
-    pub private_data: *mut c_void,
-}
-
-macro_rules! declare_s2_c_udfs {
-    ($($name:ident),*) => {
-        $(
-            paste::item! {
-                pub fn [<SedonaGeographyInitUdf $name>](out: *mut 
SedonaGeographyArrowUdf);
-            }
-        )*
-    }
-}
-
 unsafe extern "C" {
-    pub fn SedonaGeographyGlueNanoarrowVersion() -> *const c_char;
-    pub fn SedonaGeographyGlueGeoArrowVersion() -> *const c_char;
     pub fn SedonaGeographyGlueOpenSSLVersion() -> *const c_char;
     pub fn SedonaGeographyGlueS2GeometryVersion() -> *const c_char;
     pub fn SedonaGeographyGlueAbseilVersion() -> *const c_char;
     pub fn SedonaGeographyGlueTestLinkage() -> f64;
     pub fn SedonaGeographyGlueLngLatToCellId(lng: f64, lat: f64) -> u64;
-
-    declare_s2_c_udfs!(
-        Area,
-        Centroid,
-        ClosestPoint,
-        Contains,
-        ConvexHull,
-        Difference,
-        Distance,
-        Equals,
-        Intersection,
-        Intersects,
-        Length,
-        LineInterpolatePoint,
-        LineLocatePoint,
-        MaxDistance,
-        Perimeter,
-        ShortestLine,
-        SymDifference,
-        Union
-    );
+    pub fn SedonaGeographyGlueNumKernels() -> usize;
+    pub fn SedonaGeographyGlueInitKernels(
+        kernels_array: *mut c_void,
+        kernels_size_bytes: usize,
+    ) -> c_int;
 }
diff --git a/c/sedona-s2geography/src/lib.rs b/c/sedona-s2geography/src/lib.rs
index edee44fe..1cac7990 100644
--- a/c/sedona-s2geography/src/lib.rs
+++ b/c/sedona-s2geography/src/lib.rs
@@ -14,8 +14,6 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-pub mod error;
 mod geography_glue_bindgen;
 pub mod register;
 pub mod s2geography;
-pub mod scalar_kernel;
diff --git a/c/sedona-s2geography/src/register.rs 
b/c/sedona-s2geography/src/register.rs
index ebfc2816..fd67f014 100644
--- a/c/sedona-s2geography/src/register.rs
+++ b/c/sedona-s2geography/src/register.rs
@@ -14,35 +14,24 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
+
+use datafusion_common::Result;
+use sedona_common::sedona_internal_err;
 use sedona_expr::scalar_udf::ScalarKernelRef;
+use std::sync::OnceLock;
 
-use crate::scalar_kernel;
+static S2_SCALAR_KERNELS: OnceLock<Result<Vec<(String, ScalarKernelRef)>>> = 
OnceLock::new();
 
-pub fn scalar_kernels() -> Vec<(&'static str, ScalarKernelRef)> {
-    vec![
-        ("st_area", scalar_kernel::st_area_impl()),
-        ("st_centroid", scalar_kernel::st_centroid_impl()),
-        ("st_closestpoint", scalar_kernel::st_closest_point_impl()),
-        ("st_contains", scalar_kernel::st_contains_impl()),
-        ("st_convexhull", scalar_kernel::st_convex_hull_impl()),
-        ("st_difference", scalar_kernel::st_difference_impl()),
-        ("st_distance", scalar_kernel::st_distance_impl()),
-        ("st_equals", scalar_kernel::st_equals_impl()),
-        ("st_intersection", scalar_kernel::st_intersection_impl()),
-        ("st_intersects", scalar_kernel::st_intersects_impl()),
-        (
-            "st_lineinterpolatepoint",
-            scalar_kernel::st_line_interpolate_point_impl(),
-        ),
-        (
-            "st_linelocatepoint",
-            scalar_kernel::st_line_locate_point_impl(),
-        ),
-        ("st_length", scalar_kernel::st_length_impl()),
-        ("st_symdifference", scalar_kernel::st_sym_difference_impl()),
-        ("st_maxdistance", scalar_kernel::st_max_distance_impl()),
-        ("st_perimeter", scalar_kernel::st_perimeter_impl()),
-        ("st_shortestline", scalar_kernel::st_shortest_line_impl()),
-        ("st_union", scalar_kernel::st_union_impl()),
-    ]
+/// Initialize s2geography scalar kernels via extension ABI
+///
+/// This function is the entrypoint to S2Geography-based scalar kernels 
suitable for
+/// adding to a FunctionSet.
+pub fn scalar_kernels() -> Result<Vec<(&'static str, ScalarKernelRef)>> {
+    match S2_SCALAR_KERNELS.get_or_init(crate::s2geography::s2_scalar_kernels) 
{
+        Ok(kernels) => Ok(kernels
+            .iter()
+            .map(|(name, kernel)| (name.as_str(), kernel.clone()))
+            .collect()),
+        Err(err) => sedona_internal_err!("Error initializing s2geography 
kernels: {err}"),
+    }
 }
diff --git a/c/sedona-s2geography/src/s2geography.rs 
b/c/sedona-s2geography/src/s2geography.rs
index f2a82da2..a3127a71 100644
--- a/c/sedona-s2geography/src/s2geography.rs
+++ b/c/sedona-s2geography/src/s2geography.rs
@@ -14,15 +14,15 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-use std::collections::HashMap;
 
-use arrow_array::{
-    ffi::{FFI_ArrowArray, FFI_ArrowSchema},
-    ArrayRef,
-};
-use arrow_schema::{ArrowError, Fields, Schema};
+use std::sync::Arc;
 
-use crate::{error::S2GeographyError, geography_glue_bindgen::*};
+use datafusion_common::Result;
+use sedona_common::sedona_internal_err;
+use sedona_expr::scalar_udf::ScalarKernelRef;
+use sedona_extension::{extension::SedonaCScalarKernel, 
scalar_kernel::ImportedScalarKernel};
+
+use crate::geography_glue_bindgen::*;
 
 /// Compute an S2 Cell identifier from a longitude/latitude pair
 ///
@@ -34,190 +34,37 @@ pub fn s2_cell_id_from_lnglat(lnglat: (f64, f64)) -> u64 {
     unsafe { SedonaGeographyGlueLngLatToCellId(lnglat.0, lnglat.1) }
 }
 
-/// Wrapper for scalar UDFs exposed by s2geography::arrow_udf
-///
-/// Provides a minimal wrapper around the C callables that define
-/// an scalar UDF as exposed by the s2geography library.
-///
-/// These are designed to be sufficiently cheap to initialize that
-/// they can be constructed on the stack, initialized, and executed
-/// for a single batch.
-#[derive(Debug)]
-pub struct S2ScalarUDF {
-    inner: SedonaGeographyArrowUdf,
-}
-
-impl Drop for S2ScalarUDF {
-    fn drop(&mut self) {
-        if let Some(releaser) = self.inner.release {
-            unsafe { releaser(&mut self.inner) }
-        }
-    }
-}
-
-// We have a bunch of these, so we use a macro to declare them. We could
-// use a string or enum to retrieve them as well if this becomes unwieldy.
-macro_rules! define_s2_udfs {
-    ($($name:ident),*) => {
-        $(
-            pub fn $name() -> S2ScalarUDF {
-                let mut out = Self::allocate();
-                unsafe { paste::paste!([<SedonaGeographyInitUdf $name>])(&mut 
out.inner) }
-                out
-            }
-        )*
-    }
-}
-
-#[allow(non_snake_case)]
-impl S2ScalarUDF {
-    define_s2_udfs![
-        Area,
-        Centroid,
-        ClosestPoint,
-        Contains,
-        ConvexHull,
-        Difference,
-        Distance,
-        Equals,
-        Intersection,
-        Intersects,
-        Length,
-        LineInterpolatePoint,
-        LineLocatePoint,
-        MaxDistance,
-        Perimeter,
-        ShortestLine,
-        SymDifference,
-        Union
-    ];
-
-    /// Initialize the UDF instance with argument types and options
-    ///
-    /// This must be called before calling execute().
-    pub fn init(
-        &mut self,
-        arg_types: Fields,
-        options: Option<HashMap<String, String>>,
-    ) -> Result<FFI_ArrowSchema, S2GeographyError> {
-        if options.is_some() {
-            // See FFI_ArrowSchema::with_metadata() for implementation. This
-            // is to pass options like the radius to use for 
length/perimeter/area
-            // calculations that are currently hard-coded to the WGS84 mean 
radius.
-            return Err(S2GeographyError::Internal(
-                "scalar UDF options not yet implemented".to_string(),
-            ));
-        }
-
-        let arg_schema = Schema::new(arg_types);
-        let mut ffi_arg_schema = FFI_ArrowSchema::try_from(arg_schema)?;
-        let ffi_options = [0x00, 0x00, 0x00, 0x00];
-        let mut ffi_field_out = FFI_ArrowSchema::empty();
-
-        unsafe {
-            let ffi_arg_schema: *mut ArrowSchema =
-                &mut ffi_arg_schema as *mut FFI_ArrowSchema as *mut 
ArrowSchema;
-            let ffi_out: *mut ArrowSchema =
-                &mut ffi_field_out as *mut FFI_ArrowSchema as *mut ArrowSchema;
-            let errc = self.inner.init.unwrap()(
-                &mut self.inner,
-                ffi_arg_schema,
-                ffi_options.as_ptr(),
-                ffi_out,
-            );
-
-            let last_err = self.last_error();
-            if errc != 0 && last_err.is_empty() {
-                Err(S2GeographyError::Code(errc))
-            } else if errc != 0 {
-                Err(S2GeographyError::Message(errc, last_err))
-            } else {
-                Ok(ffi_field_out)
-            }
-        }
-    }
-
-    /// Execute a batch
-    ///
-    /// The resulting FFI_ArrowArray requires the FFI_ArrowSchema returned by
-    /// init() to be transformed into an [ArrayRef].
-    pub fn execute(&mut self, args: &[ArrayRef]) -> Result<FFI_ArrowArray, 
S2GeographyError> {
-        let mut args_ffi = args
-            .iter()
-            .map(|arg| arrow_array::ffi::to_ffi(&arg.to_data()))
-            .collect::<Result<Vec<_>, ArrowError>>()?;
-        let arg_ptrs = args_ffi
-            .iter_mut()
-            .map(|arg| &mut arg.0 as *mut FFI_ArrowArray)
-            .collect::<Vec<_>>();
-        let mut ffi_array_out = FFI_ArrowArray::empty();
-
-        unsafe {
-            let arg_ptrs: *mut *mut ArrowArray = arg_ptrs.as_ptr() as *mut 
*mut ArrowArray;
-            let ffi_out: *mut ArrowArray =
-                &mut ffi_array_out as *mut FFI_ArrowArray as *mut ArrowArray;
-            let errc = self.inner.execute.unwrap()(
-                &mut self.inner,
-                arg_ptrs,
-                args_ffi.len() as i64,
-                ffi_out,
-            );
-
-            let last_err = self.last_error();
-            if errc != 0 && last_err.is_empty() {
-                Err(S2GeographyError::Code(errc))
-            } else if errc != 0 {
-                Err(S2GeographyError::Message(errc, last_err))
-            } else {
-                Ok(ffi_array_out)
-            }
-        }
-    }
-
-    fn last_error(&mut self) -> String {
-        let c_str = unsafe {
-            let raw_c_str = self.inner.get_last_error.unwrap()(&mut 
self.inner);
-            std::ffi::CStr::from_ptr(raw_c_str)
-        };
-
-        c_str.to_string_lossy().into_owned()
-    }
-
-    fn allocate() -> Self {
-        Self {
-            inner: SedonaGeographyArrowUdf {
-                init: None,
-                execute: None,
-                get_last_error: None,
-                release: None,
-                private_data: std::ptr::null_mut(),
-            },
-        }
-    }
+pub fn s2_scalar_kernels() -> Result<Vec<(String, ScalarKernelRef)>> {
+    let mut ffi_scalar_kernels = Vec::<SedonaCScalarKernel>::new();
+    ffi_scalar_kernels.resize_with(unsafe { SedonaGeographyGlueNumKernels() }, 
Default::default);
+
+    let err_code = unsafe {
+        SedonaGeographyGlueInitKernels(
+            ffi_scalar_kernels.as_mut_ptr() as _,
+            size_of::<SedonaCScalarKernel>() * ffi_scalar_kernels.len(),
+        )
+    };
+
+    if err_code != 0 {
+        return sedona_internal_err!("SedonaGeographyGlueInitKernels() failed");
+    }
+
+    ffi_scalar_kernels
+        .into_iter()
+        .map(|c_kernel| {
+            let imported_kernel = ImportedScalarKernel::try_from(c_kernel)?;
+            Ok((
+                imported_kernel.function_name().unwrap().to_string(),
+                Arc::new(imported_kernel) as ScalarKernelRef,
+            ))
+        })
+        .collect()
 }
 
 /// Dependency versions for underlying libraries
 pub struct Versions {}
 
 impl Versions {
-    /// Return the statically linked nanoarrow version as a string
-    pub fn nanoarrow() -> String {
-        unsafe {
-            let raw_c_str = SedonaGeographyGlueNanoarrowVersion();
-            let c_str = std::ffi::CStr::from_ptr(raw_c_str);
-            c_str.to_string_lossy().into_owned()
-        }
-    }
-
-    /// Return the statically linked geoarrow version as a string
-    pub fn geoarrow() -> String {
-        unsafe {
-            let raw_c_str = SedonaGeographyGlueGeoArrowVersion();
-            let c_str = std::ffi::CStr::from_ptr(raw_c_str);
-            c_str.to_string_lossy().into_owned()
-        }
-    }
-
     /// Return the statically linked s2 version as a string
     pub fn s2geometry() -> String {
         unsafe {
@@ -261,11 +108,6 @@ impl Versions {
 
 #[cfg(test)]
 mod test {
-    use arrow_array::ffi;
-    use arrow_schema::DataType;
-    use sedona_schema::datatypes::WKB_GEOGRAPHY;
-    use sedona_testing::create::create_array_storage;
-
     use super::*;
 
     #[test]
@@ -285,58 +127,13 @@ mod test {
     }
 
     #[test]
-    fn scalar_udf() {
-        let mut udf = S2ScalarUDF::Length();
-
-        let out_field_ffi = udf
-            .init(
-                vec![WKB_GEOGRAPHY.to_storage_field("", true).unwrap()].into(),
-                None,
-            )
-            .unwrap();
-
-        let out_data_type = DataType::try_from(&out_field_ffi).unwrap();
-        assert_eq!(out_data_type, DataType::Float64);
-
-        let in_array = create_array_storage(
-            &[
-                Some("POINT (0 1)"),
-                Some("LINESTRING (0 0, 0 1)"),
-                Some("POLYGON ((0 0, 1 0, 0 1, 0 0))"),
-                None,
-            ],
-            &WKB_GEOGRAPHY,
-        );
-
-        let out_array_ffi = udf.execute(&[in_array]).unwrap();
-        let out_array_data = unsafe { ffi::from_ffi(out_array_ffi, 
&out_field_ffi).unwrap() };
-        let out_array = arrow_array::make_array(out_array_data);
-
-        let expected: ArrayRef = arrow_array::create_array!(
-            Float64,
-            [Some(0.0), Some(111195.10117748393), Some(0.0), None]
-        );
-        assert_eq!(&out_array, &expected);
-    }
-
-    #[test]
-    fn scalar_udf_errors() {
-        let mut udf = S2ScalarUDF::Length();
-        let err = udf.init(Fields::empty(), None).unwrap_err();
-        assert!(err
-            .to_string()
-            .contains("Expected one argument in unary s2geography UDF"));
-
-        let err = udf.execute(&[]).unwrap_err();
-        assert!(err
-            .to_string()
-            .contains("Expected one argument/one argument type in in unary 
s2geography UDF"));
+    fn test_s2_scalar_kernels() {
+        let kernels = s2_scalar_kernels().unwrap();
+        assert!(!kernels.is_empty());
     }
 
     #[test]
     fn test_versions() {
-        assert_eq!(Versions::nanoarrow(), "0.7.0-SNAPSHOT");
-        assert_eq!(Versions::geoarrow(), "0.2.0-SNAPSHOT");
         assert_eq!(Versions::s2geometry(), "0.11.1");
         assert!(Versions::abseil().starts_with("20"));
         assert!(Versions::openssl().contains("."));
diff --git a/c/sedona-s2geography/src/scalar_kernel.rs 
b/c/sedona-s2geography/src/scalar_kernel.rs
deleted file mode 100644
index 78dcab57..00000000
--- a/c/sedona-s2geography/src/scalar_kernel.rs
+++ /dev/null
@@ -1,555 +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 std::{iter::zip, sync::Arc};
-
-use arrow_schema::DataType;
-use datafusion_common::{Result, ScalarValue};
-use datafusion_expr::ColumnarValue;
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
-use sedona_schema::{
-    datatypes::{SedonaType, WKB_GEOGRAPHY},
-    matchers::{ArgMatcher, TypeMatcher},
-};
-
-use crate::s2geography::S2ScalarUDF;
-
-/// Implementation of ST_Area() for geography using s2geography
-pub fn st_area_impl() -> ScalarKernelRef {
-    S2ScalarKernel::new_ref(
-        S2ScalarUDF::Area,
-        vec![ArgMatcher::is_geography()],
-        SedonaType::Arrow(DataType::Float64),
-    )
-}
-
-/// Implementation of ST_Centroid() for geography using s2geography
-pub fn st_centroid_impl() -> ScalarKernelRef {
-    S2ScalarKernel::new_ref(
-        S2ScalarUDF::Centroid,
-        vec![ArgMatcher::is_geography()],
-        WKB_GEOGRAPHY,
-    )
-}
-
-/// Implementation of ST_ClosestPoint() for geography using s2geography
-pub fn st_closest_point_impl() -> ScalarKernelRef {
-    S2ScalarKernel::new_ref(
-        S2ScalarUDF::ClosestPoint,
-        vec![ArgMatcher::is_geography(), ArgMatcher::is_geography()],
-        WKB_GEOGRAPHY,
-    )
-}
-
-/// Implementation of ST_Contains() for geography using s2geography
-pub fn st_contains_impl() -> ScalarKernelRef {
-    S2ScalarKernel::new_ref(
-        S2ScalarUDF::Contains,
-        vec![ArgMatcher::is_geography(), ArgMatcher::is_geography()],
-        SedonaType::Arrow(DataType::Boolean),
-    )
-}
-
-/// Implementation of ST_ConvexHull() for geography using s2geography
-pub fn st_convex_hull_impl() -> ScalarKernelRef {
-    S2ScalarKernel::new_ref(
-        S2ScalarUDF::ConvexHull,
-        vec![ArgMatcher::is_geography()],
-        WKB_GEOGRAPHY,
-    )
-}
-
-/// Implementation of ST_Difference() for geography using s2geography
-pub fn st_difference_impl() -> ScalarKernelRef {
-    S2ScalarKernel::new_ref(
-        S2ScalarUDF::Difference,
-        vec![ArgMatcher::is_geography(), ArgMatcher::is_geography()],
-        WKB_GEOGRAPHY,
-    )
-}
-
-/// Implementation of ST_Distance() for geography using s2geography
-pub fn st_distance_impl() -> ScalarKernelRef {
-    S2ScalarKernel::new_ref(
-        S2ScalarUDF::Distance,
-        vec![ArgMatcher::is_geography(), ArgMatcher::is_geography()],
-        SedonaType::Arrow(DataType::Float64),
-    )
-}
-
-/// Implementation of ST_Equals() for geography using s2geography
-pub fn st_equals_impl() -> ScalarKernelRef {
-    S2ScalarKernel::new_ref(
-        S2ScalarUDF::Equals,
-        vec![ArgMatcher::is_geography(), ArgMatcher::is_geography()],
-        SedonaType::Arrow(DataType::Boolean),
-    )
-}
-
-/// Implementation of ST_Intersection() for geography using s2geography
-pub fn st_intersection_impl() -> ScalarKernelRef {
-    S2ScalarKernel::new_ref(
-        S2ScalarUDF::Intersection,
-        vec![ArgMatcher::is_geography(), ArgMatcher::is_geography()],
-        WKB_GEOGRAPHY,
-    )
-}
-
-/// Implementation of ST_Intersects() for geography using s2geography
-pub fn st_intersects_impl() -> ScalarKernelRef {
-    S2ScalarKernel::new_ref(
-        S2ScalarUDF::Intersects,
-        vec![ArgMatcher::is_geography(), ArgMatcher::is_geography()],
-        SedonaType::Arrow(DataType::Boolean),
-    )
-}
-
-/// Implementation of ST_Length() for geography using s2geography
-pub fn st_length_impl() -> ScalarKernelRef {
-    S2ScalarKernel::new_ref(
-        S2ScalarUDF::Length,
-        vec![ArgMatcher::is_geography()],
-        SedonaType::Arrow(DataType::Float64),
-    )
-}
-
-/// Implementation of ST_LineInterpolatePoint() for geography using s2geography
-pub fn st_line_interpolate_point_impl() -> ScalarKernelRef {
-    S2ScalarKernel::new_ref(
-        S2ScalarUDF::LineInterpolatePoint,
-        vec![ArgMatcher::is_geography(), ArgMatcher::is_numeric()],
-        WKB_GEOGRAPHY,
-    )
-}
-
-/// Implementation of ST_LineLocatePoint() for geography using s2geography
-pub fn st_line_locate_point_impl() -> ScalarKernelRef {
-    S2ScalarKernel::new_ref(
-        S2ScalarUDF::LineLocatePoint,
-        vec![ArgMatcher::is_geography(), ArgMatcher::is_geography()],
-        SedonaType::Arrow(DataType::Float64),
-    )
-}
-
-/// Implementation of ST_MaxDistance() for geography using s2geography
-pub fn st_max_distance_impl() -> ScalarKernelRef {
-    S2ScalarKernel::new_ref(
-        S2ScalarUDF::MaxDistance,
-        vec![ArgMatcher::is_geography(), ArgMatcher::is_geography()],
-        SedonaType::Arrow(DataType::Float64),
-    )
-}
-
-/// Implementation of ST_Perimeter() for geography using s2geography
-pub fn st_perimeter_impl() -> ScalarKernelRef {
-    S2ScalarKernel::new_ref(
-        S2ScalarUDF::Perimeter,
-        vec![ArgMatcher::is_geography()],
-        SedonaType::Arrow(DataType::Float64),
-    )
-}
-
-/// Implementation of ST_ShortestLine() for geography using s2geography
-pub fn st_shortest_line_impl() -> ScalarKernelRef {
-    S2ScalarKernel::new_ref(
-        S2ScalarUDF::ShortestLine,
-        vec![ArgMatcher::is_geography(), ArgMatcher::is_geography()],
-        WKB_GEOGRAPHY,
-    )
-}
-
-/// Implementation of ST_SymDifference() for geography using s2geography
-pub fn st_sym_difference_impl() -> ScalarKernelRef {
-    S2ScalarKernel::new_ref(
-        S2ScalarUDF::SymDifference,
-        vec![ArgMatcher::is_geography(), ArgMatcher::is_geography()],
-        WKB_GEOGRAPHY,
-    )
-}
-
-/// Implementation of ST_Union() for geography using s2geography
-pub fn st_union_impl() -> ScalarKernelRef {
-    S2ScalarKernel::new_ref(
-        S2ScalarUDF::Union,
-        vec![ArgMatcher::is_geography(), ArgMatcher::is_geography()],
-        WKB_GEOGRAPHY,
-    )
-}
-
-#[derive(Debug)]
-struct S2ScalarKernel {
-    inner_factory: fn() -> S2ScalarUDF,
-    matcher: ArgMatcher,
-}
-
-impl S2ScalarKernel {
-    /// Creates a new reference to a S2ScalarKernel
-    pub fn new_ref(
-        inner_factory: fn() -> S2ScalarUDF,
-        matchers: Vec<Arc<dyn TypeMatcher + Send + Sync>>,
-        out_type: SedonaType,
-    ) -> ScalarKernelRef {
-        Arc::new(Self {
-            inner_factory,
-            matcher: ArgMatcher::new(matchers, out_type),
-        })
-    }
-}
-
-impl SedonaScalarKernel for S2ScalarKernel {
-    fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
-        self.matcher.match_args(args)
-    }
-
-    fn invoke_batch(
-        &self,
-        arg_types: &[SedonaType],
-        args: &[ColumnarValue],
-    ) -> Result<ColumnarValue> {
-        let mut inner = (self.inner_factory)();
-
-        let arg_types_if_null = self.matcher.types_if_null(arg_types)?;
-        let args_casted_null = zip(args, &arg_types_if_null)
-            .map(|(arg, type_if_null)| 
arg.cast_to(type_if_null.storage_type(), None))
-            .collect::<Result<Vec<_>>>()?;
-
-        // S2's scalar UDFs operate on fields with extension metadata
-        let arg_fields = arg_types_if_null
-            .iter()
-            .map(|arg_type| arg_type.to_storage_field("", true))
-            .collect::<Result<Vec<_>>>()?;
-
-        // Initialize the UDF with a schema consisting of the output fields
-        let out_ffi_schema = inner.init(arg_fields.into(), None)?;
-
-        // Create arrays from each argument (scalars become arrays of size 1)
-        let arg_arrays = args_casted_null
-            .iter()
-            .map(|arg| match arg {
-                ColumnarValue::Array(array) => Ok(array.clone()),
-                ColumnarValue::Scalar(_) => arg.to_array(1),
-            })
-            .collect::<Result<Vec<_>>>()?;
-
-        // Execute the batch
-        let out_ffi_array = inner.execute(&arg_arrays)?;
-
-        // Create the ArrayRef
-        let out_array_data = unsafe { 
arrow_array::ffi::from_ffi(out_ffi_array, &out_ffi_schema)? };
-        let out_array = arrow_array::make_array(out_array_data);
-
-        // Ensure scalar inputs map to scalar output
-        for arg in args {
-            if let ColumnarValue::Array(_) = arg {
-                return Ok(ColumnarValue::Array(out_array));
-            }
-        }
-
-        Ok(ScalarValue::try_from_array(&out_array, 0)?.into())
-    }
-}
-
-#[cfg(test)]
-mod test {
-
-    use arrow_array::ArrayRef;
-    use rstest::rstest;
-    use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{WKB_GEOGRAPHY, WKB_VIEW_GEOGRAPHY};
-    use sedona_testing::{
-        create::{create_array, create_scalar},
-        testers::ScalarUdfTester,
-    };
-
-    use super::*;
-
-    #[rstest]
-    fn unary_scalar_kernel(#[values(WKB_GEOGRAPHY, WKB_VIEW_GEOGRAPHY)] 
sedona_type: SedonaType) {
-        let udf = SedonaScalarUDF::from_impl("st_length", st_length_impl());
-        let tester = ScalarUdfTester::new(udf.into(), vec![sedona_type]);
-        assert_eq!(
-            tester.return_type().unwrap(),
-            SedonaType::Arrow(DataType::Float64)
-        );
-
-        // Array -> Array
-        let result = tester
-            .invoke_wkb_array(vec![
-                Some("POINT (0 1)"),
-                Some("LINESTRING (0 0, 0 1)"),
-                Some("POLYGON ((0 0, 1 0, 0 1, 0 0))"),
-                None,
-            ])
-            .unwrap();
-
-        let expected: ArrayRef = arrow_array::create_array!(
-            Float64,
-            [Some(0.0), Some(111195.10117748393), Some(0.0), None]
-        );
-
-        assert_eq!(&result, &expected);
-
-        // Scalar -> Scalar
-        let result = tester
-            .invoke_wkb_scalar(Some("LINESTRING (0 0, 0 1)"))
-            .unwrap();
-        assert_eq!(result, ScalarValue::Float64(Some(111195.10117748393)));
-
-        // Null scalar -> Null
-        let result = tester.invoke_scalar(ScalarValue::Null).unwrap();
-        assert_eq!(result, ScalarValue::Float64(None));
-    }
-
-    #[rstest]
-    fn binary_scalar_kernel(#[values(WKB_GEOGRAPHY, WKB_VIEW_GEOGRAPHY)] 
sedona_type: SedonaType) {
-        let udf = SedonaScalarUDF::from_impl("st_intersects", 
st_intersects_impl());
-        let tester =
-            ScalarUdfTester::new(udf.into(), vec![sedona_type.clone(), 
sedona_type.clone()]);
-        assert_eq!(
-            tester.return_type().unwrap(),
-            SedonaType::Arrow(DataType::Boolean)
-        );
-
-        let point_array = create_array(
-            &[Some("POINT (0.25 0.25)"), Some("POINT (10 10)"), None],
-            &sedona_type,
-        );
-        let polygon_scalar = create_scalar(Some("POLYGON ((0 0, 1 0, 0 1, 0 
0))"), &sedona_type);
-        let point_scalar = create_scalar(Some("POINT (0.25 0.25)"), 
&sedona_type);
-
-        let expected: ArrayRef =
-            arrow_array::create_array!(Boolean, [Some(true), Some(false), 
None]);
-
-        // Array, Scalar -> Array
-        let result = tester
-            .invoke_array_scalar(point_array.clone(), polygon_scalar.clone())
-            .unwrap();
-        assert_eq!(&result, &expected);
-
-        // Scalar, Array -> Array
-        let result = tester
-            .invoke_scalar_array(polygon_scalar.clone(), point_array.clone())
-            .unwrap();
-        assert_eq!(&result, &expected);
-
-        // Scalar, Scalar -> Scalar
-        let result = tester
-            .invoke_scalar_scalar(polygon_scalar, point_scalar)
-            .unwrap();
-        assert_eq!(result, ScalarValue::Boolean(Some(true)));
-
-        // Null scalars -> Null
-        let result = tester
-            .invoke_scalar_scalar(ScalarValue::Null, ScalarValue::Null)
-            .unwrap();
-        assert_eq!(result, ScalarValue::Boolean(None));
-    }
-
-    #[test]
-    fn area() {
-        let udf = SedonaScalarUDF::from_impl("st_area", st_area_impl());
-        let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY]);
-
-        tester.assert_return_type(DataType::Float64);
-        let result = tester
-            .invoke_scalar("POLYGON ((0 0, 0 1, 1 0, 0 0))")
-            .unwrap();
-        tester.assert_scalar_result_equals(result, 6182489130.907195);
-    }
-
-    #[test]
-    fn centroid() {
-        let udf = SedonaScalarUDF::from_impl("st_centroid", 
st_centroid_impl());
-        let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY]);
-        tester.assert_return_type(WKB_GEOGRAPHY);
-        let result = tester.invoke_scalar("LINESTRING (0 0, 0 1)").unwrap();
-        tester.assert_scalar_result_equals(result, "POINT (0 0.5)");
-    }
-
-    #[test]
-    fn closest_point() {
-        let udf = SedonaScalarUDF::from_impl("st_closestpoint", 
st_closest_point_impl());
-        let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
-        tester.assert_return_type(WKB_GEOGRAPHY);
-        let result = tester
-            .invoke_scalar_scalar("LINESTRING (0 0, 0 1)", "POINT (-1 -1)")
-            .unwrap();
-        tester.assert_scalar_result_equals(result, "POINT (0 0)");
-    }
-
-    #[test]
-    fn contains() {
-        let udf = SedonaScalarUDF::from_impl("st_contains", 
st_contains_impl());
-        let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
-        tester.assert_return_type(DataType::Boolean);
-        let result = tester
-            .invoke_scalar_scalar("POLYGON ((0 0, 0 1, 1 0, 0 0))", "POINT 
(0.25 0.25)")
-            .unwrap();
-        tester.assert_scalar_result_equals(result, true);
-    }
-
-    #[test]
-    fn difference() {
-        let udf = SedonaScalarUDF::from_impl("st_difference", 
st_difference_impl());
-        let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
-        tester.assert_return_type(WKB_GEOGRAPHY);
-        let result = tester
-            .invoke_scalar_scalar("POINT (0 0)", "POINT (0 0)")
-            .unwrap();
-        tester.assert_scalar_result_equals(result, "GEOMETRYCOLLECTION EMPTY");
-    }
-
-    #[test]
-    fn distance() {
-        let udf = SedonaScalarUDF::from_impl("st_distance", 
st_distance_impl());
-        let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
-        tester.assert_return_type(DataType::Float64);
-        let result = tester
-            .invoke_scalar_scalar("POINT (0 0)", "POINT (0 0)")
-            .unwrap();
-        tester.assert_scalar_result_equals(result, 0.0);
-    }
-
-    #[test]
-    fn equals() {
-        let udf = SedonaScalarUDF::from_impl("st_equals", st_equals_impl());
-        let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
-        tester.assert_return_type(DataType::Boolean);
-        let result1 = tester
-            .invoke_scalar_scalar("POINT (0 0)", "POINT (0 0)")
-            .unwrap();
-        tester.assert_scalar_result_equals(result1, true);
-        let result2 = tester
-            .invoke_scalar_scalar("POINT (0 0)", "POINT (0 1)")
-            .unwrap();
-        tester.assert_scalar_result_equals(result2, false);
-    }
-
-    #[test]
-    fn intersection() {
-        let udf = SedonaScalarUDF::from_impl("st_intersection", 
st_intersection_impl());
-        let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
-        tester.assert_return_type(WKB_GEOGRAPHY);
-        let result = tester
-            .invoke_scalar_scalar("POINT (0 0)", "POINT (0 0)")
-            .unwrap();
-        tester.assert_scalar_result_equals(result, "POINT (0 0)");
-    }
-
-    #[test]
-    fn intersects() {
-        let udf = SedonaScalarUDF::from_impl("st_intersects", 
st_intersects_impl());
-        let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
-        tester.assert_return_type(DataType::Boolean);
-        let result1 = tester
-            .invoke_scalar_scalar("POLYGON ((0 0, 0 1, 1 0, 0 0))", "POINT 
(0.25 0.25)")
-            .unwrap();
-        tester.assert_scalar_result_equals(result1, true);
-        let result2 = tester
-            .invoke_scalar_scalar("POLYGON ((0 0, 0 1, 1 0, 0 0))", "POINT (-1 
-1)")
-            .unwrap();
-        tester.assert_scalar_result_equals(result2, false);
-    }
-
-    #[test]
-    fn length() {
-        let udf = SedonaScalarUDF::from_impl("st_length", st_length_impl());
-        let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY]);
-        tester.assert_return_type(DataType::Float64);
-        let result = tester.invoke_scalar("LINESTRING (0 0, 0 1)").unwrap();
-        tester.assert_scalar_result_equals(result, 111195.10117748393);
-    }
-
-    #[test]
-    fn line_interpolate_point() {
-        let udf =
-            SedonaScalarUDF::from_impl("st_lineinterpolatepoint", 
st_line_interpolate_point_impl());
-        let tester = ScalarUdfTester::new(
-            udf.into(),
-            vec![WKB_GEOGRAPHY, SedonaType::Arrow(DataType::Float64)],
-        );
-        tester.assert_return_type(WKB_GEOGRAPHY);
-        let result = tester
-            .invoke_scalar_scalar("LINESTRING (0 0, 0 1)", 0.5)
-            .unwrap();
-        tester.assert_scalar_result_equals(result, "POINT (0 0.5)");
-    }
-
-    #[test]
-    fn line_locate_point() {
-        let udf = SedonaScalarUDF::from_impl("st_linelocatepoint", 
st_line_locate_point_impl());
-        let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
-        tester.assert_return_type(DataType::Float64);
-        let result = tester
-            .invoke_scalar_scalar("LINESTRING (0 0, 0 1)", "POINT (0 0.5)")
-            .unwrap();
-        tester.assert_scalar_result_equals(result, 0.5);
-    }
-
-    #[test]
-    fn max_distance() {
-        let udf = SedonaScalarUDF::from_impl("st_maxdistance", 
st_max_distance_impl());
-        let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
-        tester.assert_return_type(DataType::Float64);
-        let result = tester
-            .invoke_scalar_scalar("POINT (0 0)", "LINESTRING (0 0, 0 1)")
-            .unwrap();
-        tester.assert_scalar_result_equals(result, 111195.10117748393);
-    }
-
-    #[test]
-    fn perimeter() {
-        let udf = SedonaScalarUDF::from_impl("st_perimeter", 
st_perimeter_impl());
-        let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY]);
-        let result = tester
-            .invoke_scalar("POLYGON ((0 0, 0 1, 1 0, 0 0))")
-            .unwrap();
-        tester.assert_scalar_result_equals(result, 379639.8304474758);
-    }
-
-    #[test]
-    fn shortest_line() {
-        let udf = SedonaScalarUDF::from_impl("st_shortestline", 
st_shortest_line_impl());
-        let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
-        tester.assert_return_type(WKB_GEOGRAPHY);
-        let result = tester
-            .invoke_scalar_scalar("LINESTRING (0 0, 0 1)", "POINT (0 -1)")
-            .unwrap();
-        tester.assert_scalar_result_equals(result, "LINESTRING (0 0, 0 -1)");
-    }
-
-    #[test]
-    fn sym_difference() {
-        let udf = SedonaScalarUDF::from_impl("st_symdifference", 
st_sym_difference_impl());
-        let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
-        tester.assert_return_type(WKB_GEOGRAPHY);
-        let result = tester
-            .invoke_scalar_scalar("POINT (0 0)", "POINT (0 0)")
-            .unwrap();
-        tester.assert_scalar_result_equals(result, "GEOMETRYCOLLECTION EMPTY");
-    }
-
-    #[test]
-    fn union() {
-        let udf = SedonaScalarUDF::from_impl("st_union", st_union_impl());
-        let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
-        tester.assert_return_type(WKB_GEOGRAPHY);
-        let result = tester
-            .invoke_scalar_scalar("POINT (0 0)", "POINT (0 0)")
-            .unwrap();
-        tester.assert_scalar_result_equals(result, "POINT (0 0)");
-    }
-}
diff --git a/c/sedona-s2geography/tests/mod.rs 
b/c/sedona-s2geography/tests/mod.rs
new file mode 100644
index 00000000..dee8d1b1
--- /dev/null
+++ b/c/sedona-s2geography/tests/mod.rs
@@ -0,0 +1,312 @@
+// 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 arrow_array::ArrayRef;
+use arrow_schema::DataType;
+use datafusion_common::ScalarValue;
+use rstest::rstest;
+use sedona_expr::scalar_udf::SedonaScalarUDF;
+use sedona_s2geography::s2geography::s2_scalar_kernels;
+use sedona_schema::datatypes::{SedonaType, WKB_GEOGRAPHY, WKB_VIEW_GEOGRAPHY};
+use sedona_testing::{
+    create::{create_array, create_scalar},
+    testers::ScalarUdfTester,
+};
+
+fn s2_udf(name: &str) -> SedonaScalarUDF {
+    for (kernel_name, kernel) in s2_scalar_kernels().unwrap() {
+        if name == kernel_name {
+            return SedonaScalarUDF::from_impl(name, kernel);
+        }
+    }
+
+    panic!("Can't find s2_scalar_udf named '{name}'")
+}
+
+#[rstest]
+fn unary_scalar_kernel(#[values(WKB_GEOGRAPHY, WKB_VIEW_GEOGRAPHY)] 
sedona_type: SedonaType) {
+    let udf = s2_udf("st_length");
+    let tester = ScalarUdfTester::new(udf.into(), vec![sedona_type]);
+    assert_eq!(
+        tester.return_type().unwrap(),
+        SedonaType::Arrow(DataType::Float64)
+    );
+
+    // Array -> Array
+    let result = tester
+        .invoke_wkb_array(vec![
+            Some("POINT (0 1)"),
+            Some("LINESTRING (0 0, 0 1)"),
+            Some("POLYGON ((0 0, 1 0, 0 1, 0 0))"),
+            None,
+        ])
+        .unwrap();
+
+    let expected: ArrayRef = arrow_array::create_array!(
+        Float64,
+        [Some(0.0), Some(111195.10117748393), Some(0.0), None]
+    );
+
+    assert_eq!(&result, &expected);
+
+    // Scalar -> Scalar
+    let result = tester
+        .invoke_wkb_scalar(Some("LINESTRING (0 0, 0 1)"))
+        .unwrap();
+    assert_eq!(result, ScalarValue::Float64(Some(111195.10117748393)));
+
+    // Null scalar -> Null
+    let result = tester.invoke_scalar(ScalarValue::Null).unwrap();
+    assert_eq!(result, ScalarValue::Float64(None));
+}
+
+#[rstest]
+fn binary_scalar_kernel(#[values(WKB_GEOGRAPHY, WKB_VIEW_GEOGRAPHY)] 
sedona_type: SedonaType) {
+    let udf = s2_udf("st_intersects");
+    let tester = ScalarUdfTester::new(udf.into(), vec![sedona_type.clone(), 
sedona_type.clone()]);
+    assert_eq!(
+        tester.return_type().unwrap(),
+        SedonaType::Arrow(DataType::Boolean)
+    );
+
+    let point_array = create_array(
+        &[Some("POINT (0.25 0.25)"), Some("POINT (10 10)"), None],
+        &sedona_type,
+    );
+    let polygon_scalar = create_scalar(Some("POLYGON ((0 0, 1 0, 0 1, 0 0))"), 
&sedona_type);
+    let point_scalar = create_scalar(Some("POINT (0.25 0.25)"), &sedona_type);
+
+    let expected: ArrayRef = arrow_array::create_array!(Boolean, [Some(true), 
Some(false), None]);
+
+    // Array, Scalar -> Array
+    let result = tester
+        .invoke_array_scalar(point_array.clone(), polygon_scalar.clone())
+        .unwrap();
+    assert_eq!(&result, &expected);
+
+    // Scalar, Array -> Array
+    let result = tester
+        .invoke_scalar_array(polygon_scalar.clone(), point_array.clone())
+        .unwrap();
+    assert_eq!(&result, &expected);
+
+    // Scalar, Scalar -> Scalar
+    let result = tester
+        .invoke_scalar_scalar(polygon_scalar, point_scalar)
+        .unwrap();
+    assert_eq!(result, ScalarValue::Boolean(Some(true)));
+
+    // Null scalars -> Null
+    let result = tester
+        .invoke_scalar_scalar(ScalarValue::Null, ScalarValue::Null)
+        .unwrap();
+    assert_eq!(result, ScalarValue::Boolean(None));
+}
+
+#[test]
+fn area() {
+    let udf = s2_udf("st_area");
+    let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY]);
+
+    tester.assert_return_type(DataType::Float64);
+    let result = tester
+        .invoke_scalar("POLYGON ((0 0, 0 1, 1 0, 0 0))")
+        .unwrap();
+    tester.assert_scalar_result_equals(result, 6182489130.907195);
+}
+
+#[test]
+fn centroid() {
+    let udf = s2_udf("st_centroid");
+    let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY]);
+    tester.assert_return_type(WKB_GEOGRAPHY);
+    let result = tester.invoke_scalar("LINESTRING (0 0, 0 1)").unwrap();
+    tester.assert_scalar_result_equals(result, "POINT (0 0.5)");
+}
+
+#[test]
+fn closest_point() {
+    let udf = s2_udf("st_closestpoint");
+    let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
+    tester.assert_return_type(WKB_GEOGRAPHY);
+    let result = tester
+        .invoke_scalar_scalar("LINESTRING (0 0, 0 1)", "POINT (-1 -1)")
+        .unwrap();
+    tester.assert_scalar_result_equals(result, "POINT (0 0)");
+}
+
+#[test]
+fn contains() {
+    let udf = s2_udf("st_contains");
+    let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
+    tester.assert_return_type(DataType::Boolean);
+    let result = tester
+        .invoke_scalar_scalar("POLYGON ((0 0, 0 1, 1 0, 0 0))", "POINT (0.25 
0.25)")
+        .unwrap();
+    tester.assert_scalar_result_equals(result, true);
+}
+
+#[test]
+fn difference() {
+    let udf = s2_udf("st_difference");
+    let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
+    tester.assert_return_type(WKB_GEOGRAPHY);
+    let result = tester
+        .invoke_scalar_scalar("POINT (0 0)", "POINT (0 0)")
+        .unwrap();
+    tester.assert_scalar_result_equals(result, "GEOMETRYCOLLECTION EMPTY");
+}
+
+#[test]
+fn distance() {
+    let udf = s2_udf("st_distance");
+    let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
+    tester.assert_return_type(DataType::Float64);
+    let result = tester
+        .invoke_scalar_scalar("POINT (0 0)", "POINT (0 0)")
+        .unwrap();
+    tester.assert_scalar_result_equals(result, 0.0);
+}
+
+#[test]
+fn equals() {
+    let udf = s2_udf("st_equals");
+    let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
+    tester.assert_return_type(DataType::Boolean);
+    let result1 = tester
+        .invoke_scalar_scalar("POINT (0 0)", "POINT (0 0)")
+        .unwrap();
+    tester.assert_scalar_result_equals(result1, true);
+    let result2 = tester
+        .invoke_scalar_scalar("POINT (0 0)", "POINT (0 1)")
+        .unwrap();
+    tester.assert_scalar_result_equals(result2, false);
+}
+
+#[test]
+fn intersection() {
+    let udf = s2_udf("st_intersection");
+    let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
+    tester.assert_return_type(WKB_GEOGRAPHY);
+    let result = tester
+        .invoke_scalar_scalar("POINT (0 0)", "POINT (0 0)")
+        .unwrap();
+    tester.assert_scalar_result_equals(result, "POINT (0 0)");
+}
+
+#[test]
+fn intersects() {
+    let udf = s2_udf("st_intersects");
+    let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
+    tester.assert_return_type(DataType::Boolean);
+    let result1 = tester
+        .invoke_scalar_scalar("POLYGON ((0 0, 0 1, 1 0, 0 0))", "POINT (0.25 
0.25)")
+        .unwrap();
+    tester.assert_scalar_result_equals(result1, true);
+    let result2 = tester
+        .invoke_scalar_scalar("POLYGON ((0 0, 0 1, 1 0, 0 0))", "POINT (-1 
-1)")
+        .unwrap();
+    tester.assert_scalar_result_equals(result2, false);
+}
+
+#[test]
+fn length() {
+    let udf = s2_udf("st_length");
+    let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY]);
+    tester.assert_return_type(DataType::Float64);
+    let result = tester.invoke_scalar("LINESTRING (0 0, 0 1)").unwrap();
+    tester.assert_scalar_result_equals(result, 111195.10117748393);
+}
+
+#[test]
+fn line_interpolate_point() {
+    let udf = s2_udf("st_lineinterpolatepoint");
+    let tester = ScalarUdfTester::new(
+        udf.into(),
+        vec![WKB_GEOGRAPHY, SedonaType::Arrow(DataType::Float64)],
+    );
+    tester.assert_return_type(WKB_GEOGRAPHY);
+    let result = tester
+        .invoke_scalar_scalar("LINESTRING (0 0, 0 1)", 0.5)
+        .unwrap();
+    tester.assert_scalar_result_equals(result, "POINT (0 0.5)");
+}
+
+#[test]
+fn line_locate_point() {
+    let udf = s2_udf("st_linelocatepoint");
+    let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
+    tester.assert_return_type(DataType::Float64);
+    let result = tester
+        .invoke_scalar_scalar("LINESTRING (0 0, 0 1)", "POINT (0 0.5)")
+        .unwrap();
+    tester.assert_scalar_result_equals(result, 0.5);
+}
+
+#[test]
+fn max_distance() {
+    let udf = s2_udf("st_maxdistance");
+    let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
+    tester.assert_return_type(DataType::Float64);
+    let result = tester
+        .invoke_scalar_scalar("POINT (0 0)", "LINESTRING (0 0, 0 1)")
+        .unwrap();
+    tester.assert_scalar_result_equals(result, 111195.10117748393);
+}
+
+#[test]
+fn perimeter() {
+    let udf = s2_udf("st_perimeter");
+    let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY]);
+    let result = tester
+        .invoke_scalar("POLYGON ((0 0, 0 1, 1 0, 0 0))")
+        .unwrap();
+    tester.assert_scalar_result_equals(result, 379639.8304474758);
+}
+
+#[test]
+fn shortest_line() {
+    let udf = s2_udf("st_shortestline");
+    let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
+    tester.assert_return_type(WKB_GEOGRAPHY);
+    let result = tester
+        .invoke_scalar_scalar("LINESTRING (0 0, 0 1)", "POINT (0 -1)")
+        .unwrap();
+    tester.assert_scalar_result_equals(result, "LINESTRING (0 0, 0 -1)");
+}
+
+#[test]
+fn sym_difference() {
+    let udf = s2_udf("st_symdifference");
+    let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
+    tester.assert_return_type(WKB_GEOGRAPHY);
+    let result = tester
+        .invoke_scalar_scalar("POINT (0 0)", "POINT (0 0)")
+        .unwrap();
+    tester.assert_scalar_result_equals(result, "GEOMETRYCOLLECTION EMPTY");
+}
+
+#[test]
+fn union() {
+    let udf = s2_udf("st_union");
+    let tester = ScalarUdfTester::new(udf.into(), vec![WKB_GEOGRAPHY, 
WKB_GEOGRAPHY]);
+    tester.assert_return_type(WKB_GEOGRAPHY);
+    let result = tester
+        .invoke_scalar_scalar("POINT (0 0)", "POINT (0 0)")
+        .unwrap();
+    tester.assert_scalar_result_equals(result, "POINT (0 0)");
+}
diff --git a/c/sedona-tg/Cargo.toml b/c/sedona-tg/Cargo.toml
index a7ea58b9..f6465cee 100644
--- a/c/sedona-tg/Cargo.toml
+++ b/c/sedona-tg/Cargo.toml
@@ -35,7 +35,7 @@ cc = { version = "1" }
 criterion = { workspace = true }
 rstest = { workspace = true }
 sedona = { workspace = true }
-sedona-testing = { workspace = true }
+sedona-testing = { workspace = true, features = ["criterion"] }
 wkb = { workspace = true }
 geo = { workspace = true }
 
diff --git a/rust/sedona/src/context.rs b/rust/sedona/src/context.rs
index 92940bd7..ecf5362b 100644
--- a/rust/sedona/src/context.rs
+++ b/rust/sedona/src/context.rs
@@ -223,7 +223,7 @@ impl SedonaContext {
     fn register_s2geography(&mut self) -> Result<()> {
         use sedona_proj::sd_order_lnglat;
 
-        
self.register_scalar_kernels(sedona_s2geography::register::scalar_kernels().into_iter())?;
+        
self.register_scalar_kernels(sedona_s2geography::register::scalar_kernels()?.into_iter())?;
 
         let sd_order_kernel = sd_order_lnglat::OrderLngLat::new(
             sedona_s2geography::s2geography::s2_cell_id_from_lnglat,

Reply via email to