paleolimbot commented on code in PR #363: URL: https://github.com/apache/sedona-db/pull/363#discussion_r2561507541
########## rust/sedona-functions/src/st_reverse.rs: ########## @@ -0,0 +1,355 @@ +// 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::io::Write; +use std::sync::Arc; + +use arrow_array::builder::BinaryBuilder; +use datafusion_common::{DataFusionError, Result}; +use datafusion_expr::ColumnarValue; +use datafusion_expr::{scalar_doc_sections::DOC_SECTION_OTHER, Documentation, Volatility}; +use geo_traits::{ + CoordTrait, Dimensions, GeometryCollectionTrait, GeometryTrait, LineStringTrait, + MultiLineStringTrait, MultiPointTrait, MultiPolygonTrait, PointTrait, PolygonTrait, +}; +use sedona_expr::scalar_udf::{SedonaScalarKernel, SedonaScalarUDF}; +use sedona_geometry::wkb_factory::{ + write_wkb_coord, write_wkb_empty_point, write_wkb_geometrycollection_header, + write_wkb_linestring_header, write_wkb_multilinestring_header, write_wkb_multipoint_header, + write_wkb_multipolygon_header, write_wkb_point_header, write_wkb_polygon_header, + write_wkb_polygon_ring_header, WKB_MIN_PROBABLE_BYTES, +}; +use sedona_schema::{ + datatypes::{SedonaType, WKB_GEOMETRY}, + matchers::ArgMatcher, +}; + +use crate::executor::WkbExecutor; + +/// ST_Reverse() scalar UDF +/// +/// Native implementation to reverse the vertices in a geometry +pub fn st_reverse_udf() -> SedonaScalarUDF { + SedonaScalarUDF::new( + "st_reverse", + vec![Arc::new(STReverse)], + Volatility::Immutable, + Some(st_reverse_doc()), + ) +} + +fn st_reverse_doc() -> Documentation { + Documentation::builder( + DOC_SECTION_OTHER, + "Can be used on any geometry and reverses the order of the vertices.", + "ST_Reverse (geom: Geometry)", + ) + .with_argument("geom", "geometry: Input geometry") + .with_sql_example("SELECT ST_AsText(ST_Reverse('POLYGON ((2 2, 2 3, 3 3, 3 2, 2 2))'))") + .build() +} + +#[derive(Debug)] +struct STReverse; + +impl SedonaScalarKernel for STReverse { + fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> { + let matcher = ArgMatcher::new(vec![ArgMatcher::is_geometry()], WKB_GEOMETRY); + + matcher.match_args(args) + } + + fn invoke_batch( + &self, + arg_types: &[SedonaType], + args: &[ColumnarValue], + ) -> Result<ColumnarValue> { + let executor = WkbExecutor::new(arg_types, args); + let mut builder = BinaryBuilder::with_capacity( + executor.num_iterations(), + WKB_MIN_PROBABLE_BYTES * executor.num_iterations(), + ); + + executor.execute_wkb_void(|maybe_wkb| { + match maybe_wkb { + Some(wkb) => { + invoke_scalar(&wkb, &mut builder)?; + builder.append_value([]); + } + _ => builder.append_null(), + } + Ok(()) + })?; + + executor.finish(Arc::new(builder.finish())) + } +} + +fn invoke_scalar(geom: &impl GeometryTrait<T = f64>, writer: &mut impl Write) -> Result<()> { + let dims = geom.dim(); + match geom.as_type() { + geo_traits::GeometryType::Point(pt) => { + if pt.coord().is_some() { + write_wkb_point_header(writer, dims) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + write_coord(writer, pt.coord().unwrap())?; + } else { + write_wkb_empty_point(writer, dims) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + } + } + + geo_traits::GeometryType::MultiPoint(multi_point) => { + write_wkb_multipoint_header(writer, dims, multi_point.points().count()) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + for pt in multi_point.points() { + invoke_scalar(&pt, writer)?; + } + } + + geo_traits::GeometryType::LineString(ls) => { + let coords: Vec<_> = ls.coords().collect(); + write_wkb_linestring_header(writer, dims, coords.len()) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + for coord in coords.into_iter().rev() { + write_coord(writer, coord)?; + } + } + + geo_traits::GeometryType::Polygon(pgn) => { + let num_rings = pgn.interiors().count() + pgn.exterior().is_some() as usize; + write_wkb_polygon_header(writer, dims, num_rings) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + + if let Some(exterior) = pgn.exterior() { + write_reversed_ring(writer, exterior)?; + } + + for interior in pgn.interiors() { + write_reversed_ring(writer, interior)?; + } + } + + geo_traits::GeometryType::MultiLineString(mls) => { + write_wkb_multilinestring_header(writer, dims, mls.line_strings().count()) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + for ls in mls.line_strings() { + invoke_scalar(&ls, writer)?; + } + } + + geo_traits::GeometryType::MultiPolygon(mpgn) => { + write_wkb_multipolygon_header(writer, dims, mpgn.polygons().count()) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + for pgn in mpgn.polygons() { + invoke_scalar(&pgn, writer)?; + } + } + + geo_traits::GeometryType::GeometryCollection(gcn) => { + write_wkb_geometrycollection_header(writer, dims, gcn.geometries().count()) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + for geom in gcn.geometries() { + invoke_scalar(&geom, writer)?; + } + } + + _ => { + return Err(DataFusionError::Execution( + "Unsupported geometry type for reversal operation".to_string(), + )); + } + } + Ok(()) +} + +fn write_reversed_ring(writer: &mut impl Write, ring: impl LineStringTrait<T = f64>) -> Result<()> { + let coords: Vec<_> = ring.coords().collect(); Review Comment: You should (1) be able to reuse this the LineString case above and (2) you shouldn't have to `.collect()` here (in theory `ring.coords()` is required to implement `DoubleEndedIterator` and `.rev()` should work on it. ########## rust/sedona-functions/src/st_reverse.rs: ########## @@ -0,0 +1,355 @@ +// 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::io::Write; +use std::sync::Arc; + +use arrow_array::builder::BinaryBuilder; +use datafusion_common::{DataFusionError, Result}; +use datafusion_expr::ColumnarValue; +use datafusion_expr::{scalar_doc_sections::DOC_SECTION_OTHER, Documentation, Volatility}; +use geo_traits::{ + CoordTrait, Dimensions, GeometryCollectionTrait, GeometryTrait, LineStringTrait, + MultiLineStringTrait, MultiPointTrait, MultiPolygonTrait, PointTrait, PolygonTrait, +}; +use sedona_expr::scalar_udf::{SedonaScalarKernel, SedonaScalarUDF}; +use sedona_geometry::wkb_factory::{ + write_wkb_coord, write_wkb_empty_point, write_wkb_geometrycollection_header, + write_wkb_linestring_header, write_wkb_multilinestring_header, write_wkb_multipoint_header, + write_wkb_multipolygon_header, write_wkb_point_header, write_wkb_polygon_header, + write_wkb_polygon_ring_header, WKB_MIN_PROBABLE_BYTES, +}; +use sedona_schema::{ + datatypes::{SedonaType, WKB_GEOMETRY}, + matchers::ArgMatcher, +}; + +use crate::executor::WkbExecutor; + +/// ST_Reverse() scalar UDF +/// +/// Native implementation to reverse the vertices in a geometry +pub fn st_reverse_udf() -> SedonaScalarUDF { + SedonaScalarUDF::new( + "st_reverse", + vec![Arc::new(STReverse)], + Volatility::Immutable, + Some(st_reverse_doc()), + ) +} + +fn st_reverse_doc() -> Documentation { + Documentation::builder( + DOC_SECTION_OTHER, + "Can be used on any geometry and reverses the order of the vertices.", + "ST_Reverse (geom: Geometry)", + ) + .with_argument("geom", "geometry: Input geometry") + .with_sql_example("SELECT ST_AsText(ST_Reverse('POLYGON ((2 2, 2 3, 3 3, 3 2, 2 2))'))") + .build() +} + +#[derive(Debug)] +struct STReverse; + +impl SedonaScalarKernel for STReverse { + fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> { + let matcher = ArgMatcher::new(vec![ArgMatcher::is_geometry()], WKB_GEOMETRY); + + matcher.match_args(args) + } + + fn invoke_batch( + &self, + arg_types: &[SedonaType], + args: &[ColumnarValue], + ) -> Result<ColumnarValue> { + let executor = WkbExecutor::new(arg_types, args); + let mut builder = BinaryBuilder::with_capacity( + executor.num_iterations(), + WKB_MIN_PROBABLE_BYTES * executor.num_iterations(), + ); + + executor.execute_wkb_void(|maybe_wkb| { + match maybe_wkb { + Some(wkb) => { + invoke_scalar(&wkb, &mut builder)?; + builder.append_value([]); + } + _ => builder.append_null(), + } + Ok(()) + })?; + + executor.finish(Arc::new(builder.finish())) + } +} + +fn invoke_scalar(geom: &impl GeometryTrait<T = f64>, writer: &mut impl Write) -> Result<()> { + let dims = geom.dim(); + match geom.as_type() { + geo_traits::GeometryType::Point(pt) => { + if pt.coord().is_some() { + write_wkb_point_header(writer, dims) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + write_coord(writer, pt.coord().unwrap())?; + } else { + write_wkb_empty_point(writer, dims) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + } + } + + geo_traits::GeometryType::MultiPoint(multi_point) => { + write_wkb_multipoint_header(writer, dims, multi_point.points().count()) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + for pt in multi_point.points() { + invoke_scalar(&pt, writer)?; + } + } + + geo_traits::GeometryType::LineString(ls) => { + let coords: Vec<_> = ls.coords().collect(); + write_wkb_linestring_header(writer, dims, coords.len()) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + for coord in coords.into_iter().rev() { + write_coord(writer, coord)?; + } + } + + geo_traits::GeometryType::Polygon(pgn) => { + let num_rings = pgn.interiors().count() + pgn.exterior().is_some() as usize; + write_wkb_polygon_header(writer, dims, num_rings) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + + if let Some(exterior) = pgn.exterior() { + write_reversed_ring(writer, exterior)?; + } + + for interior in pgn.interiors() { + write_reversed_ring(writer, interior)?; + } + } + + geo_traits::GeometryType::MultiLineString(mls) => { + write_wkb_multilinestring_header(writer, dims, mls.line_strings().count()) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + for ls in mls.line_strings() { + invoke_scalar(&ls, writer)?; + } + } + + geo_traits::GeometryType::MultiPolygon(mpgn) => { + write_wkb_multipolygon_header(writer, dims, mpgn.polygons().count()) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + for pgn in mpgn.polygons() { + invoke_scalar(&pgn, writer)?; + } + } + + geo_traits::GeometryType::GeometryCollection(gcn) => { + write_wkb_geometrycollection_header(writer, dims, gcn.geometries().count()) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + for geom in gcn.geometries() { + invoke_scalar(&geom, writer)?; + } + } + + _ => { + return Err(DataFusionError::Execution( + "Unsupported geometry type for reversal operation".to_string(), + )); + } + } + Ok(()) +} + +fn write_reversed_ring(writer: &mut impl Write, ring: impl LineStringTrait<T = f64>) -> Result<()> { + let coords: Vec<_> = ring.coords().collect(); + write_wkb_polygon_ring_header(writer, coords.len()) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + for coord in coords.into_iter().rev() { + write_coord(writer, coord)?; + } + Ok(()) +} + +fn write_coord(writer: &mut impl Write, coord: impl CoordTrait<T = f64>) -> Result<()> { + match coord.dim() { Review Comment: See the suggestion above (or use `write_wkb_coord_trait()`, which I think is almost identical to this) ########## rust/sedona-functions/src/st_reverse.rs: ########## @@ -0,0 +1,355 @@ +// 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::io::Write; +use std::sync::Arc; + +use arrow_array::builder::BinaryBuilder; +use datafusion_common::{DataFusionError, Result}; +use datafusion_expr::ColumnarValue; +use datafusion_expr::{scalar_doc_sections::DOC_SECTION_OTHER, Documentation, Volatility}; +use geo_traits::{ + CoordTrait, Dimensions, GeometryCollectionTrait, GeometryTrait, LineStringTrait, + MultiLineStringTrait, MultiPointTrait, MultiPolygonTrait, PointTrait, PolygonTrait, +}; +use sedona_expr::scalar_udf::{SedonaScalarKernel, SedonaScalarUDF}; +use sedona_geometry::wkb_factory::{ + write_wkb_coord, write_wkb_empty_point, write_wkb_geometrycollection_header, + write_wkb_linestring_header, write_wkb_multilinestring_header, write_wkb_multipoint_header, + write_wkb_multipolygon_header, write_wkb_point_header, write_wkb_polygon_header, + write_wkb_polygon_ring_header, WKB_MIN_PROBABLE_BYTES, +}; +use sedona_schema::{ + datatypes::{SedonaType, WKB_GEOMETRY}, + matchers::ArgMatcher, +}; + +use crate::executor::WkbExecutor; + +/// ST_Reverse() scalar UDF +/// +/// Native implementation to reverse the vertices in a geometry +pub fn st_reverse_udf() -> SedonaScalarUDF { + SedonaScalarUDF::new( + "st_reverse", + vec![Arc::new(STReverse)], + Volatility::Immutable, + Some(st_reverse_doc()), + ) +} + +fn st_reverse_doc() -> Documentation { + Documentation::builder( + DOC_SECTION_OTHER, + "Can be used on any geometry and reverses the order of the vertices.", + "ST_Reverse (geom: Geometry)", + ) + .with_argument("geom", "geometry: Input geometry") + .with_sql_example("SELECT ST_AsText(ST_Reverse('POLYGON ((2 2, 2 3, 3 3, 3 2, 2 2))'))") + .build() +} + +#[derive(Debug)] +struct STReverse; + +impl SedonaScalarKernel for STReverse { + fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> { + let matcher = ArgMatcher::new(vec![ArgMatcher::is_geometry()], WKB_GEOMETRY); + + matcher.match_args(args) + } + + fn invoke_batch( + &self, + arg_types: &[SedonaType], + args: &[ColumnarValue], + ) -> Result<ColumnarValue> { + let executor = WkbExecutor::new(arg_types, args); + let mut builder = BinaryBuilder::with_capacity( + executor.num_iterations(), + WKB_MIN_PROBABLE_BYTES * executor.num_iterations(), + ); + + executor.execute_wkb_void(|maybe_wkb| { + match maybe_wkb { + Some(wkb) => { + invoke_scalar(&wkb, &mut builder)?; + builder.append_value([]); + } + _ => builder.append_null(), + } + Ok(()) + })?; + + executor.finish(Arc::new(builder.finish())) + } +} + +fn invoke_scalar(geom: &impl GeometryTrait<T = f64>, writer: &mut impl Write) -> Result<()> { + let dims = geom.dim(); + match geom.as_type() { + geo_traits::GeometryType::Point(pt) => { + if pt.coord().is_some() { + write_wkb_point_header(writer, dims) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + write_coord(writer, pt.coord().unwrap())?; + } else { + write_wkb_empty_point(writer, dims) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + } + } + + geo_traits::GeometryType::MultiPoint(multi_point) => { + write_wkb_multipoint_header(writer, dims, multi_point.points().count()) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + for pt in multi_point.points() { + invoke_scalar(&pt, writer)?; + } + } + + geo_traits::GeometryType::LineString(ls) => { + let coords: Vec<_> = ls.coords().collect(); + write_wkb_linestring_header(writer, dims, coords.len()) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + for coord in coords.into_iter().rev() { + write_coord(writer, coord)?; + } + } + + geo_traits::GeometryType::Polygon(pgn) => { + let num_rings = pgn.interiors().count() + pgn.exterior().is_some() as usize; + write_wkb_polygon_header(writer, dims, num_rings) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + + if let Some(exterior) = pgn.exterior() { + write_reversed_ring(writer, exterior)?; + } + + for interior in pgn.interiors() { + write_reversed_ring(writer, interior)?; + } + } + + geo_traits::GeometryType::MultiLineString(mls) => { + write_wkb_multilinestring_header(writer, dims, mls.line_strings().count()) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + for ls in mls.line_strings() { + invoke_scalar(&ls, writer)?; + } + } + + geo_traits::GeometryType::MultiPolygon(mpgn) => { + write_wkb_multipolygon_header(writer, dims, mpgn.polygons().count()) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + for pgn in mpgn.polygons() { + invoke_scalar(&pgn, writer)?; + } + } + + geo_traits::GeometryType::GeometryCollection(gcn) => { + write_wkb_geometrycollection_header(writer, dims, gcn.geometries().count()) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + for geom in gcn.geometries() { + invoke_scalar(&geom, writer)?; + } + } + + _ => { + return Err(DataFusionError::Execution( + "Unsupported geometry type for reversal operation".to_string(), + )); + } + } + Ok(()) +} + +fn write_reversed_ring(writer: &mut impl Write, ring: impl LineStringTrait<T = f64>) -> Result<()> { + let coords: Vec<_> = ring.coords().collect(); + write_wkb_polygon_ring_header(writer, coords.len()) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + for coord in coords.into_iter().rev() { + write_coord(writer, coord)?; + } + Ok(()) +} + +fn write_coord(writer: &mut impl Write, coord: impl CoordTrait<T = f64>) -> Result<()> { + match coord.dim() { + Dimensions::Xy => { + write_wkb_coord(writer, (coord.x(), coord.y())) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + } + Dimensions::Xyz => { + write_wkb_coord(writer, (coord.x(), coord.y(), coord.nth_or_panic(2))) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + } + Dimensions::Xym => { + write_wkb_coord(writer, (coord.x(), coord.y(), coord.nth_or_panic(2))) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + } + Dimensions::Xyzm => { + write_wkb_coord( + writer, + ( + coord.x(), + coord.y(), + coord.nth_or_panic(2), + coord.nth_or_panic(3), + ), + ) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + } + _ => { + return Err(DataFusionError::Execution( + "Unsupported dimensions for coordinate".to_string(), + )); + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use datafusion_common::ScalarValue; + use rstest::rstest; + use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_VIEW_GEOMETRY}; + use sedona_testing::compare::assert_array_equal; + use sedona_testing::create::create_array; + use sedona_testing::testers::ScalarUdfTester; + + use super::*; + + #[rstest] + fn udf(#[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY)] sedona_type: SedonaType) { + let tester = ScalarUdfTester::new(st_reverse_udf().into(), vec![sedona_type]); + tester.assert_return_type(WKB_GEOMETRY); + + let result = tester.invoke_scalar("POINT EMPTY").unwrap(); + tester.assert_scalar_result_equals(result, "POINT EMPTY"); Review Comment: We're taking on more responsibility for this implementation, so I think we probably want to be a bit more intentional with these test cases. Maybe: - Check the LINESTRING scalar case (i.e., check that a scalar call works) - Check an `array` containing empties of all types (including Z, M, and ZM variants) - Check an `array` containing XY examples (you have these here!) - Check an `array` containing XYZ examples - Check an `array` containing XYM examples - Check an `array` containing XYZM examples ########## rust/sedona-functions/src/st_reverse.rs: ########## @@ -0,0 +1,355 @@ +// 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::io::Write; +use std::sync::Arc; + +use arrow_array::builder::BinaryBuilder; +use datafusion_common::{DataFusionError, Result}; +use datafusion_expr::ColumnarValue; +use datafusion_expr::{scalar_doc_sections::DOC_SECTION_OTHER, Documentation, Volatility}; +use geo_traits::{ + CoordTrait, Dimensions, GeometryCollectionTrait, GeometryTrait, LineStringTrait, + MultiLineStringTrait, MultiPointTrait, MultiPolygonTrait, PointTrait, PolygonTrait, +}; +use sedona_expr::scalar_udf::{SedonaScalarKernel, SedonaScalarUDF}; +use sedona_geometry::wkb_factory::{ + write_wkb_coord, write_wkb_empty_point, write_wkb_geometrycollection_header, + write_wkb_linestring_header, write_wkb_multilinestring_header, write_wkb_multipoint_header, + write_wkb_multipolygon_header, write_wkb_point_header, write_wkb_polygon_header, + write_wkb_polygon_ring_header, WKB_MIN_PROBABLE_BYTES, +}; +use sedona_schema::{ + datatypes::{SedonaType, WKB_GEOMETRY}, + matchers::ArgMatcher, +}; + +use crate::executor::WkbExecutor; + +/// ST_Reverse() scalar UDF +/// +/// Native implementation to reverse the vertices in a geometry +pub fn st_reverse_udf() -> SedonaScalarUDF { + SedonaScalarUDF::new( + "st_reverse", + vec![Arc::new(STReverse)], + Volatility::Immutable, + Some(st_reverse_doc()), + ) +} + +fn st_reverse_doc() -> Documentation { + Documentation::builder( + DOC_SECTION_OTHER, + "Can be used on any geometry and reverses the order of the vertices.", + "ST_Reverse (geom: Geometry)", + ) + .with_argument("geom", "geometry: Input geometry") + .with_sql_example("SELECT ST_AsText(ST_Reverse('POLYGON ((2 2, 2 3, 3 3, 3 2, 2 2))'))") + .build() +} + +#[derive(Debug)] +struct STReverse; + +impl SedonaScalarKernel for STReverse { + fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> { + let matcher = ArgMatcher::new(vec![ArgMatcher::is_geometry()], WKB_GEOMETRY); + + matcher.match_args(args) + } + + fn invoke_batch( + &self, + arg_types: &[SedonaType], + args: &[ColumnarValue], + ) -> Result<ColumnarValue> { + let executor = WkbExecutor::new(arg_types, args); + let mut builder = BinaryBuilder::with_capacity( + executor.num_iterations(), + WKB_MIN_PROBABLE_BYTES * executor.num_iterations(), + ); + + executor.execute_wkb_void(|maybe_wkb| { + match maybe_wkb { + Some(wkb) => { + invoke_scalar(&wkb, &mut builder)?; + builder.append_value([]); + } + _ => builder.append_null(), + } + Ok(()) + })?; + + executor.finish(Arc::new(builder.finish())) + } +} + +fn invoke_scalar(geom: &impl GeometryTrait<T = f64>, writer: &mut impl Write) -> Result<()> { + let dims = geom.dim(); + match geom.as_type() { + geo_traits::GeometryType::Point(pt) => { + if pt.coord().is_some() { + write_wkb_point_header(writer, dims) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + write_coord(writer, pt.coord().unwrap())?; + } else { + write_wkb_empty_point(writer, dims) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + } + } + + geo_traits::GeometryType::MultiPoint(multi_point) => { + write_wkb_multipoint_header(writer, dims, multi_point.points().count()) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + for pt in multi_point.points() { + invoke_scalar(&pt, writer)?; + } + } + + geo_traits::GeometryType::LineString(ls) => { + let coords: Vec<_> = ls.coords().collect(); + write_wkb_linestring_header(writer, dims, coords.len()) + .map_err(|e| DataFusionError::Execution(e.to_string()))?; + for coord in coords.into_iter().rev() { + write_coord(writer, coord)?; + } Review Comment: Totally optional, but ideally we would query the dimensions exactly once before looping. Maybe: ```rust pub fn write_wkb_coords_trait<C, I>(buf: &mut impl Write, dims: Dimensions, coords: I) -> Result<(), SedonaGeometryError> where C: CoordTrait<T = f64>, I: Iterator<Item = C>, { match dims.size() { 2 => { for coord in coords { let coord_tuple = coord.x_y(); write_wkb_coord(buf, coord_tuple)?; } } 3 => { for coord in coords { let coord_tuple: (<C as CoordTrait>::T, _, _) = (coord.x(), coord.y(), coord.nth_or_panic(2)); write_wkb_coord(buf, coord_tuple)?; } } 4 => { for coord in coords { let coord_tuple = ( coord.x(), coord.y(), coord.nth_or_panic(2), coord.nth_or_panic(3), ); write_wkb_coord(buf, coord_tuple)?; } } _ => Err(SedonaGeometryError::Invalid( "Unsupported number of dimensions".to_string(), )), } } ``` Maybe here: https://github.com/apache/sedona-db/blob/240898d4f48b18821c4190abbeaa1d7c71a4253a/rust/sedona-geometry/src/wkb_factory.rs#L395-L401 (Feel free to punt this one...we do exactly what you do here in a few other places as well!) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
