Skip to content

docs(clickhouse): coverage gap audit for the last 4 months of ClickHouse changes (25 prioritized items) - #10

Merged
fm4v merged 1 commit into
mainfrom
nik/clickhouse-4month-coverage-gap-audit
Aug 15, 2026
Merged

docs(clickhouse): coverage gap audit for the last 4 months of ClickHouse changes (25 prioritized items)#10
fm4v merged 1 commit into
mainfrom
nik/clickhouse-4month-coverage-gap-audit

Conversation

@fm4v

@fm4v fm4v commented Aug 15, 2026

Copy link
Copy Markdown
Member

What this is

A research/backlog plan auditing every ClickHouse change merged between 2026-04-15 and 2026-08-15 against what this fork can actually generate and assert today, ranked by expected bug-finding value. It is the successor to docs/plans/2026-06-13-001-feat-clickhouse-coverage-backlog-30-ideas-plan.md, whose 30 items have all shipped as 29 oracles.

One file, no code: docs/plans/2026-08-15-001-feat-clickhouse-4month-coverage-gap-plan.md.

25 coverage items plus 2 prerequisite fixes. Each entry carries goal, ClickHouse surface with links, the invariant or emission, bug class, evidence that the gap is real, soundness risk, file targets, effort, verification, so any single entry can be promoted to an implementation plan on its own.

Why now: a measured miss

On 2026-08-13 the SQLancerPP job (10 minutes of upstream SQLancer, general --database-engine CLICKHOUSE) found a silent wrong result that this fork's 98 oracles have never surfaced:

CREATE TABLE t (c1 Int32) ENGINE = MergeTree ORDER BY c1;
INSERT INTO t VALUES (0);
INSERT INTO t VALUES (100);

SELECT c1, (NOT (NOT c1)) <= 3.14 FROM t;            -- predicate is 1 for BOTH rows
SELECT count() FROM t WHERE (NOT (NOT c1)) <= 3.14;  -- 1, must be 2
SELECT count() FROM t WHERE (c1 != 0) <= 3.14;       -- 2, correct

EXPLAIN indexes = 1 prints Condition: (c1 in (-Inf, 3]). In KeyCondition.cpp the name == "not" branch of cloneDAGWithInversionPushDown treats not as purely logical, flipping need_inversion and recursing while ignoring the boolean_context flag, so the two flips cancel and NOT NOT c1 becomes bare c1. For a non-Bool column that is unsound: NOT NOT c1 means c1 != 0 and lives in {0, 1}, so the comparison against 3.14 is universally true, and the derived range prunes any part whose rows all lie above 3. Wrong on 24.8.14.39, 25.8.29.51, 26.3.12.3, 26.6.2.160 and head 26.8.1.1424, so long standing rather than a regression.

