Skip to content

feat: fan out multi-column-alias lineage to every alias#60

Merged
takaebato merged 1 commit into
masterfrom
feat/multi-alias-fanout-lineage
Jul 5, 2026
Merged

feat: fan out multi-column-alias lineage to every alias#60
takaebato merged 1 commit into
masterfrom
feat/multi-alias-fanout-lineage

Conversation

@takaebato

@takaebato takaebato commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

Implements the last follow-up noted in #55 (design agreed in discussion):
lineage for Spark's multi-column alias expr AS (a, b, …) now fans out to
every alias
instead of stopping at the first.

SELECT explode(t.arr) AS (k, v) FROM t
-- before: reads [t.arr],  lineage [arr → k]
-- after:  reads [t.arr],  lineage [arr → k, arr → v]

Design: the fan is one item (no references)

NamedExpr gains names: OutputNamesSingle(Option<Ident>) for an
ordinary item, Fan(Vec<Ident>) for a multi-column alias:

SELECT id, explode(t.arr) AS (k, v), name FROM t
Projection.exprs = [                                  ← 3 items, 4 positions
  NamedExpr { names: Single(id),  expr: Column(t.id) }        // position 0
  NamedExpr { names: Fan([k, v]), expr: Call(explode(arr)) }  // positions 1, 2
  NamedExpr { names: Single(name), expr: Column(t.name) }     // position 3
]
  • The expression exists exactly oncereads count it once by
    construction (occurrence-based, unchanged), and item walks (own_exprs)
    stay a plain map.
  • Names can never separate from their expression — there is no
    back-reference to dangle under any reorder / filter / splice (the design
    review rejected a positional Fanout { back } reference and a
    debug_assert-guarded fallback for exactly this reason).
  • Item index ≠ output position: every positional / named consumer goes
    through one slot view (output_slots / slot_count /
    resolved_output_expr), where a fan yields its shared expression once per
    name — query-output lineage, INSERT positional pairing, RETURNING, CTAS /
    CREATE VIEW, the shared positional trace (set-op branches, scalar
    subqueries, EXCLUDED), derived-table name traces, output-column scopes
    (OutputCol, fan outputs are never identity), AS d(x, y) positional
    renames, and the created-columns / insert arity checks.
  • Edge kind = the head expression's kind: explode(arr) fans out
    Transformations; a bare column t.a AS (x, y) fans out Passthroughs.

Non-breaking: output shapes are unchanged; lineage gains edges (the
first-alias-only behavior was a documented best-effort in #55, not a
contract).

Verified end-to-end: fan-out for 2 aliases, Passthrough fan-out for a bare
column, the tail alias traced through a derived table, INSERT pairing and
CTAS / CREATE VIEW feeding every target column, positional fan renames through
a derived alias list (AS d(x, y)) and a CTE column list, UNION branches
tracing positionally (existing reads-once pins unchanged). All gates green
(fmt / clippy / test --all / doc); 766 tests, 100% patch coverage
(llvm-cov ∩ diff).

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

✅ PR title follows the Conventional Commits spec.

@takaebato takaebato force-pushed the feat/multi-alias-fanout-lineage branch from f36cdbd to bf24a2f Compare July 5, 2026 04:27
@codecov

codecov Bot commented Jul 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.80952% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 94.84%. Comparing base (f326d1a) to head (55b4266).

Files with missing lines Patch % Lines
sql-insight/src/resolver/binder.rs 94.11% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #60      +/-   ##
==========================================
- Coverage   94.85%   94.84%   -0.01%     
==========================================
  Files          27       27              
  Lines        5737     5747      +10     
  Branches     5737     5747      +10     
==========================================
+ Hits         5442     5451       +9     
  Misses        216      216              
- Partials       79       80       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@takaebato takaebato force-pushed the feat/multi-alias-fanout-lineage branch 2 times, most recently from 8702d35 to 1cdb66b Compare July 5, 2026 05:40
Spark's `expr AS (a, b, …)` projects one expression under several output
names. Lineage used to reach only the first alias (`explode(t.arr) AS (k, v)`
emitted `arr → k` alone); it now fans out — every alias gets its own edge from
the expression's sources, with the head expression's kind (`Transformation`
for a call, `Passthrough` for a bare column).

The IR models the fan as **one item**: `NamedExpr` gains
`names: OutputNames` (`Single(Option<Ident>)` | `Fan(Vec<Ident>)`), so the
expression exists exactly once — `reads` count it once by construction, and
the names can never separate from it (no reference to dangle under any
reorder / filter). A fan occupies one output *position* per name, so an
item index is no longer an output position: every positional / named
consumer — query outputs, DML relation pairing, RETURNING, CTAS / CREATE
VIEW, set-operation / scalar-subquery / EXCLUDED traces, derived-table name
traces, output-column scopes, `AS d(x, y)` renames, arity checks — goes
through one slot view (`output_slots` / `slot_count` /
`resolved_output_expr`), where the fan expands. `SELECT v FROM (SELECT
explode(arr) AS (k, v) FROM t) d` reaches `t.arr`; an INSERT / CTAS feeds
every target column.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@takaebato takaebato force-pushed the feat/multi-alias-fanout-lineage branch from 1cdb66b to 55b4266 Compare July 5, 2026 05:51
@takaebato takaebato merged commit 13f18bd into master Jul 5, 2026
14 checks passed
@takaebato takaebato deleted the feat/multi-alias-fanout-lineage branch July 5, 2026 05:55
@github-actions github-actions Bot mentioned this pull request Jul 5, 2026
takaebato added a commit that referenced this pull request Jul 5, 2026
## 🤖 New release

* `sql-insight`: 0.3.0 -> 0.4.0 (✓ API compatible changes)
* `sql-insight-cli`: 0.2.1 -> 0.2.2

<details><summary><i><b>Changelog</b></i></summary><p>

## `sql-insight`

<blockquote>

##
[0.4.0](sql-insight-v0.3.0...sql-insight-v0.4.0)
- 2026-07-05

### ⚠️ Breaking Changes

#### attribute unqualified SET targets with the read-side rules
([#59](#59)) by @takaebato

#### upgrade sqlparser to 0.62
([#55](#55)) by @takaebato

### Added

- resolve Oracle join-view INSERT targets by column attribution
([#61](#61)) by @takaebato
- fan out multi-column-alias lineage to every alias
([#60](#60)) by @takaebato
- resolve Oracle inline-view INSERT targets to their base table
([#58](#58)) by @takaebato

### Fixed

- don't surface a ClickHouse ARRAY JOIN operand as a table read
([#57](#57)) by @takaebato

### Other Changes

- changelog breaking-change workflow, version-bump docs, and keywords
([#51](#51)) by @takaebato
- tidy keywords, README versions, and add a version-sync check
([#46](#46)) by @takaebato
</blockquote>

## `sql-insight-cli`

<blockquote>

##
[0.2.2](sql-insight-cli-v0.2.1...sql-insight-cli-v0.2.2)
- 2026-07-05

### Added

- *(cli)* prebuilt binary distribution — cargo binstall, completions,
man, and provenance
([#53](#53)) by @takaebato

### Fixed

- *(deps)* update rust crate clap_mangen to 0.3
([#54](#54)) by
@renovate[bot]

### Other Changes

- changelog breaking-change workflow, version-bump docs, and keywords
([#51](#51)) by @takaebato
- tidy keywords, README versions, and add a version-sync check
([#46](#46)) by @takaebato
</blockquote>


</p></details>

---
This PR was generated with
[release-plz](https://github.com/release-plz/release-plz/).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Takahiro Ebato <takahiro.ebato@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant