Copilot commented on code in PR #61030:
URL: https://github.com/apache/doris/pull/61030#discussion_r2881626829


##########
fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java:
##########
@@ -2222,6 +2227,21 @@ public boolean isEnableHboNonStrictMatchingMode() {
         "是否启用 string 类型 min max 下推。", "Set whether to enable push down string 
type minmax."})
     public boolean enablePushDownStringMinMax = false;
 
+    // Comma-separated list of MOR tables to enable value predicate pushdown.
+    @VariableMgr.VarAttr(name = ENABLE_MOR_VALUE_PREDICATE_PUSHDOWN_TABLES, 
needForward = true, description = {
+        "指定启用MOR表value列谓词下推的表列表,格式:db1.tbl1,db2.tbl2 或 * 表示所有MOR表。",
+        "Comma-separated list of MOR tables to enable value predicate 
pushdown. "
+                + "Format: db1.tbl1,db2.tbl2 or * for all MOR tables."})
+    public String enableMorValuePredicatePushdownTables = "";

Review Comment:
   `enable_mor_value_predicate_pushdown_tables` changes query semantics (see 
Test 5 in the regression suite demonstrating stale results when enabled), but 
its `@VarAttr` is not marked `affectQueryResultInPlan = true`. This can cause 
plan/session-variable capture or plan-cache reuse to ignore this setting and 
return unexpected results. Please mark this variable as 
`affectQueryResultInPlan = true` (similar to `read_mor_as_dup_tables`).



##########
fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java:
##########
@@ -732,6 +732,11 @@ public class SessionVariable implements Serializable, 
Writable {
 
     public static final String ENABLE_PUSHDOWN_STRING_MINMAX = 
"enable_pushdown_string_minmax";
 
+    public static final String ENABLE_MOR_VALUE_PREDICATE_PUSHDOWN_TABLES
+            = "enable_mor_value_predicate_pushdown_tables";
+
+    public static final String READ_MOR_AS_DUP_TABLES = 
"read_mor_as_dup_tables";
+

Review Comment:
   PR description says this change is a refactor of table-list matching, but 
this PR also introduces new session variables, Thrift fields, FE planner 
changes, and BE behavior changes (e.g., new `TOlapScanNode` flags and MOR 
pushdown handling). Please update the PR description to cover these functional 
changes, or split the PR so the refactor and the feature/behavior changes can 
be reviewed independently.



##########
fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java:
##########
@@ -4770,6 +4790,55 @@ public boolean isEnablePushDownStringMinMax() {
         return enablePushDownStringMinMax;
     }
 
+    public String getEnableMorValuePredicatePushdownTables() {
+        return enableMorValuePredicatePushdownTables;
+    }
+
+    public boolean isMorValuePredicatePushdownEnabled(String dbName, String 
tableName) {
+        return isTableInList(enableMorValuePredicatePushdownTables, dbName, 
tableName);
+    }
+
+    public boolean isReadMorAsDupEnabled(String dbName, String tableName) {
+        return isTableInList(readMorAsDupTables, dbName, tableName);
+    }
+
+    /**
+     * Check if a table matches any entry in a comma-separated table list.
+     * Parses entries the same way as TableNameInfo: split by "." to extract
+     * component parts (table, db.table, or ctl.db.table).
+     * When entry specifies db, both db and table must match.
+     * When entry is just a table name, it matches any database.

Review Comment:
   The Javadoc claims entries can be `ctl.db.table`, but `isTableInList` 
currently ignores the catalog component entirely (it only checks the last two 
components). This makes the documented `ctl.db.table` format either ineffective 
or ambiguous if multiple catalogs are in use. Either update the documentation 
to reflect the actual supported formats, or extend the helper to also match the 
catalog component (which likely requires passing catalog/ctl into the helper 
from callers).
   ```suggestion
        * Entries are split by "." and only the last two components are 
considered:
        *   - {@code table}: matches the given table name in any database.
        *   - {@code db.table}: matches only when both db and table match.
        * If more than two components are provided (for example {@code 
ctl.db.table}),
        * only the last two ({@code db.table}) are used and any leading 
components are ignored.
        * A single entry {@code "*"} matches all tables.
   ```



##########
fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/ReadMorAsDupTest.java:
##########
@@ -0,0 +1,280 @@
+// 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.
+
+package org.apache.doris.nereids.rules.analysis;
+
+import org.apache.doris.catalog.Column;
+import org.apache.doris.nereids.pattern.GeneratedPlanPatterns;
+import org.apache.doris.nereids.rules.RulePromise;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
+import org.apache.doris.nereids.util.PlanChecker;
+import org.apache.doris.utframe.TestWithFeService;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Set;
+
+class ReadMorAsDupTest extends TestWithFeService implements 
GeneratedPlanPatterns {
+    private static final String DB = "test_read_mor_as_dup_db";
+
+    @Override
+    protected void runBeforeAll() throws Exception {
+        createDatabase(DB);
+        connectContext.setDatabase(DEFAULT_CLUSTER_PREFIX + DB);
+
+        // MOR UNIQUE table (enable_unique_key_merge_on_write = false)
+        createTable("CREATE TABLE " + DB + ".mor_tbl (\n"
+                + "  k INT NOT NULL,\n"
+                + "  v1 INT,\n"
+                + "  v2 VARCHAR(100)\n"
+                + ") ENGINE=OLAP\n"
+                + "UNIQUE KEY(k)\n"
+                + "DISTRIBUTED BY HASH(k) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1', 
'enable_unique_key_merge_on_write' = 'false');");
+
+        // MOW UNIQUE table (should not be affected)
+        createTable("CREATE TABLE " + DB + ".mow_tbl (\n"
+                + "  k INT NOT NULL,\n"
+                + "  v1 INT\n"
+                + ") ENGINE=OLAP\n"
+                + "UNIQUE KEY(k)\n"
+                + "DISTRIBUTED BY HASH(k) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1', 
'enable_unique_key_merge_on_write' = 'true');");
+
+        // DUP table (should not be affected)
+        createTable("CREATE TABLE " + DB + ".dup_tbl (\n"
+                + "  k INT NOT NULL,\n"
+                + "  v1 INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(k)\n"
+                + "DISTRIBUTED BY HASH(k) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1');");
+
+        
connectContext.getSessionVariable().setDisableNereidsRules("PRUNE_EMPTY_PARTITION");
+    }
+
+    @Test
+    void testDeleteSignFilterPresentByDefault() {
+        connectContext.getSessionVariable().readMorAsDupTables = "";
+
+        PlanChecker.from(connectContext)
+                .analyze("select * from mor_tbl")
+                .rewrite()
+                .matches(
+                        logicalProject(
+                                logicalFilter(
+                                        logicalOlapScan()
+                                ).when(filter -> 
hasDeleteSignPredicate(filter.getConjuncts()))
+                        )
+                );
+    }
+
+    @Test
+    void testDeleteSignFilterSkippedWithWildcard() {
+        connectContext.getSessionVariable().readMorAsDupTables = "*";
+
+        try {
+            PlanChecker.from(connectContext)
+                    .analyze("select * from mor_tbl")
+                    .rewrite()
+                    .nonMatch(
+                            logicalFilter(
+                                    logicalOlapScan()
+                            ).when(filter -> 
hasDeleteSignPredicate(filter.getConjuncts()))
+                    );
+        } finally {
+            connectContext.getSessionVariable().readMorAsDupTables = "";
+        }
+    }
+
+    @Test
+    void testDeleteSignFilterSkippedWithTableName() {
+        connectContext.getSessionVariable().readMorAsDupTables = "mor_tbl";
+
+        try {
+            PlanChecker.from(connectContext)
+                    .analyze("select * from mor_tbl")
+                    .rewrite()
+                    .nonMatch(
+                            logicalFilter(
+                                    logicalOlapScan()
+                            ).when(filter -> 
hasDeleteSignPredicate(filter.getConjuncts()))
+                    );
+        } finally {
+            connectContext.getSessionVariable().readMorAsDupTables = "";
+        }
+    }
+
+    @Test
+    void testDeleteSignFilterSkippedWithDbTableName() {
+        connectContext.getSessionVariable().readMorAsDupTables =
+                DEFAULT_CLUSTER_PREFIX + DB + ".mor_tbl";
+
+        try {
+            PlanChecker.from(connectContext)
+                    .analyze("select * from mor_tbl")
+                    .rewrite()
+                    .nonMatch(
+                            logicalFilter(
+                                    logicalOlapScan()
+                            ).when(filter -> 
hasDeleteSignPredicate(filter.getConjuncts()))
+                    );
+        } finally {
+            connectContext.getSessionVariable().readMorAsDupTables = "";
+        }
+    }
+
+    @Test
+    void testMowTableNotAffected() {
+        // MOW table should still have delete sign filter even with 
read_mor_as_dup = *
+        connectContext.getSessionVariable().readMorAsDupTables = "*";
+
+        try {
+            // MOW tables also have delete sign filter; read_mor_as_dup should 
NOT remove it
+            PlanChecker.from(connectContext)
+                    .analyze("select * from mow_tbl")
+                    .rewrite()
+                    .matches(
+                            logicalProject(
+                                    logicalFilter(
+                                            logicalOlapScan()
+                                    ).when(filter -> 
hasDeleteSignPredicate(filter.getConjuncts()))
+                            )
+                    );
+        } finally {
+            connectContext.getSessionVariable().readMorAsDupTables = "";
+        }
+    }
+
+    @Test
+    void testPreAggOnForMorWithReadAsDup() {
+        connectContext.getSessionVariable().readMorAsDupTables = "*";
+
+        try {
+            Plan plan = PlanChecker.from(connectContext)
+                    .analyze("select * from mor_tbl")
+                    .rewrite()
+                    .getPlan();
+
+            // Find the LogicalOlapScan and verify preAggStatus is ON
+            LogicalOlapScan scan = findOlapScan(plan);
+            Assertions.assertNotNull(scan, "Should find LogicalOlapScan in 
plan");
+            Assertions.assertTrue(scan.getPreAggStatus().isOn(),
+                    "PreAggStatus should be ON when read_mor_as_dup is 
enabled");
+        } finally {
+            connectContext.getSessionVariable().readMorAsDupTables = "";
+        }
+    }
+
+    @Test
+    void testPerTableControlOnlyAffectsSpecifiedTable() {
+        // Only enable for mor_tbl, not for other tables
+        connectContext.getSessionVariable().readMorAsDupTables = "mor_tbl";
+
+        try {
+            // mor_tbl should NOT have delete sign filter
+            PlanChecker.from(connectContext)
+                    .analyze("select * from mor_tbl")
+                    .rewrite()
+                    .nonMatch(
+                            logicalFilter(
+                                    logicalOlapScan()
+                            ).when(filter -> 
hasDeleteSignPredicate(filter.getConjuncts()))
+                    );
+
+            // dup_tbl should be unaffected (no delete sign filter for DUP)
+            PlanChecker.from(connectContext)
+                    .analyze("select * from dup_tbl")
+                    .rewrite()
+                    .matches(logicalOlapScan());
+        } finally {
+            connectContext.getSessionVariable().readMorAsDupTables = "";
+        }
+    }
+
+    @Test
+    void testSessionVariableHelperMethod() {
+        // Test the isReadMorAsDupEnabled helper directly
+        connectContext.getSessionVariable().readMorAsDupTables = "";
+        Assertions.assertFalse(
+                
connectContext.getSessionVariable().isReadMorAsDupEnabled("db", "tbl"));
+
+        connectContext.getSessionVariable().readMorAsDupTables = "*";
+        Assertions.assertTrue(
+                
connectContext.getSessionVariable().isReadMorAsDupEnabled("db", "tbl"));
+        Assertions.assertTrue(
+                
connectContext.getSessionVariable().isReadMorAsDupEnabled("any_db", "any_tbl"));
+
+        connectContext.getSessionVariable().readMorAsDupTables = "mydb.mytbl";
+        Assertions.assertTrue(
+                
connectContext.getSessionVariable().isReadMorAsDupEnabled("mydb", "mytbl"));
+        Assertions.assertFalse(
+                
connectContext.getSessionVariable().isReadMorAsDupEnabled("otherdb", 
"othertbl"));
+        // "mydb.mytbl" entry requires both db and table components to match
+        Assertions.assertFalse(
+                
connectContext.getSessionVariable().isReadMorAsDupEnabled("anything", "mytbl"));
+

Review Comment:
   `testSessionVariableHelperMethod()` exercises wildcard/db.table/table-only, 
but the new matching helper also explicitly handles edge-cases like empty 
entries from double-commas and null `dbName`. Adding assertions for inputs like 
`"db.tbl,,other"` (empty entry skipped) and `dbName = null` (should not match 
`db.tbl`) would better pin the intended semantics of the refactored parser.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to