feat!: upgrade sqlparser to 0.62#55
Merged
Merged
Conversation
Adapt the resolver and normalizer to sqlparser 0.62's AST changes, and newly analyze the constructs it can now parse: ClickHouse ARRAY JOIN, Redshift ALTER SORTKEY, Spark multi-column aliases (`expr AS (a, b)`), and Oracle subquery INSERT targets. The re-exported `sqlparser` dependency moves 0.61 -> 0.62; because its AST types are part of the public API (`pub use sqlparser`), consumers must upgrade too. Under `GenericDialect`, `->` is now a binary operator, not a lambda arrow (upstream revert of an accidental enablement), so lambda tests move to `DuckDbDialect`. Follow-ups deferred to separate PRs: fan-out lineage for `expr AS (a, b)`, resolving Oracle subquery INSERT targets to their base table, and not surfacing an ARRAY JOIN array operand as a table read. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AteCEF9XeviLCkfa3EQMfc
Contributor
|
✅ PR title follows the Conventional Commits spec. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #55 +/- ##
==========================================
- Coverage 94.80% 94.76% -0.04%
==========================================
Files 27 27
Lines 5500 5542 +42
Branches 5500 5542 +42
==========================================
+ Hits 5214 5252 +38
- Misses 208 212 +4
Partials 78 78 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Merged
takaebato
added a commit
that referenced
this pull request
Jul 4, 2026
sqlparser parses the operand of `ARRAY JOIN` / `LEFT ARRAY JOIN` / `INNER ARRAY JOIN` as a `TableFactor::Table`, indistinguishable from a real table, so `SELECT s FROM t ARRAY JOIN arr` used to surface a phantom read of a table `arr`. Bind the operand as an opaque unnest instead — never a table read: a plain name reads as a column of the left relation (`t.arr`), and an array expression (`arrayConcat(t.a, t.b)`) reads its argument columns. The unnested output is exposed as a synthetic column, so a reference to the alias no longer leaks as a phantom `t.<alias>` read either. Follow-up from #55. Left as-is (both exotic): the analogous multi-table DELETE-target path (`DELETE … USING t ARRAY JOIN arr`), and a multi-array `ARRAY JOIN a, b` — which sqlparser parses as a comma-join, so `b` still looks like a table. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AteCEF9XeviLCkfa3EQMfc
takaebato
added a commit
that referenced
this pull request
Jul 4, 2026
sqlparser parses the operand of `ARRAY JOIN` / `LEFT ARRAY JOIN` / `INNER ARRAY JOIN` as a `TableFactor::Table`, indistinguishable from a real table, so `SELECT s FROM t ARRAY JOIN arr` used to surface a phantom read of a table `arr`. Bind the operand as an opaque unnest instead — never a table read: a plain name reads as a column of the left relation (`t.arr`), and an array expression (`arrayConcat(t.a, t.b)`) reads its argument columns. The unnested output is exposed as a synthetic column, so a reference to the alias no longer leaks as a phantom `t.<alias>` read either. The same special case applies on the UPDATE target's join clause (`UPDATE t ARRAY JOIN arr SET …`), and a non-Table operand shape (a derived subquery — invalid ClickHouse but parseable) falls back to the normal factor path so its reads survive. Follow-up from #55. Left as-is (both exotic): the analogous multi-table DELETE-target path (`DELETE … USING t ARRAY JOIN arr`), and a multi-array `ARRAY JOIN a, b` — which sqlparser parses as a comma-join, so `b` still looks like a table. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AteCEF9XeviLCkfa3EQMfc
takaebato
added a commit
that referenced
this pull request
Jul 4, 2026
## Summary Fixes a follow-up noted in #55: a ClickHouse `ARRAY JOIN` operand was surfaced as a (spurious) table read. sqlparser parses the operand of `ARRAY JOIN` / `LEFT ARRAY JOIN` / `INNER ARRAY JOIN` as a `TableFactor::Table`, indistinguishable from a real table — so `SELECT s FROM t ARRAY JOIN arr` reported a phantom read of a table `arr`. (ClickHouse's ARRAY JOIN operand is always an array — a column or an array expression — never a table.) This binds the ARRAY JOIN operand as an opaque unnest (a `TableFunction`, so no table read): - A plain array **column** (`arr` / `t.arr`) reads as a column of the left relation: `read_tables` no longer includes it (`SELECT s FROM t ARRAY JOIN arr` → `[t]`, not `[t, arr]`), and `t.arr` surfaces as a column read. - An array **expression** (`arrayConcat(t.a, t.b)`) reads its argument columns (`t.a`, `t.b`) — not the function name. - The unnested output (`AS x`, else the operand's own name) is exposed as a synthetic column, so a reference to it no longer leaks as a phantom `t.x` read. Edge cases covered on review: - The **UPDATE target's join clause** takes the same special case: `UPDATE t ARRAY JOIN arr SET a = 1` no longer reads a phantom `arr` table (and `t` correctly surfaces as a read — the unnest references the write target's own data). - A **non-Table operand** (a derived subquery — invalid ClickHouse but sqlparser parses it) falls back to the normal factor path, so its reads survive instead of being silently dropped. - Pipe joins (`|> JOIN`) cannot parse an ARRAY JOIN operator — unreachable. Tests: updated the ARRAY JOIN arm coverage (now expects the operand column read), plus table-level "operand is not a read table" (SELECT and UPDATE), "alias does not leak as a phantom column", "expression operand reads its arguments", and "derived operand keeps its reads" tests. All gates green (fmt / clippy / `test --all` / doc). Left as-is (both exotic): the analogous multi-table DELETE-target path (`DELETE … USING t ARRAY JOIN arr`), and a multi-array `ARRAY JOIN a, b` — which sqlparser parses as a comma-join, so `b` still looks like a table. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
takaebato
added a commit
that referenced
this pull request
Jul 4, 2026
`INSERT INTO (SELECT a, b FROM emp WHERE dept = 10) VALUES (…)` writes the view's single base table — resolve through to it instead of dropping the whole statement as an unsupported target. The view projection names the target columns (an explicit list: plain columns only, `a AS x` writes base `a`; a wildcard / expression falls back to the column-less catalog-fill / diagnostic path), the WHERE predicate is filter reads against the target (a new `Insert::target_predicate`), and a SELECT source traces through the view (`s.x → emp.a`). Only the minimal insertable-view shape (projection + FROM + WHERE) resolves through — enforced by exhaustive `Query` / `Select` destructures, so a new clause forces a decision. Everything else keeps the previous drop + `UnsupportedStatement` flag: a join / set operation / nested WITH names no single base table SQL text can determine (key-preserved rules need a catalog), and any other clause (GROUP BY / DISTINCT / ORDER BY / FETCH / …) makes the view non-insertable and could carry silently-dropped column refs. An aliased base table (`FROM emp e`) resolves the predicate's `e.dept` through the alias. `TableReference`'s `TryFrom<&Insert>` sees through the same shapes. Follow-up from #55; only `OracleDialect` parses this syntax. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AteCEF9XeviLCkfa3EQMfc
takaebato
added a commit
that referenced
this pull request
Jul 4, 2026
) ## Summary Implements a follow-up noted in #55: Oracle's inline-view INSERT (`INSERT INTO (SELECT … FROM emp) …`) now resolves through to the view's single base table as the write target, instead of dropping the whole statement as an unsupported target. Semantically the row lands in the base table — the subquery is an unnamed updatable view that restricts columns / enforces a predicate. What resolves (single-table view only): - **Write target** = the view's base table: `INSERT INTO (SELECT a FROM emp) …` writes `emp` (table level: `create_tables=[emp]`). - **Target columns** = the view projection, as an explicit column list — plain columns only; `a AS x` writes base `a`. A wildcard / expression projection makes positions indeterminate → the usual column-less path (catalog fill, or `InsertColumnsUnresolved`). The explicit list also drives the arity check (`… (SELECT a FROM emp) VALUES (1, 2)` → `InsertColumnsArityMismatch`). - **View WHERE** = filter reads against the target (what a `WITH CHECK OPTION` would enforce; sqlparser cannot parse the `WITH CHECK OPTION` keywords inside the target, so the plain predicate is what surfaces): reads `emp.dept`, never a lineage source. Carried on a new internal `Insert::target_predicate`. - **Column lineage** traces through the view: `INSERT INTO (SELECT a FROM emp) SELECT x FROM s` → `s.x → emp.a`. What stays flagged (`UnsupportedStatement`, as before): everything but the minimal insertable-view shape (projection + FROM + WHERE). A join (Oracle's key-preserved rules need a catalog), a set operation, a nested `WITH` / pipe chain, or a non-table factor names no single base table — and any other clause (GROUP BY / HAVING / DISTINCT / ORDER BY / FETCH / CONNECT BY / …) makes the view non-insertable (ORA-01732) and could carry column refs that would otherwise drop silently. Enforced by exhaustive `Query` / `Select` destructures, so a new sqlparser clause forces a keep-or-reject decision. Edge cases covered on review: - An **aliased base table** (`INSERT INTO (SELECT e.a FROM emp e WHERE e.dept = 10) …`): the predicate's `e.dept` resolves through the alias to `emp.dept` (and, as in SQL, the alias shadows the bare table name). - **Clause-bearing views** (`GROUP BY` / `ORDER BY` / `DISTINCT` / `FETCH`) previously slipped through the shape check with their column refs silently dropped — now rejected to the flag+drop path. - `INSERT OVERWRITE` / `REPLACE INTO` bucketing (`peel_to_insert`) only reads the flags — unaffected. `TableReference`'s `TryFrom<&Insert>` sees through the same shapes (and errors on the rest), so the public target-identity parse agrees with the analysis. Tests: 7 column-level cases (values / WHERE-reads / lineage-through-view / alias / wildcard / expression / arity), a table-level CRUD case, a `TryFrom<&Insert>` unit test, and the existing subquery-target diagnostic test narrowed to a join view. All gates green (fmt / clippy / `test --all` / doc). Only `OracleDialect` parses this syntax (`supports_insert_table_query`). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
takaebato
added a commit
that referenced
this pull request
Jul 5, 2026
## 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. ```sql 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: OutputNames` — `Single(Option<Ident>)` for an ordinary item, `Fan(Vec<Ident>)` for a multi-column alias: ```text 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 once** → `reads` 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 `Transformation`s; a bare column `t.a AS (x, y)` fans out `Passthrough`s. 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](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
takaebato
added a commit
that referenced
this pull request
Jul 5, 2026
## Summary The changelog body template credits ` by @author` on every normal group entry but not on the "Breaking Changes" `####` headings. This release will be the first to render that section (#55 / #59 are `feat!`), so add the same guarded attribution there: ```jinja #### … {{ commit.message }}{% if commit.remote.username %} by @{{ commit.remote.username }}{% endif %} ``` Also drops CLAUDE.md's stale breaking-changes bullet: it still described the PR-description changelog block dropped in #56, the `!` flag is already covered by the PR-title bullet, and the rendering / hand-written-migration- notes facts live at their source in release-plz.toml's comments. ## Verification `commit.remote.username` is the same variable the normal-entry line two lines below already uses, under the same guard. Rendering can't be verified locally without GitHub-integrated release-plz — after this merges, release-plz will regenerate the open release PR (#48), where the Breaking Changes headings can be eyeballed before releasing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
takaebato
added a commit
that referenced
this pull request
Jul 5, 2026
The CLI gained a real feature (#53: binstall, completions, man pages), so promote release-plz's 0.x feat→patch default to a minor bump. Add concise migration notes under the library's Breaking Changes headings (#59 SET attribution, #55 sqlparser 0.62). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Upgrades
sqlparser0.61 → 0.62 and adapts the resolver/normalizer to its ASTchanges. Beyond keeping the build green, this makes sql-insight analyze several
constructs 0.62 can now parse:
ARRAY JOIN/LEFT ARRAY JOIN/INNER ARRAY JOINALTER SORTKEY (cols)(schema-level; no column writes)expr AS (a, b)(best-effort: reads counted once,lineage to the first alias)
INSERT INTO (SELECT …) …(flagged as anunsupported write target, dropped)
Internal adaptations:
Parens-wrapped VALUES rows,ObjectNameINSERT columns,TableAliasWithoutColumns,LambdaFunctionParameter, and thepre_visit_value(&mut ValueWithSpan)visitor signature.New tests: ARRAY JOIN arm coverage (×3), Spark multi-alias lineage, ALTER SORTKEY
no-column-writes, Oracle subquery-INSERT diagnostic, typed lambda parameter,
case-insensitive / quoted INSERT-column alphabetization, aliased-INSERT target.
Lambda tests moved from
GenericDialecttoDuckDbDialect(see the breaking notebelow).
Breaking changes (reference for the release-PR CHANGELOG note)
sqlparsermoves 0.61 → 0.62. It's part of the public API(
pub use sqlparser; its AST types appear inTableReference, extractorinputs, and
&dyn Dialect), so consumers must upgrade too. AST types you maytouch changed — e.g.
Insert::columnsis nowVec<ObjectName>(wasVec<Ident>),Values::rowsisVec<Parens<Vec<Expr>>>, literals areValueWithSpan, lambda params areLambdaFunctionParameter.GenericDialect,->is now a binary operator, not a lambda arrow(upstream revert of an accidental enablement). Lambda syntax (
x -> …) needs alambda-aware dialect such as
DuckDbDialect/DatabricksDialect.Deferred to follow-up PRs
expr AS (a, b, …)— emit an edge to every alias, not justthe first (needs a one-expr → N-outputs model change).
INSERT INTO (subquery)through to its base table as the writetarget, instead of dropping it.
ARRAY JOINarray operand as a (spurious) table read.🤖 Generated with Claude Code