This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.2 by this push:
new 4369b358bc1 branch-4.2: [test](mv) Add MV residual compensation
coverage #64051 (#68389)
4369b358bc1 is described below
commit 4369b358bc1b56cdfae7b84d227291d10166c20d
Author: yujun <[email protected]>
AuthorDate: Wed Sep 23 08:29:47 2026 +0800
branch-4.2: [test](mv) Add MV residual compensation coverage #64051 (#68389)
cherry-pick: #64051
Co-authored-by: foxtail463 <[email protected]>
Co-authored-by: yangtao555 <[email protected]>
---
.../apache/doris/nereids/mv/PredicatesTest.java | 234 ++++++++++++++++-----
1 file changed, 187 insertions(+), 47 deletions(-)
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/mv/PredicatesTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/mv/PredicatesTest.java
index a324e8e9961..4c70811dcb5 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/nereids/mv/PredicatesTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/mv/PredicatesTest.java
@@ -42,6 +42,8 @@ import java.util.stream.Collectors;
/** Test the method in Predicates*/
public class PredicatesTest extends SqlTestBase {
+ private static final String SELECT_ALL_TEST_COLUMNS =
+ "select id, score, event_date, event_time, amount, tag from T1
where ";
@Override
protected void runBeforeAll() throws Exception {
@@ -51,7 +53,11 @@ public class PredicatesTest extends SqlTestBase {
createTables(
"CREATE TABLE IF NOT EXISTS T1 (\n"
+ " id bigint,\n"
- + " score bigint\n"
+ + " score bigint,\n"
+ + " event_date date,\n"
+ + " event_time datetime,\n"
+ + " amount decimal(10, 2),\n"
+ + " tag varchar(20)\n"
+ ")\n"
+ "DUPLICATE KEY(id)\n"
+ "DISTRIBUTED BY HASH(id, score) BUCKETS 10\n"
@@ -61,7 +67,11 @@ public class PredicatesTest extends SqlTestBase {
+ ")\n",
"CREATE TABLE IF NOT EXISTS T2 (\n"
+ " id bigint,\n"
- + " score bigint\n"
+ + " score bigint,\n"
+ + " event_date date,\n"
+ + " event_time datetime,\n"
+ + " amount decimal(10, 2),\n"
+ + " tag varchar(20)\n"
+ ")\n"
+ "DUPLICATE KEY(id)\n"
+ "DISTRIBUTED BY HASH(id, score) BUCKETS 10\n"
@@ -169,58 +179,146 @@ public class PredicatesTest extends SqlTestBase {
@Test
public void testResidualCompensateSupportsDnfBranchImplication() {
- PredicateRewriteContext rewriteContext = buildRewriteContext(
- "select id, score from T1 where id = 5 or id > 10",
- "select id, score from T1 where id > 10 or (score = 1 and id =
5)");
-
- PredicateCompensation compensationCandidates =
Predicates.collectCompensationCandidates(
- rewriteContext.queryStructInfo,
- rewriteContext.viewStructInfo,
- rewriteContext.viewToQuerySlotMapping,
- rewriteContext.comparisonResult,
- rewriteContext.queryContext);
- Assertions.assertNotNull(compensationCandidates);
- Assertions.assertTrue(compensationCandidates.getEquals().isEmpty());
- Assertions.assertTrue(compensationCandidates.getRanges().isEmpty());
- Assertions.assertEquals(1,
compensationCandidates.getResiduals().size());
- assertPredicateSqlEquals(compensationCandidates.getResiduals(),
- "OR[(id > 10),AND[(score = 1),(id = 5)]]");
-
- PredicateCompensation finalPredicateCompensation =
compensatePredicates(rewriteContext);
- Assertions.assertNotNull(finalPredicateCompensation);
-
Assertions.assertTrue(finalPredicateCompensation.getEquals().isEmpty());
-
Assertions.assertTrue(finalPredicateCompensation.getRanges().isEmpty());
- Assertions.assertEquals(1,
finalPredicateCompensation.getResiduals().size());
- assertPredicateSqlEquals(finalPredicateCompensation.getResiduals(),
+ assertResidualCompensationSucceeds(
+ "id = 5 or id > 10",
+ "id > 10 or (score = 1 and id = 5)",
"OR[(id > 10),AND[(score = 1),(id = 5)]]");
}
@Test
public void
testResidualCompensateSupportsStrongerRangeInDnfBranchImplication() {
- PredicateRewriteContext rewriteContext = buildRewriteContext(
- "select id, score from T1 where id > 10 or (score = 1 and id =
5)",
- "select id, score from T1 where id > 15 or (score = 1 and id =
5)");
-
- PredicateCompensation compensationCandidates =
Predicates.collectCompensationCandidates(
- rewriteContext.queryStructInfo,
- rewriteContext.viewStructInfo,
- rewriteContext.viewToQuerySlotMapping,
- rewriteContext.comparisonResult,
- rewriteContext.queryContext);
- Assertions.assertNotNull(compensationCandidates);
- Assertions.assertTrue(compensationCandidates.getEquals().isEmpty());
- Assertions.assertTrue(compensationCandidates.getRanges().isEmpty());
- Assertions.assertEquals(1,
compensationCandidates.getResiduals().size());
- assertPredicateSqlEquals(compensationCandidates.getResiduals(),
+ assertResidualCompensationSucceeds(
+ "id > 10 or (score = 1 and id = 5)",
+ "id > 15 or (score = 1 and id = 5)",
"OR[(id > 15),AND[(score = 1),(id = 5)]]");
+ }
- PredicateCompensation finalPredicateCompensation =
compensatePredicates(rewriteContext);
- Assertions.assertNotNull(finalPredicateCompensation);
-
Assertions.assertTrue(finalPredicateCompensation.getEquals().isEmpty());
-
Assertions.assertTrue(finalPredicateCompensation.getRanges().isEmpty());
- Assertions.assertEquals(1,
finalPredicateCompensation.getResiduals().size());
- assertPredicateSqlEquals(finalPredicateCompensation.getResiduals(),
- "OR[(id > 15),AND[(score = 1),(id = 5)]]");
+ @Test
+ public void
testResidualCompensateSupportsComparableLiteralTypesInDnfBranchImplication() {
+ String[][] cases = {
+ {
+ "event_date >= date '2024-01-01' or score = 1",
+ "event_date > date '2024-01-01' or score = 1"
+ },
+ {
+ "event_time <= timestamp '2024-01-02 03:04:05' or
score = 2",
+ "event_time < timestamp '2024-01-02 03:04:05' or score
= 2"
+ },
+ {
+ "amount >= 10.50 or score = 3",
+ "amount = 10.50 or score = 3"
+ },
+ {
+ "tag >= 'm' or score = 4",
+ "tag > 'm' or score = 4"
+ }
+ };
+ for (String[] testCase : cases) {
+ assertResidualCompensationSucceeds(testCase[0], testCase[1]);
+ }
+ }
+
+ @Test
+ public void
testResidualCompensateRejectsNonImpliedComparableLiteralTypesInDnfBranchImplication()
{
+ String[][] cases = {
+ {
+ "event_date > date '2024-01-01' or score = 1",
+ "event_date >= date '2024-01-01' or score = 1"
+ },
+ {
+ "event_time < timestamp '2024-01-02 03:04:05' or score
= 2",
+ "event_time <= timestamp '2024-01-02 03:04:05' or
score = 2"
+ },
+ {
+ "amount = 10.50 or score = 3",
+ "amount >= 10.50 or score = 3"
+ },
+ {
+ "tag > 'm' or score = 4",
+ "tag >= 'm' or score = 4"
+ }
+ };
+ for (String[] testCase : cases) {
+ assertResidualCompensationFails(testCase[0], testCase[1]);
+ }
+ }
+
+ @Test
+ public void
testResidualCompensateSupportsBoundaryOperatorImplicationInDnfBranch() {
+ String[][] cases = {
+ {
+ "id <= 10 or score = 1",
+ "id < 10 or score = 1"
+ },
+ {
+ "id >= 10 or score = 1",
+ "id > 10 or score = 1"
+ },
+ {
+ "id >= 10 or score = 1",
+ "id = 10 or score = 1"
+ },
+ {
+ "id <= 10 or score = 1",
+ "id = 10 or score = 1"
+ }
+ };
+ for (String[] testCase : cases) {
+ assertResidualCompensationSucceeds(testCase[0], testCase[1]);
+ }
+ }
+
+ @Test
+ public void
testResidualCompensateRejectsReverseBoundaryOperatorImplicationInDnfBranch() {
+ String[][] cases = {
+ {
+ "id < 10 or score = 1",
+ "id <= 10 or score = 1"
+ },
+ {
+ "id > 10 or score = 1",
+ "id >= 10 or score = 1"
+ },
+ {
+ "id = 10 or score = 1",
+ "id >= 10 or score = 1"
+ },
+ {
+ "id = 10 or score = 1",
+ "id <= 10 or score = 1"
+ }
+ };
+ for (String[] testCase : cases) {
+ assertResidualCompensationFails(testCase[0], testCase[1]);
+ }
+ }
+
+ @Test
+ public void testResidualCompensateHandlesNestedDnfBranches() {
+ // This case verifies branch-by-branch coverage for nested DNF
predicates.
+ // The view predicate is logically:
+ // id >= 10
+ // OR (score = 1 AND id IN (5, 6))
+ // OR (score = 2 AND id <= 3)
+ // Each expanded branch in the covered query predicate is contained by
one view branch.
+ String viewPredicate = "id >= 10 "
+ + "or (score = 1 and id = 5) "
+ + "or (score = 1 and id = 6) "
+ + "or (score = 2 and id <= 3)";
+ // The covered query predicate is logically:
+ // (id > 20 AND score IN (3, 4))
+ // OR (score = 1 AND id IN (5, 6) AND (amount = 8.00 OR tag = 'x'))
+ // OR (score = 2 AND id <= 3)
+ String coveredQueryPredicate = "((id > 20 and (score = 3 or score =
4)) "
+ + "or ((score = 1 and (id = 5 or id = 6)) and (amount = 8.00
or tag = 'x')) "
+ + "or (score = 2 and (id < 3 or id = 3)))";
+ assertResidualCompensationSucceeds(viewPredicate,
coveredQueryPredicate);
+
+ // The extra branch only satisfies score = 1. Its id = 7 is not in the
view's id IN (5, 6),
+ // and it does not satisfy id >= 10 or score = 2 AND id <= 3, so the
residual is unsafe.
+ assertResidualCompensationFails(
+ viewPredicate,
+ coveredQueryPredicate + " or (score = 1 and id = 7)");
}
@Test
@@ -417,6 +515,48 @@ public class PredicatesTest extends SqlTestBase {
rewriteContext.queryContext);
}
+ private void assertResidualCompensationSucceeds(String viewPredicate,
String queryPredicate,
+ String... expectedResidualPredicates) {
+ PredicateRewriteContext rewriteContext = buildRewriteContext(
+ SELECT_ALL_TEST_COLUMNS + viewPredicate,
+ SELECT_ALL_TEST_COLUMNS + queryPredicate);
+ PredicateCompensation compensationCandidates =
Predicates.collectCompensationCandidates(
+ rewriteContext.queryStructInfo,
+ rewriteContext.viewStructInfo,
+ rewriteContext.viewToQuerySlotMapping,
+ rewriteContext.comparisonResult,
+ rewriteContext.queryContext);
+ String message = "view predicate: " + viewPredicate + ", query
predicate: " + queryPredicate;
+ Assertions.assertNotNull(compensationCandidates, message);
+ if (expectedResidualPredicates.length != 0) {
+ assertPredicateSqlEquals(compensationCandidates.getResiduals(),
expectedResidualPredicates);
+ }
+
+ PredicateCompensation finalPredicateCompensation =
compensatePredicates(rewriteContext);
+ Assertions.assertNotNull(finalPredicateCompensation, message);
+ if (expectedResidualPredicates.length != 0) {
+
assertPredicateSqlEquals(finalPredicateCompensation.getResiduals(),
expectedResidualPredicates);
+ }
+ }
+
+ private void assertResidualCompensationFails(String viewPredicate, String
queryPredicate) {
+ PredicateRewriteContext rewriteContext = buildRewriteContext(
+ SELECT_ALL_TEST_COLUMNS + viewPredicate,
+ SELECT_ALL_TEST_COLUMNS + queryPredicate);
+ PredicateCompensation compensationCandidates =
Predicates.collectCompensationCandidates(
+ rewriteContext.queryStructInfo,
+ rewriteContext.viewStructInfo,
+ rewriteContext.viewToQuerySlotMapping,
+ rewriteContext.comparisonResult,
+ rewriteContext.queryContext);
+ String message = "view predicate: " + viewPredicate + ", query
predicate: " + queryPredicate;
+ Assertions.assertNotNull(compensationCandidates, message);
+
Assertions.assertFalse(compensationCandidates.getResiduals().isEmpty(),
message);
+
+ PredicateCompensation finalPredicateCompensation =
compensatePredicates(rewriteContext);
+ Assertions.assertNull(finalPredicateCompensation, message);
+ }
+
private static final class PredicateRewriteContext {
private final StructInfo viewStructInfo;
private final StructInfo queryStructInfo;
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]