This is an automated email from the ASF dual-hosted git repository.

morrySnow pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 5caaeb3e3d4 [fix](point query) Keep partition predicates for 
short-circuit point queries (#66926)
5caaeb3e3d4 is described below

commit 5caaeb3e3d488fd6eb319dfd6553682e47b9be59
Author: Bei Zhang <[email protected]>
AuthorDate: Tue Oct 27 04:07:04 2026 -0700

    [fix](point query) Keep partition predicates for short-circuit point 
queries (#66926)
    
    ### What problem does this PR solve?
    
    Issue Number: close #66030
    
    Related PR: #57169 #63111
    
    Problem Summary:
    
    A short-circuit point query on a LIST-partitioned table fails with
    IllegalStateException when the partition predicate is fully implied by
    the surviving partitions. Reproduce: a MoW unique-key table with
    row-store enabled, `PARTITION BY LIST (pk) (PARTITION p1 VALUES IN
    ('abcd'))`, then `SELECT * FROM t WHERE pk = 'abcd' AND _id = 1`.
    
    Root cause: partition pruning (`PruneOlapScanPartition` plus the
    `PrunePartitionPredicate` post processor) removes a partition predicate
    once the surviving partitions fully imply it. But a short-circuit point
    query needs the equality conjuncts on every key column to remain in the
    scan: `PointQueryExecutor` relies on
    `OlapScanNode.lazyEvaluateRangeLocations` re-pruning partitions and
    tablets from the scan conjuncts at execution time, then asserts the scan
    narrows to a single tablet. With the conjunct removed, the
    `Preconditions.checkState` in `PointQueryExecutor.setScanRangeLocations`
    throws IllegalStateException.
    
    Fix: extend the existing skipPrunePredicate carve-outs (session
    variable, delete statements) with
    `StatementContext.isShortCircuitQuery`, in both the rewrite rule and the
    post processor. Both sites are needed: in the main rewrite job list the
    short-circuit flag is set immediately before pruning, so the rule skips
    registering the prunable predicate; in the MV pre-rewrite job list
    pruning runs before the flag is set, so the post processor must also
    skip the deferred removal.
    
    After the fix the query returns the row, and server-side prepared
    statements re-bound to values in other partitions also return correct
    results.
---
 .../processor/post/PrunePartitionPredicate.java    |  5 +-
 .../rules/rewrite/PruneOlapScanPartition.java      |  3 +-
 .../test_point_query_list_partition.out            | 17 +++++
 .../test_point_query_list_partition.groovy         | 81 ++++++++++++++++++++++
 4 files changed, 104 insertions(+), 2 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/PrunePartitionPredicate.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/PrunePartitionPredicate.java
index 01ecf88e595..53403e8960c 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/PrunePartitionPredicate.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/PrunePartitionPredicate.java
@@ -62,8 +62,11 @@ public class PrunePartitionPredicate extends 
PlanPostProcessor {
         if (!entryOpt.isPresent()) {
             return filter;
         }
+        // Short-circuit point queries need equality conjuncts on all key 
columns kept in
+        // the scan for distribution pruning and key tuple construction, see 
PointQueryExecutor.
         boolean skipPrunePredicate = 
context.getConnectContext().getSessionVariable().skipPrunePredicate
-                || context.getStatementContext().isDelete();
+                || context.getStatementContext().isDelete()
+                || context.getStatementContext().isShortCircuitQuery();
         if (skipPrunePredicate) {
             return filter;
         }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PruneOlapScanPartition.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PruneOlapScanPartition.java
index c89234b92fe..ebc994a83e3 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PruneOlapScanPartition.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PruneOlapScanPartition.java
@@ -97,7 +97,8 @@ public class PruneOlapScanPartition implements 
RewriteRuleFactory {
                             return rewrittenLogicalRelation;
                         }
                         boolean skipPrunePredicate = 
ctx.connectContext.getSessionVariable().skipPrunePredicate
-                                || ctx.statementContext.isDelete();
+                                || ctx.statementContext.isDelete()
+                                || ctx.statementContext.isShortCircuitQuery();
                         if (!skipPrunePredicate && 
prunedRes.second.isPresent()) {
                             // Defer the predicate removal to 
PlanPostProcessor so that materialized-view
                             // rewrite still sees the original predicates. 
Otherwise, partition predicates
diff --git 
a/regression-test/data/point_query_p0/test_point_query_list_partition.out 
b/regression-test/data/point_query_p0/test_point_query_list_partition.out
new file mode 100644
index 00000000000..1206ecdce77
--- /dev/null
+++ b/regression-test/data/point_query_p0/test_point_query_list_partition.out
@@ -0,0 +1,17 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !hit_single_value --
+abcd   1
+
+-- !miss --
+
+-- !hit_multi_value --
+a      2
+
+-- !point_select_prepared_hit --
+abcd   1
+
+-- !point_select_prepared_miss --
+
+-- !point_select_prepared_multi --
+a      2
+
diff --git 
a/regression-test/suites/point_query_p0/test_point_query_list_partition.groovy 
b/regression-test/suites/point_query_p0/test_point_query_list_partition.groovy
new file mode 100644
index 00000000000..4630ffaa3ac
--- /dev/null
+++ 
b/regression-test/suites/point_query_p0/test_point_query_list_partition.groovy
@@ -0,0 +1,81 @@
+// 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("test_point_query_list_partition") {
+    // https://github.com/apache/doris/issues/66030
+    // Short-circuit point query on LIST-partitioned table used to fail with
+    // IllegalStateException when the partition predicate was pruned as 
always-true.
+    sql """DROP TABLE IF EXISTS tbl_point_query_list_partition"""
+    sql """
+        CREATE TABLE tbl_point_query_list_partition (
+            pk varchar(64),
+            _id bigint
+        )
+        UNIQUE KEY(pk, _id)
+        PARTITION BY LIST (`pk`) (
+            PARTITION p_abcd VALUES IN ('abcd'),
+            PARTITION p_ab VALUES IN ('a', 'b')
+        )
+        DISTRIBUTED BY HASH(pk, _id) BUCKETS 1
+        PROPERTIES (
+            "replication_num" = "1",
+            "enable_unique_key_merge_on_write" = "true",
+            "store_row_column" = "true",
+            "light_schema_change" = "true"
+        )
+    """
+    sql """INSERT INTO tbl_point_query_list_partition (pk, _id) VALUES 
('abcd', 1), ('a', 2)"""
+
+    explain {
+        sql("SELECT * FROM tbl_point_query_list_partition WHERE pk = 'abcd' 
AND _id = 1")
+        contains "SHORT-CIRCUIT"
+    }
+
+    // single-value list partition: predicate pk='abcd' is fully implied by 
the partition
+    qt_hit_single_value """SELECT * FROM tbl_point_query_list_partition WHERE 
pk = 'abcd' AND _id = 1"""
+    qt_miss """SELECT * FROM tbl_point_query_list_partition WHERE pk = 'abcd' 
AND _id = 2"""
+    // multi-value list partition: predicate pk='a' is not fully implied
+    qt_hit_multi_value """SELECT * FROM tbl_point_query_list_partition WHERE 
pk = 'a' AND _id = 2"""
+
+    // server-side prepared statement path
+    def user = context.config.jdbcUser
+    def password = context.config.jdbcPassword
+    String jdbcUrl = context.config.jdbcUrl
+    String urlWithoutSchema = jdbcUrl.substring(jdbcUrl.indexOf("://") + 3)
+    def sql_ip = urlWithoutSchema.substring(0, urlWithoutSchema.indexOf(":"))
+    def sql_port
+    if (urlWithoutSchema.indexOf("/") >= 0) {
+        sql_port = urlWithoutSchema.substring(urlWithoutSchema.indexOf(":") + 
1, urlWithoutSchema.indexOf("/"))
+    } else {
+        sql_port = urlWithoutSchema.substring(urlWithoutSchema.indexOf(":") + 
1)
+    }
+    def prepare_url = "jdbc:mysql://" + sql_ip + ":" + sql_port + "/" + 
context.dbName + "?&useServerPrepStmts=true"
+
+    connect(user, password, prepare_url) {
+        def stmt = prepareStatement "select * from 
tbl_point_query_list_partition where pk = ? and _id = ?"
+        assertEquals(stmt.class, com.mysql.cj.jdbc.ServerPreparedStatement);
+        stmt.setString(1, 'abcd')
+        stmt.setLong(2, 1)
+        qe_point_select_prepared_hit stmt
+        stmt.setString(1, 'abcd')
+        stmt.setLong(2, 2)
+        qe_point_select_prepared_miss stmt
+        stmt.setString(1, 'a')
+        stmt.setLong(2, 2)
+        qe_point_select_prepared_multi stmt
+    }
+}


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

Reply via email to