Skip to content

fix: don't surface a ClickHouse ARRAY JOIN operand as a table read#57

Merged
takaebato merged 1 commit into
masterfrom
fix/array-join-operand-not-a-table
Jul 4, 2026
Merged

fix: don't surface a ClickHouse ARRAY JOIN operand as a table read#57
takaebato merged 1 commit into
masterfrom
fix/array-join-operand-not-a-table

Conversation

@takaebato

@takaebato takaebato commented Jul 4, 2026

Copy link
Copy Markdown
Owner

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

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

✅ PR title follows the Conventional Commits spec.

@codecov

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.77%. Comparing base (b0eb0dd) to head (a12b033).

Files with missing lines Patch % Lines
sql-insight/src/resolver/binder/query.rs 95.23% 2 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##           master      #57   +/-   ##
=======================================
  Coverage   94.76%   94.77%           
=======================================
  Files          27       27           
  Lines        5542     5590   +48     
  Branches     5542     5590   +48     
=======================================
+ Hits         5252     5298   +46     
- Misses        212      214    +2     
  Partials       78       78           

☔ 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 fix/array-join-operand-not-a-table branch from 6c96097 to 0a6cec5 Compare July 4, 2026 08:43
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 takaebato force-pushed the fix/array-join-operand-not-a-table branch from 0a6cec5 to a12b033 Compare July 4, 2026 08:51
@takaebato takaebato merged commit 9d76891 into master Jul 4, 2026
14 checks passed
@takaebato takaebato deleted the fix/array-join-operand-not-a-table branch July 4, 2026 09:03
@github-actions github-actions Bot mentioned this pull request Jul 4, 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