This is an automated email from the ASF dual-hosted git repository. huajianlan 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 2757b4bf7ec [chore](nereids) Added compatibility with mysql alias conflict (#38104) 2757b4bf7ec is described below commit 2757b4bf7ec7b92616ac95e24f924c036ad6bc84 Author: toms <94617906+toms1...@users.noreply.github.com> AuthorDate: Fri Jul 26 22:16:36 2024 +0800 [chore](nereids) Added compatibility with mysql alias conflict (#38104) throw table name/alias conflict exception to keep same behavior with mysql for example: ```sql select * from test.a b, test.b ``` error: ``` Not unique table/alias: 'b' ``` --- .../nereids/rules/analysis/BindExpression.java | 46 ++++ .../rules/analysis/BindSlotReferenceTest.java | 40 ++- .../data/nereids_syntax_p0/alias_conflict.out | 29 +++ .../limit_push_down/limit_push_down.groovy | 2 +- .../suites/nereids_syntax_p0/alias_conflict.groovy | 271 +++++++++++++++++++++ 5 files changed, 376 insertions(+), 12 deletions(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java index 15f8dabc8f7..db39d661d3e 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java @@ -69,6 +69,7 @@ import org.apache.doris.nereids.trees.plans.algebra.Aggregate; import org.apache.doris.nereids.trees.plans.algebra.SetOperation; import org.apache.doris.nereids.trees.plans.algebra.SetOperation.Qualifier; import org.apache.doris.nereids.trees.plans.logical.LogicalAggregate; +import org.apache.doris.nereids.trees.plans.logical.LogicalCatalogRelation; import org.apache.doris.nereids.trees.plans.logical.LogicalExcept; import org.apache.doris.nereids.trees.plans.logical.LogicalFilter; import org.apache.doris.nereids.trees.plans.logical.LogicalGenerate; @@ -104,6 +105,7 @@ import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; +import com.google.common.collect.Sets; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; @@ -116,6 +118,7 @@ import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -508,6 +511,8 @@ public class BindExpression implements AnalysisRuleFactory { LogicalJoin<Plan, Plan> join = ctx.root; CascadesContext cascadesContext = ctx.cascadesContext; + checkConflictAlias(join); + SimpleExprAnalyzer analyzer = buildSimpleExprAnalyzer( join, cascadesContext, join.children(), true, true); @@ -532,6 +537,47 @@ public class BindExpression implements AnalysisRuleFactory { join.children(), null); } + private void checkConflictAlias(Plan plan) { + Set<String> existsTableNames = Sets.newLinkedHashSet(); + Consumer<String> checkAlias = tableAliasName -> { + if (!existsTableNames.add(tableAliasName)) { + String tableName = tableAliasName.substring(tableAliasName.lastIndexOf('.') + 1); + throw new AnalysisException("Not unique table/alias: '" + tableName + "'"); + } + }; + + boolean stopCheckChildren = true; + plan.foreach(p -> { + if (p instanceof LogicalSubQueryAlias) { + String alias = ((LogicalSubQueryAlias<?>) p).getAlias(); + String dbName = getDbName(p.children().get(0)); + String result = dbName + "." + alias; + checkAlias.accept(result); + return stopCheckChildren; + + } else if (p instanceof LogicalCatalogRelation) { + String table = ((LogicalCatalogRelation) p).qualifiedName(); + checkAlias.accept(table); + return stopCheckChildren; + } else { + return !stopCheckChildren; + } + }); + } + + private String getDbName(Plan plan) { + if (plan instanceof LogicalCatalogRelation) { + return ((LogicalCatalogRelation) plan).qualifiedName().split("\\.")[0] + + ((LogicalCatalogRelation) plan).qualifiedName().split("\\.")[1]; + } else if (plan instanceof LogicalSubQueryAlias) { + return ((LogicalSubQueryAlias<?>) plan).getQualifier().get(0) + + ((LogicalSubQueryAlias<?>) plan).getQualifier().get(1); + + } else { + return "default-catalog" + "default-db"; + } + } + private LogicalJoin<Plan, Plan> bindUsingJoin(MatchingContext<UsingJoin<Plan, Plan>> ctx) { UsingJoin<Plan, Plan> using = ctx.root; CascadesContext cascadesContext = ctx.cascadesContext; diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/BindSlotReferenceTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/BindSlotReferenceTest.java index 097bf79c051..1d5369228a7 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/BindSlotReferenceTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/BindSlotReferenceTest.java @@ -28,6 +28,7 @@ import org.apache.doris.nereids.trees.plans.JoinType; import org.apache.doris.nereids.trees.plans.logical.LogicalAggregate; import org.apache.doris.nereids.trees.plans.logical.LogicalJoin; import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan; +import org.apache.doris.nereids.trees.plans.logical.LogicalPlan; import org.apache.doris.nereids.trees.plans.logical.LogicalProject; import org.apache.doris.nereids.trees.plans.logical.LogicalSubQueryAlias; import org.apache.doris.nereids.util.MemoTestUtils; @@ -40,6 +41,8 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.util.List; + class BindSlotReferenceTest { @BeforeEach @@ -58,11 +61,18 @@ class BindSlotReferenceTest { @Test public void testAmbiguousSlot() { - LogicalOlapScan scan1 = new LogicalOlapScan(StatementScopeIdGenerator.newRelationId(), PlanConstructor.student); - LogicalOlapScan scan2 = new LogicalOlapScan(StatementScopeIdGenerator.newRelationId(), PlanConstructor.student); - LogicalJoin<LogicalOlapScan, LogicalOlapScan> join = new LogicalJoin<>( - JoinType.CROSS_JOIN, scan1, scan2, null); - LogicalProject<LogicalJoin<LogicalOlapScan, LogicalOlapScan>> project = new LogicalProject<>( + String qualifiedName = "internal.db.student"; + List<String> qualifier = ImmutableList.copyOf(qualifiedName.split("\\.")); + LogicalOlapScan scan1 = new LogicalOlapScan(StatementScopeIdGenerator.newRelationId(), PlanConstructor.student, + qualifier); + LogicalOlapScan scan2 = new LogicalOlapScan(StatementScopeIdGenerator.newRelationId(), PlanConstructor.student, + qualifier); + LogicalSubQueryAlias<LogicalOlapScan> aliasedScan2 = new LogicalSubQueryAlias<>("scan2_alias", scan2); + + LogicalJoin<LogicalPlan, LogicalPlan> join = new LogicalJoin<>( + JoinType.CROSS_JOIN, scan1, aliasedScan2, null); + + LogicalProject<LogicalPlan> project = new LogicalProject<>( ImmutableList.of(new UnboundSlot("id")), join); AnalysisException exception = Assertions.assertThrows(AnalysisException.class, @@ -73,14 +83,18 @@ class BindSlotReferenceTest { } /* - select t1.id from student t1 join on student t2 on t1.di=t2.id group by id; + select t1.id from student t1 join student t2 on t1.id=t2.id group by id; group_by_key bind on t1.id, not t2.id */ @Test public void testGroupByOnJoin() { - LogicalOlapScan scan1 = new LogicalOlapScan(StatementScopeIdGenerator.newRelationId(), PlanConstructor.student); + String qualifiedName = "internal.db.student"; + List<String> qualifier = ImmutableList.copyOf(qualifiedName.split("\\.")); + LogicalOlapScan scan1 = new LogicalOlapScan(StatementScopeIdGenerator.newRelationId(), PlanConstructor.student, + qualifier); LogicalSubQueryAlias<LogicalOlapScan> sub1 = new LogicalSubQueryAlias<>("t1", scan1); - LogicalOlapScan scan2 = new LogicalOlapScan(StatementScopeIdGenerator.newRelationId(), PlanConstructor.student); + LogicalOlapScan scan2 = new LogicalOlapScan(StatementScopeIdGenerator.newRelationId(), PlanConstructor.student, + qualifier); LogicalSubQueryAlias<LogicalOlapScan> sub2 = new LogicalSubQueryAlias<>("t2", scan2); LogicalJoin<LogicalSubQueryAlias<LogicalOlapScan>, LogicalSubQueryAlias<LogicalOlapScan>> join = new LogicalJoin<>(JoinType.CROSS_JOIN, sub1, sub2, null); @@ -100,14 +114,18 @@ class BindSlotReferenceTest { } /* - select count(1) from student t1 join on student t2 on t1.di=t2.id group by id; + select count(1) from student t1 join student t2 on t1.di=t2.id group by id; group by key is ambiguous */ @Test public void testGroupByOnJoinAmbiguous() { - LogicalOlapScan scan1 = new LogicalOlapScan(StatementScopeIdGenerator.newRelationId(), PlanConstructor.student); + String qualifiedName = "internal.db.student"; + List<String> qualifier = ImmutableList.copyOf(qualifiedName.split("\\.")); + LogicalOlapScan scan1 = new LogicalOlapScan(StatementScopeIdGenerator.newRelationId(), PlanConstructor.student, + qualifier); LogicalSubQueryAlias<LogicalOlapScan> sub1 = new LogicalSubQueryAlias<>("t1", scan1); - LogicalOlapScan scan2 = new LogicalOlapScan(StatementScopeIdGenerator.newRelationId(), PlanConstructor.student); + LogicalOlapScan scan2 = new LogicalOlapScan(StatementScopeIdGenerator.newRelationId(), PlanConstructor.student, + qualifier); LogicalSubQueryAlias<LogicalOlapScan> sub2 = new LogicalSubQueryAlias<>("t2", scan2); LogicalJoin<LogicalSubQueryAlias<LogicalOlapScan>, LogicalSubQueryAlias<LogicalOlapScan>> join = new LogicalJoin<>(JoinType.CROSS_JOIN, sub1, sub2, null); diff --git a/regression-test/data/nereids_syntax_p0/alias_conflict.out b/regression-test/data/nereids_syntax_p0/alias_conflict.out new file mode 100644 index 00000000000..497d8c8269c --- /dev/null +++ b/regression-test/data/nereids_syntax_p0/alias_conflict.out @@ -0,0 +1,29 @@ +-- This file is automatically generated. You should know what you did if you want to edit this +-- !select_normal -- +1 + +-- !catalog_normal -- + +-- !view_normal -- + +-- !select_no_conflict -- +1 a 12 1 a 12 + +-- !select_no_conflict -- +1 a 12 1 a 12 + +-- !select_diff_alias -- +1 a 12 1 a 12 + +-- !select_nested_no_conflict -- +1 a 12 1 a 12 + +-- !select_cross_db_no_conflict -- +1 a 12 1 a 12 + +-- !child_query_no_conflict1 -- +1 1 a 12 + +-- !child_query_no_conflict2 -- +1 a 12 1 a 12 + diff --git a/regression-test/suites/nereids_rules_p0/limit_push_down/limit_push_down.groovy b/regression-test/suites/nereids_rules_p0/limit_push_down/limit_push_down.groovy index 00ebb05ab6e..3aec22fa928 100644 --- a/regression-test/suites/nereids_rules_p0/limit_push_down/limit_push_down.groovy +++ b/regression-test/suites/nereids_rules_p0/limit_push_down/limit_push_down.groovy @@ -207,5 +207,5 @@ suite("limit_push_down") { qt_limit_right_outer_join_full_outer_join_cross_join """explain shape plan SELECT t1.id FROM t1 RIGHT OUTER JOIN t2 ON t1.id = t2.id FULL OUTER JOIN t3 ON t1.id = t3.id CROSS JOIN t4 LIMIT 1;""" // `limit 1, left outer join, right outer join, full outer join, cross join`: - qt_limit_left_outer_join_right_outer_join_full_outer_join_cross_join """explain shape plan SELECT * FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.id RIGHT OUTER JOIN t3 ON t1.id = t3.id FULL OUTER JOIN t4 ON t1.id = t4.id inner JOIN t4 on TRUE LIMIT 1;""" + qt_limit_left_outer_join_right_outer_join_full_outer_join_cross_join """explain shape plan SELECT * FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.id RIGHT OUTER JOIN t3 ON t1.id = t3.id FULL OUTER JOIN t4 ON t1.id = t4.id inner JOIN t4 as x on TRUE LIMIT 1;""" } \ No newline at end of file diff --git a/regression-test/suites/nereids_syntax_p0/alias_conflict.groovy b/regression-test/suites/nereids_syntax_p0/alias_conflict.groovy new file mode 100644 index 00000000000..a6bc70387fe --- /dev/null +++ b/regression-test/suites/nereids_syntax_p0/alias_conflict.groovy @@ -0,0 +1,271 @@ +// 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("alias_conflict") { + + sql """ DROP TABLE IF EXISTS `test_alias_conflict1` """ + sql """ DROP TABLE IF EXISTS `test_alias_conflict2` """ + sql """ DROP TABLE IF EXISTS `test_alias_conflict3` """ + sql """ DROP DATABASE IF EXISTS `alias_conflict1` """ + sql """ DROP DATABASE IF EXISTS `alias_conflict2` """ + sql """ DROP CATALOG IF EXISTS `jdbc_alias_conflict` """ + + + sql """ CREATE DATABASE IF NOT EXISTS `alias_conflict1` """ + sql """ CREATE DATABASE IF NOT EXISTS `alias_conflict2` """ + + sql """ + CREATE TABLE `test_alias_conflict1` ( + `id` varchar(64) NULL, + `name` varchar(64) NULL, + `age` int NULL + ) ENGINE=OLAP + DUPLICATE KEY(`id`,`name`) + COMMENT 'OLAP' + DISTRIBUTED BY HASH(`id`,`name`) BUCKETS 4 + PROPERTIES ( + "replication_allocation" = "tag.location.default: 1", + "in_memory" = "false", + "storage_format" = "V2", + "disable_auto_compaction" = "false" + ); + """ + + sql """ + CREATE TABLE `test_alias_conflict2` ( + `id` varchar(64) NULL, + `name` varchar(64) NULL, + `age` int NULL + ) ENGINE=OLAP + DUPLICATE KEY(`id`,`name`) + COMMENT 'OLAP' + DISTRIBUTED BY HASH(`id`,`name`) BUCKETS 5 + PROPERTIES ( + "replication_allocation" = "tag.location.default: 1", + "in_memory" = "false", + "storage_format" = "V2", + "disable_auto_compaction" = "false" + ); + """ + + sql """ + CREATE TABLE `test_alias_conflict3` ( + `id` varchar(64) NULL, + `name` varchar(64) NULL, + `age` int NULL + ) ENGINE=OLAP + DUPLICATE KEY(`id`,`name`) + COMMENT 'OLAP' + DISTRIBUTED BY HASH(`id`,`name`) BUCKETS 6 + PROPERTIES ( + "replication_allocation" = "tag.location.default: 1", + "in_memory" = "false", + "storage_format" = "V2", + "disable_auto_compaction" = "false" + ); + """ + + sql """ + CREATE TABLE `alias_conflict1`.`test_alias_conflict1` ( + `id` varchar(64) NULL, + `name` varchar(64) NULL, + `age` int NULL + ) ENGINE=OLAP + DUPLICATE KEY(`id`,`name`) + COMMENT 'OLAP' + DISTRIBUTED BY HASH(`id`,`name`) BUCKETS 3 + PROPERTIES ( + "replication_allocation" = "tag.location.default: 1", + "in_memory" = "false", + "storage_format" = "V2", + "disable_auto_compaction" = "false" + ); + """ + + sql """ + CREATE TABLE `alias_conflict1`.`test_alias_conflict2` ( + `id` varchar(64) NULL, + `name` varchar(64) NULL, + `age` int NULL + ) ENGINE=OLAP + DUPLICATE KEY(`id`,`name`) + COMMENT 'OLAP' + DISTRIBUTED BY HASH(`id`,`name`) BUCKETS 3 + PROPERTIES ( + "replication_allocation" = "tag.location.default: 1", + "in_memory" = "false", + "storage_format" = "V2", + "disable_auto_compaction" = "false" + ); + """ + + sql """ + CREATE TABLE `alias_conflict1`.`test_alias_conflict8` ( + `id` varchar(64) NULL, + `name` varchar(64) NULL, + `age` int NULL + ) ENGINE=OLAP + DUPLICATE KEY(`id`,`name`) + COMMENT 'OLAP' + DISTRIBUTED BY HASH(`id`,`name`) BUCKETS 3 + PROPERTIES ( + "replication_allocation" = "tag.location.default: 1", + "in_memory" = "false", + "storage_format" = "V2", + "disable_auto_compaction" = "false" + ); + """ + + sql """ + CREATE TABLE `alias_conflict2`.`test_alias_conflict1` ( + `id` varchar(64) NULL, + `name` varchar(64) NULL, + `age` int NULL + ) ENGINE=OLAP + DUPLICATE KEY(`id`,`name`) + COMMENT 'OLAP' + DISTRIBUTED BY HASH(`id`,`name`) BUCKETS 3 + PROPERTIES ( + "replication_allocation" = "tag.location.default: 1", + "in_memory" = "false", + "storage_format" = "V2", + "disable_auto_compaction" = "false" + ); + """ + + sql """insert into test_alias_conflict1 values('1','a',12);""" + sql """insert into test_alias_conflict2 values('1','a',12);""" + sql """insert into test_alias_conflict3 values('1','a',12);""" + sql """insert into alias_conflict1.test_alias_conflict1 values('1','a',12);""" + sql """insert into alias_conflict2.test_alias_conflict1 values('1','a',12);""" + + //create view + sql """create view alias_conflict2.test_alias_conflict8 as select * from alias_conflict1.test_alias_conflict8;""" + + //create catalog + sql """ + CREATE CATALOG jdbc_alias_conflict properties( + 'type'='jdbc', + 'user'='${context.config.jdbcUser}', + 'password'='${context.config.jdbcPassword}', + 'jdbc_url' = '${context.config.jdbcUrl}', + 'driver_url' = 'https://repo1.maven.org/maven2/com/mysql/mysql-connector-j/8.4.0/mysql-connector-j-8.4.0.jar', + 'driver_class' = 'com.mysql.cj.jdbc.Driver' + ); + """ + + + // Valid query + qt_select_normal """select t3.id from test_alias_conflict1 t1 inner join test_alias_conflict2 t2 on true inner join test_alias_conflict3 t3 on t3.id = t2.id;""" + + qt_catalog_normal """select * from internal.alias_conflict1.test_alias_conflict2, jdbc_alias_conflict.alias_conflict2.test_alias_conflict1;""" + + qt_view_normal """select * from alias_conflict2.test_alias_conflict8, alias_conflict1.test_alias_conflict8""" + + // Test for alias conflict + test { + sql "select * from test_alias_conflict1 t, test_alias_conflict1 t;" + exception "Not unique table/alias: 't'" + } + + // Test for table name conflict + test { + sql "select * from test_alias_conflict1 t1, test_alias_conflict2 t1;" + exception "Not unique table/alias: 't1'" + } + + + + // Test for view name conflict + test { + sql "select * from alias_conflict2.test_alias_conflict8 t1, alias_conflict2.test_alias_conflict1 t1;" + exception "Not unique table/alias: 't1'" + } + + // Test for view name conflict + test { + sql "select * from (select * from alias_conflict2.test_alias_conflict8) t1, (select 100 id) t1;" + exception "Not unique table/alias: 't1'" + } + + // Test for more table conflicts + test { + sql "select * from test_alias_conflict1, test_alias_conflict1 b, test_alias_conflict1 c, test_alias_conflict1" + exception "Not unique table/alias: 'test_alias_conflict1'" + } + + test { + sql """select * from test_alias_conflict1 + join test_alias_conflict1 b on test_alias_conflict1.id = b.id + join test_alias_conflict1 c on b.id = c.id + join test_alias_conflict1 on true""" + exception "Not unique table/alias: 'test_alias_conflict1'" + } + + // Complex query with alias conflict + test { + sql "select * from (select * from test_alias_conflict1) a, (select * from test_alias_conflict1) a;" + exception "Not unique table/alias: 'a'" + } + + // Complex query with alias conflict + test { + sql "select 1 from (select 1 from test_alias_conflict1 where 1 = 1 group by 1 order by 1 limit 1 ) a, (select 1 from test_alias_conflict1 where 1 = 1 group by 1 order by 1 limit 1 ) a;" + exception "Not unique table/alias: 'a'" + } + + // Test for no conflict + qt_select_no_conflict """select * from test_alias_conflict1 t1, test_alias_conflict2 t2 where t1.id = t2.id;""" + + qt_select_no_conflict "select * from alias_conflict1.test_alias_conflict1, alias_conflict2.test_alias_conflict1;" + + + // Test case where alias are different + qt_select_diff_alias """select * from test_alias_conflict2 a, test_alias_conflict2 b;""" + + // Test case where aliases conflict within subqueries should not raise error + qt_select_nested_no_conflict """select * from + ( + select * from test_alias_conflict1 a + ) b + join + ( + select * from test_alias_conflict1 a + ) c + on b.id = c.id;""" + + + + // Test case for cross database table names with no conflict + qt_select_cross_db_no_conflict """select * from alias_conflict1.test_alias_conflict1 a, alias_conflict2.test_alias_conflict1 b where a.id = b.id;""" + + + qt_child_query_no_conflict1 """select * from (select 1) as no_conflict1_tbl, alias_conflict1.test_alias_conflict1 as no_conflict1_tbl;""" + + + qt_child_query_no_conflict2 """select * from (select * from alias_conflict1.test_alias_conflict1) as no_conflict1_tbl2, alias_conflict1.test_alias_conflict1 as no_conflict1_tbl2""" + + sql """ DROP TABLE IF EXISTS `test_alias_conflict1` """ + sql """ DROP TABLE IF EXISTS `test_alias_conflict2` """ + sql """ DROP TABLE IF EXISTS `test_alias_conflict3` """ + sql """ DROP TABLE IF EXISTS `alias_conflict1`.`test_alias_conflict1` """ + sql """ DROP TABLE IF EXISTS `alias_conflict2`.`test_alias_conflict1` """ + sql """ DROP DATABASE IF EXISTS `alias_conflict1` """ + sql """ DROP DATABASE IF EXISTS `alias_conflict2` """ + sql """ DROP CATALOG IF EXISTS `jdbc_alias_conflict` """ +} + --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org