Description
When a column-to-column INTERSECTS (or CONTAINS / WITHIN) join uses a DISJOIN() table function as an operand, the genomic interval of the DISJOIN result resolves to the operator's passthrough original interval columns, not the disjoined segments the operator produces.
DISJOIN(genes) projects SELECT t.*, s.kc AS disjoin_chrom, s.seg_start AS disjoin_start, s.seg_end AS disjoin_end … — i.e. the original genes chrom/start/end (via t.*) plus the disjoined segments under the separate reserved disjoin_* names. In … a JOIN DISJOIN(genes) d ON a.interval INTERSECTS d.interval, d.interval binds to d.chrom/d.start/d.end — the originals — so the overlap is computed against genes' original intervals, and the disjoined segments are reachable only as d.disjoin_start/d.disjoin_end.
Reproduce (transpile("SELECT a.chrom, a.start, d.start AS d_start, d.\"end\" AS d_end FROM peaks a JOIN DISJOIN(genes) d ON a.interval INTERSECTS d.interval", tables=["peaks","genes"])):
SELECT a.chrom, a.start, d.start AS d_start, d."end" AS d_end
FROM peaks AS a
JOIN (
WITH __giql_dj_ref AS (...), __giql_dj_tgt AS (...), __giql_dj_segs AS (...)
SELECT t.*, s.kc AS disjoin_chrom, s.seg_start AS disjoin_start, s.seg_end AS disjoin_end
FROM __giql_dj_tgt AS t JOIN __giql_dj_segs AS s ON ...
) AS d
ON (a."chrom" = d."chrom" AND a."start" < d."end" AND a."end" > d."start")
The overlap predicate keys off d."start" / d."end" (the t.* originals), never disjoin_start / disjoin_end.
Expected behavior
a.interval INTERSECTS DISJOIN(genes) d should overlap against the disjoined segments — the operator's output — since that is what the query asks to intersect. On a DISJOIN result, the genomic interval should map to disjoin_chrom/disjoin_start/disjoin_end (equivalently: DISJOIN should project its segments as the canonical chrom/start/end and expose the source intervals, if at all, under distinct names). The precise contract — segments-as-interval vs. keeping both and choosing which interval means — is a design decision for this issue to settle, along with whether the current originals-as-interval behavior is ever the intended one.
Root cause
The genomic-column resolver's column-join branch resolves an operand's interval to that relation's default chrom/start/end columns. For a DISJOIN subquery those default names come from the t.* passthrough (the original intervals), while the disjoined segments carry the reserved disjoin_* names, so interval never selects the segments. Pre-existing behavior, unchanged by #167 — the resolver column-join branch was not touched by that refactor. Surfaced during PR #197 (#167) review (reviewer R5, empirically confirmed on both DuckDB and DataFusion). Filed as an independent follow-up, out of scope for #167.
Description
When a column-to-column
INTERSECTS(orCONTAINS/WITHIN) join uses aDISJOIN()table function as an operand, the genomicintervalof theDISJOINresult resolves to the operator's passthrough original interval columns, not the disjoined segments the operator produces.DISJOIN(genes)projectsSELECT t.*, s.kc AS disjoin_chrom, s.seg_start AS disjoin_start, s.seg_end AS disjoin_end …— i.e. the originalgeneschrom/start/end(viat.*) plus the disjoined segments under the separate reserveddisjoin_*names. In… a JOIN DISJOIN(genes) d ON a.interval INTERSECTS d.interval,d.intervalbinds tod.chrom/d.start/d.end— the originals — so the overlap is computed againstgenes' original intervals, and the disjoined segments are reachable only asd.disjoin_start/d.disjoin_end.Reproduce (
transpile("SELECT a.chrom, a.start, d.start AS d_start, d.\"end\" AS d_end FROM peaks a JOIN DISJOIN(genes) d ON a.interval INTERSECTS d.interval", tables=["peaks","genes"])):The overlap predicate keys off
d."start"/d."end"(thet.*originals), neverdisjoin_start/disjoin_end.Expected behavior
a.interval INTERSECTS DISJOIN(genes) dshould overlap against the disjoined segments — the operator's output — since that is what the query asks to intersect. On aDISJOINresult, the genomicintervalshould map todisjoin_chrom/disjoin_start/disjoin_end(equivalently:DISJOINshould project its segments as the canonicalchrom/start/endand expose the source intervals, if at all, under distinct names). The precise contract — segments-as-interval vs. keeping both and choosing whichintervalmeans — is a design decision for this issue to settle, along with whether the current originals-as-intervalbehavior is ever the intended one.Root cause
The genomic-column resolver's column-join branch resolves an operand's
intervalto that relation's defaultchrom/start/endcolumns. For aDISJOINsubquery those default names come from thet.*passthrough (the original intervals), while the disjoined segments carry the reserveddisjoin_*names, sointervalnever selects the segments. Pre-existing behavior, unchanged by #167 — the resolver column-join branch was not touched by that refactor. Surfaced during PR #197 (#167) review (reviewer R5, empirically confirmed on both DuckDB and DataFusion). Filed as an independent follow-up, out of scope for #167.