feat: resolve Oracle join-view INSERT targets by column attribution - #61
Merged
Conversation
Contributor
|
✅ PR title follows the Conventional Commits spec. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #61 +/- ##
==========================================
+ Coverage 94.84% 94.92% +0.07%
==========================================
Files 27 27
Lines 5747 5893 +146
Branches 5747 5893 +146
==========================================
+ Hits 5451 5594 +143
+ Misses 216 212 -4
- Partials 80 87 +7 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
takaebato
force-pushed
the
feat/join-view-insert-target
branch
2 times, most recently
from
July 5, 2026 06:56
54d1c02 to
93d8c6d
Compare
`INSERT INTO (SELECT e.id, e.name FROM emp e JOIN dept d ON … WHERE …) …` used to drop + flag; the row lands in whichever relation **every** projected column attributes to — a qualified column text-only via its qualifier (like a multi-table UPDATE's `SET t2.col`), an unqualified one by the catalog-owner rule — so it now resolves: `emp` is the write target with the projection as its column list, the companion `dept` surfaces as a scanned table read (carried on a new `Insert::target_context`, which gates visibility but feeds no data — table lineage ignores it), and the join ON + WHERE are filter reads over the view's relations (`target_predicate`). The classic comma-join spelling (`FROM emp e, dept d WHERE …`) resolves the same way. No single determined target keeps the previous drop + flag: a non-column projection item, or a column that is ambiguous / unresolved / owned by a different relation than its siblings — shapes real Oracle rejects too (the INTO columns must all belong to one key-preserved table; key-preservedness itself isn't verified — that needs unique-key metadata the catalog doesn't carry). The shape gate (`insert_target_view`, now returning the `Select`) admits joins of plain tables only; `TableReference`'s `TryFrom<&Insert>` stays shape-determined (single-table views only — a join view's base table needs binder resolution). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
takaebato
force-pushed
the
feat/join-view-insert-target
branch
from
July 5, 2026 07:17
93d8c6d to
8f400af
Compare
`InsertTargetView` held both the raw `&Select` and `factors` derived from its FROM — the same information twice, kept consistent only by the gate being the sole constructor (a consumer pairing `factors` with a clause it wasn't derived from would silently misbind). Dissolve `select` into what consumers actually need, all gate-extracted: `projection`, `join_operators` (the constraint carriers — no relation data, so nothing overlaps `factors`), and `selection`. ON-vs-USING interpretation stays in the binder (`join_on`); the binder-side predicate collection collapses to a plain chain over the extracted fields. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A declared CTE only disqualifies the view when the *target* attributes to it: a CTE as a join-view companion resolves (best-effort scan), a top-level CTE consumed by the source traces through its body into relation lineage, and a WITH inside the target view stays gate-rejected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A companion factor naming a declared CTE used to bind as a best-effort base-table scan, surfacing the CTE's name as a phantom table read (and its column references against that name, instead of through the body like a CteRef). A CTE factor makes the view not-a-view-over-base-tables, so the whole statement now flags and drops — the check moves into the factor loop, covering target and companion uniformly (the post-attribution CTE-target check is subsumed and removed). Binding the companion as a real CteRef stays deliberately unbuilt: no engine executes a WITH + inline-view-target INSERT, so the machinery isn't worth it until one does. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merged
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
Implements the remaining inline-view deferral from #58 (design agreed in
discussion): an Oracle join-view INSERT now resolves to its base table by
column attribution, instead of dropping the whole statement.
Attribution rule (composes two existing mechanisms — no new rules)
The row lands in the one relation every projected column agrees on:
e.id)SET t2.colname)unqualified_write_binding, #59) over the view's relationsAny column ambiguous / unresolved / owned by a different relation than its
siblings — or any factor naming a declared CTE (not a base table) — → drop +
flag as before. Real Oracle rejects those shapes too (the INTO columns must
all belong to one key-preserved table); key-preservedness
itself isn't verified — that needs unique-key metadata the catalog doesn't
carry — attribution assumes a valid statement. The classic comma-join
spelling (
FROM emp e, dept d WHERE …) resolves identically.Companion relations:
Insert::target_contextThe joined tables other than the target (here
dept) become scanned context:children()now includes the context, so the scansurfaces;
empitself keeps the sink rule — it reads because its columnsare referenced, not via a scan, consistent with the single-table view path)
feeding_scanswalksinputonly,matching the semantics (the join gates which rows are visible; the value
path is the source)
target_predicate(filter reads over thefull view scope, aliases included)
TableReference'sTryFrom<&Insert>stays shape-determined: the gateadmits joins of plain tables, but a join view's base table needs binder
resolution, so the plain identity parse resolves single-table views only
(documented; join →
Err).Edge cases covered on review
fabricate a write to the CTE) or companion (used to surface the CTE name as
a phantom base-table read) — now flags the whole statement, uniformly in
the factor loop. A WITH inside the view stays gate-rejected; a top-level
CTE consumed only by the source still traces through its body (pinned).
reads — consistent with the SELECT path, where reads come from reference
sites (pinned).
key-preservedness is per-instance and unverifiable without key metadata).
s → emponly, nodept → emp) — pinned at table level.wildcard / expression projections, ClickHouse
TABLE FUNCTIONtargets(moved code) incl. an unrepresentable function name.
data (
InsertTargetView { factors, projection, join_operators, selection }— parse-don't-validate): the unreachable defensive arms are deleted, and
no consumer re-reads the
Query, sofactorscan't be paired with aclause it wasn't derived from.
Tests
Qualified projection (catalog-free), comma form, SELECT-source lineage through
the view (
s.x → emp.id), straddling columns → flag, unqualified without acatalog → flag, unqualified with a catalog attributing by owner
(Oracle-cased catalog), table-level companion read + lineage, USING /
self-join / gate-rejection pins, all CTE placements (factor → flag, inside
the view → flag, top-level source-only → traces through the body), and the
#58 flag test narrowed to an undetermined shape. All gates green (fmt /
clippy /
test --all/ doc); 788 tests, 100% patch coverage on sourcelines (llvm-cov ∩ diff).
🤖 Generated with Claude Code