Skip to content

Commit d44d4e0

Browse files
Plan the filter-analysis dummy query to the real processing stage
`collectFiltersForAnalysis` built its throwaway plan with default `SelectQueryOptions` (stage `Complete`). On a shard executing at `WithMergeableState` this speculatively planned the IN-set subqueries above the aggregation boundary, which the real execution never plans; a nested distributed read inside such a subquery tripped the `max_distributed_depth` check while planning a plan that is discarded and never executed (`TOO_LARGE_DISTRIBUTED_DEPTH`). Thread the caller's processing stage into the dummy planner so it plans exactly what the real query plans. Predicates that exist only above the stage boundary no longer reach the dummy plan; they previously could contribute shard/part pruning for nested distributed reads, and that contribution is forgone. Results are unchanged. Related: ClickHouse#98754
1 parent 380a72a commit d44d4e0

3 files changed

Lines changed: 79 additions & 3 deletions

File tree

src/Planner/Planner.cpp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,12 @@ void checkStoragesSupportTransactions(const PlannerContextPtr & planner_context)
240240
* 4. Extract filters from ReadFromDummy query plan steps from query plan leaf nodes.
241241
*/
242242

243-
FiltersForTableExpressionMap collectFiltersForAnalysis(const QueryTreeNodePtr & query_tree, const QueryTreeNodes & table_nodes, const ContextPtr & query_context, const ActionsDAG * post_filter)
243+
FiltersForTableExpressionMap collectFiltersForAnalysis(
244+
const QueryTreeNodePtr & query_tree,
245+
const QueryTreeNodes & table_nodes,
246+
const ContextPtr & query_context,
247+
const ActionsDAG * post_filter,
248+
QueryProcessingStage::Enum to_stage)
244249
{
245250
bool collect_filters = false;
246251
const auto & settings = query_context->getSettingsRef();
@@ -321,7 +326,14 @@ FiltersForTableExpressionMap collectFiltersForAnalysis(const QueryTreeNodePtr &
321326
dummy_storage_to_table.emplace(dummy_storage, from_table_expression);
322327
}
323328

324-
SelectQueryOptions select_query_options;
329+
/// Plan the dummy query to the same processing stage as the real query.
330+
/// A default (Complete) stage would plan constructs the real execution
331+
/// never reaches - e.g. on a shard executing at WithMergeableState, the
332+
/// IN-set subqueries above the aggregation boundary - and anything that
333+
/// throws while planning them (such as the `max_distributed_depth` check
334+
/// for a nested distributed read) would fail the query for a plan that is
335+
/// discarded and never executed.
336+
SelectQueryOptions select_query_options(to_stage);
325337
Planner planner(updated_query_tree, select_query_options);
326338
planner.buildQueryPlanIfNeeded();
327339

@@ -410,7 +422,7 @@ FiltersForTableExpressionMap collectFiltersForAnalysis(const QueryTreeNodePtr &
410422
auto table_expressions_nodes
411423
= extractTableExpressions(query_tree_node, false /* add_array_join */, true /* recursive */);
412424

413-
return collectFiltersForAnalysis(query_tree_node, table_expressions_nodes, context, post_filter);
425+
return collectFiltersForAnalysis(query_tree_node, table_expressions_nodes, context, post_filter, select_query_options.to_stage);
414426
}
415427

416428
/// Extend lifetime of query context, storages, and table locks
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
a 0.025
2+
a 0.025
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
-- Tags: shard
2+
3+
-- Filter-pushdown pre-analysis (`collectFiltersForAnalysis`) builds a dummy plan
4+
-- for queries whose join tree contains a `View`. It must be planned to the same
5+
-- processing stage as the real query: on a shard executing at `WithMergeableState`
6+
-- a `Complete`-stage dummy plan would plan the IN-set subquery above the
7+
-- aggregation boundary, which the real execution never plans, and the nested
8+
-- distributed read inside it tripped the `max_distributed_depth` check during
9+
-- planning of a plan that is discarded.
10+
11+
DROP TABLE IF EXISTS t_04932;
12+
DROP VIEW IF EXISTS v_04932;
13+
14+
CREATE TABLE t_04932 (k String, m String, v Float64) ENGINE = MergeTree ORDER BY k;
15+
CREATE VIEW v_04932 AS SELECT * FROM t_04932;
16+
INSERT INTO t_04932 VALUES ('a', 'm1', 5), ('a', 'm2', 200);
17+
18+
SET max_distributed_depth = 1;
19+
20+
-- Shared CTE over a distributed read of a view, referenced again from a
21+
-- non-GLOBAL IN subquery. Both shards read the same table, so sums double;
22+
-- the ratio is scale-invariant.
23+
WITH c1 AS
24+
(
25+
SELECT k, m, sum(v) AS v
26+
FROM remote('127.0.0.{1,2}', currentDatabase(), v_04932)
27+
GROUP BY k, m
28+
),
29+
c2 AS
30+
(
31+
SELECT k, sumIf(v, m = 'm1') / sumIf(v, m = 'm2') AS r
32+
FROM c1
33+
GROUP BY k
34+
)
35+
SELECT k, r
36+
FROM c2
37+
WHERE k IN (SELECT k FROM c2 GROUP BY k HAVING max(r) > 0.001)
38+
ORDER BY k;
39+
40+
-- The identical query under a permissive depth cap must give the same result:
41+
-- the depth setting must not affect results, only (real) dispatch limits.
42+
SET max_distributed_depth = 5;
43+
44+
WITH c1 AS
45+
(
46+
SELECT k, m, sum(v) AS v
47+
FROM remote('127.0.0.{1,2}', currentDatabase(), v_04932)
48+
GROUP BY k, m
49+
),
50+
c2 AS
51+
(
52+
SELECT k, sumIf(v, m = 'm1') / sumIf(v, m = 'm2') AS r
53+
FROM c1
54+
GROUP BY k
55+
)
56+
SELECT k, r
57+
FROM c2
58+
WHERE k IN (SELECT k FROM c2 GROUP BY k HAVING max(r) > 0.001)
59+
ORDER BY k;
60+
61+
DROP VIEW v_04932;
62+
DROP TABLE t_04932;

0 commit comments

Comments
 (0)