This is an automated email from the ASF dual-hosted git repository.
jiayu 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 f157155 feat(rust/sedona-expr): Use Covers filter for ST_Equals for
more Geoparquet pruning (#216)
f157155 is described below
commit f1571555ade1907ba9ffe2ea88d3ca4a92f1d937
Author: Peter Nguyen <[email protected]>
AuthorDate: Mon Oct 13 17:03:36 2025 -0700
feat(rust/sedona-expr): Use Covers filter for ST_Equals for more Geoparquet
pruning (#216)
---
rust/sedona-expr/src/spatial_filter.rs | 72 +++++++++++++++++++++++++++++-----
1 file changed, 62 insertions(+), 10 deletions(-)
diff --git a/rust/sedona-expr/src/spatial_filter.rs
b/rust/sedona-expr/src/spatial_filter.rs
index f4d9781..246362b 100644
--- a/rust/sedona-expr/src/spatial_filter.rs
+++ b/rust/sedona-expr/src/spatial_filter.rs
@@ -177,7 +177,7 @@ impl SpatialFilter {
let args = parse_args(raw_args);
let fun_name = scalar_fun.fun().name();
match fun_name {
- "st_intersects" | "st_equals" | "st_touches" | "st_crosses" |
"st_overlaps" => {
+ "st_intersects" | "st_touches" | "st_crosses" | "st_overlaps" => {
if args.len() != 2 {
return sedona_internal_err!("unexpected argument count in
filter evaluation");
}
@@ -199,6 +199,28 @@ impl SpatialFilter {
_ => Ok(Some(Self::Unknown)),
}
}
+ "st_equals" => {
+ if args.len() != 2 {
+ return sedona_internal_err!("unexpected argument count in
filter evaluation");
+ }
+
+ match (&args[0], &args[1]) {
+ (ArgRef::Col(column), ArgRef::Lit(literal))
+ | (ArgRef::Lit(literal), ArgRef::Col(column)) => {
+ if !is_prunable_geospatial_literal(literal) {
+ return Ok(Some(Self::Unknown));
+ }
+ match literal_bounds(literal) {
+ Ok(literal_bounds) => {
+ Ok(Some(Self::Covers(column.clone(),
literal_bounds)))
+ }
+ Err(e) =>
Err(DataFusionError::External(Box::new(e))),
+ }
+ }
+ // Not between a literal and a column
+ _ => Ok(Some(Self::Unknown)),
+ }
+ }
"st_within" | "st_covered_by" | "st_coveredby" => {
if args.len() != 2 {
return sedona_internal_err!("unexpected argument count in
filter evaluation");
@@ -575,15 +597,8 @@ mod test {
}
#[rstest]
- fn predicate_from_expr_commutative_functions(
- #[values(
- "st_intersects",
- "st_equals",
- "st_touches",
- "st_crosses",
- "st_overlaps"
- )]
- func_name: &str,
+ fn predicate_from_expr_commutative_intersects_functions(
+ #[values("st_intersects", "st_touches", "st_crosses", "st_overlaps")]
func_name: &str,
) {
let column: Arc<dyn PhysicalExpr> = Arc::new(Column::new("geometry",
0));
let storage_field = WKB_GEOMETRY.to_storage_field("", true).unwrap();
@@ -620,6 +635,43 @@ mod test {
);
}
+ #[rstest]
+ fn predicate_from_expr_equals_function(#[values("st_equals")] func_name:
&str) {
+ let column: Arc<dyn PhysicalExpr> = Arc::new(Column::new("geometry",
0));
+ let storage_field = WKB_GEOMETRY.to_storage_field("", true).unwrap();
+ let literal: Arc<dyn PhysicalExpr> =
Arc::new(Literal::new_with_metadata(
+ create_scalar(Some("POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0))"),
&WKB_GEOMETRY),
+ Some(storage_field.metadata().into()),
+ ));
+
+ // Test functions that should result in Covers filter
+ let func = create_dummy_spatial_function(func_name, 2);
+ let expr: Arc<dyn PhysicalExpr> = Arc::new(ScalarFunctionExpr::new(
+ func_name,
+ Arc::new(func.clone()),
+ vec![column.clone(), literal.clone()],
+ Arc::new(Field::new("", DataType::Boolean, true)),
+ ));
+ let predicate = SpatialFilter::try_from_expr(&expr).unwrap();
+ assert!(
+ matches!(predicate, SpatialFilter::Covers(_, _)),
+ "Function {func_name} should produce Covers filter"
+ );
+
+ // Test reversed argument order
+ let expr_reversed: Arc<dyn PhysicalExpr> =
Arc::new(ScalarFunctionExpr::new(
+ func_name,
+ Arc::new(func),
+ vec![literal.clone(), column.clone()],
+ Arc::new(Field::new("", DataType::Boolean, true)),
+ ));
+ let predicate_reversed =
SpatialFilter::try_from_expr(&expr_reversed).unwrap();
+ assert!(
+ matches!(predicate_reversed, SpatialFilter::Covers(_, _)),
+ "Function {func_name} with reversed args should produce Covers
filter"
+ );
+ }
+
#[rstest]
fn predicate_from_expr_within_covered_by_functions(
#[values("st_within", "st_covered_by", "st_coveredby")] func_name:
&str,