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 5f4cc0badd7 branch:4.2: [fix](point query) Keep point-query scan when
partition pruning is empty #67161 (#68293)
5f4cc0badd7 is described below
commit 5f4cc0badd7b0bd129d2a20ea4bec7f044723463
Author: morrySnow <[email protected]>
AuthorDate: Mon Sep 21 10:38:11 2026 +0800
branch:4.2: [fix](point query) Keep point-query scan when partition pruning
is empty #67161 (#68293)
picked from #67161
Co-authored-by: zhangyipeng.0818 <[email protected]>
---
.../java/org/apache/doris/catalog/OlapTable.java | 21 +++-
.../LogicalResultSinkToShortCircuitPointQuery.java | 16 ++-
.../rules/rewrite/PruneOlapScanPartition.java | 12 +-
.../trees/plans/commands/ExecuteCommand.java | 16 ++-
.../apache/doris/qe/ShortCircuitQueryContext.java | 6 +-
.../java/org/apache/doris/qe/StmtExecutor.java | 10 +-
.../org/apache/doris/catalog/OlapTableTest.java | 29 +++++
.../rules/rewrite/ShortCircuitPointQueryTest.java | 130 +++++++++++++++++----
.../doris/qe/ShortCircuitQueryContextTest.java | 31 +++++
.../test_point_query_partition_not_exists.out | 20 ++++
.../test_point_query_partition_not_exists.groovy | 130 +++++++++++++++++++++
11 files changed, 383 insertions(+), 38 deletions(-)
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java
b/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java
index 3fe0ba94c8e..ae7dd033d97 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java
@@ -121,6 +121,7 @@ import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
@@ -183,6 +184,10 @@ public class OlapTable extends Table implements
MTMVRelatedTableIf, GsonPostProc
@SerializedName(value = "itp", alternate = {"idToPartition"})
@Getter
protected ConcurrentHashMap<Long, Partition> idToPartition = new
ConcurrentHashMap<>();
+ // Incremented only when the formal partition id set changes. Prepared
short-circuit point query cache uses this
+ // to invalidate stale partition pruning metadata without reacting to
ordinary data writes. This is transient and
+ // rebuilt from zero after deserialization because prepared statement
cache is also in-memory.
+ private transient AtomicLong partitionTopologyVersion = new AtomicLong(0L);
// handled in postgsonprocess
@Getter
protected Map<String, Partition> nameToPartition = Maps.newTreeMap();
@@ -1286,8 +1291,11 @@ public class OlapTable extends Table implements
MTMVRelatedTableIf, GsonPostProc
}
public void addPartition(Partition partition) {
- idToPartition.put(partition.getId(), partition);
+ Partition previousPartition = idToPartition.put(partition.getId(),
partition);
nameToPartition.put(partition.getName(), partition);
+ if (previousPartition == null) {
+ bumpPartitionTopologyVersion();
+ }
}
// This is a private method.
@@ -1303,6 +1311,7 @@ public class OlapTable extends Table implements
MTMVRelatedTableIf, GsonPostProc
if (partition != null) {
idToPartition.remove(partition.getId());
nameToPartition.remove(partitionName);
+ bumpPartitionTopologyVersion();
RecyclePartitionParam recyclePartitionParam = new
RecyclePartitionParam();
fillInfo(partition, recyclePartitionParam);
dropPartitionCommon(dbId, isForceDrop, recyclePartitionParam,
partition, reserveTablets);
@@ -1588,6 +1597,14 @@ public class OlapTable extends Table implements
MTMVRelatedTableIf, GsonPostProc
return new ArrayList<>(idToPartition.keySet());
}
+ public long getPartitionTopologyVersion() {
+ return partitionTopologyVersion.get();
+ }
+
+ private void bumpPartitionTopologyVersion() {
+ partitionTopologyVersion.incrementAndGet();
+ }
+
public Set<String> getCopiedBfColumns() {
if (bfColumns == null) {
return null;
@@ -2018,6 +2035,7 @@ public class OlapTable extends Table implements
MTMVRelatedTableIf, GsonPostProc
@Override
public void gsonPostProcess() throws IOException {
+ partitionTopologyVersion = new AtomicLong(0L);
// HACK: the index id in MaterializedIndexMeta is not equals to the
index id
// saved in OlapTable, because the table restore from snapshot is not
reset
@@ -2172,6 +2190,7 @@ public class OlapTable extends Table implements
MTMVRelatedTableIf, GsonPostProc
idToPartition.put(newPartition.getId(), newPartition);
nameToPartition.put(newPartition.getName(), newPartition);
+ bumpPartitionTopologyVersion();
DataProperty dataProperty =
partitionInfo.getDataProperty(oldPartition.getId());
ReplicaAllocation replicaAlloc =
partitionInfo.getReplicaAllocation(oldPartition.getId());
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/LogicalResultSinkToShortCircuitPointQuery.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/LogicalResultSinkToShortCircuitPointQuery.java
index 4f5dbb345c9..51bdc44b66b 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/LogicalResultSinkToShortCircuitPointQuery.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/LogicalResultSinkToShortCircuitPointQuery.java
@@ -19,6 +19,7 @@ package org.apache.doris.nereids.rules.rewrite;
import org.apache.doris.catalog.Column;
import org.apache.doris.catalog.OlapTable;
+import org.apache.doris.datasource.doris.RemoteOlapTable;
import org.apache.doris.nereids.StatementContext;
import org.apache.doris.nereids.rules.Rule;
import org.apache.doris.nereids.rules.RuleType;
@@ -32,6 +33,7 @@ import
org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
import org.apache.doris.qe.ConnectContext;
import org.apache.doris.qe.ConnectContext.ConnectType;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
@@ -61,7 +63,8 @@ public class LogicalResultSinkToShortCircuitPointQuery
implements RewriteRuleFac
&& expression.child(1).isLiteral());
}
- private boolean scanMatchShortCircuitCondition(LogicalOlapScan olapScan) {
+ @VisibleForTesting
+ boolean scanMatchShortCircuitCondition(LogicalOlapScan olapScan) {
ConnectContext connectContext = ConnectContext.get();
if (!connectContext.getSessionVariable().isEnableShortCircuitQuery()) {
return false;
@@ -78,7 +81,18 @@ public class LogicalResultSinkToShortCircuitPointQuery
implements RewriteRuleFac
if (connectContext.getConnectType() == ConnectType.ARROW_FLIGHT_SQL) {
return false;
}
+ // Lazy point-query pruning does not preserve explicit
PARTITION/TABLET restrictions.
+ // Keep these queries on the normal execution path so the physical
scan enforces them.
+ if (!olapScan.getManuallySpecifiedPartitions().isEmpty()
+ || !olapScan.getManuallySpecifiedTabletIds().isEmpty()) {
+ return false;
+ }
OlapTable olapTable = olapScan.getTable();
+ // Remote Doris metadata refresh replaces the RemoteOlapTable
instance. A prepared context retains the old
+ // instance, so its table-local topology version cannot observe remote
partition changes.
+ if (olapTable instanceof RemoteOlapTable) {
+ return false;
+ }
if (olapTable.hasVariantColumns()) {
return false;
}
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 b0cfde50962..845449952bd 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
@@ -132,14 +132,20 @@ public class PruneOlapScanPartition implements
RewriteRuleFactory {
if (prunedPartitions == null) {
return Pair.of(null, Optional.empty());
}
+ boolean hasPartitionPredicate =
prunedPartitionsByFilters.hasPartitionPredicate
+ || !scan.getManuallySpecifiedPartitions().isEmpty()
+ || !scan.getManuallySpecifiedTabletIds().isEmpty();
if (prunedPartitions.isEmpty()) {
+ // Keep scan for short-circuit point queries even when no
partition matches.
+ // ShortCircuitQueryContext needs the scan to initialize and cache
the point-query path.
+ if (filter != null && ctx.statementContext.isShortCircuitQuery()) {
+ return Pair.of(scan.withSelectedPartitionIds(prunedPartitions,
hasPartitionPredicate),
+ Optional.empty());
+ }
return Pair.of(new LogicalEmptyRelation(
ConnectContext.get().getStatementContext().getNextRelationId(),
ctx.root.getOutput()), Optional.empty());
}
- boolean hasPartitionPredicate =
prunedPartitionsByFilters.hasPartitionPredicate
- || !scan.getManuallySpecifiedPartitions().isEmpty()
- || !scan.getManuallySpecifiedTabletIds().isEmpty();
return Pair.of(scan.withSelectedPartitionIds(prunedPartitions,
hasPartitionPredicate),
prunedPartitionsByFilters.prunedPartitionPredicate);
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ExecuteCommand.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ExecuteCommand.java
index 5bedc88e811..203deb626a8 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ExecuteCommand.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ExecuteCommand.java
@@ -17,7 +17,6 @@
package org.apache.doris.nereids.trees.plans.commands;
-import org.apache.doris.analysis.Queriable;
import org.apache.doris.analysis.StmtType;
import org.apache.doris.analysis.TableScanParams;
import org.apache.doris.nereids.StatementContext;
@@ -41,6 +40,8 @@ import org.apache.doris.qe.PreparedStatementContext;
import org.apache.doris.qe.ShortCircuitQueryContext;
import org.apache.doris.qe.StmtExecutor;
+import com.google.common.base.Preconditions;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -170,11 +171,14 @@ public class ExecuteCommand extends Command {
preparedStmtCtx.shortCircuitQueryContext = Optional.empty();
statementContext.setShortCircuitQueryContext(null);
executor.execute();
- if (executor.getContext().getStatementContext().isShortCircuitQuery())
{
- // cache short-circuit plan
- preparedStmtCtx.shortCircuitQueryContext = Optional.of(
- new ShortCircuitQueryContext(executor.planner(),
(Queriable) executor.getParsedStmt()));
-
statementContext.setShortCircuitQueryContext(preparedStmtCtx.shortCircuitQueryContext.get());
+ StatementContext executedStatementContext =
executor.getContext().getStatementContext();
+ ShortCircuitQueryContext shortCircuitQueryContext =
+ executedStatementContext.getShortCircuitQueryContext();
+ if (shortCircuitQueryContext != null) {
+
Preconditions.checkState(executedStatementContext.isShortCircuitQuery());
+ // Publish the exact context used by this execution so its
topology generation stays
+ // bound to the cached partition pruner in the same planner scan
node.
+ preparedStmtCtx.shortCircuitQueryContext =
Optional.of(shortCircuitQueryContext);
}
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/qe/ShortCircuitQueryContext.java
b/fe/fe-core/src/main/java/org/apache/doris/qe/ShortCircuitQueryContext.java
index 1602b641972..4cda24153d3 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/ShortCircuitQueryContext.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/ShortCircuitQueryContext.java
@@ -56,6 +56,7 @@ public class ShortCircuitQueryContext {
public final int schemaVersion;
public final OlapTable tbl;
private final long fileCacheQueryLimitBytes;
+ private final long partitionTopologyVersion;
public final OlapScanNode scanNode;
public final Queriable analzyedQuery;
@@ -109,6 +110,7 @@ public class ShortCircuitQueryContext {
this.scanNode = olapScanNode;
this.tbl = this.scanNode.getOlapTable();
this.schemaVersion = this.tbl.getBaseSchemaVersion();
+ this.partitionTopologyVersion = this.tbl.getPartitionTopologyVersion();
this.analzyedQuery = analzyedQuery;
}
@@ -122,13 +124,15 @@ public class ShortCircuitQueryContext {
this.tbl = tbl;
this.schemaVersion = schemaVersion;
this.fileCacheQueryLimitBytes = fileCacheQueryLimitBytes;
+ this.partitionTopologyVersion = tbl.getPartitionTopologyVersion();
this.scanNode = null;
this.analzyedQuery = null;
}
public boolean isReusable(ConnectContext ctx) {
return this.tbl.getBaseSchemaVersion() == this.schemaVersion
- && this.fileCacheQueryLimitBytes ==
ctx.getSessionVariable().fileCacheQueryLimitBytes;
+ && this.fileCacheQueryLimitBytes ==
ctx.getSessionVariable().fileCacheQueryLimitBytes
+ && this.tbl.getPartitionTopologyVersion() ==
this.partitionTopologyVersion;
}
public void sanitize() {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java
b/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java
index b349cf9160d..d8e228e65e8 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java
@@ -1415,10 +1415,12 @@ public class StmtExecutor {
RowBatch batch;
CoordInterface coordBase = null;
if (statementContext.isShortCircuitQuery()) {
- ShortCircuitQueryContext shortCircuitQueryContext =
- statementContext.getShortCircuitQueryContext() != null
- ?
statementContext.getShortCircuitQueryContext()
- : new ShortCircuitQueryContext(planner,
(Queriable) parsedStmt);
+ ShortCircuitQueryContext shortCircuitQueryContext =
statementContext.getShortCircuitQueryContext();
+ if (shortCircuitQueryContext == null) {
+ shortCircuitQueryContext = new
ShortCircuitQueryContext(planner, (Queriable) parsedStmt);
+ // ExecuteCommand publishes this same context after a
successful first prepared execution.
+
statementContext.setShortCircuitQueryContext(shortCircuitQueryContext);
+ }
coordBase = new PointQueryExecutor(shortCircuitQueryContext,
context.getSessionVariable().getMaxMsgSizeOfResultReceiver());
context.getState().setIsQuery(true);
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/catalog/OlapTableTest.java
b/fe/fe-core/src/test/java/org/apache/doris/catalog/OlapTableTest.java
index 50fab95e69c..e7ef1774f43 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/catalog/OlapTableTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/catalog/OlapTableTest.java
@@ -78,6 +78,35 @@ public class OlapTableTest {
Assert.assertEquals(132L, stats.getIndexLength());
}
+ @Test
+ public void testPartitionTopologyVersionChangesWithPartitionIdSet() {
+ OlapTable olapTable = new OlapTable();
+ olapTable.setPartitionInfo(new SinglePartitionInfo());
+
+ long version = olapTable.getPartitionTopologyVersion();
+ addPartitionForTopologyVersionTest(olapTable, 1L, "p1");
+ Assert.assertEquals(version + 1,
olapTable.getPartitionTopologyVersion());
+
+ version = olapTable.getPartitionTopologyVersion();
+ olapTable.replacePartition(newPartitionForTopologyVersionTest(2L,
"p1"), new RecyclePartitionParam());
+ Assert.assertEquals(version + 1,
olapTable.getPartitionTopologyVersion());
+
+ version = olapTable.getPartitionTopologyVersion();
+ olapTable.dropPartitionAndReserveTablet("p1");
+ Assert.assertEquals(version + 1,
olapTable.getPartitionTopologyVersion());
+ }
+
+ private void addPartitionForTopologyVersionTest(OlapTable olapTable, long
partitionId, String partitionName) {
+ olapTable.getPartitionInfo().addPartition(partitionId, new
DataProperty(TStorageMedium.HDD),
+ new ReplicaAllocation((short) 1), false, true);
+ olapTable.addPartition(newPartitionForTopologyVersionTest(partitionId,
partitionName));
+ }
+
+ private Partition newPartitionForTopologyVersionTest(long partitionId,
String partitionName) {
+ MaterializedIndex index = new MaterializedIndex(partitionId,
MaterializedIndex.IndexState.NORMAL);
+ return new Partition(partitionId, partitionName, index, new
RandomDistributionInfo(1));
+ }
+
private Replica mockReplica(Replica.ReplicaState state, long dataSize,
long localSegmentSize,
long remoteSegmentSize, long localIndexSize, long remoteIndexSize)
{
Replica replica = Mockito.mock(Replica.class);
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ShortCircuitPointQueryTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ShortCircuitPointQueryTest.java
index a9b2815b3d9..512033b35a5 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ShortCircuitPointQueryTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ShortCircuitPointQueryTest.java
@@ -17,7 +17,13 @@
package org.apache.doris.nereids.rules.rewrite;
+import org.apache.doris.catalog.Database;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.catalog.OlapTable;
+import org.apache.doris.catalog.Tablet;
import org.apache.doris.common.FeConstants;
+import org.apache.doris.datasource.doris.RemoteOlapTable;
+import org.apache.doris.nereids.trees.expressions.StatementScopeIdGenerator;
import org.apache.doris.nereids.trees.plans.Plan;
import org.apache.doris.nereids.trees.plans.logical.LogicalEmptyRelation;
import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
@@ -35,8 +41,9 @@ import java.lang.reflect.Field;
/**
* Regression test:
* For short-circuit point query, we should not rewrite LogicalOlapScan to
LogicalEmptyRelation
- * even if the table partitions are empty. Otherwise PreparedStatement could
not cache
- * ShortCircuitQueryContext and may downgrade to normal planning repeatedly.
+ * even if the table partitions are empty or partition pruning selects no
partitions.
+ * Current execution still needs the scan to initialize the point-query path;
PreparedStatement
+ * cacheability for empty selected partitions is handled separately.
*/
class ShortCircuitPointQueryTest extends TestWithFeService
implements MemoPatternMatchSupported {
@@ -57,24 +64,115 @@ class ShortCircuitPointQueryTest extends TestWithFeService
+ " \"light_schema_change\" = \"true\",\n"
+ " \"store_row_column\" = \"true\"\n"
+ ");");
+ createTable("CREATE TABLE `tbl_partitioned_point_query` (\n"
+ + " `order_id` bigint NOT NULL,\n"
+ + " `pay_date` date NOT NULL,\n"
+ + " `v1` varchar(30) NULL\n"
+ + ") ENGINE=OLAP\n"
+ + "UNIQUE KEY(`order_id`, `pay_date`)\n"
+ + "PARTITION BY RANGE(`pay_date`) (\n"
+ + " PARTITION `p20260805` VALUES [(\"2026-08-05\"),
(\"2026-08-06\"))\n"
+ + ")\n"
+ + "DISTRIBUTED BY HASH(`order_id`) BUCKETS 1\n"
+ + "PROPERTIES (\n"
+ + " \"replication_num\" = \"1\",\n"
+ + " \"enable_unique_key_merge_on_write\" = \"true\",\n"
+ + " \"light_schema_change\" = \"true\",\n"
+ + " \"store_row_column\" = \"true\"\n"
+ + ");");
}
@Test
void testShortCircuitPointQueryKeepOlapScanWhenTableEmpty() {
+ Plan plan = rewrite("select * from tbl_point_query where `key` = 1");
+
+
Assertions.assertTrue(connectContext.getStatementContext().isShortCircuitQuery());
+ Assertions.assertTrue(plan.anyMatch(p -> p instanceof
LogicalOlapScan));
+ Assertions.assertFalse(plan.anyMatch(p -> p instanceof
LogicalEmptyRelation));
+ }
+
+ @Test
+ void testShortCircuitPointQueryKeepOlapScanWhenNoPartitionMatches() {
+ Plan plan = rewrite("select * from tbl_partitioned_point_query "
+ + "where order_id = 1 and pay_date = '2026-08-04'");
+
+
Assertions.assertTrue(connectContext.getStatementContext().isShortCircuitQuery());
+ Assertions.assertTrue(plan.anyMatch(p -> p instanceof LogicalOlapScan
+ && ((LogicalOlapScan) p).isPartitionPruned()
+ && ((LogicalOlapScan) p).getSelectedPartitionIds().isEmpty()
+ && ((LogicalOlapScan) p).hasPartitionPredicate()));
+ Assertions.assertFalse(plan.anyMatch(p -> p instanceof
LogicalEmptyRelation));
+ }
+
+ @Test
+ void testNonPointQueryWithNoMatchingPartitionPrunesToEmptyRelation() {
+ Plan plan = rewrite("select * from tbl_partitioned_point_query "
+ + "where pay_date = '2026-08-04'");
+
+
Assertions.assertFalse(connectContext.getStatementContext().isShortCircuitQuery());
+ Assertions.assertTrue(plan.anyMatch(p -> p instanceof
LogicalEmptyRelation));
+ Assertions.assertFalse(plan.anyMatch(p -> p instanceof
LogicalOlapScan));
+ }
+
+ @Test
+ void
testShortCircuitPointQueryWithMatchingPartitionKeepsSelectedPartition() {
+ Plan plan = rewrite("select * from tbl_partitioned_point_query "
+ + "where order_id = 1 and pay_date = '2026-08-05'");
+
+
Assertions.assertTrue(connectContext.getStatementContext().isShortCircuitQuery());
+ Assertions.assertTrue(plan.anyMatch(p -> p instanceof LogicalOlapScan
+ && ((LogicalOlapScan) p).isPartitionPruned()
+ && !((LogicalOlapScan)
p).getSelectedPartitionIds().isEmpty()));
+ Assertions.assertFalse(plan.anyMatch(p -> p instanceof
LogicalEmptyRelation));
+ }
+
+ @Test
+ void testPointQueryWithManualPartitionDoesNotUseShortCircuit() {
+ rewrite("select * from tbl_partitioned_point_query
partition(p20260805) "
+ + "where order_id = 1 and pay_date = '2026-08-05'");
+
+
Assertions.assertFalse(connectContext.getStatementContext().isShortCircuitQuery());
+ }
+
+ @Test
+ void testPointQueryWithManualTabletDoesNotUseShortCircuit() throws
Exception {
+ long tabletId = getTabletId("p20260805");
+ rewrite("select * from tbl_partitioned_point_query tablet(" + tabletId
+ ") "
+ + "where order_id = 1 and pay_date = '2026-08-05'");
+
+
Assertions.assertFalse(connectContext.getStatementContext().isShortCircuitQuery());
+ }
+
+ @Test
+ void testRemoteOlapTableDoesNotUseShortCircuit() throws Exception {
+ Database database =
Env.getCurrentInternalCatalog().getDbOrMetaException("test");
+ OlapTable table = (OlapTable)
database.getTableOrMetaException("tbl_point_query");
+ RemoteOlapTable remoteTable = RemoteOlapTable.fromOlapTable(table);
+ LogicalOlapScan scan = new
LogicalOlapScan(StatementScopeIdGenerator.newRelationId(), remoteTable);
+
+
Assertions.assertTrue(connectContext.getSessionVariable().isEnableShortCircuitQuery());
+ Assertions.assertTrue(remoteTable.getEnableLightSchemaChange());
+ Assertions.assertTrue(remoteTable.getEnableUniqueKeyMergeOnWrite());
+ Assertions.assertTrue(remoteTable.storeRowColumn());
+ Assertions.assertFalse(new LogicalResultSinkToShortCircuitPointQuery()
+ .scanMatchShortCircuitCondition(scan));
+ }
+
+ private long getTabletId(String partitionName) throws Exception {
+ Database database =
Env.getCurrentInternalCatalog().getDbOrMetaException("test");
+ OlapTable table = (OlapTable)
database.getTableOrMetaException("tbl_partitioned_point_query");
+ Tablet tablet =
table.getPartition(partitionName).getBaseIndex().getTablets().iterator().next();
+ return tablet.getId();
+ }
+
+ private Plan rewrite(String sql) {
boolean originRunningUnitTest = FeConstants.runningUnitTest;
FeConstants.runningUnitTest = false;
try {
- String sql = "select * from tbl_point_query where `key` = 1";
- Plan plan = PlanChecker.from(connectContext)
+ return PlanChecker.from(connectContext)
.analyze(sql)
.rewrite()
.getPlan();
-
- // short-circuit flag should be set
-
Assertions.assertTrue(connectContext.getStatementContext().isShortCircuitQuery());
- // should still keep scan node for point query path
- Assertions.assertTrue(plan.anyMatch(p -> p instanceof
LogicalOlapScan));
- Assertions.assertFalse(plan.anyMatch(p -> p instanceof
LogicalEmptyRelation));
} finally {
FeConstants.runningUnitTest = originRunningUnitTest;
}
@@ -109,16 +207,4 @@ class ShortCircuitPointQueryTest extends TestWithFeService
Assertions.assertTrue(connectContext.getStatementContext().isShortCircuitQuery());
}
- private Plan rewrite(String sql) {
- boolean originRunningUnitTest = FeConstants.runningUnitTest;
- FeConstants.runningUnitTest = false;
- try {
- return PlanChecker.from(connectContext)
- .analyze(sql)
- .rewrite()
- .getPlan();
- } finally {
- FeConstants.runningUnitTest = originRunningUnitTest;
- }
- }
}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/qe/ShortCircuitQueryContextTest.java
b/fe/fe-core/src/test/java/org/apache/doris/qe/ShortCircuitQueryContextTest.java
index cdbd6491e5e..f5a13b13cdd 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/qe/ShortCircuitQueryContextTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/qe/ShortCircuitQueryContextTest.java
@@ -17,12 +17,23 @@
package org.apache.doris.qe;
+import org.apache.doris.catalog.Column;
+import org.apache.doris.catalog.KeysType;
+import org.apache.doris.catalog.MaterializedIndex;
import org.apache.doris.catalog.OlapTable;
+import org.apache.doris.catalog.Partition;
+import org.apache.doris.catalog.PrimitiveType;
+import org.apache.doris.catalog.RandomDistributionInfo;
+import org.apache.doris.catalog.SinglePartitionInfo;
+import org.apache.doris.thrift.TStorageType;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
+import java.util.Collections;
+import java.util.List;
+
public class ShortCircuitQueryContextTest {
private OlapTable table(String name, int schemaVersion) {
OlapTable table = Mockito.spy(new OlapTable());
@@ -55,4 +66,24 @@ public class ShortCircuitQueryContextTest {
Assertions.assertFalse(context.isReusable(connectContext(0)));
}
+
+ @Test
+ public void testReusableRequiresSamePartitionTopologyVersion() {
+ long baseIndexId = 2L;
+ Column key = new Column("k", PrimitiveType.INT);
+ key.setIsKey(true);
+ List<Column> baseSchema = Collections.singletonList(key);
+ OlapTable table = new OlapTable(1L, "tbl", baseSchema,
KeysType.DUP_KEYS,
+ new SinglePartitionInfo(), new RandomDistributionInfo(1));
+ table.setIndexMeta(baseIndexId, "tbl", baseSchema, 10, 0, (short) 1,
+ TStorageType.COLUMN, KeysType.DUP_KEYS);
+ table.setBaseIndexId(baseIndexId);
+ ShortCircuitQueryContext context = new ShortCircuitQueryContext(table,
10, -1);
+
+ Assertions.assertTrue(context.isReusable(connectContext(-1)));
+ table.addPartition(new Partition(3L, "p1",
+ new MaterializedIndex(baseIndexId,
MaterializedIndex.IndexState.NORMAL),
+ new RandomDistributionInfo(1)));
+ Assertions.assertFalse(context.isReusable(connectContext(-1)));
+ }
}
diff --git
a/regression-test/data/point_query_p0/test_point_query_partition_not_exists.out
b/regression-test/data/point_query_p0/test_point_query_partition_not_exists.out
new file mode 100644
index 00000000000..8c5343b4dcf
--- /dev/null
+++
b/regression-test/data/point_query_p0/test_point_query_partition_not_exists.out
@@ -0,0 +1,20 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !direct_no_partition --
+
+-- !prepared_no_partition_first --
+
+-- !prepared_no_partition_repeat --
+
+-- !prepared_added_partition --
+1 2026-08-04 added
+
+-- !prepared_existing_partition --
+2 2026-08-05 existing
+
+-- !direct_manual_partition_conflict --
+
+-- !direct_manual_tablet_conflict --
+
+-- !prepared_manual_partition_conflict --
+
+-- !prepared_manual_tablet_conflict --
diff --git
a/regression-test/suites/point_query_p0/test_point_query_partition_not_exists.groovy
b/regression-test/suites/point_query_p0/test_point_query_partition_not_exists.groovy
new file mode 100644
index 00000000000..b373ec4d94b
--- /dev/null
+++
b/regression-test/suites/point_query_p0/test_point_query_partition_not_exists.groovy
@@ -0,0 +1,130 @@
+// 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.
+
+import com.mysql.cj.jdbc.ServerPreparedStatement
+
+import java.sql.Date
+
+suite("test_point_query_partition_not_exists") {
+ sql "CREATE DATABASE IF NOT EXISTS regression_test_point_query_p0"
+ sql "USE regression_test_point_query_p0"
+ sql "DROP TABLE IF EXISTS test_point_query_partition_not_exists"
+ sql """
+ CREATE TABLE test_point_query_partition_not_exists (
+ order_id BIGINT NOT NULL,
+ pay_date DATE NOT NULL,
+ value VARCHAR(30) NULL
+ ) ENGINE=OLAP
+ UNIQUE KEY(order_id, pay_date)
+ PARTITION BY RANGE(pay_date) (
+ PARTITION p20260805 VALUES [("2026-08-05"), ("2026-08-06")),
+ PARTITION p20260806 VALUES [("2026-08-06"), ("2026-08-07"))
+ )
+ DISTRIBUTED BY HASH(order_id) BUCKETS 1
+ PROPERTIES (
+ "replication_num" = "1",
+ "enable_unique_key_merge_on_write" = "true",
+ "light_schema_change" = "true",
+ "store_row_column" = "true"
+ )
+ """
+ sql """
+ INSERT INTO test_point_query_partition_not_exists VALUES
+ (2, '2026-08-05', 'existing'),
+ (3, '2026-08-06', 'excluded')
+ """
+ sql "SET enable_nereids_planner = true"
+ sql "SET enable_fallback_to_original_planner = false"
+
+ explain {
+ sql """
+ SELECT * FROM test_point_query_partition_not_exists
+ WHERE order_id = 1 AND pay_date = '2026-08-04'
+ """
+ contains "SHORT-CIRCUIT"
+ }
+
+ order_qt_direct_no_partition """
+ SELECT * FROM test_point_query_partition_not_exists
+ WHERE order_id = 1 AND pay_date = '2026-08-04'
+ """
+
+ String url = getServerPrepareJdbcUrl(
+ context.config.jdbcUrl, "regression_test_point_query_p0", false)
+ connect(context.config.jdbcUser, context.config.jdbcPassword, url) {
+ def stmt = prepareStatement """
+ SELECT * FROM test_point_query_partition_not_exists
+ WHERE order_id = ? AND pay_date = ?
+ """
+ assertEquals(ServerPreparedStatement, stmt.class)
+
+ stmt.setLong(1, 1)
+ stmt.setDate(2, Date.valueOf("2026-08-04"))
+ qe_prepared_no_partition_first stmt
+ qe_prepared_no_partition_repeat stmt
+
+ sql """
+ ALTER TABLE test_point_query_partition_not_exists
+ ADD PARTITION p20260804 VALUES [("2026-08-04"), ("2026-08-05"))
+ """
+ sql "INSERT INTO test_point_query_partition_not_exists VALUES (1,
'2026-08-04', 'added')"
+ stmt.setLong(1, 1)
+ stmt.setDate(2, Date.valueOf("2026-08-04"))
+ qe_prepared_added_partition stmt
+
+ stmt.setLong(1, 2)
+ stmt.setDate(2, Date.valueOf("2026-08-05"))
+ qe_prepared_existing_partition stmt
+ stmt.close()
+ }
+
+ def tablets = sql_return_maparray """
+ SHOW TABLETS FROM test_point_query_partition_not_exists
PARTITION(p20260805)
+ """
+ def p20260805TabletId = tablets[0].TabletId
+
+ order_qt_direct_manual_partition_conflict """
+ SELECT * FROM test_point_query_partition_not_exists
PARTITION(p20260805)
+ WHERE order_id = 3 AND pay_date = '2026-08-06'
+ """
+ order_qt_direct_manual_tablet_conflict """
+ SELECT * FROM test_point_query_partition_not_exists
TABLET(${p20260805TabletId})
+ WHERE order_id = 3 AND pay_date = '2026-08-06'
+ """
+
+ connect(context.config.jdbcUser, context.config.jdbcPassword, url) {
+ def partitionStmt = prepareStatement """
+ SELECT * FROM test_point_query_partition_not_exists
PARTITION(p20260805)
+ WHERE order_id = ? AND pay_date = ?
+ """
+ assertEquals(ServerPreparedStatement, partitionStmt.class)
+ partitionStmt.setLong(1, 3)
+ partitionStmt.setDate(2, Date.valueOf("2026-08-06"))
+ qe_prepared_manual_partition_conflict partitionStmt
+ partitionStmt.close()
+
+ def tabletStmt = prepareStatement """
+ SELECT * FROM test_point_query_partition_not_exists
TABLET(${p20260805TabletId})
+ WHERE order_id = ? AND pay_date = ?
+ """
+ assertEquals(ServerPreparedStatement, tabletStmt.class)
+ tabletStmt.setLong(1, 3)
+ tabletStmt.setDate(2, Date.valueOf("2026-08-06"))
+ qe_prepared_manual_tablet_conflict tabletStmt
+ tabletStmt.close()
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]