This is an automated email from the ASF dual-hosted git repository. yiguolei pushed a commit to branch branch-2.1 in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-2.1 by this push: new c5d9e178bea [fix](mtmv) Fix result wrong when query rewrite by mv if query contains null_unsafe equals expression (#39629) (#40041) c5d9e178bea is described below commit c5d9e178beae92963196bbb20ff068e985e02049 Author: seawinde <149132972+seawi...@users.noreply.github.com> AuthorDate: Thu Aug 29 00:31:36 2024 +0800 [fix](mtmv) Fix result wrong when query rewrite by mv if query contains null_unsafe equals expression (#39629) (#40041) ## Proposed changes commitId: 5d4ad028 pr: https://github.com/apache/doris/pull/39629 --- .../rules/exploration/mv/EquivalenceClass.java | 41 ++++++----- .../nereids/rules/exploration/mv/Predicates.java | 13 ++-- ...etMapping.java => EquivalenceClassMapping.java} | 33 +++++---- .../mv/unsafe_equals/null_un_safe_equals.out | 11 +++ .../mv/unsafe_equals/null_un_safe_equals.groovy | 85 ++++++++++++++++++++++ 5 files changed, 142 insertions(+), 41 deletions(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/EquivalenceClass.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/EquivalenceClass.java index d6b59b6866c..75a03fade29 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/EquivalenceClass.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/EquivalenceClass.java @@ -21,12 +21,9 @@ import org.apache.doris.nereids.trees.expressions.SlotReference; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Set; /** * EquivalenceClass, this is used for equality propagation when predicate compensation @@ -40,14 +37,19 @@ public class EquivalenceClass { * a: [a, b], * b: [a, b] * } + * or column a = a, + * this would be + * { + * a: [a, a] + * } */ - private Map<SlotReference, Set<SlotReference>> equivalenceSlotMap = new LinkedHashMap<>(); - private List<Set<SlotReference>> equivalenceSlotList; + private Map<SlotReference, List<SlotReference>> equivalenceSlotMap = new LinkedHashMap<>(); + private List<List<SlotReference>> equivalenceSlotList; public EquivalenceClass() { } - public EquivalenceClass(Map<SlotReference, Set<SlotReference>> equivalenceSlotMap) { + public EquivalenceClass(Map<SlotReference, List<SlotReference>> equivalenceSlotMap) { this.equivalenceSlotMap = equivalenceSlotMap; } @@ -56,13 +58,13 @@ public class EquivalenceClass { */ public void addEquivalenceClass(SlotReference leftSlot, SlotReference rightSlot) { - Set<SlotReference> leftSlotSet = equivalenceSlotMap.get(leftSlot); - Set<SlotReference> rightSlotSet = equivalenceSlotMap.get(rightSlot); + List<SlotReference> leftSlotSet = equivalenceSlotMap.get(leftSlot); + List<SlotReference> rightSlotSet = equivalenceSlotMap.get(rightSlot); if (leftSlotSet != null && rightSlotSet != null) { // Both present, we need to merge if (leftSlotSet.size() < rightSlotSet.size()) { // We swap them to merge - Set<SlotReference> tmp = rightSlotSet; + List<SlotReference> tmp = rightSlotSet; rightSlotSet = leftSlotSet; leftSlotSet = tmp; } @@ -80,7 +82,7 @@ public class EquivalenceClass { equivalenceSlotMap.put(leftSlot, rightSlotSet); } else { // None are present, add to same equivalence class - Set<SlotReference> equivalenceClass = new LinkedHashSet<>(); + List<SlotReference> equivalenceClass = new ArrayList<>(); equivalenceClass.add(leftSlot); equivalenceClass.add(rightSlot); equivalenceSlotMap.put(leftSlot, equivalenceClass); @@ -88,7 +90,7 @@ public class EquivalenceClass { } } - public Map<SlotReference, Set<SlotReference>> getEquivalenceSlotMap() { + public Map<SlotReference, List<SlotReference>> getEquivalenceSlotMap() { return equivalenceSlotMap; } @@ -101,15 +103,15 @@ public class EquivalenceClass { */ public EquivalenceClass permute(Map<SlotReference, SlotReference> mapping) { - Map<SlotReference, Set<SlotReference>> permutedEquivalenceSlotMap = new HashMap<>(); - for (Map.Entry<SlotReference, Set<SlotReference>> slotReferenceSetEntry : equivalenceSlotMap.entrySet()) { + Map<SlotReference, List<SlotReference>> permutedEquivalenceSlotMap = new HashMap<>(); + for (Map.Entry<SlotReference, List<SlotReference>> slotReferenceSetEntry : equivalenceSlotMap.entrySet()) { SlotReference mappedSlotReferenceKey = mapping.get(slotReferenceSetEntry.getKey()); if (mappedSlotReferenceKey == null) { // can not permute then need to return null return null; } - Set<SlotReference> equivalenceValueSet = slotReferenceSetEntry.getValue(); - final Set<SlotReference> mappedSlotReferenceSet = new HashSet<>(); + List<SlotReference> equivalenceValueSet = slotReferenceSetEntry.getValue(); + final List<SlotReference> mappedSlotReferenceSet = new ArrayList<>(); for (SlotReference target : equivalenceValueSet) { SlotReference mappedSlotReferenceValue = mapping.get(target); if (mappedSlotReferenceValue == null) { @@ -123,15 +125,14 @@ public class EquivalenceClass { } /** - * Return the list of equivalence set, remove duplicate + * Return the list of equivalence list, remove duplicate */ - public List<Set<SlotReference>> getEquivalenceSetList() { - + public List<List<SlotReference>> getEquivalenceSetList() { if (equivalenceSlotList != null) { return equivalenceSlotList; } - List<Set<SlotReference>> equivalenceSets = new ArrayList<>(); - Set<Set<SlotReference>> visited = new HashSet<>(); + List<List<SlotReference>> equivalenceSets = new ArrayList<>(); + List<List<SlotReference>> visited = new ArrayList<>(); equivalenceSlotMap.values().forEach(slotSet -> { if (!visited.contains(slotSet)) { equivalenceSets.add(slotSet); diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/Predicates.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/Predicates.java index 8475bc6b46a..20ded0415ad 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/Predicates.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/Predicates.java @@ -18,7 +18,7 @@ package org.apache.doris.nereids.rules.exploration.mv; import org.apache.doris.nereids.CascadesContext; -import org.apache.doris.nereids.rules.exploration.mv.mapping.EquivalenceClassSetMapping; +import org.apache.doris.nereids.rules.exploration.mv.mapping.EquivalenceClassMapping; import org.apache.doris.nereids.rules.exploration.mv.mapping.SlotMapping; import org.apache.doris.nereids.rules.expression.ExpressionNormalization; import org.apache.doris.nereids.rules.expression.ExpressionOptimization; @@ -33,6 +33,7 @@ import org.apache.doris.nereids.util.Utils; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; @@ -98,15 +99,15 @@ public class Predicates { if (queryEquivalenceClass.isEmpty() && !viewEquivalenceClass.isEmpty()) { return null; } - EquivalenceClassSetMapping queryToViewEquivalenceMapping = - EquivalenceClassSetMapping.generate(queryEquivalenceClass, viewEquivalenceClassQueryBased); + EquivalenceClassMapping queryToViewEquivalenceMapping = + EquivalenceClassMapping.generate(queryEquivalenceClass, viewEquivalenceClassQueryBased); // can not map all target equivalence class, can not compensate if (queryToViewEquivalenceMapping.getEquivalenceClassSetMap().size() < viewEquivalenceClass.getEquivalenceSetList().size()) { return null; } // do equal compensate - Set<Set<SlotReference>> mappedQueryEquivalenceSet = + Set<List<SlotReference>> mappedQueryEquivalenceSet = queryToViewEquivalenceMapping.getEquivalenceClassSetMap().keySet(); queryEquivalenceClass.getEquivalenceSetList().forEach( queryEquivalenceSet -> { @@ -120,9 +121,9 @@ public class Predicates { } } else { // compensate the equivalence both in query and view, but query has more equivalence - Set<SlotReference> viewEquivalenceSet = + List<SlotReference> viewEquivalenceSet = queryToViewEquivalenceMapping.getEquivalenceClassSetMap().get(queryEquivalenceSet); - Set<SlotReference> copiedQueryEquivalenceSet = new HashSet<>(queryEquivalenceSet); + List<SlotReference> copiedQueryEquivalenceSet = new ArrayList<>(queryEquivalenceSet); copiedQueryEquivalenceSet.removeAll(viewEquivalenceSet); SlotReference first = viewEquivalenceSet.iterator().next(); for (SlotReference slotReference : copiedQueryEquivalenceSet) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/mapping/EquivalenceClassSetMapping.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/mapping/EquivalenceClassMapping.java similarity index 54% rename from fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/mapping/EquivalenceClassSetMapping.java rename to fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/mapping/EquivalenceClassMapping.java index d4ec09c24a4..03ad43b182b 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/mapping/EquivalenceClassSetMapping.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/mapping/EquivalenceClassMapping.java @@ -21,6 +21,7 @@ import org.apache.doris.nereids.rules.exploration.mv.EquivalenceClass; import org.apache.doris.nereids.trees.expressions.SlotReference; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -30,39 +31,41 @@ import java.util.Set; * This will extract the equivalence class set in EquivalenceClass and mapping set in * two different EquivalenceClass. */ -public class EquivalenceClassSetMapping extends Mapping { +public class EquivalenceClassMapping extends Mapping { - private final Map<Set<SlotReference>, Set<SlotReference>> equivalenceClassSetMap; + private final Map<List<SlotReference>, List<SlotReference>> equivalenceClassSetMap; - public EquivalenceClassSetMapping(Map<Set<SlotReference>, - Set<SlotReference>> equivalenceClassSetMap) { + public EquivalenceClassMapping(Map<List<SlotReference>, + List<SlotReference>> equivalenceClassSetMap) { this.equivalenceClassSetMap = equivalenceClassSetMap; } - public static EquivalenceClassSetMapping of(Map<Set<SlotReference>, Set<SlotReference>> equivalenceClassSetMap) { - return new EquivalenceClassSetMapping(equivalenceClassSetMap); + public static EquivalenceClassMapping of(Map<List<SlotReference>, List<SlotReference>> equivalenceClassSetMap) { + return new EquivalenceClassMapping(equivalenceClassSetMap); } /** * Generate source equivalence set map to target equivalence set */ - public static EquivalenceClassSetMapping generate(EquivalenceClass source, EquivalenceClass target) { + public static EquivalenceClassMapping generate(EquivalenceClass source, EquivalenceClass target) { - Map<Set<SlotReference>, Set<SlotReference>> equivalenceClassSetMap = new HashMap<>(); - List<Set<SlotReference>> sourceSets = source.getEquivalenceSetList(); - List<Set<SlotReference>> targetSets = target.getEquivalenceSetList(); + Map<List<SlotReference>, List<SlotReference>> equivalenceClassSetMap = new HashMap<>(); + List<List<SlotReference>> sourceSets = source.getEquivalenceSetList(); + List<List<SlotReference>> targetSets = target.getEquivalenceSetList(); - for (Set<SlotReference> sourceSet : sourceSets) { - for (Set<SlotReference> targetSet : targetSets) { + for (List<SlotReference> sourceList : sourceSets) { + Set<SlotReference> sourceSet = new HashSet<>(sourceList); + for (List<SlotReference> targetList : targetSets) { + Set<SlotReference> targetSet = new HashSet<>(targetList); if (sourceSet.containsAll(targetSet)) { - equivalenceClassSetMap.put(sourceSet, targetSet); + equivalenceClassSetMap.put(sourceList, targetList); } } } - return EquivalenceClassSetMapping.of(equivalenceClassSetMap); + return EquivalenceClassMapping.of(equivalenceClassSetMap); } - public Map<Set<SlotReference>, Set<SlotReference>> getEquivalenceClassSetMap() { + public Map<List<SlotReference>, List<SlotReference>> getEquivalenceClassSetMap() { return equivalenceClassSetMap; } } diff --git a/regression-test/data/nereids_rules_p0/mv/unsafe_equals/null_un_safe_equals.out b/regression-test/data/nereids_rules_p0/mv/unsafe_equals/null_un_safe_equals.out new file mode 100644 index 00000000000..874bfea0ea0 --- /dev/null +++ b/regression-test/data/nereids_rules_p0/mv/unsafe_equals/null_un_safe_equals.out @@ -0,0 +1,11 @@ +-- This file is automatically generated. You should know what you did if you want to edit this +-- !query1_0_before -- +1 o mm +2 o mi +4 o yy + +-- !query1_0_after -- +1 o mm +2 o mi +4 o yy + diff --git a/regression-test/suites/nereids_rules_p0/mv/unsafe_equals/null_un_safe_equals.groovy b/regression-test/suites/nereids_rules_p0/mv/unsafe_equals/null_un_safe_equals.groovy new file mode 100644 index 00000000000..74322e2b8c2 --- /dev/null +++ b/regression-test/suites/nereids_rules_p0/mv/unsafe_equals/null_un_safe_equals.groovy @@ -0,0 +1,85 @@ +package mv.unsafe_equals +// 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("null_unsafe_equals") { + String db = context.config.getDbNameByFile(context.file) + sql "use ${db}" + sql "set runtime_filter_mode=OFF"; + sql "SET ignore_shape_nodes='PhysicalDistribute,PhysicalProject'" + + sql """ + drop table if exists orders + """ + + sql """ + CREATE TABLE IF NOT EXISTS orders ( + o_orderkey INTEGER NULL, + o_custkey INTEGER NULL, + o_orderstatus CHAR(1) NULL, + o_totalprice DECIMALV3(15,2) NULL, + o_orderdate DATE NULL, + o_orderpriority CHAR(15) NULL, + o_clerk CHAR(15) NULL, + o_shippriority INTEGER NULL, + O_COMMENT VARCHAR(79) NULL + ) + DUPLICATE KEY(o_orderkey, o_custkey) + PARTITION BY RANGE(o_orderdate) ( + PARTITION `day_2` VALUES LESS THAN ('2023-12-9'), + PARTITION `day_3` VALUES LESS THAN ("2023-12-11"), + PARTITION `day_4` VALUES LESS THAN ("2023-12-30") + ) + DISTRIBUTED BY HASH(o_orderkey) BUCKETS 3 + PROPERTIES ( + "replication_num" = "1" + ); + """ + + sql """ + insert into orders values + (null, 1, 'o', 9.5, '2023-12-08', 'a', 'b', 1, 'yy'), + (1, null, 'o', 10.5, '2023-12-08', 'a', 'b', 1, 'yy'), + (2, 1, null, 11.5, '2023-12-09', 'a', 'b', 1, 'yy'), + (3, 1, 'o', null, '2023-12-10', 'a', 'b', 1, 'yy'), + (3, 1, 'o', 33.5, null, 'a', 'b', 1, 'yy'), + (4, 2, 'o', 43.2, '2023-12-11', null,'d',2, 'mm'), + (5, 2, 'o', 56.2, '2023-12-12', 'c',null, 2, 'mi'), + (5, 2, 'o', 1.2, '2023-12-12', 'c','d', null, 'mi'); + """ + + def mv1_0 = + """ + select count(*), o_orderstatus, o_comment + from orders + group by + o_orderstatus, o_comment; + """ + // query contains the filter which is 'o_orderstatus = o_orderstatus' should reject null + def query1_0 = + """ + select count(*), o_orderstatus, o_comment + from orders + where o_orderstatus = o_orderstatus + group by + o_orderstatus, o_comment; + """ + order_qt_query1_0_before "${query1_0}" + check_mv_rewrite_success(db, mv1_0, query1_0, "mv1_0") + order_qt_query1_0_after "${query1_0}" + sql """ DROP MATERIALIZED VIEW IF EXISTS mv1_0""" +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org