From 38a5034af3564cda3075a0ca28b981044363ff3f Mon Sep 17 00:00:00 2001 From: feiniaofeiafei Date: Sun, 13 Sep 2026 22:23:35 +0800 Subject: [PATCH 1/2] [fix](fe) Preserve inequality relations when retaining input predicates ### What problem does this PR solve? Problem Summary: Predicate inference records retained greater-than and greater-than-or-equal predicates as equalities. The false equality can make another necessary input predicate appear redundant. A QUALIFY condition a > b AND rn > b AND a > rn can consequently lose a > rn and return extra rows. Record the retained predicate's actual relation in the working graph. ### Release note Fix extra rows returned when inequality predicate inference removes a necessary filter, including QUALIFY queries over window outputs. ### Check List (For Author) - Test: Unit Test / Regression test / Manual test - All 25 UnequalPredicateInferTest tests passed. New semantic checks cover 234 relation/order/qualifier combinations and 64 value assignments per combination; the new test fails on the unmodified baseline. - infer_predicate_qualify passed after generating expected output with the regression runner and rerunning comparisons. - DISABLE_BUILD_UI=ON ./build.sh --fe passed, including Checkstyle. Deployed the FE library and verified the SQL wrong-result reproducer. - Behavior changed: Yes. Necessary inequality predicates are retained. - Does this need documentation: No. --- .../rules/rewrite/UnequalPredicateInfer.java | 3 +- .../rewrite/UnequalPredicateInferTest.java | 73 +++++++++++++++++++ .../infer_predicate_qualify.out | 15 ++++ .../infer_predicate_qualify.groovy | 54 ++++++++++++++ 4 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 regression-test/data/nereids_rules_p0/infer_predicate_qualify.out create mode 100644 regression-test/suites/nereids_rules_p0/infer_predicate_qualify.groovy diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/UnequalPredicateInfer.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/UnequalPredicateInfer.java index adf369221e1ab1..44042ee992bf86 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/UnequalPredicateInfer.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/UnequalPredicateInfer.java @@ -448,7 +448,8 @@ public Set chooseInputPredicates(Relation[][] chosen) { clear(chosen, left, right, type); } else if (deduced[left][right] != type) { keep[i] = true; - set(deduced, left, right, Relation.EQ); + // Preserve the relation of the retained predicate; an inequality is not an equality. + set(deduced, left, right, type); expandGraph(deduced, left, right); if (type == Relation.EQ) { expandGraph(deduced, right, left); diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/UnequalPredicateInferTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/UnequalPredicateInferTest.java index 7bd43c98929bc2..f2888ebbd51a58 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/UnequalPredicateInferTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/UnequalPredicateInferTest.java @@ -36,14 +36,18 @@ import org.apache.doris.nereids.types.IntegerType; import org.apache.doris.nereids.util.PredicateInferUtils; +import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; public class UnequalPredicateInferTest { @@ -685,4 +689,73 @@ public void testInferWithTransitiveEqualityWithCastDatev2andDate() { EqualTo expected = new EqualTo(a, b); Assertions.assertTrue(result.contains(expected) || result.contains(expected.commute()), "Expected to find a = b in the result."); } + + @Test + public void testInputPredicateSemantics() { + SlotReference a = new SlotReference("a", IntegerType.INSTANCE, false, ImmutableList.of("t")); + SlotReference b = new SlotReference("b", IntegerType.INSTANCE, false, ImmutableList.of("t")); + List> relations = new ArrayList<>(); + for (Relation first : ImmutableList.of(Relation.GT, Relation.GTE)) { + for (Relation second : ImmutableList.of(Relation.GT, Relation.GTE, Relation.EQ)) { + for (Relation third : ImmutableList.of(Relation.GT, Relation.GTE)) { + relations.add(ImmutableList.of(first, second, third)); + } + } + } + relations.add(ImmutableList.of(Relation.EQ, Relation.EQ, Relation.EQ)); + // Window outputs have no table qualifier. Also cover same-table and cross-table slots. + for (List qualifier : ImmutableList.of(ImmutableList.of(), + ImmutableList.of("t"), ImmutableList.of("other"))) { + SlotReference rn = new SlotReference("rn", IntegerType.INSTANCE, false, qualifier); + for (List types : relations) { + List predicates = ImmutableList.of(comparison(a, b, types.get(0)), + comparison(rn, b, types.get(1)), comparison(a, rn, types.get(2))); + for (List permutation : Collections2.permutations(predicates)) { + Set inputs = new LinkedHashSet<>(permutation); + Set inferred = UnequalPredicateInfer.inferUnequalPredicates(inputs); + for (int av = 0; av <= 3; av++) { + for (int bv = 0; bv <= 3; bv++) { + for (int rv = 0; rv <= 3; rv++) { + Map values = ImmutableMap.of(a, av, b, bv, rn, rv); + boolean expected = inputs.stream().allMatch(p -> evaluateComparison(p, values)); + boolean actual = inferred.stream().allMatch(p -> evaluateComparison(p, values)); + Assertions.assertEquals(expected, actual, + () -> "inputs=" + inputs + ", inferred=" + inferred + ", values=" + values); + } + } + } + } + } + } + } + + private static Expression comparison(Expression left, Expression right, Relation relation) { + switch (relation) { + case GT: + return new GreaterThan(left, right); + case GTE: + return new GreaterThanEqual(left, right); + case EQ: + return new EqualTo(left, right); + default: + throw new AssertionError("Unexpected relation: " + relation); + } + } + + private static boolean evaluateComparison(Expression expression, Map values) { + int left = values.get(expression.child(0)); + int right = values.get(expression.child(1)); + if (expression instanceof GreaterThan) { + return left > right; + } else if (expression instanceof GreaterThanEqual) { + return left >= right; + } else if (expression instanceof LessThan) { + return left < right; + } else if (expression instanceof LessThanEqual) { + return left <= right; + } else if (expression instanceof EqualTo) { + return left == right; + } + throw new AssertionError("Unexpected comparison: " + expression); + } } diff --git a/regression-test/data/nereids_rules_p0/infer_predicate_qualify.out b/regression-test/data/nereids_rules_p0/infer_predicate_qualify.out new file mode 100644 index 00000000000000..141f1db1e8d237 --- /dev/null +++ b/regression-test/data/nereids_rules_p0/infer_predicate_qualify.out @@ -0,0 +1,15 @@ +-- This file is automatically generated. You should know what you did if you want to edit this +-- !strict -- + +-- !non_strict -- +1 2 1 1 +2 2 1 2 + +-- !mixed -- +1 2 1 1 + +-- !reordered -- + +-- !matching_row -- +3 4 1 3 + diff --git a/regression-test/suites/nereids_rules_p0/infer_predicate_qualify.groovy b/regression-test/suites/nereids_rules_p0/infer_predicate_qualify.groovy new file mode 100644 index 00000000000000..e29879738d8e17 --- /dev/null +++ b/regression-test/suites/nereids_rules_p0/infer_predicate_qualify.groovy @@ -0,0 +1,54 @@ +// 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("infer_predicate_qualify") { + sql "drop table if exists infer_predicate_qualify_input" + sql """ + create table infer_predicate_qualify_input (k bigint not null, a bigint not null, b bigint not null) + duplicate key(k) distributed by hash(k) buckets 1 + properties("replication_num"="1") + """ + sql "insert into infer_predicate_qualify_input values (1,2,1),(2,2,1)" + + order_qt_strict """ + select t.k, t.a, t.b, row_number() over (order by t.k) as rn + from infer_predicate_qualify_input t + qualify t.a > t.b and rn > t.b and t.a > rn + """ + order_qt_non_strict """ + select t.k, t.a, t.b, row_number() over (order by t.k) as rn + from infer_predicate_qualify_input t + qualify t.a >= t.b and rn >= t.b and t.a >= rn + """ + order_qt_mixed """ + select t.k, t.a, t.b, row_number() over (order by t.k) as rn + from infer_predicate_qualify_input t + qualify t.a >= t.b and rn >= t.b and t.a > rn + """ + order_qt_reordered """ + select t.k, t.a, t.b, row_number() over (order by t.k) as rn + from infer_predicate_qualify_input t + qualify t.a > rn and rn > t.b and t.a > t.b + """ + // Include a row satisfying all strict comparisons, so preserving every row is also detected. + sql "insert into infer_predicate_qualify_input values (3,4,1)" + order_qt_matching_row """ + select t.k, t.a, t.b, row_number() over (order by t.k) as rn + from infer_predicate_qualify_input t + qualify t.a > t.b and rn > t.b and t.a > rn + """ +} From 1acbe050b88ea9687e4594e2d83cd9285a59b1c0 Mon Sep 17 00:00:00 2001 From: feiniaofeiafei Date: Sun, 13 Sep 2026 22:50:46 +0800 Subject: [PATCH 2/2] [fix](fe) Restrict predicate substitution to value-preserving contexts ### What problem does this PR solve? Problem Summary: Comparison equality does not imply that raw operands are interchangeable inside arbitrary expressions. Replacing a DATE slot with an equal DATETIME slot changes CAST-to-string output, and replacing negative floating-point zero with positive zero changes SIGNBIT. Predicate inference can therefore add filters not implied by the query and discard matching rows. Restrict substitution inside expressions to exactly matching supported scalar types with value-preserving equality. Keep direct comparisons, IN and their negations on the unwrapped operand, while retaining the existing determinism and cast-analysis checks. Do not change the shared cast extraction or inequality inference paths. ### Release note Fix missing rows caused by inferred predicates that substitute comparison-equal values inside type-sensitive or representation-sensitive expressions. ### Check List (For Author) - Test: Unit Test / Regression test / Manual test - All 65 tests in InferPredicateByReplaceTest and UnequalPredicateInferTest passed. New tests cover type/precision boundaries, floating-point signed zero, safe same-type substitutions, widening casts, and direct comparison/IN/NOT IN propagation. - Six regression suites passed: infer_predicate_replace_type, infer_predicate_qualify, infer_unequal_predicates, extend_infer_equal_predicate, infer_predicate, infer_datetimev2_cast_precision. Existing expected files are unchanged; new expected files were generated with the regression runner. - Date/string-length and signed-zero SQL witnesses returned no rows before the fix and the expected rows after deployment. - DISABLE_BUILD_UI=ON ./build.sh --fe passed, including Checkstyle. Deployed the final FE lib to the test cluster and verified the jar checksum. - Behavior changed: Yes. Unsafe expression substitutions are rejected while direct comparison propagation is preserved. - Does this need documentation: No. --- .../rewrite/InferPredicateByReplace.java | 29 +++- .../rewrite/InferPredicateByReplaceTest.java | 125 ++++++++++++++++++ .../infer_predicate_replace_type.out | 36 +++++ .../infer_predicate_replace_type.groovy | 89 +++++++++++++ 4 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 regression-test/data/nereids_rules_p0/infer_predicate_replace_type.out create mode 100644 regression-test/suites/nereids_rules_p0/infer_predicate_replace_type.groovy diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/InferPredicateByReplace.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/InferPredicateByReplace.java index c2ca99f0b61b9c..91f86d06ef2514 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/InferPredicateByReplace.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/InferPredicateByReplace.java @@ -35,6 +35,7 @@ import org.apache.doris.nereids.trees.expressions.literal.Literal; import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor; import org.apache.doris.nereids.trees.plans.Plan; +import org.apache.doris.nereids.types.DataType; import org.apache.doris.nereids.types.DecimalV2Type; import org.apache.doris.nereids.types.DecimalV3Type; import org.apache.doris.nereids.util.ExpressionUtils; @@ -153,12 +154,15 @@ private static Set getEqualSetAndDoReplace(T ExpressionAnalyzer analyzer = new ReplaceAnalyzer(null, new Scope(ImmutableList.of()), null, false, false); Set res = new LinkedHashSet<>(); for (T equals : equalSet) { - Map replaceMap = new HashMap<>(); - replaceMap.put(equals, replaceToThis); if (!exprPredicates.containsKey(equals)) { continue; } + Map replaceMap = new HashMap<>(); + replaceMap.put(equals, replaceToThis); for (Expression predicate : exprPredicates.get(equals)) { + if (!canReplace(equals, replaceToThis, predicate)) { + continue; + } Expression newPredicates = ExpressionUtils.replace(predicate, replaceMap); try { Expression analyzed = analyzer.analyze(newPredicates); @@ -171,6 +175,27 @@ private static Set getEqualSetAndDoReplace(T return res; } + private static boolean canReplace(Expression source, Expression target, Expression predicate) { + Expression comparison = predicate instanceof Not ? predicate.child(0) : predicate; + // Direct comparisons observe comparison equality rather than a value's type or representation. + // Do not descend through functions, casts or OR to apply this exception. + if ((comparison instanceof ComparisonPredicate || comparison instanceof InPredicate) + && comparison.child(0).equals(source)) { + return true; + } + DataType type = source.getDataType(); + // Comparison equality across types does not preserve type-sensitive expressions such as CAST to STRING. + if (!type.equals(target.getDataType())) { + return false; + } + // Only substitute types whose equality preserves the value observed by enclosing expressions. + // In particular, FLOAT/DOUBLE equality cannot distinguish signed zero, but SIGNBIT can. + // Comparisons can still be propagated separately by UnequalPredicateInfer. + return type.isBooleanType() || type.isIntegralType() || type.isDecimalLikeType() + || type.isStringLikeType() || type.isIPType() + || (type.isDateLikeType() && !type.isTimeStampTzType()); + } + /* Extract the equivalence relationship a=b, and when case (d_tinyint as int)=d_int is encountered, remove the cast and extract d_tinyint=d_int EqualPairs is the output parameter and the equivalent pair of predicate derivation input, diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/InferPredicateByReplaceTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/InferPredicateByReplaceTest.java index 5c174cb6348f10..42c0cf832cb807 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/InferPredicateByReplaceTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/InferPredicateByReplaceTest.java @@ -29,27 +29,50 @@ import org.apache.doris.nereids.trees.expressions.SlotReference; import org.apache.doris.nereids.trees.expressions.functions.scalar.Abs; import org.apache.doris.nereids.trees.expressions.functions.scalar.DateTrunc; +import org.apache.doris.nereids.trees.expressions.functions.scalar.Length; +import org.apache.doris.nereids.trees.expressions.functions.scalar.SignBit; +import org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral; import org.apache.doris.nereids.trees.expressions.literal.DateTimeLiteral; import org.apache.doris.nereids.trees.expressions.literal.DateTimeV2Literal; +import org.apache.doris.nereids.trees.expressions.literal.DoubleLiteral; import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral; import org.apache.doris.nereids.trees.expressions.literal.StringLiteral; import org.apache.doris.nereids.trees.expressions.literal.VarcharLiteral; +import org.apache.doris.nereids.types.ArrayType; import org.apache.doris.nereids.types.BigIntType; +import org.apache.doris.nereids.types.BooleanType; +import org.apache.doris.nereids.types.CharType; +import org.apache.doris.nereids.types.DataType; import org.apache.doris.nereids.types.DateTimeType; import org.apache.doris.nereids.types.DateTimeV2Type; import org.apache.doris.nereids.types.DateType; +import org.apache.doris.nereids.types.DateV2Type; +import org.apache.doris.nereids.types.DecimalV3Type; +import org.apache.doris.nereids.types.DoubleType; +import org.apache.doris.nereids.types.FloatType; +import org.apache.doris.nereids.types.IPv4Type; +import org.apache.doris.nereids.types.IPv6Type; import org.apache.doris.nereids.types.IntegerType; import org.apache.doris.nereids.types.StringType; import org.apache.doris.nereids.types.TimeStampNsType; import org.apache.doris.nereids.types.TinyIntType; +import org.apache.doris.nereids.types.VarcharType; import org.apache.doris.nereids.util.PredicateInferUtils; +import org.apache.doris.nereids.util.TypeCoercionUtils; import com.google.common.collect.ImmutableList; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import java.util.ArrayList; import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; +import java.util.stream.Stream; public class InferPredicateByReplaceTest { @Test @@ -245,4 +268,106 @@ public void testNotInferWithTransitiveEqualitySameTable() { Set result = InferPredicateByReplace.infer(inputs); Assertions.assertEquals(2, result.size()); } + + static Stream replacementTypes() { + List cases = new ArrayList<>(); + for (DataType type : ImmutableList.of(BooleanType.INSTANCE, IntegerType.INSTANCE, BigIntType.INSTANCE, + StringType.INSTANCE, DateV2Type.INSTANCE, DateTimeV2Type.of(0), DateTimeV2Type.of(6), + DecimalV3Type.createDecimalV3Type(9, 2), TimeStampNsType.INSTANCE, + CharType.createCharType(10), VarcharType.createVarcharType(10), + IPv4Type.INSTANCE, IPv6Type.INSTANCE)) { + cases.add(Arguments.of(type, type, true)); + } + for (List pair : ImmutableList.>of( + ImmutableList.of(DateV2Type.INSTANCE, DateTimeV2Type.of(0)), + ImmutableList.of(DateTimeV2Type.of(0), DateTimeV2Type.of(6)), + ImmutableList.of(IntegerType.INSTANCE, BigIntType.INSTANCE), + ImmutableList.of(DecimalV3Type.createDecimalV3Type(9, 2), + DecimalV3Type.createDecimalV3Type(9, 3)))) { + cases.add(Arguments.of(pair.get(0), pair.get(1), false)); + cases.add(Arguments.of(pair.get(1), pair.get(0), false)); + } + cases.add(Arguments.of(FloatType.INSTANCE, FloatType.INSTANCE, false)); + cases.add(Arguments.of(DoubleType.INSTANCE, DoubleType.INSTANCE, false)); + cases.add(Arguments.of(ArrayType.of(DoubleType.INSTANCE), ArrayType.of(DoubleType.INSTANCE), false)); + return cases.stream(); + } + + @ParameterizedTest(name = "{0} -> {1}, replace={2}") + @MethodSource("replacementTypes") + public void testTypeSensitiveReplacement(DataType sourceType, DataType targetType, boolean canReplace) { + SlotReference a = new SlotReference("a", sourceType); + SlotReference b = new SlotReference("b", targetType); + Expression equality = TypeCoercionUtils.processComparisonPredicate(new EqualTo(a, b)); + Expression predicate = new EqualTo(new Length(new Cast(a, StringType.INSTANCE)), new IntegerLiteral(10)); + Set inputs = new LinkedHashSet<>(ImmutableList.of(equality, predicate)); + Set result = InferPredicateByReplace.infer(inputs); + if (canReplace) { + Expression expected = new EqualTo(new Length(new Cast(b, StringType.INSTANCE)), new IntegerLiteral(10)); + Assertions.assertTrue(result.contains(expected), () -> "Missing " + expected + " in " + result); + } else { + Assertions.assertEquals(inputs, result); + } + } + + @Test + public void testSignedZeroInOr() { + SlotReference x = new SlotReference("x", DoubleType.INSTANCE); + SlotReference y = new SlotReference("y", DoubleType.INSTANCE); + Expression predicate = new Or(new SignBit(x), new GreaterThan(x, new DoubleLiteral(1.0))); + Set inputs = new LinkedHashSet<>(ImmutableList.of(new EqualTo(x, y), predicate)); + // x = -0.0 and y = +0.0 satisfy the input, but not the predicate with x replaced by y. + Assertions.assertEquals(inputs, InferPredicateByReplace.infer(inputs)); + Assertions.assertEquals(inputs, PredicateInferUtils.inferPredicate(inputs)); + } + + @Test + public void testSafeComparisonPropagation() { + SlotReference x = new SlotReference("x", DoubleType.INSTANCE, true, ImmutableList.of("left")); + SlotReference y = new SlotReference("y", DoubleType.INSTANCE, true, ImmutableList.of("right")); + Set floating = new LinkedHashSet<>(ImmutableList.of(new EqualTo(x, y), + new GreaterThan(x, new DoubleLiteral(1.0)))); + Assertions.assertTrue(PredicateInferUtils.inferPredicate(floating) + .contains(new GreaterThan(y, new DoubleLiteral(1.0)))); + + SlotReference small = new SlotReference("small", IntegerType.INSTANCE, true, ImmutableList.of("left")); + SlotReference wide = new SlotReference("wide", BigIntType.INSTANCE, true, ImmutableList.of("right")); + Set integers = new LinkedHashSet<>(ImmutableList.of( + new EqualTo(new Cast(small, BigIntType.INSTANCE), wide), + new GreaterThan(small, new IntegerLiteral(1)))); + Assertions.assertTrue(PredicateInferUtils.inferPredicate(integers).stream() + .anyMatch(p -> p instanceof GreaterThan && p.child(0).equals(wide) + && p.child(1).equals(new BigIntLiteral(1)))); + } + + @Test + public void testDirectComparisonReplacement() { + SlotReference small = new SlotReference("small", IntegerType.INSTANCE); + SlotReference wide = new SlotReference("wide", BigIntType.INSTANCE); + Expression equality = new EqualTo(new Cast(small, BigIntType.INSTANCE), wide); + List predicates = ImmutableList.of( + new Not(new EqualTo(small, new IntegerLiteral(10))), + new InPredicate(small, ImmutableList.of(new IntegerLiteral(10), new IntegerLiteral(20))), + new Not(new InPredicate(small, + ImmutableList.of(new IntegerLiteral(10), new IntegerLiteral(20))))); + List expected = ImmutableList.of( + new Not(new EqualTo(wide, new BigIntLiteral(10))), + new InPredicate(wide, ImmutableList.of(new BigIntLiteral(10), new BigIntLiteral(20))), + new Not(new InPredicate(wide, ImmutableList.of(new BigIntLiteral(10), new BigIntLiteral(20))))); + for (int i = 0; i < predicates.size(); i++) { + Set inputs = new LinkedHashSet<>(ImmutableList.of(equality, predicates.get(i))); + Assertions.assertTrue(InferPredicateByReplace.infer(inputs).contains(expected.get(i))); + } + } + + @Test + public void testSameTypeBehindWideningCasts() { + SlotReference a = new SlotReference("a", IntegerType.INSTANCE); + SlotReference b = new SlotReference("b", IntegerType.INSTANCE); + Expression equality = new EqualTo(new Cast(a, BigIntType.INSTANCE), new Cast(b, BigIntType.INSTANCE)); + Expression predicate = new EqualTo(new Length(new Cast(a, StringType.INSTANCE)), new IntegerLiteral(1)); + Set inputs = new LinkedHashSet<>(ImmutableList.of(equality, predicate)); + Expression expected = new EqualTo(new Length(new Cast(b, StringType.INSTANCE)), new IntegerLiteral(1)); + Assertions.assertTrue(InferPredicateByReplace.infer(inputs).contains(expected)); + } } diff --git a/regression-test/data/nereids_rules_p0/infer_predicate_replace_type.out b/regression-test/data/nereids_rules_p0/infer_predicate_replace_type.out new file mode 100644 index 00000000000000..18d06474878861 --- /dev/null +++ b/regression-test/data/nereids_rules_p0/infer_predicate_replace_type.out @@ -0,0 +1,36 @@ +-- This file is automatically generated. You should know what you did if you want to edit this +-- !date_length -- +1 10 + +-- !datetime_length -- +1 10 + +-- !date_same_type -- +1 1 + +-- !date_comparison -- +1 10 + +-- !signed_zero_facts -- +1 true true false +2 true false true +3 true false false +4 true false false +5 true true true + +-- !signed_zero_or -- +1 +3 +5 + +-- !signed_zero_or_reversed -- +2 +3 +5 + +-- !float_comparison -- +3 + +-- !decimal_scale -- +1 10 + diff --git a/regression-test/suites/nereids_rules_p0/infer_predicate_replace_type.groovy b/regression-test/suites/nereids_rules_p0/infer_predicate_replace_type.groovy new file mode 100644 index 00000000000000..ada35647762caa --- /dev/null +++ b/regression-test/suites/nereids_rules_p0/infer_predicate_replace_type.groovy @@ -0,0 +1,89 @@ +// 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("infer_predicate_replace_type") { + sql "drop table if exists infer_replace_date_l" + sql """ + create table infer_replace_date_l (id int not null, d date not null) + duplicate key(id) distributed by hash(id) buckets 1 properties("replication_num"="1") + """ + sql "drop table if exists infer_replace_date_r" + sql """ + create table infer_replace_date_r (id int not null, ts datetime(0) not null) + duplicate key(id) distributed by hash(id) buckets 1 properties("replication_num"="1") + """ + sql "insert into infer_replace_date_l values (1,'2024-01-01')" + sql "insert into infer_replace_date_r values (10,'2024-01-01 00:00:00')" + + order_qt_date_length """ + select l.id, r.id from infer_replace_date_l l join infer_replace_date_r r on l.d = r.ts + where length(cast(l.d as string)) = 10 + """ + order_qt_datetime_length """ + select l.id, r.id from infer_replace_date_l l join infer_replace_date_r r on l.d = r.ts + where length(cast(r.ts as string)) = 19 + """ + order_qt_date_same_type """ + select l.id, r.id from infer_replace_date_l l join infer_replace_date_l r on l.d = r.d + where length(cast(l.d as string)) = 10 + """ + order_qt_date_comparison """ + select l.id, r.id from infer_replace_date_l l join infer_replace_date_r r on l.d = r.ts + where l.d > cast('2023-12-31' as date) + """ + + sql "drop table if exists infer_replace_fp" + sql """ + create table infer_replace_fp (id bigint not null, x double not null, y double not null) + duplicate key(id) distributed by hash(id) buckets 1 properties("replication_num"="1") + """ + sql """ + insert into infer_replace_fp values + (1,cast('-0.0' as double),cast('0.0' as double)), + (2,cast('0.0' as double),cast('-0.0' as double)), + (3,2,2),(4,0,0),(5,-2,-2) + """ + order_qt_signed_zero_facts """ + select id, x = y, signbit(x), signbit(y) from infer_replace_fp + """ + order_qt_signed_zero_or """ + select id from infer_replace_fp where x = y and (signbit(x) or x > cast(1 as double)) + """ + order_qt_signed_zero_or_reversed """ + select id from infer_replace_fp where x = y and (signbit(y) or y > cast(1 as double)) + """ + order_qt_float_comparison """ + select id from infer_replace_fp where x = y and x > cast(1 as double) + """ + + sql "drop table if exists infer_replace_decimal_l" + sql """ + create table infer_replace_decimal_l (id int not null, d decimal(9,2) not null) + duplicate key(id) distributed by hash(id) buckets 1 properties("replication_num"="1") + """ + sql "drop table if exists infer_replace_decimal_r" + sql """ + create table infer_replace_decimal_r (id int not null, d decimal(9,3) not null) + duplicate key(id) distributed by hash(id) buckets 1 properties("replication_num"="1") + """ + sql "insert into infer_replace_decimal_l values (1,1.20)" + sql "insert into infer_replace_decimal_r values (10,1.200)" + order_qt_decimal_scale """ + select l.id, r.id from infer_replace_decimal_l l join infer_replace_decimal_r r on l.d = r.d + where length(cast(l.d as string)) = 4 + """ +}