use vortex_array::expr::Expression;
use vortex_array::expr::gt;
use vortex_array::expr::lit;
use vortex_array::scalar_fn::ScalarFnId;
use vortex_array::scalar_fn::ScalarFnVTable;
use vortex_array::stats::rewrite::StatsRewriteCtx;
use vortex_array::stats::rewrite::StatsRewriteRule;
use vortex_error::VortexResult;
use super::aabb_stat;
use super::geometry_and_constant;
use super::min_dist_sq;
use super::query_aabb;
use crate::scalar_fn::intersects::GeoIntersects;
#[derive(Debug)]
pub struct GeoIntersectsPrune;
impl StatsRewriteRule for GeoIntersectsPrune {
fn scalar_fn_id(&self) -> ScalarFnId {
GeoIntersects.id()
}
fn falsify(
&self,
expr: &Expression,
ctx: &StatsRewriteCtx<'_>,
) -> VortexResult<Option<Expression>> {
let Some((geom, constant)) = geometry_and_constant(expr, ctx)? else {
return Ok(None);
};
let Some(query) = query_aabb(constant, ctx)? else {
return Ok(None);
};
Ok(Some(gt(min_dist_sq(&aabb_stat(geom), query), lit(0.0))))
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use vortex_array::VortexSessionExecute;
use vortex_array::dtype::DType;
use vortex_array::dtype::Nullability;
use vortex_array::dtype::PType;
use vortex_array::expr::Expression;
use vortex_array::expr::lit;
use vortex_array::expr::root;
use vortex_array::scalar_fn::EmptyOptions;
use vortex_array::scalar_fn::ScalarFnVTableExt;
use vortex_array::stats::rewrite::StatsRewriteCtx;
use vortex_array::stats::rewrite::StatsRewriteRule;
use vortex_error::VortexResult;
use super::GeoIntersectsPrune;
use crate::prune::test_harness::aabb_zone_map;
use crate::prune::test_harness::empty_zone_map;
use crate::scalar_fn::intersects::GeoIntersects;
use crate::test_harness::geo_session;
use crate::test_harness::point_column;
fn falsify_intersects(geom_first: bool) -> VortexResult<Option<Expression>> {
let session = geo_session();
let mut ctx = session.create_execution_ctx();
let scope = point_column(vec![0.0], vec![0.0])?.dtype().clone();
let query = point_column(vec![1.0], vec![0.5])?.execute_scalar(0, &mut ctx)?;
let operands = if geom_first {
[root(), lit(query)]
} else {
[lit(query), root()]
};
let predicate = GeoIntersects.new_expr(EmptyOptions, operands);
GeoIntersectsPrune.falsify(&predicate, &StatsRewriteCtx::new(&session, &scope))
}
#[rstest]
#[case(true)]
#[case(false)]
fn falsifies_either_operand_order(#[case] geom_first: bool) -> VortexResult<()> {
assert!(falsify_intersects(geom_first)?.is_some());
Ok(())
}
#[test]
fn unsupported_scope_is_not_pruned() -> VortexResult<()> {
let session = geo_session();
let mut ctx = session.create_execution_ctx();
let scope = DType::Primitive(PType::F64, Nullability::NonNullable);
let query = point_column(vec![0.0], vec![0.0])?.execute_scalar(0, &mut ctx)?;
let predicate = GeoIntersects.new_expr(EmptyOptions, [root(), lit(query)]);
let ctx = StatsRewriteCtx::new(&session, &scope);
assert!(GeoIntersectsPrune.falsify(&predicate, &ctx)?.is_none());
Ok(())
}
#[test]
fn prunes_disjoint_keeps_touching_and_containing() -> VortexResult<()> {
let session = geo_session();
let mut ctx = session.create_execution_ctx();
let point_dtype = point_column(vec![0.0], vec![0.0])?.dtype().clone();
let zone_map = aabb_zone_map(
&point_dtype,
&[
[0.5, 0.0, 1.5, 1.0],
[-1.0, 0.0, 1.0, 1.0],
[3.0, 0.0, 4.0, 1.0],
],
)?;
let query = point_column(vec![1.0], vec![0.5])?.execute_scalar(0, &mut ctx)?;
let predicate = GeoIntersects.new_expr(EmptyOptions, [root(), lit(query)]);
let proof = predicate
.falsify(&point_dtype, &session)?
.expect("intersects filter should be falsifiable");
let mask = zone_map.prune(&proof, &session)?;
assert_eq!(mask.iter().collect::<Vec<bool>>(), vec![false, false, true]);
Ok(())
}
#[test]
fn missing_aabb_stat_keeps_all_zones() -> VortexResult<()> {
let session = geo_session();
let mut ctx = session.create_execution_ctx();
let point_dtype = point_column(vec![0.0], vec![0.0])?.dtype().clone();
let zone_map = empty_zone_map(&point_dtype)?;
let query = point_column(vec![0.0], vec![0.0])?.execute_scalar(0, &mut ctx)?;
let proof = GeoIntersects
.new_expr(EmptyOptions, [root(), lit(query)])
.falsify(&point_dtype, &session)?
.expect("intersects filter should be falsifiable");
let mask = zone_map.prune(&proof, &session)?;
assert_eq!(mask.iter().collect::<Vec<bool>>(), vec![false, false]);
Ok(())
}
}