-
Notifications
You must be signed in to change notification settings - Fork 3.8k
[fix](mv) Align extra-join elimination safety check #62527
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -100,6 +100,105 @@ void testLOJWithUK() throws Exception { | |
| dropConstraint("alter table T2 drop constraint uk"); | ||
| } | ||
|
|
||
| @Test | ||
| void testLOJWithUKAndOtherJoinConjuncts() throws Exception { | ||
| connectContext.getSessionVariable().setDisableNereidsRules("INFER_PREDICATES,PRUNE_EMPTY_PARTITION"); | ||
| CascadesContext c1 = createCascadesContext( | ||
| "select * from T1", | ||
| connectContext | ||
| ); | ||
| Plan p1 = PlanChecker.from(c1) | ||
| .analyze() | ||
| .rewrite() | ||
| .getPlan().child(0); | ||
| addConstraint("alter table T2 add constraint uk_other_join_conjunct unique (id)"); | ||
| try { | ||
| CascadesContext c2 = createCascadesContext( | ||
| "select * from T1 left outer join T2 " | ||
| + "on T1.id = T2.id and T2.id = 1", | ||
| connectContext | ||
| ); | ||
| Plan p2 = PlanChecker.from(c2) | ||
| .analyze() | ||
| .rewrite() | ||
| .applyExploration(RuleSet.BUSHY_TREE_JOIN_REORDER) | ||
| .getAllPlan().get(0).child(0); | ||
| HyperGraph h1 = HyperGraph.builderForMv(p1).build(); | ||
| HyperGraph h2 = HyperGraph.builderForMv(p2).build(); | ||
| ComparisonResult res = HyperGraphComparator.isLogicCompatible(h1, h2, constructContext(p1, p2, c1)); | ||
| Assertions.assertFalse(res.isInvalid()); | ||
| Assertions.assertTrue(res.getViewExpressions().isEmpty()); | ||
| } finally { | ||
| dropConstraint("alter table T2 drop constraint uk_other_join_conjunct"); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The added tests only cover rejection paths plus the helper method. They never assert the positive regression described in the PR body: a unique right side with Because this patch is specifically about relaxing |
||
| void testLOJWithUKAndFilterOnEliminatedNode() throws Exception { | ||
| connectContext.getSessionVariable().setDisableNereidsRules("INFER_PREDICATES,PRUNE_EMPTY_PARTITION"); | ||
| CascadesContext c1 = createCascadesContext( | ||
| "select T1.id from T1", | ||
| connectContext | ||
| ); | ||
| Plan p1 = PlanChecker.from(c1) | ||
| .analyze() | ||
| .rewrite() | ||
| .getPlan().child(0); | ||
| addConstraint("alter table T2 add constraint uk_loj_filter unique (id)"); | ||
| try { | ||
| CascadesContext c2 = createCascadesContext( | ||
| "select T1.id from T1 left outer join T2 " | ||
| + "on T1.id = T2.id where T2.id is null", | ||
| connectContext | ||
| ); | ||
| Plan p2 = PlanChecker.from(c2) | ||
| .analyze() | ||
| .rewrite() | ||
| .applyExploration(RuleSet.BUSHY_TREE_JOIN_REORDER) | ||
| .getAllPlan().get(0).child(0); | ||
| HyperGraph h1 = HyperGraph.builderForMv(p1).build(); | ||
| HyperGraph h2 = HyperGraph.builderForMv(p2).build(); | ||
| ComparisonResult res = HyperGraphComparator.isLogicCompatible(h1, h2, constructContext(p1, p2, c1)); | ||
| Assertions.assertTrue(res.isInvalid()); | ||
| } finally { | ||
| dropConstraint("alter table T2 drop constraint uk_loj_filter"); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| void testInnerJoinWithPKFKAndSlotFreeFilterOnEliminatedNode() throws Exception { | ||
| connectContext.getSessionVariable().setDisableNereidsRules("INFER_PREDICATES,PRUNE_EMPTY_PARTITION"); | ||
| CascadesContext c1 = createCascadesContext( | ||
| "select * from T1", | ||
| connectContext | ||
| ); | ||
| Plan p1 = PlanChecker.from(c1) | ||
| .analyze() | ||
| .rewrite() | ||
| .getPlan().child(0); | ||
| addConstraint("alter table T2 add constraint pk_slot_free_filter primary key (id)"); | ||
| addConstraint("alter table T1 add constraint fk_slot_free_filter foreign key (id) references T2(id)"); | ||
| try { | ||
| CascadesContext c2 = createCascadesContext( | ||
| "select * from T1 inner join (select * from T2 where 1 = 0) T2 " | ||
| + "on T1.id = T2.id", | ||
| connectContext | ||
| ); | ||
| Plan p2 = PlanChecker.from(c2) | ||
| .analyze() | ||
| .rewrite() | ||
| .applyExploration(RuleSet.BUSHY_TREE_JOIN_REORDER) | ||
| .getAllPlan().get(0).child(0); | ||
| HyperGraph h1 = HyperGraph.builderForMv(p1).build(); | ||
| HyperGraph h2 = HyperGraph.builderForMv(p2).build(); | ||
| ComparisonResult res = HyperGraphComparator.isLogicCompatible(h1, h2, constructContext(p1, p2, c1)); | ||
| Assertions.assertTrue(res.isInvalid()); | ||
| } finally { | ||
| dropConstraint("alter table T1 drop constraint fk_slot_free_filter"); | ||
| dropConstraint("alter table T2 drop constraint pk_slot_free_filter"); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| void testLOJWithPKFK() throws Exception { | ||
| connectContext.getSessionVariable().setDisableNereidsRules("INFER_PREDICATES,PRUNE_EMPTY_PARTITION"); | ||
|
|
@@ -143,6 +242,36 @@ void testLOJWithPKFK() throws Exception { | |
| dropConstraint("alter table T2 drop constraint pk"); | ||
| } | ||
|
|
||
| @Test | ||
| void testInnerJoinWithPKFKAndMultiNodeResidualFilter() throws Exception { | ||
| connectContext.getSessionVariable().setDisableNereidsRules("INFER_PREDICATES,PRUNE_EMPTY_PARTITION"); | ||
| CascadesContext c1 = createCascadesContext( | ||
| "select * from T1", | ||
| connectContext | ||
| ); | ||
| Plan p1 = PlanChecker.from(c1) | ||
| .analyze() | ||
| .rewrite() | ||
| .getPlan().child(0); | ||
| addConstraint("alter table T2 add constraint pk primary key (id)"); | ||
| addConstraint("alter table T1 add constraint fk foreign key (id) references T2(id)"); | ||
| CascadesContext c2 = createCascadesContext( | ||
| "select * from T1 inner join T2 " | ||
| + "on T1.id = T2.id where T1.score > T2.score", | ||
| connectContext | ||
| ); | ||
| Plan p2 = PlanChecker.from(c2) | ||
| .analyze() | ||
| .rewrite() | ||
| .applyExploration(RuleSet.BUSHY_TREE_JOIN_REORDER) | ||
| .getAllPlan().get(0).child(0); | ||
| HyperGraph h1 = HyperGraph.builderForMv(p1).build(); | ||
| HyperGraph h2 = HyperGraph.builderForMv(p2).build(); | ||
| ComparisonResult res = HyperGraphComparator.isLogicCompatible(h1, h2, constructContext(p1, p2, c1)); | ||
| Assertions.assertTrue(res.isInvalid()); | ||
| dropConstraint("alter table T2 drop constraint pk"); | ||
| } | ||
|
|
||
| @Disabled | ||
| @Test | ||
| void testLOJWithPKFKAndUK1() throws Exception { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| -- This file is automatically generated. You should know what you did if you want to edit this | ||
| -- !join_elim_filter_edge -- | ||
| 1 Alice | ||
| 2 Bob | ||
| 3 Charlie |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| package mv.join_elim_p_f_key | ||
| // 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("join_elim_filter_edge") { | ||
| String db = context.config.getDbNameByFile(context.file) | ||
| sql "use ${db}" | ||
| sql "SET enable_nereids_planner=true" | ||
| sql "SET enable_fallback_to_original_planner=false" | ||
| sql "SET enable_materialized_view_rewrite=true" | ||
| sql "SET enable_nereids_timeout = false" | ||
|
|
||
| sql """DROP MATERIALIZED VIEW IF EXISTS mv_join_elim_filter_edge""" | ||
| sql """DROP TABLE IF EXISTS join_elim_filter_edge_t1""" | ||
| sql """DROP TABLE IF EXISTS join_elim_filter_edge_t2""" | ||
|
|
||
| sql """CREATE TABLE join_elim_filter_edge_t1 ( | ||
| id INT, | ||
| name VARCHAR(50) | ||
| ) | ||
| DUPLICATE KEY(id) | ||
| DISTRIBUTED BY HASH(id) BUCKETS 1 | ||
| PROPERTIES ("replication_allocation" = "tag.location.default: 1");""" | ||
|
|
||
| sql """CREATE TABLE join_elim_filter_edge_t2 ( | ||
| id INT, | ||
| value INT | ||
| ) | ||
| UNIQUE KEY(id) | ||
| DISTRIBUTED BY HASH(id) BUCKETS 1 | ||
| PROPERTIES ( | ||
| "replication_allocation" = "tag.location.default: 1", | ||
| "enable_unique_key_merge_on_write" = "true" | ||
| );""" | ||
|
|
||
| sql """ALTER TABLE join_elim_filter_edge_t2 ADD CONSTRAINT uk_id UNIQUE (id)""" | ||
|
|
||
| sql """INSERT INTO join_elim_filter_edge_t1 VALUES | ||
| (1, 'Alice'), | ||
| (2, 'Bob'), | ||
| (3, 'Charlie');""" | ||
|
|
||
| sql """INSERT INTO join_elim_filter_edge_t2 VALUES | ||
| (1, 10), | ||
| (2, -5), | ||
| (4, 20);""" | ||
|
|
||
| sql """ANALYZE TABLE join_elim_filter_edge_t1 WITH SYNC;""" | ||
| sql """ANALYZE TABLE join_elim_filter_edge_t2 WITH SYNC;""" | ||
| sql """ALTER TABLE join_elim_filter_edge_t1 MODIFY COLUMN name SET STATS ('row_count'='3');""" | ||
| sql """ALTER TABLE join_elim_filter_edge_t2 MODIFY COLUMN value SET STATS ('row_count'='3');""" | ||
|
|
||
| create_async_mv(db, "mv_join_elim_filter_edge", """ | ||
| SELECT | ||
| t1.id, | ||
| t1.name | ||
| FROM join_elim_filter_edge_t1 t1 | ||
| LEFT JOIN join_elim_filter_edge_t2 t2 | ||
| ON t1.id = t2.id AND t2.id = 1 | ||
| """) | ||
|
|
||
| def querySql = """SELECT id, name FROM join_elim_filter_edge_t1""" | ||
| mv_rewrite_success_without_check_chosen(querySql, "mv_join_elim_filter_edge") | ||
| order_qt_join_elim_filter_edge """SELECT id, name FROM join_elim_filter_edge_t1 ORDER BY 1, 2""" | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not use e.getReferenceNodes()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FilterEdge.getReferenceNodes() is the whole filter child subtree, not the nodes actually referenced by the filter predicate. In HyperGraph.addFilter() we build the edge with subTreeNodes, so for a filter above t1 LEFT JOIN t2, getReferenceNodes() contains both sides even if the predicate only uses preserved-side columns, e.g. WHERE t1.id > 1. Using it here would therefore over-block safe elimination. getInputNodes() is narrower: it tracks the actual predicate lineage from input slots (including projected aliases), and only falls back to the whole subtree for slot-free predicates such as 1 = 0, where the filter really affects the entire child subtree. That is the dependency we want for this safety check.