fix(sql): fix UNION ALL column mismatch under aggregates - #7210
Conversation
An aggregate over a UNION ALL of aliased sub-queries (e.g. count() or sum() over branches that project "close price, timestamp mts") crashed code generation with an AssertionError, surfacing as a 500 in the web console and a failure when getting a column type. propagateTopDownColumns0() emitted a model's columns to its UNION sibling by name. The optimizer's column-propagation machinery resolves literals against the target's alias map, so across a UNION boundary it only matches columns whose source name equals the sibling's alias. The emit also fires only for the union head at non-top-level, so it reaches just the head's immediate sibling. When the outer query selects nothing from the union, this pruned that one branch down to the few matching-by-name columns while leaving the other branches intact. The union sides then diverged in column count and checkIfSetCastIsRequired() tripped its assertion. UNION columns match by position, not name. The indexed-by-position propagation added in #6744 already handles cross-branch propagation correctly, so this removes the superseded by-name emit. For same-alias unions the two are equivalent; for differently-aliased unions the by-name emit was silently wrong. LATEST ON is not essential to the bug -- it just forces the CHOOSE-wrapper branch structure that keeps the aliases distinct and routes through this path. Add regression tests for count() and sum() over a UNION ALL of aliased sub-queries: directly and through a VIEW using LATEST ON branches (the original report), plus a minimal two-branch case with no LATEST ON that isolates the actual trigger -- alias divergence between branches. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Code review (automated, level 2)Multi-agent review across correctness + caller-impact, performance + adversarial-performance, test coverage, and code-quality/conventions. Each finding was verified against source before reporting. Concurrency / resource / Rust / cross-context passes had no surface on a single-threaded, no-Rust, no-resource compile-path removal. CriticalNone. ModerateNone outstanding. One coverage gap was found and fixed within this commit (see Minor #2). Minor — both found independently by two agents, both fixed in this commit
Downgraded (false positives)None. Both deep agents reached NO ISSUE / NO REGRESSION on the fix, with structural and empirical backing:
Summary
🤖 Generated with Claude Code |
Add cairo.sql.legacy.union.column.propagation (default false) to restore the pre-fix by-name column emit to UNION siblings, as an operational rollback for the optimizer change. EntPropServerConfiguration and EntCairoConfigurationWrapper extend the OSS classes, so enterprise servers honor the flag with no extra wiring; the interface getter is a default method, so no other implementer needs changes. Simplify the view test DDL to create one table and clone the rest with CREATE TABLE ... (LIKE ...), matching the original reproducer. Add a test that flips the flag on and asserts the previously-fixed query crashes again, guarding the switch against silently becoming a no-op. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address level-3 review findings, all test-side: - testAggregateOverUnionAllLegacyFlagReintroducesCrash now resets the flag in a finally block. Test overrides are otherwise cleared only at @afterclass and JUnit method order is not fixed, so the enabled flag could leak into later UnionTest methods and crash them. Also narrow the catch to AssertionError so the test cannot pass on an unrelated Exception (e.g. a SQL typo). - Register cairo.sql.legacy.union.column.propagation in ServerMainTest.testShowParameters, which asserts an exhaustive list of every property; the new key would otherwise fail it. - Unroll the CREATE TABLE ... (LIKE ...) loop in ViewQueryTest into three explicit statements. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Code review (automated, level 3 — full mission-critical pass)10 agents (correctness, concurrency, performance, resource-management, tests, code-quality, PR-metadata, cross-context/enterprise, fresh-context adversarial, adversarial-performance; Rust agent skipped — no All findings below were fixed in 892d580. Critical
Moderate
Minor
Downgraded (false positives)None — the agents were disciplined. Notable non-issues they verified rather than assumed: concurrency (final field, safe publication, correctly excluded from the dynamic-reload set); performance (net compile-path win — Summary
🤖 Generated with Claude Code |
|
/azp run macwin |
|
Azure Pipelines successfully started running 1 pipeline(s). |
[PR Coverage check]😍 pass : 15 / 15 (100.00%) file detail
|
Brings the latest OSS bug fixes from the enterprise main branch's pinned OSS base (779676d) into our OSS sm_dag_startup branch: parquet/latest-by/ ASOF/count fixes (#7195), SHOW CREATE TABLE view rejection (#7208), UNION ALL column mismatch under aggregates (#7210), count() over GROUP BY subquery (#7202), and the posting-index partition-squash fix (#7203). Clean auto-merge, no conflicts. Not pushed.
Problem
An aggregate (
count(),sum(), ...) over aUNION ALLof aliased sub-queries crashed query compilation with anAssertionError, which surfaced as a 500 in the web console and a "failure when getting column type" error. Minimal reproducer:It was originally reported through a view built as a
UNION ALLofLATEST ONsub-queries, butLATEST ONis incidental — it only forces the per-branch CHOOSE-wrapper structure that keeps the projection aliases distinct.Root cause
SqlOptimiser.propagateTopDownColumns0()emitted a model's columns to itsUNIONsibling by name. The optimizer's column-propagation machinery resolves literals against the target's alias map, so across aUNIONboundary it only matches columns whose source name happens to equal the sibling's alias. The emit also fires only for the union head at non-top-level, so it reaches just the head's immediate sibling.When the outer query selects nothing from the union (an aggregate), this pruned that one branch down to the few matching-by-name columns while leaving the other branches intact. The branches then disagreed on column count, and
SqlCodeGenerator.checkIfSetCastIsRequired()tripped itscolumnCount == metadataB.getColumnCount()assertion.Fix
UNIONcolumns are matched by position, not name. The indexed, by-position propagation added in #6744 already handles cross-branch column propagation correctly, so this removes the superseded by-name emit.Rollback flag
cairo.sql.legacy.union.column.propagation(defaultfalse) restores the old by-name emit, as an operational rollback if the optimizer change has an unforeseen effect on a workload.EntPropServerConfigurationandEntCairoConfigurationWrapperextend the OSS classes, so enterprise servers honor the flag with no extra wiring; the interface getter is adefaultmethod, so no other config implementer needs changes.Tradeoffs and risk
SqlParserTestmodel assertions, which print each branch's pruned column list, are unchanged — query plans are stable.Tests
UnionTest.testAggregateOverUnionAllOfLatestOn—count()andsum()over a 4-branchUNION ALLofLATEST ONsub-queries.UnionTest.testAggregateOverUnionAllWithAliasMismatch— minimal 2-branch case with noLATEST ON, isolating alias divergence as the trigger.UnionTest.testAggregateOverUnionAllLegacyFlagReintroducesCrash— with the rollback flag on, the otherwise-fixed query fails again, guarding the switch against becoming a no-op.ViewQueryTest.testAggregateOverUnionAllOfLatestOnView— the original report, through aCREATE VIEW(one table plusCREATE TABLE ... (LIKE ...)clones).AssertionError) and pass with it.🤖 Generated with Claude Code