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]