ClickHouseKeyConditionOracle would have caught this on the first iteration that emitted the shape. The grammar never emits the shape. A repo-wide grep returns zero hits for NOT (NOT, IS TRUE, IS FALSE, IS UNKNOWN, IS NOT DISTINCT FROM, ESCAPE and indexHint, while ClickHouse shipped or extended index pruning for most of those shapes inside this very window. Item 1 closes it, and Appendix C promotes the lesson to a rule: anything the upstream job finds and this fork does not is a grammar gap by definition and goes straight to P0.

The underlying ClickHouse bug is not yet filed.

The three structural patterns behind every gap

  1. Sound oracles starved by a narrow grammar. The oracle set is mature; the predicate, FROM-list and DDL grammars lag it. Items 1, 4, 7, 8, 10, 15, 17, 18, 21 are generator-only changes that light up oracles which already exist and are already validated.
  2. Deliberate float avoidance has become a blind spot. The rule "exact-integer aggregates and non-float group keys" is still right for aggregate oracles, but it has been over-applied. ClickHouse has an open cluster of wrong-result bugs whose entire trigger is NaN plus a negated float comparison feeding part, granule or statistics pruning (#113417, #112036, #106533, plus the unmerged fix #107074). A pruning oracle is NaN-safe by construction: same query, index on versus off, row sets compared as multisets, no float ever summed. Item 2.
  3. Whole young subsystems are dark. Plan-based parallel replicas, make_distributed_plan, serialize_query_plan, the DPhyp and DPsub join enumerators, the new statistics types, pipe operators, IEJoin, GROUPS window frames and the second wave of text-index work all landed in this window. Several already have open wrong-result issues filed by other sources.

Prerequisite fixes, from the same triage

Two of the five nightly failure families are our own false positives, and both are checklist violations, so they land before new surface is added:

  • 0a. ComparatorHelper.trimTrailingDotZeros normalises '0.0' into '0', so checkLimitByCap sees one String key twice and reports a cap violation that does not exist. Replay against head shows uniqExact(c0) = count() = 10000 and LIMIT 1 BY returning exactly 10000 rows. Fix: assert the cap server-side.
  • 0b. The engine pool picked ReplacingMergeTree() ORDER BY c0 with c0 Bool and no ver column, so a background merge collapsed 7 visible rows to 2 between the two TLP queries (91 = 7x13, 26 = 2x13). Fix: reject degenerate-domain dedupe keys, or always emit the ver argument.

Full triage of the five runs behind this plan

Runs 2026-08-01, 08-04, 08-07, 08-10 and 08-13, all on CH 26.8.1.1 asan+ubsan. Every reproducer was replayed against fresh clickhouse/clickhouse-server:head 26.8.1.1424 on the dev VM.

Family Runs Verdict
LimitRanking LIMIT-BY cap violation 08-04, 08-07 our false positive, fix 0a
TLPWhere: result sets mismatch (91 and 26) 08-07 our false positive, fix 0b
Sorted vs plain UNION ALL diverged under outer LIMIT_BY 08-10 known bug ClickHouse/ClickHouse#106125, open
LOGICAL_ERROR: Left and right columns have same names, server abort all 5 known bug ClickHouse/ClickHouse#114113, open
PP WHERE oracle result sets mismatch (9 and 7) 08-13 new, unfiled, item 1

Two side findings recorded in the plan:

  • A strictly better repro for Wrong results: SummingMergeTree FINAL drops a present row when reading only a summation column that is 0 for it (read-in-order + column pruning) ClickHouse#106125 than the one on the issue: with mini (k UInt32, v_nonzero Int32, v_zero UInt8) ENGINE = SummingMergeTree ORDER BY k and two identical inserts of (1,100,0),(2,200,0), SELECT k, v_nonzero, v_zero FROM mini FINAL returns 2 rows while SELECT count() FROM mini FINAL returns 0, and both become correct after OPTIMIZE TABLE ... FINAL. Query-time FINAL applies the zero-row-deletion rule over only the columns the query reads, so reading no columns drops every row.
  • LOGICAL_ERROR (Code 49) in a three-way join with a VIEW ClickHouse#114113 aborts the sanitizer server during the first PP oracle in 3 of 5 runs, costing that job its remaining 2 to 3 oracles and producing 100 junk Failed to create any table reproducer files. Roughly 75 percent of the PP job's runtime is wasted on every nightly. Restarting the server between oracles in ci/jobs/sqlancer_pp_job.sh recovers it independently of the upstream fix, and item 4 notes the error must be pinned in ClickHouseErrors once the fork starts generating that shape.

Priorities

# Item Kind Pri Effort
0a Fix LimitRanking cap assertion (server-side) Fix P0 S
0b Fix degenerate ReplacingMergeTree dedupe key Fix P0 S
1 Boolean-position and three-valued predicate forms Gen P0 S
2 NaN-aware negated-comparison pruning oracle Oracle P0 M
3 Parallel-replicas / distributed-plan equivalence Oracle P0 M
4 Multi-table joins containing a VIEW Gen P0 S
5 Join-order algorithm sweep (greedy/DPhyp/DPsub) Oracle P0 S
6 Codec roundtrip oracle Oracle+Gen P0 M
7 to 18 GROUPS frames, negative LIMIT, pipe operators, IEJoin, new statistics types, query condition cache poisoning, text index second wave, Tuple summing engines, sparse columns, comparison-chain rewrites, indexHint, mixed-direction sort keys mixed P1 S to M
19 to 25 MV lifecycle, ALTER surface, projections with WHERE, Nullable(Tuple), QBit, AT TIME ZONE, continuous queries and what-if indexes mixed P2 S to L

Suggested order (Appendix A): 0a and 0b, then item 1, then item 4 before item 3 (the view-in-join emission is the prerequisite for item 3's strongest positive control, ClickHouse/ClickHouse#111727), then items 2 and 11 together since they share a positive control, and item 15 early despite being P1 because it is S effort, changes no assertion, and lights up every existing pruning, count and FINAL oracle for free.

Method, and how to re-run it

  • Change inventory from qa_intelligence: merged, non-backport, non-cherry-pick, non-sync PRs in the window. 150 new_feature, 58 experimental, and the semantics-relevant subset of 468 improvement plus 269 performance. Both SQL queries are in the doc.
  • Blind-spot ranking: open, human-filed issues from the same window whose titles carry wrong-result vocabulary, grouped by component. Joins 9, DataLake 8, MergeTree 7, Functions 7, Optimizer 7, S3Queue 6, Mutations 5, ParallelReplicas 4. That ranking is why item 3 is P0.
  • Capability sweep: a regex pass over src/sqlancer/clickhouse/**/*.java for every setting name, clause, type and function the inventory names. An item only enters the plan if the sweep shows zero or clearly partial coverage, and each entry names the file that would have to change so the claim stays checkable.
  • Appendix C documents the whole procedure so the next window can be audited the same way.

Scope discipline

Appendix B lists what was deliberately excluded and why, with volumes: AI functions, WASM UDFs, Web UI and terminal, Keeper, PromQL, Arrow Flight, data lakes, message queues, formats, backup, crash-durability and roughly 80 pure-performance PRs. Data lakes (8 open wrong-results) and message queues (6) are called out as the strongest candidates for a separate plan with an external fixture rather than gaps in this one.

Every one of the 179 referenced PRs and issues was verified via the GitHub API to exist and to use the correct pull or issues path.

Every item inherits a 9-rule cross-cutting soundness checklist, which is this fork's accumulated false-positive ledger. Both prerequisite fixes are violations of rules that predate the checklist.

…tems)

Backlog plan auditing every ClickHouse change merged in the last four
months against what this fork can generate and assert today, ranked by
expected bug-finding value. Successor to the 2026-06-13 30-ideas plan,
whose items have all shipped.

Method: 150 new_feature + 58 experimental + 737 semantics-relevant
improvement/performance PRs from qa_intelligence for the window;
open human-filed wrong-result issues from the same window grouped by
component as ground truth for where other sources find bugs we do not;
a regex capability sweep over src/sqlancer/clickhouse for every setting,
clause, type and function the change inventory names; and a finding-by-
finding triage of the five NightlySQLancer runs from 2026-08-01 to
08-13 with every reproducer replayed against head 26.8.1.1424.

The plan is grounded in a measured miss. On 2026-08-13 the SQLancerPP
job (10 minutes of upstream SQLancer) found a silent wrong result this
fork's 98 oracles have never surfaced: NOT (NOT key) in value position
collapses to the bare key in KeyCondition, deriving a bogus key range
and pruning parts that hold matching rows. Wrong on 24.8 through head,
so long standing rather than a regression. ClickHouseKeyConditionOracle
would have caught it on the first iteration that emitted the shape; the
grammar never emits it. Item 1 closes that, and Appendix C makes the
rule general: anything the upstream job finds and this fork does not is
a grammar gap and goes straight to P0.

Two of the five triaged nightly families are our own false positives,
and both are landed here as prerequisite fixes 0a and 0b because they
cost triage time on every run: ComparatorHelper.trimTrailingDotZeros
normalises '0.0' to '0' and breaks the LIMIT BY per-key cap assertion,
and the engine pool accepts a Bool sorting key for ReplacingMergeTree
so visible cardinality changes mid-iteration.

Every one of the 179 referenced PRs and issues was verified to exist
with the correct pull/issues path. Each entry carries goal, ClickHouse
surface with links, invariant or emission, bug class, evidence that the
gap is real, soundness risk, file targets, effort and verification, so
any single entry can be promoted to an implementation plan.
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