Description
Migrate the DuckDB IEJoin column-to-column INTERSECTS join rewrite (IntersectsDuckDBIEJoinTransformer) from a pre-pass transformer into a registered, target-specific (DuckDBTarget, Intersects) override expander in the operator-expander registry. This is the DuckDB counterpart to #167 (which makes the naive overlap predicate the GenericTarget default and removes binning); together they move all INTERSECTS join strategy out of the pre-pass transformers and into the registry.
End state:
Motivation
Complete the operator-expander registry migration. The registry scaffolding (#138) and per-operator migrations landed for DISTANCE (#140), the INTERSECTS/CONTAINS/WITHIN predicates (#141), NEAREST (#142), DISJOIN (#143), and CLUSTER/MERGE (#144) — but the INTERSECTS join strategies (binned + DuckDB IEJoin) were left as pre-pass transformers selected by a hardcoded global capability branch. #167 removes the binned pre-pass and makes the GenericTarget default the naive predicate; this issue removes the IEJoin pre-pass and re-homes it as a registered DuckDB override, so INTERSECTS join strategy is fully registry-driven and target-overridable.
Expected Outcome
- DuckDB column-to-column INTERSECTS joins are produced by a registered
(DuckDBTarget, Intersects) override expander, not the pre-pass transformer.
- The override declines outer joins (and any shape it cannot express) and defers to the
GenericTarget naive-predicate default; no reliance on the removed binned transformer.
- The global
uses_iejoin branch in transpile.py is removed; strategy selection is via has_override.
- Existing IEJoin results for supported shapes are preserved (regression), verified against DuckDB (oracle/integration test).
Interaction with existing IEJoin-dialect issues
With #167's naive predicate as the universal fallback, shapes the IEJoin override declines now defer to a capable default rather than the removed binned transformer. Several open IEJoin-dialect limitation issues may become closeable as "handled by the default" instead of requiring IEJoin support: #95 (LEFT/RIGHT JOIN), #96 (multiple INTERSECTS), #97 (self-joins), #98 (extra ON/WHERE predicates), #99 (bare */unqualified columns), #109 (top-level modifiers), #110 (CTEs/subquery operands), #111 (3+ tables). This issue makes a per-shape keep-in-IEJoin vs defer-to-default decision.
Relevant code
src/giql/transformer.py — IntersectsDuckDBIEJoinTransformer, _IEJoinSides, _has_outer_join_intersects, transform_to_sql (decline/None fallback), _classify_extras
src/giql/transpile.py — uses_iejoin gating (~156, 214-235), has_override seam (181)
src/giql/expander.py — ExpanderRegistry, ExpansionContext, has_override
src/giql/targets.py — DuckDBTarget, Capabilities (range_join_strategy)
Dependencies
Out of scope
Description
Migrate the DuckDB IEJoin column-to-column INTERSECTS join rewrite (
IntersectsDuckDBIEJoinTransformer) from a pre-pass transformer into a registered, target-specific(DuckDBTarget, Intersects)override expander in the operator-expander registry. This is the DuckDB counterpart to #167 (which makes the naive overlap predicate theGenericTargetdefault and removes binning); together they move all INTERSECTS join strategy out of the pre-pass transformers and into the registry.End state:
(DuckDBTarget, Intersects)override that supersedes theGenericTargetnaive-predicate default (Drop the binned INTERSECTS join path in favor of the naive overlap predicate #167) for the shapes it supports.GenericTargetdefault (the naive predicate) rather than the removed binned transformer._has_outer_join_intersects, transformer.py:82), so the override declines LEFT/RIGHT/FULL joins and defers to the naive-predicate default (which handles outer correctly per Drop the binned INTERSECTS join path in favor of the naive overlap predicate #167). This reframes Support LEFT/RIGHT JOIN in the DuckDB IEJoin dialect #95 ("Support LEFT/RIGHT JOIN in the DuckDB IEJoin dialect") as "defer to the default" and likely moots it.uses_iejoinstrategy branch intranspile.py(~156, 214-235) is removed in favor of the registry override viaExpanderRegistry.has_override(transpile.py:181).Motivation
Complete the operator-expander registry migration. The registry scaffolding (#138) and per-operator migrations landed for DISTANCE (#140), the INTERSECTS/CONTAINS/WITHIN predicates (#141), NEAREST (#142), DISJOIN (#143), and CLUSTER/MERGE (#144) — but the INTERSECTS join strategies (binned + DuckDB IEJoin) were left as pre-pass transformers selected by a hardcoded global capability branch. #167 removes the binned pre-pass and makes the
GenericTargetdefault the naive predicate; this issue removes the IEJoin pre-pass and re-homes it as a registered DuckDB override, so INTERSECTS join strategy is fully registry-driven and target-overridable.Expected Outcome
(DuckDBTarget, Intersects)override expander, not the pre-pass transformer.GenericTargetnaive-predicate default; no reliance on the removed binned transformer.uses_iejoinbranch intranspile.pyis removed; strategy selection is viahas_override.Interaction with existing IEJoin-dialect issues
With #167's naive predicate as the universal fallback, shapes the IEJoin override declines now defer to a capable default rather than the removed binned transformer. Several open IEJoin-dialect limitation issues may become closeable as "handled by the default" instead of requiring IEJoin support: #95 (LEFT/RIGHT JOIN), #96 (multiple INTERSECTS), #97 (self-joins), #98 (extra ON/WHERE predicates), #99 (bare */unqualified columns), #109 (top-level modifiers), #110 (CTEs/subquery operands), #111 (3+ tables). This issue makes a per-shape keep-in-IEJoin vs defer-to-default decision.
Relevant code
src/giql/transformer.py—IntersectsDuckDBIEJoinTransformer,_IEJoinSides,_has_outer_join_intersects,transform_to_sql(decline/None fallback),_classify_extrassrc/giql/transpile.py—uses_iejoingating (~156, 214-235),has_overrideseam (181)src/giql/expander.py—ExpanderRegistry,ExpansionContext,has_overridesrc/giql/targets.py—DuckDBTarget,Capabilities(range_join_strategy)Dependencies
GenericTargetdefault that this override defers to must exist first).Out of scope
GenericTargetnaive-predicate default and binned removal — Drop the binned INTERSECTS join path in favor of the naive overlap predicate #167.