This is an automated email from the ASF dual-hosted git repository.
englefly pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new bb3156d30e6 [fix](topn) Guard TopN filters across Repeat and
AssertNumRows (#70194)
bb3156d30e6 is described below
commit bb3156d30e678d05a770aa741016b18a85f218e3
Author: minghong <[email protected]>
AuthorDate: Fri Nov 6 11:53:53 2026 +0800
[fix](topn) Guard TopN filters across Repeat and AssertNumRows (#70194)
### What problem does this PR solve?
Issue Number: N/A
Related PR: #70100
Problem Summary:
A TopN runtime filter above `UNION ALL` can reach scans below operators
that synthesize NULLs. Another UNION branch can publish a bound before
those scans finish, causing incorrect results:
- **Grouping sets:** rows rejected on their original grouping key can
still contribute to a grouping set that replaces that key with NULL.
Filtering them before Repeat produces incomplete aggregate counts or
loses the grand-total row. For one million input rows, `ORDER BY k NULLS
FIRST LIMIT 1` can return `(NULL, 1024)` instead of `(NULL, 1000000)`.
- **Scalar subqueries:** filtering before AssertNumRows can remove the
scalar's only row, making it synthesize a NULL row instead. A scalar
result of `20` unioned with `10` can consequently become NULL and win
`ASC NULLS FIRST`; `COALESCE(..., -1)` also fails with `NULLS LAST`.
Filtering can additionally remove multiple rows and suppress a required
cardinality error.
Only push a TopN filter through Repeat when every probe input slot is
common to all grouping sets, using the existing
common-grouping-expression helper. Stop pushdown at AssertNumRows to
preserve its input cardinality and empty-input behavior. Keep filtering
on common grouping keys and unaffected UNION branches.
### Release note
Fix incorrect TopN results for grouping sets and scalar subqueries, and
preserve scalar-subquery cardinality errors when TopN runtime filters
are enabled.
### Check List (For Author)
- Test
- [x] Regression test
- [x] Unit Test
- [x] Manual test
- [ ] No need to test or manual test
Validation:
- `./build.sh --fe -j16` — successful; zero Checkstyle violations.
- `./run-fe-ut.sh --run
org.apache.doris.nereids.postprocess.TopNRuntimeFilterTest` — 12 tests
passed. The new Repeat test covers 36
grouping-set/expression/sort/NULL-order combinations. The Assert test
covers eight combinations and directly visits the assertion, with a
positive control on its child, so it does not depend on CBO's
scalar-join orientation.
- `./run-regression-test.sh --run -d nereids_p0 -s
topn_filter_grouping_sets,topn_filter_assert_num_rows` — two suites
passed, with 35 result checks and eight expected-cardinality-error
checks. Covers GROUPING SETS/ROLLUP/CUBE, common grouping keys, scalar
one-row/empty/multi-row inputs, COALESCE, and both NULL orders. Expected
output was generated by the regression runner.
- The new Assert regression plan assertion fails on the unpatched FE:
the filter has two scan targets instead of only the unaffected UNION
scan.
- Manual reproduction confirmed incomplete grouping-set counts,
synthetic scalar NULLs, and suppressed scalar-cardinality errors before
the corresponding fixes.
Validation used a single-BE native-OLAP cluster. FE was built from this
branch; BE reused an existing ASAN master build (`7acfd9bfc27`).
- Behavior changed:
- [ ] No.
- [x] Yes. Prevent unsafe scan filtering across Repeat and AssertNumRows
while retaining safe targets.
- Does this need documentation?
- [x] No.
- [ ] Yes.
### Check List (For Reviewer who merge this PR)
- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label
---
.../processor/post/TopnFilterPushDownVisitor.java | 20 +++++
.../nereids/postprocess/TopNRuntimeFilterTest.java | 89 ++++++++++++++++++++++
.../nereids_p0/topn_filter_assert_num_rows.out | 49 ++++++++++++
.../data/nereids_p0/topn_filter_grouping_sets.out | 58 ++++++++++++++
.../nereids_p0/topn_filter_assert_num_rows.groovy | 79 +++++++++++++++++++
.../nereids_p0/topn_filter_grouping_sets.groovy | 74 ++++++++++++++++++
6 files changed, 369 insertions(+)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/TopnFilterPushDownVisitor.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/TopnFilterPushDownVisitor.java
index 0efae9d7df8..197f63456c1 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/TopnFilterPushDownVisitor.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/TopnFilterPushDownVisitor.java
@@ -27,6 +27,7 @@ import
org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitors;
import org.apache.doris.nereids.trees.plans.Plan;
import org.apache.doris.nereids.trees.plans.algebra.TopN;
import org.apache.doris.nereids.trees.plans.algebra.Union;
+import org.apache.doris.nereids.trees.plans.physical.PhysicalAssertNumRows;
import org.apache.doris.nereids.trees.plans.physical.PhysicalCTEAnchor;
import org.apache.doris.nereids.trees.plans.physical.PhysicalCTEProducer;
import org.apache.doris.nereids.trees.plans.physical.PhysicalFileScan;
@@ -37,6 +38,7 @@ import
org.apache.doris.nereids.trees.plans.physical.PhysicalOdbcScan;
import org.apache.doris.nereids.trees.plans.physical.PhysicalOlapScan;
import org.apache.doris.nereids.trees.plans.physical.PhysicalProject;
import org.apache.doris.nereids.trees.plans.physical.PhysicalRelation;
+import org.apache.doris.nereids.trees.plans.physical.PhysicalRepeat;
import org.apache.doris.nereids.trees.plans.physical.PhysicalSetOperation;
import org.apache.doris.nereids.trees.plans.physical.PhysicalTopN;
import org.apache.doris.nereids.trees.plans.physical.PhysicalWindow;
@@ -158,6 +160,14 @@ public class TopnFilterPushDownVisitor extends
PlanVisitor<Boolean, PushDownCont
return false;
}
+ @Override
+ public Boolean visitPhysicalAssertNumRows(
+ PhysicalAssertNumRows<? extends Plan> assertNumRows,
PushDownContext ctx) {
+ // Filtering its input can hide a cardinality error or make a scalar
subquery
+ // synthesize a NULL row that did not exist before filtering.
+ return false;
+ }
+
@Override
public Boolean visitPhysicalTopN(PhysicalTopN<? extends Plan> topn,
PushDownContext ctx) {
if (topn.equals(ctx.topn)) {
@@ -166,6 +176,16 @@ public class TopnFilterPushDownVisitor extends
PlanVisitor<Boolean, PushDownCont
return false;
}
+ @Override
+ public Boolean visitPhysicalRepeat(PhysicalRepeat<? extends Plan> repeat,
PushDownContext ctx) {
+ // Repeat erases grouping keys absent from a grouping set to NULL.
Filtering those
+ // keys before Repeat can discard rows that still contribute to the
NULL group.
+ if
(!repeat.getCommonGroupingSetExpressions().containsAll(ctx.probeExpr.getInputSlots()))
{
+ return false;
+ }
+ return repeat.child().accept(this, ctx);
+ }
+
@Override
public Boolean visitPhysicalWindow(PhysicalWindow<? extends Plan> window,
PushDownContext ctx) {
return false;
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/TopNRuntimeFilterTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/TopNRuntimeFilterTest.java
index 63654f74851..837aa1b45eb 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/TopNRuntimeFilterTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/TopNRuntimeFilterTest.java
@@ -21,17 +21,22 @@ import org.apache.doris.nereids.datasets.ssb.SSBTestBase;
import org.apache.doris.nereids.glue.translator.PlanTranslatorContext;
import org.apache.doris.nereids.processor.post.PlanPostProcessors;
import org.apache.doris.nereids.processor.post.TopnFilterContext;
+import org.apache.doris.nereids.processor.post.TopnFilterPushDownVisitor;
+import
org.apache.doris.nereids.processor.post.TopnFilterPushDownVisitor.PushDownContext;
import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.Slot;
import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.Coalesce;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Nullable;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Substring;
import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral;
import org.apache.doris.nereids.trees.plans.Plan;
import org.apache.doris.nereids.trees.plans.SortPhase;
+import org.apache.doris.nereids.trees.plans.physical.PhysicalAssertNumRows;
import
org.apache.doris.nereids.trees.plans.physical.PhysicalLazyMaterializeOlapScan;
import org.apache.doris.nereids.trees.plans.physical.PhysicalPlan;
import org.apache.doris.nereids.trees.plans.physical.PhysicalRelation;
+import org.apache.doris.nereids.trees.plans.physical.PhysicalRepeat;
import org.apache.doris.nereids.trees.plans.physical.PhysicalTopN;
import org.apache.doris.nereids.trees.plans.physical.TopnFilter;
import org.apache.doris.nereids.util.MemoPatternMatchSupported;
@@ -133,6 +138,90 @@ public class TopNRuntimeFilterTest extends SSBTestBase
implements MemoPatternMat
}
}
+ @Test
+ public void testTopNFilterThroughRepeat() {
+ double previousRatio =
connectContext.getSessionVariable().topnFilterRatio;
+ boolean previousPushTopn =
connectContext.getSessionVariable().pushTopnToAgg;
+ String previousDisabledRules = String.join(",",
+
connectContext.getSessionVariable().getDisableNereidsRuleNames());
+ connectContext.getSessionVariable().topnFilterRatio = 1000000;
+ connectContext.getSessionVariable().pushTopnToAgg = false;
+
connectContext.getSessionVariable().setDisableNereidsRules(previousDisabledRules
+ + ",PRUNE_EMPTY_PARTITION,PUSH_DOWN_TOP_N_THROUGH_UNION");
+ try {
+ for (String groupingSets : List.of("((k), ())", "((k), (id))",
"((k, id), (k))")) {
+ for (String orderKey : List.of("k", "k + 1", "coalesce(k,
0)")) {
+ for (String direction : List.of("ASC", "DESC")) {
+ for (String nullOrder : List.of("FIRST", "LAST")) {
+ String sql = "SELECT k, c FROM (SELECT k, count(*)
AS c FROM topn_safety "
+ + "GROUP BY GROUPING SETS " + groupingSets
+ + " UNION ALL SELECT c_custkey, CAST(0 AS
BIGINT) FROM customer) u "
+ + "ORDER BY " + orderKey + " " + direction
+ " NULLS " + nullOrder + " LIMIT 1";
+ PlanChecker checker =
PlanChecker.from(connectContext).analyze(sql).rewrite().implement();
+ PhysicalPlan plan = new
PlanPostProcessors(checker.getCascadesContext())
+ .process(checker.getPhysicalPlan());
+
Assertions.assertTrue(plan.containsType(PhysicalRepeat.class),
plan.treeString());
+ List<TopnFilter> filters =
checker.getCascadesContext()
+ .getTopnFilterContext().getTopnFilters();
+ Assertions.assertFalse(filters.isEmpty(),
plan.treeString());
+ long targetCount = filters.stream().flatMap(filter
-> filter.targets.keySet().stream())
+ .distinct().count();
+ // Only the common grouping key keeps its value
through every Repeat expansion.
+ Assertions.assertEquals(groupingSets.equals("((k,
id), (k))") ? 2 : 1,
+ targetCount, sql + "\n" + filters);
+ }
+ }
+ }
+ }
+ } finally {
+ connectContext.getSessionVariable().topnFilterRatio =
previousRatio;
+ connectContext.getSessionVariable().pushTopnToAgg =
previousPushTopn;
+
connectContext.getSessionVariable().setDisableNereidsRules(previousDisabledRules);
+ }
+ }
+
+ @Test
+ public void testTopNFilterDoesNotCrossAssertNumRows() {
+ double previousRatio =
connectContext.getSessionVariable().topnFilterRatio;
+ String previousDisabledRules = String.join(",",
+
connectContext.getSessionVariable().getDisableNereidsRuleNames());
+ connectContext.getSessionVariable().topnFilterRatio = 1000000;
+
connectContext.getSessionVariable().setDisableNereidsRules(previousDisabledRules
+ + ",PRUNE_EMPTY_PARTITION,PUSH_DOWN_TOP_N_THROUGH_UNION");
+ try {
+ for (String direction : List.of("ASC", "DESC")) {
+ for (boolean nullsFirst : List.of(true, false)) {
+ String sql = "SELECT k FROM (SELECT (SELECT k FROM
topn_safety) AS k "
+ + "UNION ALL SELECT c_custkey FROM customer) u
ORDER BY k " + direction
+ + " NULLS " + (nullsFirst ? "FIRST" : "LAST") + "
LIMIT 1";
+ PlanChecker checker =
PlanChecker.from(connectContext).analyze(sql).rewrite().implement();
+ PhysicalPlan plan = checker.getPhysicalPlan();
+ List<PhysicalAssertNumRows<? extends Plan>> assertions =
plan.collectToList(
+ node -> node instanceof PhysicalAssertNumRows);
+ Assertions.assertEquals(1, assertions.size(),
plan.treeString());
+ PhysicalAssertNumRows<? extends Plan> assertion =
assertions.get(0);
+ List<PhysicalTopN<? extends Plan>> topns =
plan.collectToList(node -> node instanceof PhysicalTopN);
+ Assertions.assertFalse(topns.isEmpty(), plan.treeString());
+ Slot slot = assertion.getOutput().get(0);
+ for (Expression probe : List.of(slot, new Coalesce(slot,
new IntegerLiteral(-1)))) {
+ PushDownContext context = new
PushDownContext(topns.get(0), probe, nullsFirst);
+ TopnFilterContext filters = new TopnFilterContext();
+ TopnFilterPushDownVisitor visitor = new
TopnFilterPushDownVisitor(filters);
+ // Visit Assert directly so the test does not depend
on the scalar join's probe side.
+ Assertions.assertFalse(assertion.accept(visitor,
context), sql);
+
Assertions.assertTrue(filters.getTopnFilters().isEmpty(), sql);
+ // The child accepts this filter; Assert must protect
its row count and empty-row behavior.
+
Assertions.assertTrue(assertion.child().accept(visitor, context), sql);
+
Assertions.assertFalse(filters.getTopnFilters().isEmpty(), sql);
+ }
+ }
+ }
+ } finally {
+ connectContext.getSessionVariable().topnFilterRatio =
previousRatio;
+
connectContext.getSessionVariable().setDisableNereidsRules(previousDisabledRules);
+ }
+ }
+
@Test
public void testTopNFilterSupportsNestedOrderKeyWithSiblingPredicate() {
double previousRatio =
connectContext.getSessionVariable().topnFilterRatio;
diff --git a/regression-test/data/nereids_p0/topn_filter_assert_num_rows.out
b/regression-test/data/nereids_p0/topn_filter_assert_num_rows.out
new file mode 100644
index 00000000000..60fe60d09ba
--- /dev/null
+++ b/regression-test/data/nereids_p0/topn_filter_assert_num_rows.out
@@ -0,0 +1,49 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !enabled --
+10
+
+-- !disabled --
+10
+
+-- !enabled --
+10
+
+-- !disabled --
+10
+
+-- !enabled --
+10
+
+-- !disabled --
+10
+
+-- !enabled --
+10
+
+-- !disabled --
+10
+
+-- !enabled --
+\N
+
+-- !disabled --
+\N
+
+-- !enabled --
+10
+
+-- !disabled --
+10
+
+-- !enabled --
+-1
+
+-- !disabled --
+-1
+
+-- !enabled --
+-1
+
+-- !disabled --
+-1
+
diff --git a/regression-test/data/nereids_p0/topn_filter_grouping_sets.out
b/regression-test/data/nereids_p0/topn_filter_grouping_sets.out
new file mode 100644
index 00000000000..507bd24897a
--- /dev/null
+++ b/regression-test/data/nereids_p0/topn_filter_grouping_sets.out
@@ -0,0 +1,58 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !without_filter --
+\N 1000000
+
+-- !with_filter --
+\N 1000000
+
+-- !without_filter --
+\N 1000000
+
+-- !with_filter --
+\N 1000000
+
+-- !without_filter --
+\N 1000000
+
+-- !with_filter --
+\N 1000000
+
+-- !without_filter --
+\N 1000000
+
+-- !with_filter --
+\N 1000000
+
+-- !without_filter --
+\N 1000000
+
+-- !with_filter --
+\N 1000000
+
+-- !without_filter --
+\N 1000000
+
+-- !with_filter --
+\N 1000000
+
+-- !without_filter --
+\N 1000000
+
+-- !with_filter --
+\N 1000000
+
+-- !without_filter --
+\N 1000000
+
+-- !with_filter --
+\N 1000000
+
+-- !without_filter --
+\N 1000000
+
+-- !with_filter --
+\N 1000000
+
+-- !common_grouping_key --
+20 \N 1000000
+
diff --git
a/regression-test/suites/nereids_p0/topn_filter_assert_num_rows.groovy
b/regression-test/suites/nereids_p0/topn_filter_assert_num_rows.groovy
new file mode 100644
index 00000000000..e6bf784d077
--- /dev/null
+++ b/regression-test/suites/nereids_p0/topn_filter_assert_num_rows.groovy
@@ -0,0 +1,79 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("topn_filter_assert_num_rows") {
+ sql "DROP TABLE IF EXISTS topn_assert_t"
+ sql """CREATE TABLE topn_assert_t (id BIGINT NOT NULL, k INT NULL)
+ DUPLICATE KEY(id) DISTRIBUTED BY HASH(id) BUCKETS 1
+ PROPERTIES("replication_num" = "1")"""
+ sql "DROP TABLE IF EXISTS topn_assert_b"
+ sql """CREATE TABLE topn_assert_b (id BIGINT NOT NULL, k INT NULL)
+ DUPLICATE KEY(id) DISTRIBUTED BY HASH(id) BUCKETS 1
+ PROPERTIES("replication_num" = "1")"""
+ sql "INSERT INTO topn_assert_t SELECT number, 20 FROM numbers('number' =
'1000000')"
+ sql "INSERT INTO topn_assert_b VALUES (0, 10)"
+ // Statistics allow the scalar branch to become the nested loop join's
probe side.
+ sql "ANALYZE TABLE topn_assert_t WITH SYNC"
+ sql "ANALYZE TABLE topn_assert_b WITH SYNC"
+
+ sql "SET enable_sql_cache = false"
+ sql "SET enable_query_cache = false"
+ sql "SET disable_nereids_rules = 'PUSH_DOWN_TOP_N_THROUGH_UNION'"
+ sql "SET force_sort_algorithm = 'heap'"
+ sql "SET batch_size = 1024"
+
+ for (int target : [999999, 1000000]) {
+ for (String expression : ["k", "coalesce(k, -1)"]) {
+ for (String nullOrder : ["FIRST", "LAST"]) {
+ // The scalar returns one row for 999999 and no rows for
1000000.
+ // Filtering its input must not manufacture or suppress its
NULL result.
+ String query = """SELECT ${expression} AS sort_key FROM (
+ SELECT (SELECT k FROM topn_assert_t WHERE abs(id -
${target}) = 0) AS k
+ UNION ALL SELECT k FROM topn_assert_b
+ ) u ORDER BY sort_key NULLS ${nullOrder} LIMIT 1"""
+ sql "SET topn_filter_ratio = 1000000"
+ explain {
+ sql query
+ contains "VASSERT NUMBER OF ROWS"
+ contains "assert number of rows: EQ 1"
+ // Keep the other UNION scan as a target, without reaching
the scalar's scan.
+ multiContains "TOPN OPT:", 1
+ }
+ qt_enabled query
+ sql "SET topn_filter_ratio = 0"
+ qt_disabled query
+ }
+ }
+ }
+
+ for (String expression : ["k", "coalesce(k, -1)"]) {
+ for (String nullOrder : ["FIRST", "LAST"]) {
+ for (int ratio : [0, 1000000]) {
+ sql "SET topn_filter_ratio = ${ratio}"
+ // Both rows must reach Assert: even with NULLS LAST, dropping
them
+ // could turn a required scalar-cardinality error into a
successful query.
+ test {
+ sql """SELECT ${expression} AS sort_key FROM (
+ SELECT (SELECT k FROM topn_assert_t WHERE abs(id -
999999) <= 1) AS k
+ UNION ALL SELECT k FROM topn_assert_b
+ ) u ORDER BY sort_key NULLS ${nullOrder} LIMIT 1"""
+ exception "Expected EQ 1"
+ }
+ }
+ }
+ }
+}
diff --git a/regression-test/suites/nereids_p0/topn_filter_grouping_sets.groovy
b/regression-test/suites/nereids_p0/topn_filter_grouping_sets.groovy
new file mode 100644
index 00000000000..423b16b5335
--- /dev/null
+++ b/regression-test/suites/nereids_p0/topn_filter_grouping_sets.groovy
@@ -0,0 +1,74 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("topn_filter_grouping_sets") {
+ sql "DROP TABLE IF EXISTS topn_repeat_t"
+ sql """CREATE TABLE topn_repeat_t (id BIGINT NOT NULL, k INT NULL)
+ DUPLICATE KEY(id) DISTRIBUTED BY HASH(id) BUCKETS 1
+ PROPERTIES("replication_num" = "1")"""
+ sql "DROP TABLE IF EXISTS topn_repeat_b"
+ sql """CREATE TABLE topn_repeat_b (id BIGINT NOT NULL, k INT NULL)
+ DUPLICATE KEY(id) DISTRIBUTED BY HASH(id) BUCKETS 1
+ PROPERTIES("replication_num" = "1")"""
+ sql "INSERT INTO topn_repeat_t SELECT number, 20 FROM numbers('number' =
'1000000')"
+ sql "INSERT INTO topn_repeat_b VALUES (0, 10)"
+
+ sql "SET enable_sql_cache = false"
+ sql "SET enable_query_cache = false"
+ // Keep a single parent TopN so the small UNION branch can publish a bound
+ // before the other branch has finished reading the aggregate's input.
+ sql "SET disable_nereids_rules = 'PUSH_DOWN_TOP_N_THROUGH_UNION'"
+ sql "SET push_topn_to_agg = false"
+ sql "SET force_sort_algorithm = 'heap'"
+ sql "SET batch_size = 1024"
+ sql "SET topn_filter_ratio = 1000000"
+
+ explain {
+ sql """SELECT k, COUNT(*) FROM topn_repeat_t
+ GROUP BY GROUPING SETS ((k), ()) ORDER BY k NULLS FIRST LIMIT
1"""
+ contains "VREPEAT"
+ notContains "TOPN OPT:"
+ }
+ // A key present in every grouping set can still be filtered at the scan.
+ explain {
+ sql """SELECT k, id, COUNT(*) FROM topn_repeat_t
+ GROUP BY GROUPING SETS ((k, id), (k)) ORDER BY k, id NULLS
FIRST LIMIT 1"""
+ contains "VREPEAT"
+ contains "TOPN OPT:"
+ }
+
+ for (String grouping : ["GROUPING SETS ((k), ())", "ROLLUP(k)",
"CUBE(k)"]) {
+ for (String ordering : ["k ASC NULLS FIRST", "k DESC NULLS FIRST",
"coalesce(k, 0)"]) {
+ String query = """SELECT k, c FROM (
+ SELECT k, COUNT(*) AS c FROM topn_repeat_t GROUP BY
${grouping}
+ UNION ALL
+ SELECT k, CAST(0 AS BIGINT) AS c FROM topn_repeat_b
+ ) u ORDER BY ${ordering} LIMIT 1"""
+ explain {
+ sql query
+ contains "VREPEAT"
+ contains "TOPN OPT:"
+ }
+ sql "SET topn_filter_ratio = 0"
+ qt_without_filter query
+ sql "SET topn_filter_ratio = 1000000"
+ qt_with_filter query
+ }
+ }
+ qt_common_grouping_key """SELECT k, id, COUNT(*) FROM topn_repeat_t
+ GROUP BY GROUPING SETS ((k, id), (k)) ORDER BY k, id NULLS FIRST
LIMIT 1"""
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]