This is an automated email from the ASF dual-hosted git repository. Wei-hao-Li pushed a commit to branch fixProject in repository https://gitbox.apache.org/repos/asf/iotdb.git
commit c145cf0dd28a064712dfd14551a0a10aba5e6f24 Author: Weihao Li <[email protected]> AuthorDate: Mon Jun 1 17:25:10 2026 +0800 fix Signed-off-by: Weihao Li <[email protected]> --- .../planner/optimizations/SortElimination.java | 60 ++++++++++++++++++++-- .../plan/relational/analyzer/SortTest.java | 44 ++++++++++++++++ 2 files changed, 101 insertions(+), 3 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/SortElimination.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/SortElimination.java index 8df10d5d8ec..dbb2008f136 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/SortElimination.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/SortElimination.java @@ -25,14 +25,21 @@ import org.apache.iotdb.commons.queryengine.plan.relational.planner.Symbol; import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.FillNode; import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.GapFillNode; import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.PatternRecognitionNode; +import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.ProjectNode; import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.SortNode; import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.StreamSortNode; import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.ValueFillNode; import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.WindowNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanVisitor; +import org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.rule.PruneTableScanColumns; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.DeviceTableScanNode; +import com.google.common.collect.ImmutableSet; + import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; /** * <b>Optimization phase:</b> Distributed plan planning. @@ -42,6 +49,8 @@ import java.util.Collections; * SortNode can be eliminated. * <li>When order by all IDColumns and time, the SortNode can be eliminated. * <li>When StreamSortIndex==OrderBy size()-1, remove this StreamSortNode + * <li>After SortNode elimination, visitProject will remove redundant identity ProjectNodes above + * TableScan and pushes column pruning into the scan. */ public class SortElimination implements PlanOptimizer { @@ -64,6 +73,43 @@ public class SortElimination implements PlanOptimizer { return newNode; } + @Override + public PlanNode visitProject(ProjectNode node, Context context) { + Context newContext = new Context(); + PlanNode child = node.getChild().accept(this, newContext); + context.setCannotEliminateSort(newContext.cannotEliminateSort); + + // Remove useless ProjectNode and prune columns of TableScanNode + return eliminateProjectOverTableScan(node, child) + .orElseGet(() -> node.replaceChildren(Collections.singletonList(child))); + } + + private static Optional<PlanNode> eliminateProjectOverTableScan( + ProjectNode project, PlanNode child) { + if (!(child instanceof DeviceTableScanNode) || !project.isIdentity()) { + return Optional.empty(); + } + + // Notice that SortNode may have been eliminated in TableDistributedPlanGenerator + DeviceTableScanNode tableScan = (DeviceTableScanNode) child; + int projectOutputsSize = project.getOutputSymbols().size(); + int scanOutputsSize = tableScan.getOutputSymbols().size(); + if (projectOutputsSize > scanOutputsSize) { + return Optional.empty(); + } + + List<Symbol> projectOutputs = project.getOutputSymbols(); + Set<Symbol> scanOutputs = ImmutableSet.copyOf(tableScan.getOutputSymbols()); + if (!scanOutputs.containsAll(projectOutputs)) { + return Optional.empty(); + } + + if (projectOutputsSize == scanOutputsSize) { + return Optional.of(tableScan); + } + return PruneTableScanColumns.pruneColumns(tableScan, ImmutableSet.copyOf(projectOutputs)); + } + @Override public PlanNode visitSort(SortNode node, Context context) { Context newContext = new Context(); @@ -75,9 +121,7 @@ public class SortElimination implements PlanOptimizer { && orderingScheme.getOrderBy().get(0).getName().equals(context.getTimeColumnName())) { return child; } - return context.canEliminateSort() && node.isOrderByAllIdsAndTime() - ? child - : node.replaceChildren(Collections.singletonList(child)); + return node.replaceChildren(Collections.singletonList(child)); } @Override @@ -152,6 +196,8 @@ public class SortElimination implements PlanOptimizer { private String timeColumnName = null; + private boolean sortEliminated = false; + Context() {} public void addDeviceEntrySize(int deviceEntrySize) { @@ -177,5 +223,13 @@ public class SortElimination implements PlanOptimizer { public void setTimeColumnName(String timeColumnName) { this.timeColumnName = timeColumnName; } + + public boolean isSortEliminated() { + return sortEliminated; + } + + public void markSortEliminated() { + this.sortEliminated = true; + } } } diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/SortTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/SortTest.java index 40797da29a9..f7b393b724a 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/SortTest.java +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/SortTest.java @@ -35,6 +35,7 @@ import org.apache.iotdb.db.queryengine.plan.planner.plan.DistributedQueryPlan; import org.apache.iotdb.db.queryengine.plan.planner.plan.LogicalQueryPlan; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.sink.IdentitySinkNode; import org.apache.iotdb.db.queryengine.plan.relational.metadata.Metadata; +import org.apache.iotdb.db.queryengine.plan.relational.planner.PlanTester; import org.apache.iotdb.db.queryengine.plan.relational.planner.SymbolAllocator; import org.apache.iotdb.db.queryengine.plan.relational.planner.TableLogicalPlanner; import org.apache.iotdb.db.queryengine.plan.relational.planner.distribute.TableDistributedPlanner; @@ -42,6 +43,8 @@ import org.apache.iotdb.db.queryengine.plan.relational.planner.node.DeviceTableS import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ExchangeNode; import org.apache.iotdb.db.queryengine.plan.statement.component.Ordering; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import org.junit.BeforeClass; import org.junit.Test; @@ -58,6 +61,12 @@ import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.TestUtils import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.TestUtils.TEST_MATADATA; import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.TestUtils.assertTableScan; import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.TestUtils.getChildrenNode; +import static org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanAssert.assertPlan; +import static org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.exchange; +import static org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.mergeSort; +import static org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.output; +import static org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.project; +import static org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.tableScan; import static org.apache.iotdb.db.queryengine.plan.statement.component.Ordering.ASC; import static org.apache.iotdb.db.queryengine.plan.statement.component.Ordering.DESC; import static org.junit.Assert.assertEquals; @@ -767,4 +776,39 @@ public class SortTest { expectedPushDownOffset, isPushLimitToEachDevice); } + + @Test + public void singleDeviceOrderByAllIdsAndTimeDescTest() { + PlanTester planTester = new PlanTester(); + planTester.createPlan( + "SELECT s1, s2, s3 FROM table1 " + + "WHERE time >= 1000 AND time <= 2000 AND tag1='beijing' AND tag2='A1'" + + "ORDER BY tag1, tag2, tag3, time DESC"); + assertPlan( + planTester.getFragmentPlan(0), + output( + tableScan( + "testdb.table1", + ImmutableList.of("s1", "s2", "s3"), + ImmutableSet.of("time", "s1", "s2", "s3")))); + } + + @Test + public void multiDeviceOrderByAllIdsAndTimeDescTest() { + PlanTester planTester = new PlanTester(); + planTester.createPlan( + "SELECT s1, s2, s3 FROM table1 " + + "WHERE time >= 1000 AND time <= 2000 AND tag1='beijing' " + + "ORDER BY tag1, tag2, tag3, time DESC"); + assertPlan( + planTester.getFragmentPlan(0), + output(project(mergeSort(exchange(), exchange(), exchange())))); + // Device in multi-region + assertPlan( + planTester.getFragmentPlan(1), + tableScan( + "testdb.table1", + ImmutableList.of("time", "tag1", "tag2", "tag3", "s1", "s2", "s3"), + ImmutableSet.of("time", "tag1", "tag2", "tag3", "s1", "s2", "s3"))); + } }
