Describe the bug
The planner's automatic build-side swap (query_plan_join_swap_table, default auto) changes the result of an ANY INNER JOIN with OR-ed conditions in ON. On identical single-part tables, single-threaded, the swapped plan deterministically returns 6 rows and the unswapped plan 3 rows — the swapped output is a strict superset. EXPLAIN actions = 1 shows both plans keep Type: inner | Strictness: any | Algorithm: HashJoin while the table read order flips, so the ANY deduplication is applied to opposite sides. With a single equality condition (no OR) both plans agree, so the multi-disjunct key path is the trigger.
How to reproduce
CREATE TABLE l (a Int32, b Int32) ENGINE = MergeTree ORDER BY a;
CREATE TABLE r (a Int32, b Int32) ENGINE = MergeTree ORDER BY a;
INSERT INTO l SELECT number % 8, number % 3 FROM numbers(12);
INSERT INTO r SELECT number % 5, number % 8 FROM numbers(12);
SELECT * FROM l ANY JOIN r ON l.b = r.b OR l.a = r.a ORDER BY ALL
SETTINGS max_threads = 1, query_plan_join_swap_table = 'true'; -- 6 rows
SELECT * FROM l ANY JOIN r ON l.b = r.b OR l.a = r.a ORDER BY ALL
SETTINGS max_threads = 1, query_plan_join_swap_table = 'false'; -- 3 rows
Outputs, stable across 20/20 runs per arm (clickhouse local, one part per table, max_threads = 1):
-- swap 'true' (and the default 'auto', which swaps here):
0 0 0 0
0 2 0 2
1 1 1 1
2 2 2 7
3 0 3 3
4 1 4 4
-- swap 'false':
0 0 0 0
0 2 0 2
1 1 1 1
Whatever row choice ANY is permitted to make per key, the two plans do not even agree on the number of emitted rows, and the divergence is controlled purely by a performance plan setting. Note also that both outputs emit only a small subset of the left keys that have matches (for example l = (2, 1) matches r = (4, 1) via b but is emitted by neither plan), so the intended ANY + OR semantics may be off in both plans — the deterministic swap-controlled difference is the unambiguous part.
Affected versions
Reproduced on master 26.8.1.1068 and on 26.7. Not bisected.
Related
Related: #112640 (the ANY LEFT JOIN + OR face of the swap: racy under max_threads > 1; this INNER face is deterministic and single-threaded)
Related: #111645
Found by an automatic optimizer-testing framework (differential testing of optimizer settings, query plans, and equivalent rewrites).
Describe the bug
The planner's automatic build-side swap (
query_plan_join_swap_table, defaultauto) changes the result of anANY INNER JOINwith OR-ed conditions inON. On identical single-part tables, single-threaded, the swapped plan deterministically returns 6 rows and the unswapped plan 3 rows — the swapped output is a strict superset.EXPLAIN actions = 1shows both plans keepType: inner | Strictness: any | Algorithm: HashJoinwhile the table read order flips, so theANYdeduplication is applied to opposite sides. With a single equality condition (noOR) both plans agree, so the multi-disjunct key path is the trigger.How to reproduce
Outputs, stable across 20/20 runs per arm (
clickhouse local, one part per table,max_threads = 1):Whatever row choice
ANYis permitted to make per key, the two plans do not even agree on the number of emitted rows, and the divergence is controlled purely by a performance plan setting. Note also that both outputs emit only a small subset of the left keys that have matches (for examplel = (2, 1)matchesr = (4, 1)viabbut is emitted by neither plan), so the intendedANY+ORsemantics may be off in both plans — the deterministic swap-controlled difference is the unambiguous part.Affected versions
Reproduced on master 26.8.1.1068 and on 26.7. Not bisected.
Related
Related: #112640 (the
ANY LEFT JOIN+ORface of the swap: racy undermax_threads > 1; thisINNERface is deterministic and single-threaded)Related: #111645
Found by an automatic optimizer-testing framework (differential testing of optimizer settings, query plans, and equivalent rewrites).