Skip to content

ST_Intersects pushdown: vortex.geo.intersects kernel, DuckDB lowering, spatial function overrides#8704

Open
HarukiMoriarty wants to merge 4 commits into
developfrom
nemo/geo-intersects
Open

ST_Intersects pushdown: vortex.geo.intersects kernel, DuckDB lowering, spatial function overrides#8704
HarukiMoriarty wants to merge 4 commits into
developfrom
nemo/geo-intersects

Conversation

@HarukiMoriarty

Copy link
Copy Markdown
Contributor

Rationale for this change

ST_Intersects over native geometry columns currently evaluates inside DuckDB, exporting every row as GEOMETRY just to be tested. This adds a native vortex.geo.intersects kernel and pushes the filter into the scan: on SpatialBench SF1 (6M points vs a literal polygon), 406ms (DuckDB filter) / 36.6ms (SPATIAL_JOIN) -> 12.4ms pushed. Part of the native geospatial lane, following the ST_Distance/ST_DWithin pushdown.

What changes are included in this PR?

  • vortex-geo: GeoIntersects kernel.
  • vortex-duckdb: lower st_intersects(native column, GEOMETRY literal) like st_distance.
  • vortex-duckdb: shadow spatial's ST_Intersects with a non-fallible copy — DuckDB won't push can-throw filters through the projection every view-registered table has. The ST_DWithin override generalizes into a table-driven registry (spatial_overrides.hpp) shared by registration and the join-condition restore pass.

Note: drop GeoDistance's columnar point fast paths. geo is row-oriented, so kernels materialize rows regardless; hand-rolled columnar paths belong in a future columnar geometry compute library (pushed Q1: 5.4ms -> 12.6ms, still far ahead of unpushed).

OGC intersects (not disjoint; boundary contact counts) between two native
geometry operands, each a column or a constant literal. Constant pairs fold
to a ConstantArray; column shapes materialize geo_types rows and delegate
to geo::Intersects. The function id is kept stable so a future stats-pruning
rewrite can key on it.

Signed-off-by: Nemo Yu <zyu379@wisc.edu>
geo computes row by row, so every kernel materializes geo_types geometries
from the columnar storage first; the hand-rolled point fast paths were
fragments of a columnar geometry compute library that does not exist yet.
Keep the kernels as thin materialize-and-delegate wrappers until that
library exists (noted in scalar_fn/mod.rs); on SpatialBench SF1 the pushed
Q1 filter goes from 5.4ms to 12.6ms, still well ahead of the unpushed path.

Signed-off-by: Nemo Yu <zyu379@wisc.edu>
A two-argument st_intersects over a native geometry column and a constant
GEOMETRY literal (WKB, decoded to its native scalar at plan time) now
pushes into the Vortex scan via pushdown_complex_filter, like st_distance
and st_dwithin. The shared operand extraction moves into a
two_geo_operands helper and the lowered-name list lives only in the match.

Signed-off-by: Nemo Yu <zyu379@wisc.edu>
DuckDB refuses to push can-throw filters through a projection
(pushdown_projection.cpp), spatial marks ST_Intersects fallible, and every
view-wrapped scan sits under a projection, so those filters never reached
the Vortex scan. Shadow the function with a copy whose error mode is
cleared, the same trick the ST_DWithin override uses for its folded radius.

The overrides are now table-driven: spatial_overrides.hpp holds one
(name, arity, tweak) row per shadowed function, shared by the single
registration entry point (duckdb_vx_register_spatial_overrides) and the
join-condition restore pass (RestoreSpatialOverrides, formerly
RestoreStDWithin). Adding an override is one table row plus a lowering arm.

On SpatialBench SF1 the view-wrapped 6M-row intersects probe drops from
406ms (DuckDB scalar filter) to the pushed in-scan path, and Q6's zone-side
conjunct now pushes while its ST_Within spatial join stays intact; Q1-Q9
row counts validate.

Signed-off-by: Nemo Yu <zyu379@wisc.edu>
@codspeed-hq

codspeed-hq Bot commented Jul 9, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 2 improved benchmarks
❌ 2 regressed benchmarks
✅ 1626 untouched benchmarks
⏩ 42 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation rebuild_naive 91.4 µs 109 µs -16.17%
Simulation chunked_varbinview_opt_canonical_into[(100, 100)] 305.4 µs 340.6 µs -10.32%
Simulation chunked_varbinview_canonical_into[(1000, 10)] 191 µs 154.7 µs +23.42%
Simulation bitwise_not_vortex_buffer_mut[128] 273.6 ns 244.4 ns +11.93%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing nemo/geo-intersects (32dd2cc) with develop (1bcaf3f)

Open in CodSpeed

Footnotes

  1. 42 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

class StDWithinRestore final : public LogicalOperatorVisitor {
// Whether `expr` is a call bound through one of the Vortex user-catalog overrides (see
// `SpatialOverrides` in spatial_overrides.hpp).
bool IsSpatialOverrideCall(const BoundFunctionExpression &expr) {

@myrrc myrrc Jul 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move this code to a separate translation unit spatial_overrides.hpp/cpp as it's not really related to a scalar function pushdown, but overriding some of functions.
Moreover, when 2.0 comes it would be harder for me to remove scalar functions pushdown (upstreamed).


/// Shadow spatial's `fn_override.name` in the user catalog with tweaked copies of its overloads;
/// a no-op when the function is not registered (`spatial` not loaded).
static void RegisterSpatialOverride(ClientContext &context, const SpatialOverride &fn_override) {

@myrrc myrrc Jul 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also move this code to a spatial translation unit

@myrrc

myrrc commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Let's add a sqllogictest which checks our queries don't break if we override the functions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants