| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
`TableFunctionEval::executeImpl` wraps the generated query in a `StorageView`, the same
way the `view` table function does. `InterpreterSelectQuery` therefore resolves a
`StorageView` and its `analyze` lambda calls `StorageView::replaceWithSubquery`. There the
first table expression has no `database_and_table_name`, because the source is a table
function, so a fake `db.table` name has to be synthesized from the table function name.
The hard-coded list of names that resolve to a view knew only `view`, `viewIfPermitted`
and `merge`, so for `eval` no name was produced and the function threw
`LOGICAL_ERROR: Incorrect table expression`. In a debug or sanitizer build
`abortOnFailedAssertion` turns that into SIGABRT; in a release build it is returned to the
client, so a valid `EXPLAIN` is rejected with an internal error.
The AST-based arms of `EXPLAIN AST optimize = 1` and `EXPLAIN SYNTAX` build that
interpreter directly through `ExplainAnalyzedSyntaxVisitor`, so
`EXPLAIN AST optimize = 1 SELECT * FROM eval('SELECT 5 AS x')` was enough to reach it.
Found by the AST fuzzer in `Stress test (arm_debug)` on master while it was running
04512_eval_table_function.sql.
`viewIfPermitted` was added to the same list in the same shape by fbb2e14 when that
table function was introduced. `eval` (7019b6d) did not get the same treatment.
The parser lowercases a table function name before choosing the layer and `EvalLayer`
emits the canonical `eval`, so the exact comparison already covers every spelling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Internal second-model review: adjudication log (click to expand)
Pre-publication review by an independent model (engine: codex; 0 findings), plus my own cold
Severity: ❌ blocker / ⚠️ major / 💡 nit. DISAGREE verdicts carry recorded evidence and are Session id: cron:clickhouse-review-slot-10:20260921-044300 |
Sorry, something went wrong.
|
Workflow [PR], commit [6d36d98] AI ReviewSummaryThis PR adds the missing eval branch in StorageView::replaceWithSubquery, which is the legacy rewrite path used by EXPLAIN AST optimize = 1 and EXPLAIN SYNTAX for StorageView-backed sources. I traced that path through both InterpreterSelectQuery and InterpreterExplainQuery, checked the current StorageView-producing table functions, and did not find any remaining correctness or coverage gaps in the patch. The new stateless test exercises the failing eval cases and keeps a view control case in the same path. Final Verdict
|
Sorry, something went wrong.
Build profile diff (arm_release)Comparing 6d36d98f2 with master d14841253 (stripped binary size, per-symbol sizes and ThinLTO time; compile times per translation unit against the most recent warmup build that recompiled it). ✅ No significant changes. Binary sizesprograms/clickhouse-stripped: smaller than the master baseline by the known offset between the two builds, so the difference is not shown. A delta that differs from the offset by more than 50% of it is shown, in either direction. The official master build is compiled with -g and a pull request build is not, and XRay counts debug instructions towards its instrumentation threshold, so master instruments thousands of functions more and its binary is ~0.4% larger no matter what the pull request does. Compile time of recompiled translation units7 translation units recompiled, 10 s compile time in total, 7 of them have a recent master baseline. |
Sorry, something went wrong.
| SELECT | ||
| countIf(explain ILIKE '%Function eval%') AS table_function_node_left, | ||
| countIf(explain ILIKE '%Identifier x%') AS asterisk_resolved | ||
| FROM (EXPLAIN AST optimize = 1 SELECT * FROM EVAL('SELECT 5 AS x')); |
There was a problem hiding this comment.
@groeneai can you confirm no other issues happen with the eval function for every other explain statement, or sql statement?
Sorry, something went wrong.
There was a problem hiding this comment.
Confirmed, and measured: 80 eval shapes on two binaries built from the same tree with only this line differing, one process per shape, debug builds, so a LOGICAL_ERROR aborts and cannot be missed. Coverage is all 10 ASTExplainQuery::ExplainKind values with their option flags, both AST based arms over non-SELECT and nested tops, and 21 statements with no EXPLAIN at all (full list below).
EXPLAIN kinds, each with its option flags (optimize, graph, oneline, run_query_tree_passes, run_passes, json, indexes, actions, header, compact): AST, SYNTAX, QUERY TREE, PLAN, PIPELINE, ESTIMATE, ANALYZE, WHATIF, TABLE OVERRIDE, CURRENT TRANSACTION.
AST based arms over: INSERT, INSERT INTO FUNCTION, CREATE TABLE AS SELECT, CREATE VIEW AS SELECT, CREATE MATERIALIZED VIEW AS SELECT, UNION, CTE, IN, scalar subquery, subquery, JOIN with eval on either or both sides, comma join, DISTINCT with GROUP BY and ORDER BY, FINAL, SAMPLE, PREWHERE, ARRAY JOIN, eval nested in view / viewIfPermitted / remote, eval inside eval, eval((SELECT ...)), query level SETTINGS, and the fuzzer query from the CI fatal log. Controls: the same shapes with view, merge and numbers.
Without EXPLAIN: SELECT, count, ordered multi row read, JOIN, IN, CTE, subquery, INSERT SELECT, CREATE TABLE AS SELECT, CREATE VIEW then SELECT from it, CREATE MATERIALIZED VIEW, DESCRIBE, remote, cluster, eval nested in view and viewIfPermitted, expression context, FINAL, PREWHERE, and the experimental setting turned off.
Aborting on master, passing with this PR:
EXPLAIN AST optimize = 1 SELECT * FROM eval('SELECT 5 AS x');
EXPLAIN AST graph = 1, optimize = 1 SELECT * FROM eval('SELECT 5 AS x');
EXPLAIN AST graph = 1, optimize = 1 SELECT DISTINCT * FROM eval((SELECT toLowCardinality('SELECT 5 AS lc') LIMIT 539));
EXPLAIN AST optimize = 1 INSERT INTO t SELECT * FROM eval('SELECT 5 AS x');
EXPLAIN SYNTAX INSERT INTO t SELECT * FROM eval('SELECT 5 AS x');
EXPLAIN AST optimize = 1 CREATE TABLE t ENGINE = Memory AS SELECT * FROM eval('SELECT 5 AS x');
EXPLAIN SYNTAX CREATE TABLE t ENGINE = Memory AS SELECT * FROM eval('SELECT 5 AS x');
EXPLAIN AST optimize = 1 CREATE VIEW v AS SELECT * FROM eval('SELECT 5 AS x');
EXPLAIN AST optimize = 1 SELECT * FROM eval('SELECT 5 AS x') UNION ALL SELECT 6 AS x;
EXPLAIN AST optimize = 1 SELECT * FROM (SELECT * FROM eval('SELECT 5 AS x'));
EXPLAIN AST optimize = 1 WITH c AS (SELECT * FROM eval('SELECT 5 AS x')) SELECT * FROM c;
EXPLAIN AST optimize = 1 SELECT * FROM eval('SELECT 5 AS x') WHERE x IN (SELECT * FROM eval('SELECT 5 AS y'));
EXPLAIN AST optimize = 1 SELECT * FROM eval('SELECT 5 AS x') AS a JOIN numbers(1) AS n ON 1;
EXPLAIN AST optimize = 1 SELECT * FROM numbers(1) AS n JOIN eval('SELECT 5 AS x') AS a ON 1;
EXPLAIN AST optimize = 1 SELECT * FROM eval('SELECT 5 AS x') AS a JOIN eval('SELECT 5 AS x') AS b USING (x);
EXPLAIN AST optimize = 1 SELECT * FROM eval('SELECT 5 AS x') AS a, eval('SELECT 6 AS y') AS b;
EXPLAIN AST optimize = 1 SELECT DISTINCT x FROM eval('SELECT 5 AS x') GROUP BY x ORDER BY x LIMIT 1;
EXPLAIN AST optimize = 1 SELECT * FROM eval('SELECT 5 AS x') FINAL;
EXPLAIN AST optimize = 1 SELECT * FROM eval('SELECT 5 AS x') SAMPLE 1 / 2;
EXPLAIN AST optimize = 1 SELECT * FROM eval('SELECT 5 AS x') PREWHERE x > 1;
EXPLAIN AST optimize = 1 SELECT a FROM eval('SELECT [1, 2] AS arr') ARRAY JOIN arr AS a;
EXPLAIN AST optimize = 1 SELECT * FROM eval((SELECT 'SELECT 5 AS x'));
EXPLAIN AST optimize = 1 CREATE MATERIALIZED VIEW mv ENGINE = Memory AS SELECT * FROM eval('SELECT 5 AS x');
EXPLAIN AST optimize = 1 SELECT (SELECT count() FROM eval('SELECT 5 AS x'));
EXPLAIN AST optimize = 1 INSERT INTO FUNCTION null('x UInt8') SELECT * FROM eval('SELECT 5 AS x');
SELECT count() FROM (EXPLAIN AST optimize = 1 SELECT * FROM eval('SELECT 5 AS x'));
EXPLAIN AST optimize = 1 SELECT * FROM eval('SELECT 5 AS x') SETTINGS max_threads = 1;Tell me if you want any of these pinned in the test, I kept it to five cases for a one line fix.
Sorry, something went wrong.
There was a problem hiding this comment.
@groeneai Kept it to five cases for a one line fix.
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Changelog category (leave one):
Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):
Fixed LOGICAL_ERROR: Incorrect table expression when EXPLAIN AST optimize = 1 or EXPLAIN SYNTAX reads from the experimental table function eval.
Description
EXPLAIN AST optimize = 1 SELECT * FROM eval('SELECT 5 AS x') failed with
LOGICAL_ERROR: Incorrect table expression: a SIGABRT in a debug or sanitizer build, an internal
error returned to the client in a release build. Reachable with
allow_experimental_eval_table_function = 1 on 26.7, 26.8 and 26.9.
TableFunctionEval::executeImpl wraps the generated query in a StorageView, as the view table
function does, so InterpreterSelectQuery calls StorageView::replaceWithSubquery. There the first
table expression has no database_and_table_name, the source being a table function, so a fake
db.table name must be synthesized from the function name. The list of view-producing names held
only view, viewIfPermitted and merge, so eval got none and the function threw.
fbb2e14d543f4 closed the same one-line gap for viewIfPermitted. The AST-based arms of
EXPLAIN AST optimize = 1 and EXPLAIN SYNTAX build that interpreter directly, which is how an
ordinary query reaches it.
Found by the AST fuzzer on master while it was running 04512_eval_table_function.sql, CIDB
test_name Logical error: Incorrect table expression (STID: 3510-55f2), first occurrence in 180
days: Stress test (arm_debug) on sha a1cfa117f551,
report
and job.
No open issue exists.
Validated in both directions with a deterministic repro whose stack matches CI's frame for frame,
100/100 runs of the new test with and without settings randomization, and an A/B over 259 existing
view / parameterized-view / EXPLAIN tests with identical failure sets.
After the rewrite EXPLAIN SYNTAX still prints FROM eval(...), as it already does for view.
Workflow [PR]
Sync PR [sync-upstream/pr/121272]