diff --git a/docs/reference/sql/st_geomcollfromtext.qmd b/docs/reference/sql/st_geomcollfromtext.qmd new file mode 100644 index 000000000..b4b4900c8 --- /dev/null +++ b/docs/reference/sql/st_geomcollfromtext.qmd @@ -0,0 +1,48 @@ +--- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +title: ST_GeomCollFromText +description: Constructs a GeometryCollection from Well-Known Text (WKT), erroring if the input is not a GeometryCollection. +kernels: + - returns: geometry + args: + - name: wkt + type: string + - returns: geometry + args: + - name: wkt + type: string + - name: srid + type: crs +--- + +## Description + +Parses a WKT string and returns a geometry. Raises an error if the WKT does not represent a GeometryCollection. + +## Examples + +```sql +SELECT ST_GeomCollFromText('GEOMETRYCOLLECTION (POINT (0 0), LINESTRING (0 0, 1 1))'); +``` + +With an SRID or CRS: + +```sql +SELECT ST_GeomCollFromText('GEOMETRYCOLLECTION (POINT (0 0), LINESTRING (0 0, 1 1))', 4326); +``` diff --git a/docs/reference/sql/st_linefromtext.qmd b/docs/reference/sql/st_linefromtext.qmd new file mode 100644 index 000000000..7a745dc00 --- /dev/null +++ b/docs/reference/sql/st_linefromtext.qmd @@ -0,0 +1,50 @@ +--- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +title: ST_LineFromText +description: Constructs a LineString from Well-Known Text (WKT), erroring if the input is not a LineString. +kernels: + - returns: geometry + args: + - name: wkt + type: string + - returns: geometry + args: + - name: wkt + type: string + - name: srid + type: crs +--- + +## Description + +Parses a WKT string and returns a geometry. Raises an error if the WKT does not represent a LineString. + +Aliases: `ST_LineStringFromText`. + +## Examples + +```sql +SELECT ST_LineFromText('LINESTRING (0 0, 1 1, 2 2)'); +``` + +With an SRID or CRS: + +```sql +SELECT ST_LineFromText('LINESTRING (0 0, 1 1, 2 2)', 4326); +``` diff --git a/docs/reference/sql/st_mlinefromtext.qmd b/docs/reference/sql/st_mlinefromtext.qmd new file mode 100644 index 000000000..4661c1f05 --- /dev/null +++ b/docs/reference/sql/st_mlinefromtext.qmd @@ -0,0 +1,48 @@ +--- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +title: ST_MLineFromText +description: Constructs a MultiLineString from Well-Known Text (WKT), erroring if the input is not a MultiLineString. +kernels: + - returns: geometry + args: + - name: wkt + type: string + - returns: geometry + args: + - name: wkt + type: string + - name: srid + type: crs +--- + +## Description + +Parses a WKT string and returns a geometry. Raises an error if the WKT does not represent a MultiLineString. + +## Examples + +```sql +SELECT ST_MLineFromText('MULTILINESTRING ((0 0, 1 1), (2 2, 3 3))'); +``` + +With an SRID or CRS: + +```sql +SELECT ST_MLineFromText('MULTILINESTRING ((0 0, 1 1), (2 2, 3 3))', 4326); +``` diff --git a/docs/reference/sql/st_mpointfromtext.qmd b/docs/reference/sql/st_mpointfromtext.qmd new file mode 100644 index 000000000..1773354e6 --- /dev/null +++ b/docs/reference/sql/st_mpointfromtext.qmd @@ -0,0 +1,48 @@ +--- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +title: ST_MPointFromText +description: Constructs a MultiPoint from Well-Known Text (WKT), erroring if the input is not a MultiPoint. +kernels: + - returns: geometry + args: + - name: wkt + type: string + - returns: geometry + args: + - name: wkt + type: string + - name: srid + type: crs +--- + +## Description + +Parses a WKT string and returns a geometry. Raises an error if the WKT does not represent a MultiPoint. + +## Examples + +```sql +SELECT ST_MPointFromText('MULTIPOINT ((0 0), (1 1), (2 2))'); +``` + +With an SRID or CRS: + +```sql +SELECT ST_MPointFromText('MULTIPOINT ((0 0), (1 1), (2 2))', 4326); +``` diff --git a/docs/reference/sql/st_mpolyfromtext.qmd b/docs/reference/sql/st_mpolyfromtext.qmd new file mode 100644 index 000000000..03057a0a4 --- /dev/null +++ b/docs/reference/sql/st_mpolyfromtext.qmd @@ -0,0 +1,48 @@ +--- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +title: ST_MPolyFromText +description: Constructs a MultiPolygon from Well-Known Text (WKT), erroring if the input is not a MultiPolygon. +kernels: + - returns: geometry + args: + - name: wkt + type: string + - returns: geometry + args: + - name: wkt + type: string + - name: srid + type: crs +--- + +## Description + +Parses a WKT string and returns a geometry. Raises an error if the WKT does not represent a MultiPolygon. + +## Examples + +```sql +SELECT ST_MPolyFromText('MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((2 2, 3 2, 3 3, 2 2)))'); +``` + +With an SRID or CRS: + +```sql +SELECT ST_MPolyFromText('MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((2 2, 3 2, 3 3, 2 2)))', 4326); +``` diff --git a/docs/reference/sql/st_pointfromtext.qmd b/docs/reference/sql/st_pointfromtext.qmd new file mode 100644 index 000000000..937816676 --- /dev/null +++ b/docs/reference/sql/st_pointfromtext.qmd @@ -0,0 +1,48 @@ +--- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +title: ST_PointFromText +description: Constructs a Point from Well-Known Text (WKT), erroring if the input is not a Point. +kernels: + - returns: geometry + args: + - name: wkt + type: string + - returns: geometry + args: + - name: wkt + type: string + - name: srid + type: crs +--- + +## Description + +Parses a WKT string and returns a geometry. Raises an error if the WKT does not represent a Point. + +## Examples + +```sql +SELECT ST_PointFromText('POINT (30 10)'); +``` + +With an SRID or CRS: + +```sql +SELECT ST_PointFromText('POINT (30 10)', 4326); +``` diff --git a/docs/reference/sql/st_polygonfromtext.qmd b/docs/reference/sql/st_polygonfromtext.qmd new file mode 100644 index 000000000..3d329a74b --- /dev/null +++ b/docs/reference/sql/st_polygonfromtext.qmd @@ -0,0 +1,48 @@ +--- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +title: ST_PolygonFromText +description: Constructs a Polygon from Well-Known Text (WKT), erroring if the input is not a Polygon. +kernels: + - returns: geometry + args: + - name: wkt + type: string + - returns: geometry + args: + - name: wkt + type: string + - name: srid + type: crs +--- + +## Description + +Parses a WKT string and returns a geometry. Raises an error if the WKT does not represent a Polygon. + +## Examples + +```sql +SELECT ST_PolygonFromText('POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0))'); +``` + +With an SRID or CRS: + +```sql +SELECT ST_PolygonFromText('POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0))', 4326); +``` diff --git a/python/sedonadb/tests/functions/test_functions.py b/python/sedonadb/tests/functions/test_functions.py index 95c36c0ae..cc3d9a6ad 100644 --- a/python/sedonadb/tests/functions/test_functions.py +++ b/python/sedonadb/tests/functions/test_functions.py @@ -1697,6 +1697,115 @@ def test_st_geomfromewkt(eng, ewkt, expected, expected_srid): ) +# --- ST_XxxFromText typed constructors --- + +# (fn_name, matching_wkt, wrong_wkt) +_TYPED_CONSTRUCTOR_CASES = [ + ("ST_PointFromText", "POINT (1 2)", "LINESTRING (0 0, 1 1)"), + ("ST_LineFromText", "LINESTRING (0 0, 1 1)", "POINT (1 2)"), + ("ST_PolygonFromText", "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))", "POINT (1 2)"), + # geoarrow-c renders MULTIPOINT without per-point parens + ("ST_MPointFromText", "MULTIPOINT (0 0, 1 1)", "POINT (1 2)"), + ("ST_MLineFromText", "MULTILINESTRING ((0 0, 1 1))", "POINT (1 2)"), + ("ST_MPolyFromText", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 1, 0 0)))", "POINT (1 2)"), + ( + "ST_GeomCollFromText", + "GEOMETRYCOLLECTION (POINT (0 0))", + "POINT (1 2)", + ), +] + + +@pytest.mark.parametrize("eng", [SedonaDB, PostGIS]) +@pytest.mark.parametrize(("fn_name", "wkt", "_wrong"), _TYPED_CONSTRUCTOR_CASES) +def test_typed_geom_constructors_accept_correct_type(eng, fn_name, wkt, _wrong): + eng = eng.create_or_skip() + eng.assert_query_result(f"SELECT {fn_name}('{wkt}')", wkt) + + +@pytest.mark.parametrize("eng", [SedonaDB]) +@pytest.mark.parametrize( + ("fn_name", "_matching", "wrong_wkt"), _TYPED_CONSTRUCTOR_CASES +) +def test_typed_geom_constructors_reject_wrong_type(eng, fn_name, _matching, wrong_wkt): + # PostGIS typed constructors are aliases for ST_GeomFromText and do not validate type + eng = eng.create_or_skip() + with pytest.raises(Exception): + eng.assert_query_result(f"SELECT {fn_name}('{wrong_wkt}')", None) + + +@pytest.mark.parametrize("eng", [SedonaDB, PostGIS]) +@pytest.mark.parametrize(("fn_name", "wkt", "_wrong"), _TYPED_CONSTRUCTOR_CASES) +def test_typed_geom_constructors_accept_srid(eng, fn_name, wkt, _wrong): + eng = eng.create_or_skip() + eng.assert_query_result(f"SELECT ST_SRID({fn_name}('{wkt}', 4326))", 4326) + + +@pytest.mark.parametrize("eng", [SedonaDB, PostGIS]) +@pytest.mark.parametrize( + ("fn_name", "empty_wkt"), + [ + ("ST_PointFromText", "POINT EMPTY"), + ("ST_LineFromText", "LINESTRING EMPTY"), + ("ST_PolygonFromText", "POLYGON EMPTY"), + ("ST_MPointFromText", "MULTIPOINT EMPTY"), + ("ST_MLineFromText", "MULTILINESTRING EMPTY"), + ("ST_MPolyFromText", "MULTIPOLYGON EMPTY"), + ("ST_GeomCollFromText", "GEOMETRYCOLLECTION EMPTY"), + ], +) +def test_typed_geom_constructors_accept_matching_empty(eng, fn_name, empty_wkt): + """Each constructor accepts its own EMPTY type (correct type, empty geometry).""" + eng = eng.create_or_skip() + # geoarrow-c renders POINT EMPTY as POINT (nan nan) + expected = "POINT (nan nan)" if empty_wkt == "POINT EMPTY" else empty_wkt + eng.assert_query_result(f"SELECT {fn_name}('{empty_wkt}')", expected) + + +@pytest.mark.parametrize("eng", [SedonaDB, PostGIS]) +@pytest.mark.parametrize( + "fn_name", + [fn for fn, _, _ in _TYPED_CONSTRUCTOR_CASES], +) +def test_typed_geom_constructors_null_input(eng, fn_name): + eng = eng.create_or_skip() + eng.assert_query_result(f"SELECT {fn_name}(NULL)", None) + + +@pytest.mark.parametrize("eng", [SedonaDB]) +def test_st_linestringfromtext_alias(eng): + # PostGIS does not have ST_LineStringFromText + eng = eng.create_or_skip() + eng.assert_query_result( + "SELECT ST_LineStringFromText('LINESTRING (0 0, 1 1)')", "LINESTRING (0 0, 1 1)" + ) + + +@pytest.mark.parametrize("eng", [SedonaDB]) +@pytest.mark.parametrize( + ("fn_name", "wkt", "wrong_empty"), + [ + ("ST_PointFromText", "POINT (1 2)", "LINESTRING EMPTY"), + ("ST_LineFromText", "LINESTRING (0 0, 1 1)", "POINT EMPTY"), + ("ST_PolygonFromText", "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))", "POINT EMPTY"), + ("ST_MPointFromText", "MULTIPOINT ((0 0))", "LINESTRING EMPTY"), + ("ST_MLineFromText", "MULTILINESTRING ((0 0, 1 1))", "POINT EMPTY"), + ( + "ST_MPolyFromText", + "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 1, 0 0)))", + "POINT EMPTY", + ), + ("ST_GeomCollFromText", "GEOMETRYCOLLECTION (POINT (0 0))", "LINESTRING EMPTY"), + ], +) +def test_typed_geom_constructors_reject_wrong_empty(eng, fn_name, wkt, wrong_empty): + """EMPTY of wrong type is rejected just like non-empty wrong type.""" + # PostGIS typed constructors are aliases for ST_GeomFromText and do not validate type + eng = eng.create_or_skip() + with pytest.raises(Exception): + eng.assert_query_result(f"SELECT {fn_name}('{wrong_empty}')", None) + + @pytest.mark.parametrize("eng", [SedonaDB, PostGIS]) @pytest.mark.parametrize( ("geom"), diff --git a/rust/sedona-functions/src/register.rs b/rust/sedona-functions/src/register.rs index 1ffee6e96..f45cc0c7b 100644 --- a/rust/sedona-functions/src/register.rs +++ b/rust/sedona-functions/src/register.rs @@ -61,8 +61,15 @@ pub fn default_function_set() -> FunctionSet { crate::st_geomfromwkb::st_geomfromwkb_udf, crate::st_geomfromwkb::st_geomfromwkbunchecked_udf, crate::st_geomfromwkt::st_geogfromwkt_udf, + crate::st_geomfromwkt::st_geomcollfromtext_udf, crate::st_geomfromwkt::st_geomfromewkt_udf, crate::st_geomfromwkt::st_geomfromwkt_udf, + crate::st_geomfromwkt::st_linefromtext_udf, + crate::st_geomfromwkt::st_mlinefromtext_udf, + crate::st_geomfromwkt::st_mpointfromtext_udf, + crate::st_geomfromwkt::st_mpolyfromtext_udf, + crate::st_geomfromwkt::st_pointfromtext_udf, + crate::st_geomfromwkt::st_polygonfromtext_udf, crate::st_haszm::st_hasm_udf, crate::st_haszm::st_hasz_udf, crate::st_interiorringn::st_interiorringn_udf, diff --git a/rust/sedona-functions/src/st_geomfromwkt.rs b/rust/sedona-functions/src/st_geomfromwkt.rs index ca3d1539a..161d63773 100644 --- a/rust/sedona-functions/src/st_geomfromwkt.rs +++ b/rust/sedona-functions/src/st_geomfromwkt.rs @@ -35,6 +35,8 @@ use wkb::writer::{write_geometry, WriteOptions}; use wkb::Endianness; use wkt::Wkt; +use sedona_geometry::types::GeometryTypeId; + use crate::executor::WkbExecutor; use crate::st_setsrid::SRIDifiedKernel; @@ -45,6 +47,7 @@ use crate::st_setsrid::SRIDifiedKernel; pub fn st_geomfromwkt_udf() -> SedonaScalarUDF { let kernel = Arc::new(STGeoFromWKT { out_type: WKB_GEOMETRY, + expected_geom_type: None, }); let sridified_kernel = Arc::new(SRIDifiedKernel::new(kernel.clone())); @@ -67,12 +70,14 @@ pub fn st_geogfromwkt_udf() -> SedonaScalarUDF { // Inner kernel for SRIDified has no CRS - the SRID argument sets it let inner_kernel = Arc::new(STGeoFromWKT { out_type: WKB_GEOGRAPHY, + expected_geom_type: None, }); let sridified_kernel = Arc::new(SRIDifiedKernel::new(inner_kernel)); // Standalone kernel returns WGS84 CRS by default let standalone_kernel = Arc::new(STGeoFromWKT { out_type: WKB_GEOGRAPHY_WGS84.clone(), + expected_geom_type: None, }); let udf = SedonaScalarUDF::new( @@ -83,9 +88,48 @@ pub fn st_geogfromwkt_udf() -> SedonaScalarUDF { udf.with_aliases(vec!["st_geogfromtext".to_string()]) } +fn make_typed_geom_udf(name: &'static str, expected: GeometryTypeId) -> SedonaScalarUDF { + let kernel = Arc::new(STGeoFromWKT { + out_type: WKB_GEOMETRY, + expected_geom_type: Some(expected), + }); + let sridified_kernel = Arc::new(SRIDifiedKernel::new(kernel.clone())); + SedonaScalarUDF::new(name, vec![sridified_kernel, kernel], Volatility::Immutable) +} + +pub fn st_geomcollfromtext_udf() -> SedonaScalarUDF { + make_typed_geom_udf("st_geomcollfromtext", GeometryTypeId::GeometryCollection) +} + +pub fn st_linefromtext_udf() -> SedonaScalarUDF { + make_typed_geom_udf("st_linefromtext", GeometryTypeId::LineString) + .with_aliases(vec!["st_linestringfromtext".to_string()]) +} + +pub fn st_mlinefromtext_udf() -> SedonaScalarUDF { + make_typed_geom_udf("st_mlinefromtext", GeometryTypeId::MultiLineString) +} + +pub fn st_mpointfromtext_udf() -> SedonaScalarUDF { + make_typed_geom_udf("st_mpointfromtext", GeometryTypeId::MultiPoint) +} + +pub fn st_mpolyfromtext_udf() -> SedonaScalarUDF { + make_typed_geom_udf("st_mpolyfromtext", GeometryTypeId::MultiPolygon) +} + +pub fn st_pointfromtext_udf() -> SedonaScalarUDF { + make_typed_geom_udf("st_pointfromtext", GeometryTypeId::Point) +} + +pub fn st_polygonfromtext_udf() -> SedonaScalarUDF { + make_typed_geom_udf("st_polygonfromtext", GeometryTypeId::Polygon) +} + #[derive(Debug)] struct STGeoFromWKT { out_type: SedonaType, + expected_geom_type: Option, } impl SedonaScalarKernel for STGeoFromWKT { @@ -111,7 +155,7 @@ impl SedonaScalarKernel for STGeoFromWKT { for item in as_string_view_array(&arg_array)? { if let Some(wkt_bytes) = item { - invoke_scalar(wkt_bytes, &mut builder)?; + invoke_scalar(wkt_bytes, self.expected_geom_type, &mut builder)?; builder.append_value(vec![]); } else { builder.append_null(); @@ -123,10 +167,25 @@ impl SedonaScalarKernel for STGeoFromWKT { } } -fn invoke_scalar(wkt_bytes: &str, builder: &mut BinaryBuilder) -> Result<()> { +fn invoke_scalar( + wkt_bytes: &str, + expected_geom_type: Option, + builder: &mut BinaryBuilder, +) -> Result<()> { let geometry: Wkt = Wkt::from_str(wkt_bytes).map_err(|err| exec_datafusion_err!("WKT parse error: {err}"))?; + if let Some(expected) = expected_geom_type { + let actual_id = wkt_geometry_type_id(&geometry); + if actual_id != expected { + return Err(exec_datafusion_err!( + "Expected {} but got {}", + expected.geojson_id(), + actual_id.geojson_id() + )); + } + } + write_geometry( builder, &geometry, @@ -137,6 +196,18 @@ fn invoke_scalar(wkt_bytes: &str, builder: &mut BinaryBuilder) -> Result<()> { .map_err(|err| sedona_internal_datafusion_err!("WKB write error: {err}")) } +fn wkt_geometry_type_id(geom: &Wkt) -> GeometryTypeId { + match geom { + Wkt::Point(_) => GeometryTypeId::Point, + Wkt::LineString(_) => GeometryTypeId::LineString, + Wkt::Polygon(_) => GeometryTypeId::Polygon, + Wkt::MultiPoint(_) => GeometryTypeId::MultiPoint, + Wkt::MultiLineString(_) => GeometryTypeId::MultiLineString, + Wkt::MultiPolygon(_) => GeometryTypeId::MultiPolygon, + Wkt::GeometryCollection(_) => GeometryTypeId::GeometryCollection, + } +} + /// ST_GeomFromEWKT() UDF implementation /// /// An implementation of EWKT reading using GeoRust's wkt crate. @@ -242,7 +313,7 @@ fn invoke_scalar_with_srid( geom_builder: &mut BinaryBuilder, srid_builder: &mut StringViewBuilder, ) -> Result<()> { - invoke_scalar(wkt_bytes, geom_builder)?; + invoke_scalar(wkt_bytes, None, geom_builder)?; srid_builder.append_option(srid); Ok(()) } @@ -468,5 +539,77 @@ mod tests { let udf: ScalarUDF = st_geogfromwkt_udf().into(); assert!(udf.aliases().contains(&"st_geogfromtext".to_string())); + + let udf: ScalarUDF = st_linefromtext_udf().into(); + assert!(udf.aliases().contains(&"st_linestringfromtext".to_string())); + } + + #[rstest] + #[case("POINT (1 2)", st_pointfromtext_udf())] + #[case("LINESTRING (0 0, 1 1)", st_linefromtext_udf())] + #[case("POLYGON ((0 0, 1 0, 1 1, 0 0))", st_polygonfromtext_udf())] + #[case("MULTIPOINT ((0 0), (1 1))", st_mpointfromtext_udf())] + #[case("MULTILINESTRING ((0 0, 1 1), (2 2, 3 3))", st_mlinefromtext_udf())] + #[case("MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)))", st_mpolyfromtext_udf())] + #[case("GEOMETRYCOLLECTION (POINT (0 0))", st_geomcollfromtext_udf())] + fn typed_constructors_accept_correct_type(#[case] wkt: &str, #[case] udf: SedonaScalarUDF) { + let tester = ScalarUdfTester::new(udf.into(), vec![SedonaType::Arrow(DataType::Utf8)]); + assert!(tester.invoke_scalar(wkt).is_ok(), "expected Ok for {wkt}"); + } + + #[rstest] + #[case(st_pointfromtext_udf(), "LINESTRING (0 0, 1 1)", "Point")] + #[case(st_linefromtext_udf(), "POINT (1 2)", "LineString")] + #[case(st_polygonfromtext_udf(), "POINT (1 2)", "Polygon")] + #[case(st_mpointfromtext_udf(), "POINT (1 2)", "MultiPoint")] + #[case(st_mlinefromtext_udf(), "POINT (1 2)", "MultiLineString")] + #[case(st_mpolyfromtext_udf(), "POINT (1 2)", "MultiPolygon")] + #[case( + st_geomcollfromtext_udf(), + "LINESTRING (0 0, 1 1)", + "GeometryCollection" + )] + fn typed_constructors_reject_wrong_type( + #[case] udf: SedonaScalarUDF, + #[case] wrong_wkt: &str, + #[case] expected_type_name: &str, + ) { + let tester = ScalarUdfTester::new(udf.into(), vec![SedonaType::Arrow(DataType::Utf8)]); + let err = tester.invoke_scalar(wrong_wkt).unwrap_err(); + assert!( + err.message() + .contains(&format!("Expected {expected_type_name}")), + "unexpected error: {err}" + ); + } + + #[rstest] + #[case("POINT (1 2)", st_pointfromtext_udf())] + #[case("LINESTRING (0 0, 1 1)", st_linefromtext_udf())] + #[case("POLYGON ((0 0, 1 0, 1 1, 0 0))", st_polygonfromtext_udf())] + #[case("MULTIPOINT ((0 0), (1 1))", st_mpointfromtext_udf())] + #[case("MULTILINESTRING ((0 0, 1 1), (2 2, 3 3))", st_mlinefromtext_udf())] + #[case("MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)))", st_mpolyfromtext_udf())] + #[case("GEOMETRYCOLLECTION (POINT (0 0))", st_geomcollfromtext_udf())] + fn typed_constructors_accept_srid(#[case] wkt: &str, #[case] udf: SedonaScalarUDF) { + let tester = ScalarUdfTester::new( + udf.into(), + vec![ + SedonaType::Arrow(DataType::Utf8), + SedonaType::Arrow(DataType::UInt32), + ], + ); + let return_type = tester + .return_type_with_scalar_scalar(Some(wkt), Some(4326u32)) + .unwrap(); + assert_eq!( + return_type, + SedonaType::Wkb(Edges::Planar, lnglat()), + "wrong return type for {wkt}" + ); + assert_scalar_equal_wkb_geometry( + &tester.invoke_scalar_scalar(wkt, 4326u32).unwrap(), + Some(wkt), + ); } }