Skip to content

Resolve a DISJOIN operand's interval to the disjoined segments, not the original intervals #198

Description

@conradbzura

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions