You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sedona.apache.org by pa...@apache.org on 2026/06/03 02:06:38 UTC
(sedona-db) branch main updated: fix(c/sedona-proj): Allow bound parameter for CRS argument of ST_Transform (#904)
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 6ec3d02b7 fix(c/sedona-proj): Allow bound parameter for CRS argument of ST_Transform (#904)
6ec3d02b7 is described below
commit 6ec3d02b76ad73947ba86e3bd8c62b5f308d2beb
Author: Dewey Dunnington <de...@dunnington.ca>
AuthorDate: Tue Jun 2 21:06:33 2026 -0500
fix(c/sedona-proj): Allow bound parameter for CRS argument of ST_Transform (#904)
---
c/sedona-proj/src/st_transform.rs | 54 +++++++++++++++++++++-
python/sedonadb/tests/functions/test_transforms.py | 39 ++++++++++++++++
2 files changed, 92 insertions(+), 1 deletion(-)
diff --git a/c/sedona-proj/src/st_transform.rs b/c/sedona-proj/src/st_transform.rs
index f6fa9b096..fb219ec2e 100644
--- a/c/sedona-proj/src/st_transform.rs
+++ b/c/sedona-proj/src/st_transform.rs
@@ -59,6 +59,14 @@ impl SedonaScalarKernel for STTransform {
if inputs.len() == 2 {
match (inputs[0], inputs[1]) {
+ // Null output CRS is usually a sentinel for a parameter that hasn't been bound yet
+ // For the purposes of computing an output type, we pretend it is a scalar string.
+ (ArgInput::Geo(edges, _), ArgInput::Null) => {
+ Ok(Some(output_type_from_scalar_crs_value(
+ edges,
+ &ScalarValue::Utf8(Some("0".to_string())),
+ )?))
+ }
// ScalarCrs output always returns a Wkb output type with concrete Crs
(ArgInput::Geo(edges, _), ArgInput::ScalarCrs(scalar_value))
| (ArgInput::ItemCrs(edges), ArgInput::ScalarCrs(scalar_value)) => Ok(Some(
@@ -74,6 +82,14 @@ impl SedonaScalarKernel for STTransform {
}
} else if inputs.len() == 3 {
match (inputs[0], inputs[1], inputs[2]) {
+ // Null output CRS is usually a sentinel for a parameter that hasn't been bound yet
+ // For the purposes of computing an output type, we pretend it is a scalar string.
+ (ArgInput::Geo(edges, _), _, ArgInput::Null) => {
+ Ok(Some(output_type_from_scalar_crs_value(
+ edges,
+ &ScalarValue::Utf8(Some("0".to_string())),
+ )?))
+ }
// ScalarCrs output always returns a Wkb output type with concrete Crs
(
ArgInput::Geo(edges, _),
@@ -317,12 +333,18 @@ enum ArgInput<'a> {
/// Array CRS input. Supported for second and third arguments. When present
/// as the last (to) argument, this forces Item CRS output.
ArrayCrs,
+ /// A literal null
+ Null,
/// Sentinel for anything else
Unsupported,
}
impl<'a> ArgInput<'a> {
fn from_return_type_arg(arg_type: &'a SedonaType, scalar_arg: Option<&'a ScalarValue>) -> Self {
+ if ArgMatcher::is_null().match_type(arg_type) {
+ return Self::Null;
+ }
+
if let Ok((SedonaType::Wkb(edges, _) | SedonaType::WkbView(edges, _), maybe_crs_type)) =
parse_item_crs_arg_type(arg_type)
{
@@ -783,7 +805,7 @@ mod tests {
}
#[test]
- fn test_invoke_null_crs_to() {
+ fn test_invoke_null_utf8_crs_to() {
let udf = SedonaScalarUDF::from_impl("st_transform", st_transform_impl());
let tester = ScalarUdfTester::new(
udf.clone().into(),
@@ -825,6 +847,36 @@ mod tests {
assert_eq!(result, create_scalar(None, &WKB_GEOMETRY));
}
+ #[test]
+ fn test_invoke_null_crs_to() {
+ let udf = SedonaScalarUDF::from_impl("st_transform", st_transform_impl());
+ let tester = ScalarUdfTester::new(
+ udf.clone().into(),
+ vec![WKB_GEOMETRY, SedonaType::Arrow(DataType::Null)],
+ );
+
+ // A null scalar CRS should generate WKB_GEOMETRY output with a type
+ // level CRS that is unset; however, all the output will be null.
+ let result = tester
+ .invoke_scalar_scalar("POINT (0 1)", ScalarValue::Null)
+ .unwrap();
+ assert_eq!(result, create_scalar(None, &WKB_GEOMETRY));
+
+ // This should also work for the three arg variant
+ let tester = ScalarUdfTester::new(
+ udf.clone().into(),
+ vec![
+ WKB_GEOMETRY,
+ SedonaType::Arrow(DataType::Null),
+ SedonaType::Arrow(DataType::Null),
+ ],
+ );
+ let result = tester
+ .invoke_scalar_scalar_scalar("POINT (0 1)", ScalarValue::Null, ScalarValue::Null)
+ .unwrap();
+ assert_eq!(result, create_scalar(None, &WKB_GEOMETRY));
+ }
+
#[test]
fn test_invoke_unset_crs_to() {
let udf = SedonaScalarUDF::from_impl("st_transform", st_transform_impl());
diff --git a/python/sedonadb/tests/functions/test_transforms.py b/python/sedonadb/tests/functions/test_transforms.py
index 65e0631b1..652b5c0a3 100644
--- a/python/sedonadb/tests/functions/test_transforms.py
+++ b/python/sedonadb/tests/functions/test_transforms.py
@@ -16,6 +16,7 @@
# under the License.
import geopandas
+import geopandas.testing
import pyproj
import pytest
from sedonadb.testing import PostGIS, SedonaDB, geom_or_null, val_or_null
@@ -88,6 +89,44 @@ def test_st_srid(eng, geom, srid, expected_srid):
)
+def test_st_transform_bind_crs(con):
+ # Check the two argument version
+ df = con.sql(
+ "SELECT ST_Transform(ST_Point(0, 1, 4326), $1) AS geom", params=("EPSG:3857",)
+ ).to_pandas()
+ expected = con.sql(
+ "SELECT ST_Transform(ST_Point(0, 1, 4326), 'EPSG:3857') AS geom"
+ ).to_pandas()
+ geopandas.testing.assert_geodataframe_equal(df, expected)
+
+ # Check the three argument version
+ df = con.sql(
+ "SELECT ST_Transform(ST_Point(0, 1), 4326, $1) AS geom", params=("EPSG:3857",)
+ ).to_pandas()
+ expected = con.sql(
+ "SELECT ST_Transform(ST_Point(0, 1), 4326, 'EPSG:3857') AS geom"
+ ).to_pandas()
+ geopandas.testing.assert_geodataframe_equal(df, expected)
+
+
+def test_st_set_crs_bind_crs(con):
+ df = con.sql(
+ "SELECT ST_SetCrs(ST_Point(0, 1), $1) AS geom", params=("EPSG:3857",)
+ ).to_pandas()
+ expected = con.sql(
+ "SELECT ST_SetCrs(ST_Point(0, 1), 'EPSG:3857') AS geom"
+ ).to_pandas()
+ geopandas.testing.assert_geodataframe_equal(df, expected)
+
+
+def test_st_set_srid_bind_srid(con):
+ df = con.sql(
+ "SELECT ST_SetSRID(ST_Point(0, 1), $1) AS geom", params=(3857,)
+ ).to_pandas()
+ expected = con.sql("SELECT ST_SetSRID(ST_Point(0, 1), 3857) AS geom").to_pandas()
+ geopandas.testing.assert_geodataframe_equal(df, expected)
+
+
# PostGIS does not have an API ST_SetCrs, ST_Crs
@pytest.mark.parametrize("eng", [SedonaDB])
@pytest.mark.parametrize(