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,