From 01d5901356ce5306841e752d21387779605bb44d Mon Sep 17 00:00:00 2001 From: morrySnow <101034200+morrySnow@users.noreply.github.com> Date: Wed, 25 Oct 2023 18:52:14 +0800 Subject: [PATCH] [fix](Nereids) cte should support nested name reuse (#25858) for example: ```sql with a as (with a as (select * from t) select * from a) select * from a; with a as (select * from t1), b as (with a as (select * from a) select * from a) select * from b; ``` --- .../org/apache/doris/nereids/CTEContext.java | 10 +--- .../nereids/rules/analysis/AnalyzeCTE.java | 23 ++++--- .../nereids_p0/cte/test_cte_name_reuse.out | 10 ++++ .../nereids_p0/cte/test_cte_name_reuse.groovy | 60 +++++++++++++++++++ 4 files changed, 87 insertions(+), 16 deletions(-) create mode 100644 regression-test/data/nereids_p0/cte/test_cte_name_reuse.out create mode 100644 regression-test/suites/nereids_p0/cte/test_cte_name_reuse.groovy diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/CTEContext.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/CTEContext.java index 6bc6787070..9653811492 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/CTEContext.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/CTEContext.java @@ -60,7 +60,8 @@ public class CTEContext { : ImmutableMap.builder() .putAll(previousCteContext.cteContextMap) .put(name, this) - .build(); + // if inner name same with outer name, use inner name in this scope. + .buildKeepingLast(); this.cteId = cteId; } @@ -68,13 +69,6 @@ public class CTEContext { this.analyzedPlan = analyzedPlan; } - /** - * check if cteName can be found in current order - */ - public boolean containsCTE(String cteName) { - return findCTEContext(cteName).isPresent(); - } - /** * Get for CTE reuse. */ diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/AnalyzeCTE.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/AnalyzeCTE.java index a465df6da1..0fe083c1e9 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/AnalyzeCTE.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/AnalyzeCTE.java @@ -34,11 +34,13 @@ import org.apache.doris.nereids.trees.plans.logical.LogicalPlan; import org.apache.doris.nereids.trees.plans.logical.LogicalSubQueryAlias; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; /** * Register CTE, includes checking columnAliases, checking CTE name, analyzing each CTE and store the @@ -53,6 +55,18 @@ public class AnalyzeCTE extends OneAnalysisRuleFactory { return logicalCTE().thenApply(ctx -> { LogicalCTE logicalCTE = ctx.root; + // step 0. check duplicate cte name + Set uniqueAlias = Sets.newHashSet(); + List aliases = logicalCTE.getAliasQueries().stream() + .map(LogicalSubQueryAlias::getAlias) + .collect(Collectors.toList()); + for (String alias : aliases) { + if (uniqueAlias.contains(alias)) { + throw new AnalysisException("CTE name [" + alias + "] cannot be used more than once."); + } + uniqueAlias.add(alias); + } + // step 1. analyzed all cte plan Pair>> result = analyzeCte(logicalCTE, ctx.cascadesContext); CascadesContext outerCascadesCtx = CascadesContext.newContextWithCteContext( @@ -76,17 +90,10 @@ public class AnalyzeCTE extends OneAnalysisRuleFactory { List> aliasQueries = logicalCTE.getAliasQueries(); List> cteProducerPlans = new ArrayList<>(); for (LogicalSubQueryAlias aliasQuery : aliasQueries) { - String cteName = aliasQuery.getAlias(); - if (outerCteCtx.containsCTE(cteName)) { - throw new AnalysisException("CTE name [" + cteName + "] cannot be used more than once."); - } - // we should use a chain to ensure visible of cte - CTEContext innerCteCtx = outerCteCtx; - LogicalPlan parsedCtePlan = (LogicalPlan) aliasQuery.child(); CascadesContext innerCascadesCtx = CascadesContext.newContextWithCteContext( - cascadesContext, parsedCtePlan, innerCteCtx); + cascadesContext, parsedCtePlan, outerCteCtx); innerCascadesCtx.newAnalyzer().analyze(); LogicalPlan analyzedCtePlan = (LogicalPlan) innerCascadesCtx.getRewritePlan(); checkColumnAlias(aliasQuery, analyzedCtePlan.getOutput()); diff --git a/regression-test/data/nereids_p0/cte/test_cte_name_reuse.out b/regression-test/data/nereids_p0/cte/test_cte_name_reuse.out new file mode 100644 index 0000000000..52966eaa68 --- /dev/null +++ b/regression-test/data/nereids_p0/cte/test_cte_name_reuse.out @@ -0,0 +1,10 @@ +-- This file is automatically generated. You should know what you did if you want to edit this +-- !reuse_name_with_other_outer_cte -- +1 1 1 1 + +-- !reuse_name_with_other_outer_cte_and_use_outer_same_name_cte -- +1 1 + +-- !reuse_name_with_self_outer_cte -- +1 1 + diff --git a/regression-test/suites/nereids_p0/cte/test_cte_name_reuse.groovy b/regression-test/suites/nereids_p0/cte/test_cte_name_reuse.groovy new file mode 100644 index 0000000000..5d472716f0 --- /dev/null +++ b/regression-test/suites/nereids_p0/cte/test_cte_name_reuse.groovy @@ -0,0 +1,60 @@ +// 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("test_cte_name_reuse)") { + sql "SET enable_nereids_planner=true" + sql "SET enable_pipeline_engine=true" + sql "SET enable_fallback_to_original_planner=false" + + sql """ + drop table if exists test_cte_name_reuse; + """ + + sql """ + CREATE TABLE `test_cte_name_reuse` ( + `id` int(11) NULL, + `msg` text NULL + ) ENGINE=OLAP + DUPLICATE KEY(`id`) + COMMENT 'OLAP' + DISTRIBUTED BY HASH(`id`) BUCKETS 10 + PROPERTIES ( + "replication_allocation" = "tag.location.default: 1", + "min_load_replica_num" = "-1", + "is_being_synced" = "false", + "storage_format" = "V2", + "light_schema_change" = "true", + "disable_auto_compaction" = "false", + "enable_single_replica_compaction" = "false" + ); + """ + + sql """ + insert into test_cte_name_reuse values(1, "1"); + """ + + qt_reuse_name_with_other_outer_cte """ + with a as (select * from test_cte_name_reuse), b as (with a as (select * from test_cte_name_reuse) select * from a) select * from a, b; + """ + + qt_reuse_name_with_other_outer_cte_and_use_outer_same_name_cte """ + with a as (select * from test_cte_name_reuse), b as (with a as (select * from a) select * from a) select * from b; + """ + + qt_reuse_name_with_self_outer_cte """ + with a as (with a as (select * from test_cte_name_reuse) select * from a) select * from a; + """ +}