From 3026367787089193f0a70f97e8d34ea871f066fa Mon Sep 17 00:00:00 2001 From: AMashenkov Date: Wed, 8 Apr 2026 20:23:16 +0300 Subject: [PATCH 01/12] wip --- .../rule/logical/FilterScanMergeRule.java | 15 +++++ .../mapping/test_partition_pruning.test | 58 ++++++------------- 2 files changed, 33 insertions(+), 40 deletions(-) diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/FilterScanMergeRule.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/FilterScanMergeRule.java index 77828bb5de7..42cf90fc975 100644 --- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/FilterScanMergeRule.java +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/FilterScanMergeRule.java @@ -26,6 +26,7 @@ import org.apache.calcite.plan.RelTraitSet; import org.apache.calcite.rel.RelNode; import org.apache.calcite.rel.logical.LogicalFilter; +import org.apache.calcite.rel.logical.LogicalValues; import org.apache.calcite.rex.RexBuilder; import org.apache.calcite.rex.RexInputRef; import org.apache.calcite.rex.RexNode; @@ -99,6 +100,20 @@ public RexNode visitInputRef(RexInputRef ref) { // We need to replace RexInputRef with RexLocalRef because TableScan doesn't have inputs. condition = RexUtils.replaceInputRefs(condition); + // Eliminate scan if always false condition found. + if (condition.isAlwaysFalse()) { + call.transformTo(LogicalValues.createEmpty(cluster, scan.getRowType())); + call.getPlanner().prune(filter); + return; + } + + // Eliminate always true condition. + if (condition.isAlwaysTrue()) { + call.transformTo(scan); + call.getPlanner().prune(filter); + return; + } + // Set default traits, real traits will be calculated for physical node. RelTraitSet trait = cluster.traitSet(); diff --git a/modules/sql-engine/src/test/resources/mapping/test_partition_pruning.test b/modules/sql-engine/src/test/resources/mapping/test_partition_pruning.test index d0932226f45..f3df6dc3497 100644 --- a/modules/sql-engine/src/test/resources/mapping/test_partition_pruning.test +++ b/modules/sql-engine/src/test/resources/mapping/test_partition_pruning.test @@ -164,7 +164,6 @@ Fragment#4 est: (rows=1) --- # Self join, different predicates that produce disjoint set of partitions -# TODO https://issues.apache.org/jira/browse/IGNITE-28389: Fix the test. We expect the mapper should eliminate all the disjoined parts. N1 SELECT /*+ DISABLE_RULE('NestedLoopJoinConverter', 'HashJoinConverter', 'CorrelatedNestedLoopJoin') */ * FROM t1_n1n2n3 as t1, t1_n1n2n3 as t2 @@ -173,48 +172,27 @@ SELECT /*+ DISABLE_RULE('NestedLoopJoinConverter', 'HashJoinConverter', 'Correla Fragment#2 root distribution: single executionNodes: [N1] - exchangeSourceNodes: {3=[N1, N2, N3]} - colocationGroup[-1]: {nodes=[N1], sourceIds=[-1, 3], assignments={}, partitionsWithConsistencyTokens={N1=[]}} - colocationGroup[3]: {nodes=[N1], sourceIds=[-1, 3], assignments={}, partitionsWithConsistencyTokens={N1=[]}} + colocationGroup[-1]: {nodes=[N1], sourceIds=[-1], assignments={}, partitionsWithConsistencyTokens={N1=[]}} tree: - Receiver + MergeJoin + predicate: =(ID, ID$0) fieldNames: [ID, C1, C2, ID$0, C1$0, C2$0] - sourceFragmentId: 3 + type: inner est: (rows=1) - -Fragment#3 - distribution: table PUBLIC.T1_N1N2N3 in zone ZONE_1 - executionNodes: [N1, N2, N3] - targetNodes: [N1] - colocationGroup[0]: {nodes=[N1, N2, N3], sourceIds=[0, 1], assignments={part_0=N1:3, part_1=N2:3, part_2=N3:3}, partitionsWithConsistencyTokens={N1=[part_0:3], N2=[part_1:3], N3=[part_2:3]}} - colocationGroup[1]: {nodes=[N1, N2, N3], sourceIds=[0, 1], assignments={part_0=N1:3, part_1=N2:3, part_2=N3:3}, partitionsWithConsistencyTokens={N1=[part_0:3], N2=[part_1:3], N3=[part_2:3]}} - partitions: [T1_N1N2N3=[N1={0}, N2={1}, N3={2}]] - tree: - Sender - distribution: single - targetFragmentId: 2 - est: (rows=6250) - MergeJoin - predicate: =(ID, ID$0) - fieldNames: [ID, C1, C2, ID$0, C1$0, C2$0] - type: inner - est: (rows=6250) - Sort - collation: [ID ASC] - est: (rows=25000) - TableScan - table: PUBLIC.T1_N1N2N3 - predicate: false - fieldNames: [ID, C1, C2] - est: (rows=25000) - Sort - collation: [ID ASC] - est: (rows=25000) - TableScan - table: PUBLIC.T1_N1N2N3 - predicate: false - fieldNames: [ID, C1, C2] - est: (rows=25000) + Sort + collation: [ID ASC] + est: (rows=1) + Values + fieldNames: [ID, C1, C2] + tuples: [] + est: (rows=1) + Sort + collation: [ID ASC] + est: (rows=1) + Values + fieldNames: [ID, C1, C2] + tuples: [] + est: (rows=1) --- # Correlated # Prune partitions from left arm statically, and pass meta to the right arm. From 6c8dcc6f6a4f3ac449db44d405470c37b029553b Mon Sep 17 00:00:00 2001 From: AMashenkov Date: Thu, 9 Apr 2026 13:02:58 +0300 Subject: [PATCH 02/12] wip. Add rules. --- .../sql/engine/prepare/PlannerHelper.java | 2 + .../sql/engine/prepare/PlannerPhase.java | 18 ++++++- .../rule/logical/FilterScanMergeRule.java | 1 + .../ProjectFilterScanMergePlannerTest.java | 52 +++++++++++++++++++ 4 files changed, 72 insertions(+), 1 deletion(-) diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerHelper.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerHelper.java index 89f9134fec1..96153b0b754 100644 --- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerHelper.java +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerHelper.java @@ -181,6 +181,8 @@ public static IgniteRel optimize(SqlNode sqlNode, IgnitePlanner planner) { rel = planner.transform(PlannerPhase.HEP_FILTER_PUSH_DOWN, rel.getTraitSet(), rel); rel = planner.transform(PlannerPhase.HEP_PROJECT_PUSH_DOWN, rel.getTraitSet(), rel); + + rel = planner.transform(PlannerPhase.HEP_EMPTY_NODES_ELIMINATION, rel.getTraitSet(), rel); if (fastQueryOptimizationEnabled()) { // the sole purpose of this code block is to limit scope of `simpleOperation` variable. diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerPhase.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerPhase.java index 303f08400a4..964b934c46e 100644 --- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerPhase.java +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerPhase.java @@ -161,6 +161,22 @@ public Program getProgram(PlanningContext ctx) { } }, + HEP_EMPTY_NODES_ELIMINATION( + "Heuristic phase to eliminate empty nodes", + PruneEmptyRules.PROJECT_INSTANCE, + PruneEmptyRules.FILTER_INSTANCE, + PruneEmptyRules.SORT_INSTANCE, + PruneEmptyRules.AGGREGATE_INSTANCE, + PruneEmptyRules.JOIN_LEFT_INSTANCE, + PruneEmptyRules.JOIN_RIGHT_INSTANCE + ) { + /** {@inheritDoc} */ + @Override + public Program getProgram(PlanningContext ctx) { + return hep(getRules(ctx)); + } + }, + HEP_OPTIMIZE_JOIN_ORDER( "Heuristic phase to optimize join order" ) { @@ -206,7 +222,7 @@ public Program getProgram(PlanningContext ctx) { CoreRules.JOIN_PUSH_EXPRESSIONS, IgniteJoinConditionPushRule.INSTANCE, CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES, - + FilterIntoJoinRule.FilterIntoJoinRuleConfig.DEFAULT .withOperandSupplier(b0 -> b0.operand(LogicalFilter.class).oneInput(b1 -> diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/FilterScanMergeRule.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/FilterScanMergeRule.java index 42cf90fc975..448c8d77a35 100644 --- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/FilterScanMergeRule.java +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/FilterScanMergeRule.java @@ -104,6 +104,7 @@ public RexNode visitInputRef(RexInputRef ref) { if (condition.isAlwaysFalse()) { call.transformTo(LogicalValues.createEmpty(cluster, scan.getRowType())); call.getPlanner().prune(filter); + call.getPlanner().prune(scan); return; } diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java index 71c015daf1d..125249da19a 100644 --- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java +++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.Objects; +import java.util.function.Predicate; import java.util.function.UnaryOperator; import java.util.stream.Collectors; import org.apache.calcite.rex.RexNode; @@ -27,7 +28,9 @@ import org.apache.ignite.internal.sql.engine.prepare.bounds.SearchBounds; import org.apache.ignite.internal.sql.engine.rel.IgniteAggregate; import org.apache.ignite.internal.sql.engine.rel.IgniteIndexScan; +import org.apache.ignite.internal.sql.engine.rel.IgniteProject; import org.apache.ignite.internal.sql.engine.rel.IgniteTableScan; +import org.apache.ignite.internal.sql.engine.rel.IgniteValues; import org.apache.ignite.internal.sql.engine.schema.IgniteSchema; import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions; import org.apache.ignite.internal.type.NativeTypes; @@ -275,6 +278,55 @@ public void testFilterIdentityFilterMerge() throws Exception { "ProjectFilterTransposeRule", "FilterProjectTransposeRule"); } + @Test + public void testAlwaysTrueFilterMerge() throws Exception { + String sql = "SELECT a, c FROM tbl WHERE a > 1 OR a < 3 OR a IS NULL"; + + assertPlan(sql, publicSchema, isInstanceOf(IgniteTableScan.class) + .and(scan -> scan.projects() == null) + .and(scan -> scan.condition() == null) + .and(scan -> ImmutableIntList.of(0, 2).equals(scan.requiredColumns())), + "ProjectFilterTransposeRule", "FilterProjectTransposeRule"); + } + + @Test + public void testAlwaysFalseFilterMerge() throws Exception { + Predicate hasEmptyValuesOnly = isInstanceOf(IgniteValues.class).and(values -> values.getTuples().isEmpty()); + + // Table scan elimination. + String sql = "SELECT a, c FROM tbl WHERE a > 1 AND a < 0"; + assertPlan(sql, publicSchema, hasEmptyValuesOnly); + + sql = "SELECT a, c FROM (SELECT a, c FROM tbl WHERE a > 1) WHERE c = 1 AND c IS NULL"; + assertPlan(sql, publicSchema, hasEmptyValuesOnly); + + sql = "SELECT a, c FROM (SELECT a, c FROM tbl WHERE a > 1) WHERE a < 0"; + assertPlan(sql, publicSchema, hasEmptyValuesOnly); + + // JOIN branch elimination. + sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 LEFT JOIN tbl AS t2 ON t1.a = t2.a WHERE t2.a = 1 AND t2.a IS NULL AND t1.c = 1"; + assertPlan(sql, publicSchema, hasEmptyValuesOnly); + + sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 LEFT JOIN tbl AS t2 ON (t1.a = t2.a AND t2.a = 1 AND t2.a = 2) WHERE t1.c = 1"; + assertPlan(sql, publicSchema, isInstanceOf(IgniteProject.class) + .and(project -> project.getProjects().size() == 3) + .and(hasChildThat(isInstanceOf(IgniteTableScan.class) + .and(scan -> scan.projects() == null) + .and(scan -> "=($t1, 1)".equals(scan.condition().toString())) + )), + "ProjectFilterTransposeRule", "FilterProjectTransposeRule"); + + // JOIN elimination. + sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 INNER JOIN tbl AS t2 ON t1.a = t2.a WHERE t2.a = 1 AND t2.a IS NULL"; + assertPlan(sql, publicSchema, hasEmptyValuesOnly); + + sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 INNER JOIN tbl AS t2 ON t1.a = t2.a AND t2.a = 1 AND t2.a = 2"; + assertPlan(sql, publicSchema, hasEmptyValuesOnly); + +// sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 INNER JOIN tbl AS t2 ON t1.a = t2.a WHERE t1.a = 1 AND t2.a = 2"; +// assertPlan(sql, publicSchema, hasEmptyValuesOnly); + } + /** * Convert search bounds to RexNodes. */ From 0c115e338859a9aeebccde5b62bf1137e1690f06 Mon Sep 17 00:00:00 2001 From: AMashenkov Date: Thu, 9 Apr 2026 13:04:35 +0300 Subject: [PATCH 03/12] minor --- .../internal/sql/engine/prepare/PlannerPhase.java | 7 +++++++ .../planner/ProjectFilterScanMergePlannerTest.java | 14 ++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerPhase.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerPhase.java index 964b934c46e..c34f0037e36 100644 --- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerPhase.java +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerPhase.java @@ -222,6 +222,13 @@ public Program getProgram(PlanningContext ctx) { CoreRules.JOIN_PUSH_EXPRESSIONS, IgniteJoinConditionPushRule.INSTANCE, CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES, + + PruneEmptyRules.PROJECT_INSTANCE, + PruneEmptyRules.FILTER_INSTANCE, + PruneEmptyRules.SORT_INSTANCE, + PruneEmptyRules.AGGREGATE_INSTANCE, + PruneEmptyRules.JOIN_LEFT_INSTANCE, + PruneEmptyRules.JOIN_RIGHT_INSTANCE, FilterIntoJoinRule.FilterIntoJoinRuleConfig.DEFAULT .withOperandSupplier(b0 -> diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java index 125249da19a..12135ef6c72 100644 --- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java +++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java @@ -308,13 +308,11 @@ public void testAlwaysFalseFilterMerge() throws Exception { assertPlan(sql, publicSchema, hasEmptyValuesOnly); sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 LEFT JOIN tbl AS t2 ON (t1.a = t2.a AND t2.a = 1 AND t2.a = 2) WHERE t1.c = 1"; - assertPlan(sql, publicSchema, isInstanceOf(IgniteProject.class) - .and(project -> project.getProjects().size() == 3) - .and(hasChildThat(isInstanceOf(IgniteTableScan.class) - .and(scan -> scan.projects() == null) + assertPlan(sql, publicSchema, isInstanceOf(IgniteTableScan.class) + .and(scan -> scan.projects() != null) + .and(scan -> scan.condition() != null) .and(scan -> "=($t1, 1)".equals(scan.condition().toString())) - )), - "ProjectFilterTransposeRule", "FilterProjectTransposeRule"); + ); // JOIN elimination. sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 INNER JOIN tbl AS t2 ON t1.a = t2.a WHERE t2.a = 1 AND t2.a IS NULL"; @@ -323,8 +321,8 @@ public void testAlwaysFalseFilterMerge() throws Exception { sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 INNER JOIN tbl AS t2 ON t1.a = t2.a AND t2.a = 1 AND t2.a = 2"; assertPlan(sql, publicSchema, hasEmptyValuesOnly); -// sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 INNER JOIN tbl AS t2 ON t1.a = t2.a WHERE t1.a = 1 AND t2.a = 2"; -// assertPlan(sql, publicSchema, hasEmptyValuesOnly); + sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 INNER JOIN tbl AS t2 ON t1.a = t2.a WHERE t1.a = 1 AND t2.a = 2"; + assertPlan(sql, publicSchema, hasEmptyValuesOnly); } /** From ff976998a4b6817f592f07a52b754c0a925a6d9c Mon Sep 17 00:00:00 2001 From: AMashenkov Date: Thu, 9 Apr 2026 13:07:04 +0300 Subject: [PATCH 04/12] minor --- .../engine/planner/ProjectFilterScanMergePlannerTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java index 12135ef6c72..53634cda0cb 100644 --- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java +++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java @@ -298,10 +298,12 @@ public void testAlwaysFalseFilterMerge() throws Exception { assertPlan(sql, publicSchema, hasEmptyValuesOnly); sql = "SELECT a, c FROM (SELECT a, c FROM tbl WHERE a > 1) WHERE c = 1 AND c IS NULL"; - assertPlan(sql, publicSchema, hasEmptyValuesOnly); + assertPlan(sql, publicSchema, hasEmptyValuesOnly, + "ProjectFilterTransposeRule", "FilterProjectTransposeRule"); sql = "SELECT a, c FROM (SELECT a, c FROM tbl WHERE a > 1) WHERE a < 0"; - assertPlan(sql, publicSchema, hasEmptyValuesOnly); + assertPlan(sql, publicSchema, hasEmptyValuesOnly, + "ProjectFilterTransposeRule", "FilterProjectTransposeRule"); // JOIN branch elimination. sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 LEFT JOIN tbl AS t2 ON t1.a = t2.a WHERE t2.a = 1 AND t2.a IS NULL AND t1.c = 1"; From d7e85942a83539c17d617b19c1e977e0ed8f73ec Mon Sep 17 00:00:00 2001 From: AMashenkov Date: Thu, 9 Apr 2026 13:38:04 +0300 Subject: [PATCH 05/12] styles --- .../sql/engine/prepare/PlannerHelper.java | 2 +- .../sql/engine/prepare/PlannerPhase.java | 2 +- .../mapping/test_partition_pruning.test | 27 +++++-------------- 3 files changed, 8 insertions(+), 23 deletions(-) diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerHelper.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerHelper.java index 96153b0b754..1d565ccd4dd 100644 --- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerHelper.java +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerHelper.java @@ -181,7 +181,7 @@ public static IgniteRel optimize(SqlNode sqlNode, IgnitePlanner planner) { rel = planner.transform(PlannerPhase.HEP_FILTER_PUSH_DOWN, rel.getTraitSet(), rel); rel = planner.transform(PlannerPhase.HEP_PROJECT_PUSH_DOWN, rel.getTraitSet(), rel); - + rel = planner.transform(PlannerPhase.HEP_EMPTY_NODES_ELIMINATION, rel.getTraitSet(), rel); if (fastQueryOptimizationEnabled()) { diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerPhase.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerPhase.java index c34f0037e36..639ed9d0867 100644 --- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerPhase.java +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerPhase.java @@ -229,7 +229,7 @@ public Program getProgram(PlanningContext ctx) { PruneEmptyRules.AGGREGATE_INSTANCE, PruneEmptyRules.JOIN_LEFT_INSTANCE, PruneEmptyRules.JOIN_RIGHT_INSTANCE, - + FilterIntoJoinRule.FilterIntoJoinRuleConfig.DEFAULT .withOperandSupplier(b0 -> b0.operand(LogicalFilter.class).oneInput(b1 -> diff --git a/modules/sql-engine/src/test/resources/mapping/test_partition_pruning.test b/modules/sql-engine/src/test/resources/mapping/test_partition_pruning.test index f3df6dc3497..c000706bd72 100644 --- a/modules/sql-engine/src/test/resources/mapping/test_partition_pruning.test +++ b/modules/sql-engine/src/test/resources/mapping/test_partition_pruning.test @@ -169,30 +169,15 @@ SELECT /*+ DISABLE_RULE('NestedLoopJoinConverter', 'HashJoinConverter', 'Correla FROM t1_n1n2n3 as t1, t1_n1n2n3 as t2 WHERE t1.id = t2.id and t1.id IN (1, 3) and t2.id IN (42, 44) --- -Fragment#2 root - distribution: single +Fragment#1 root + distribution: broadcast executionNodes: [N1] colocationGroup[-1]: {nodes=[N1], sourceIds=[-1], assignments={}, partitionsWithConsistencyTokens={N1=[]}} - tree: - MergeJoin - predicate: =(ID, ID$0) - fieldNames: [ID, C1, C2, ID$0, C1$0, C2$0] - type: inner + tree: + Values + fieldNames: [ID, C1, C2, ID0, C10, C20] + tuples: [] est: (rows=1) - Sort - collation: [ID ASC] - est: (rows=1) - Values - fieldNames: [ID, C1, C2] - tuples: [] - est: (rows=1) - Sort - collation: [ID ASC] - est: (rows=1) - Values - fieldNames: [ID, C1, C2] - tuples: [] - est: (rows=1) --- # Correlated # Prune partitions from left arm statically, and pass meta to the right arm. From 9c6f0ebe224a073f9117dacb6cfbee4f46e217d8 Mon Sep 17 00:00:00 2001 From: AMashenkov Date: Thu, 9 Apr 2026 13:58:35 +0300 Subject: [PATCH 06/12] styles --- .../planner/ProjectFilterScanMergePlannerTest.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java index 53634cda0cb..8c179b1e066 100644 --- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java +++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java @@ -28,7 +28,6 @@ import org.apache.ignite.internal.sql.engine.prepare.bounds.SearchBounds; import org.apache.ignite.internal.sql.engine.rel.IgniteAggregate; import org.apache.ignite.internal.sql.engine.rel.IgniteIndexScan; -import org.apache.ignite.internal.sql.engine.rel.IgniteProject; import org.apache.ignite.internal.sql.engine.rel.IgniteTableScan; import org.apache.ignite.internal.sql.engine.rel.IgniteValues; import org.apache.ignite.internal.sql.engine.schema.IgniteSchema; @@ -311,10 +310,10 @@ public void testAlwaysFalseFilterMerge() throws Exception { sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 LEFT JOIN tbl AS t2 ON (t1.a = t2.a AND t2.a = 1 AND t2.a = 2) WHERE t1.c = 1"; assertPlan(sql, publicSchema, isInstanceOf(IgniteTableScan.class) - .and(scan -> scan.projects() != null) - .and(scan -> scan.condition() != null) - .and(scan -> "=($t1, 1)".equals(scan.condition().toString())) - ); + .and(scan -> scan.projects() != null) + .and(scan -> scan.condition() != null) + .and(scan -> "=($t1, 1)".equals(scan.condition().toString())) + ); // JOIN elimination. sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 INNER JOIN tbl AS t2 ON t1.a = t2.a WHERE t2.a = 1 AND t2.a IS NULL"; From 9b0e2e1d756d2274cbf3c2eab043461e17ecc6c0 Mon Sep 17 00:00:00 2001 From: AMashenkov Date: Thu, 9 Apr 2026 16:52:47 +0300 Subject: [PATCH 07/12] Add PruneTableModifyRule --- .../ignite/internal/sql/engine/ItDmlTest.java | 12 ++++ .../sql/engine/prepare/PlannerPhase.java | 2 + .../rule/logical/PruneTableModifyRule.java | 72 +++++++++++++++++++ .../ProjectFilterScanMergePlannerTest.java | 43 ++++++++--- 4 files changed, 118 insertions(+), 11 deletions(-) create mode 100644 modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/PruneTableModifyRule.java diff --git a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java index f2011008192..bacd1fc1a84 100644 --- a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java +++ b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java @@ -55,6 +55,7 @@ import org.apache.ignite.lang.IgniteException; import org.apache.ignite.tx.Transaction; import org.apache.ignite.tx.TransactionOptions; +import org.hamcrest.Matchers; import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Disabled; @@ -1179,6 +1180,17 @@ public void rejectInvalidColumnNumberOnInsert() { ); } + @Test + public void insertFromSelectWithAlwaysFalseCondition() { + sql("CREATE TABLE test (id INT PRIMARY KEY, val REAL)"); + sql("CREATE TABLE test2 (id INT PRIMARY KEY, val REAL)"); + + assertQuery("INSERT INTO test2 SELECT id, val FROM test WHERE val > 1 AND val < 0") + .matches(Matchers.not(containsSubPlan("TableModify"))) + .returns(0) // TODO https://issues.apache.org/jira/browse/IGNITE-23512: Long value is expected here. + .check(); + } + private static Stream decimalLimits() { return Stream.of( arguments(SqlTypeName.BIGINT.getName(), Long.MAX_VALUE, Long.MIN_VALUE), diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerPhase.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerPhase.java index 639ed9d0867..9631e6d28b8 100644 --- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerPhase.java +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerPhase.java @@ -78,6 +78,7 @@ import org.apache.ignite.internal.sql.engine.rule.logical.IgniteProjectCorrelateTransposeRule; import org.apache.ignite.internal.sql.engine.rule.logical.LogicalOrToUnionRule; import org.apache.ignite.internal.sql.engine.rule.logical.ProjectScanMergeRule; +import org.apache.ignite.internal.sql.engine.rule.logical.PruneTableModifyRule; import org.apache.ignite.internal.sql.engine.util.Commons; /** @@ -278,6 +279,7 @@ public Program getProgram(PlanningContext ctx) { PruneEmptyRules.CORRELATE_LEFT_INSTANCE, PruneEmptyRules.CORRELATE_RIGHT_INSTANCE, + PruneTableModifyRule.INSTANCE, // Useful of this rule is not clear now. // CoreRules.AGGREGATE_REDUCE_FUNCTIONS, diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/PruneTableModifyRule.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/PruneTableModifyRule.java new file mode 100644 index 00000000000..60ea0e6243e --- /dev/null +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/PruneTableModifyRule.java @@ -0,0 +1,72 @@ +/* + * 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.ignite.internal.sql.engine.rule.logical; + +import java.util.Collections; +import org.apache.calcite.plan.RelOptRule; +import org.apache.calcite.plan.RelOptRuleCall; +import org.apache.calcite.plan.RelRule; +import org.apache.calcite.plan.RelTraitSet; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.core.TableModify; +import org.apache.calcite.rel.core.Values; +import org.apache.calcite.rel.rules.SubstitutionRule; +import org.apache.ignite.internal.sql.engine.rule.logical.PruneTableModifyRule.Config; +import org.immutables.value.Value; + +/** + * Rule that eliminates table modify node if it doesn't have any source rows. + */ +@Value.Enclosing +public class PruneTableModifyRule extends RelRule implements SubstitutionRule { + public static final RelOptRule INSTANCE = Config.DEFAULT.toRule(); + + /** + * Constructor. + * + * @param config Rule configuration. + */ + private PruneTableModifyRule(PruneTableModifyRule.Config config) { + super(config); + } + + @Override public void onMatch(RelOptRuleCall call) { + TableModify singleRel = call.rel(0); + + RelNode singleValue = call.builder().values(singleRel.getRowType(), 0L).build(); + RelTraitSet traits = singleRel.getTraitSet(); + // propagate all traits (except convention) from the original singleRel + if (singleValue.getConvention() != null) { + traits = traits.replace(singleValue.getConvention()); + } + singleValue = singleValue.copy(traits, Collections.emptyList()); + call.transformTo(singleValue); + } + + /** Rule configuration. */ + @Value.Immutable(singleton = false) + public interface Config extends RuleFactoryConfig { + Config DEFAULT = ImmutablePruneTableModifyRule.Config.builder() + .withDescription("PruneTableModify") + .withRuleFactory(PruneTableModifyRule::new) + .withOperandSupplier(b0 -> + b0.operand(TableModify.class).oneInput(b1 -> + b1.operand(Values.class).predicate(Values::isEmpty).noInputs())) + .build(); + } +} diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java index 8c179b1e066..1ae5e6faad5 100644 --- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java +++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java @@ -22,6 +22,7 @@ import java.util.function.Predicate; import java.util.function.UnaryOperator; import java.util.stream.Collectors; +import org.apache.calcite.rex.RexLiteral; import org.apache.calcite.rex.RexNode; import org.apache.calcite.util.ImmutableIntList; import org.apache.ignite.internal.sql.engine.framework.TestBuilders.TableBuilder; @@ -278,7 +279,7 @@ public void testFilterIdentityFilterMerge() throws Exception { } @Test - public void testAlwaysTrueFilterMerge() throws Exception { + public void testAlwaysTrueFilterPruning() throws Exception { String sql = "SELECT a, c FROM tbl WHERE a > 1 OR a < 3 OR a IS NULL"; assertPlan(sql, publicSchema, isInstanceOf(IgniteTableScan.class) @@ -289,8 +290,8 @@ public void testAlwaysTrueFilterMerge() throws Exception { } @Test - public void testAlwaysFalseFilterMerge() throws Exception { - Predicate hasEmptyValuesOnly = isInstanceOf(IgniteValues.class).and(values -> values.getTuples().isEmpty()); + public void testAlwaysFalseFilterPruning() throws Exception { + Predicate hasEmptyValuesOnly = hasEmptyValuesOnlyPredicate(); // Table scan elimination. String sql = "SELECT a, c FROM tbl WHERE a > 1 AND a < 0"; @@ -308,22 +309,42 @@ public void testAlwaysFalseFilterMerge() throws Exception { sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 LEFT JOIN tbl AS t2 ON t1.a = t2.a WHERE t2.a = 1 AND t2.a IS NULL AND t1.c = 1"; assertPlan(sql, publicSchema, hasEmptyValuesOnly); - sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 LEFT JOIN tbl AS t2 ON (t1.a = t2.a AND t2.a = 1 AND t2.a = 2) WHERE t1.c = 1"; + sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 INNER JOIN tbl AS t2 ON t1.a = t2.a WHERE t2.a = 1 AND t2.a IS NULL"; + assertPlan(sql, publicSchema, hasEmptyValuesOnly); + + sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 INNER JOIN tbl AS t2 ON t1.a = t2.a WHERE t1.a = 1 AND t2.a = 2"; + assertPlan(sql, publicSchema, hasEmptyValuesOnlyPredicate()); + } + + @Test + public void testJoinWithAlwaysFalseConditionPruning() throws Exception { + String sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 LEFT JOIN tbl AS t2 ON (t1.a = t2.a AND t2.a = 1 AND t2.a = 2) WHERE t1.c = 1"; assertPlan(sql, publicSchema, isInstanceOf(IgniteTableScan.class) .and(scan -> scan.projects() != null) .and(scan -> scan.condition() != null) .and(scan -> "=($t1, 1)".equals(scan.condition().toString())) ); - // JOIN elimination. - sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 INNER JOIN tbl AS t2 ON t1.a = t2.a WHERE t2.a = 1 AND t2.a IS NULL"; - assertPlan(sql, publicSchema, hasEmptyValuesOnly); - sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 INNER JOIN tbl AS t2 ON t1.a = t2.a AND t2.a = 1 AND t2.a = 2"; - assertPlan(sql, publicSchema, hasEmptyValuesOnly); + assertPlan(sql, publicSchema, hasEmptyValuesOnlyPredicate()); + } - sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 INNER JOIN tbl AS t2 ON t1.a = t2.a WHERE t1.a = 1 AND t2.a = 2"; - assertPlan(sql, publicSchema, hasEmptyValuesOnly); + @Test + public void testAlwaysFalseFilterPruningWithDml() throws Exception { + Predicate zeroDmlResultPredicate = isInstanceOf(IgniteValues.class) + .and(values -> values.getTuples().size() == 1) // single row + .and(values -> values.getTuples().get(0).size() == 1) // row of single column + .and(values -> RexLiteral.longValue(values.getTuples().get(0).get(0)) == 0L); + + String sql = "INSERT INTO tbl (a, c) SELECT a, b FROM tbl WHERE a > 1 AND a < 0"; + assertPlan(sql, publicSchema, zeroDmlResultPredicate); + + sql = "INSERT INTO tbl (a, c) (SELECT a, c FROM (SELECT a, c FROM tbl WHERE a > 1) WHERE a < 0)"; + assertPlan(sql, publicSchema, zeroDmlResultPredicate); + } + + private Predicate hasEmptyValuesOnlyPredicate() { + return isInstanceOf(IgniteValues.class).and(values -> values.getTuples().isEmpty()); } /** From 8bf5cc3db6b805251b754e64a14a1324923f6986 Mon Sep 17 00:00:00 2001 From: AMashenkov Date: Fri, 10 Apr 2026 14:07:50 +0300 Subject: [PATCH 08/12] fix FragmentMappingTest --- .../src/test/resources/mapping/test_partition_pruning.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sql-engine/src/test/resources/mapping/test_partition_pruning.test b/modules/sql-engine/src/test/resources/mapping/test_partition_pruning.test index c000706bd72..1fd90eeff0b 100644 --- a/modules/sql-engine/src/test/resources/mapping/test_partition_pruning.test +++ b/modules/sql-engine/src/test/resources/mapping/test_partition_pruning.test @@ -173,7 +173,7 @@ Fragment#1 root distribution: broadcast executionNodes: [N1] colocationGroup[-1]: {nodes=[N1], sourceIds=[-1], assignments={}, partitionsWithConsistencyTokens={N1=[]}} - tree: + tree: Values fieldNames: [ID, C1, C2, ID0, C10, C20] tuples: [] From fa1c6997defc7fcee7f95e663a73c34835de314f Mon Sep 17 00:00:00 2001 From: AMashenkov Date: Fri, 10 Apr 2026 14:07:55 +0300 Subject: [PATCH 09/12] fix test --- .../internal/sql/engine/ItSqlUsesKeyValueDeleteTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSqlUsesKeyValueDeleteTest.java b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSqlUsesKeyValueDeleteTest.java index 3664c79c38c..d0b5dc2f234 100644 --- a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSqlUsesKeyValueDeleteTest.java +++ b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSqlUsesKeyValueDeleteTest.java @@ -93,11 +93,11 @@ void lookupOnOutOfRangeKey() { sql(tx, "INSERT INTO simple_key VALUES (2147483647, 0), (-2147483648, 0);"); assertQuery((InternalTransaction) tx, "DELETE FROM simple_key WHERE id = 2147483648") - .returns(0L) + .returns(0) // TODO https://issues.apache.org/jira/browse/IGNITE-23512: Long value is expected here. .check(); assertQuery((InternalTransaction) tx, "DELETE FROM simple_key WHERE id = -2147483649") - .returns(0L) + .returns(0) // TODO https://issues.apache.org/jira/browse/IGNITE-23512: Long value is expected here. .check(); } finally { tx.rollback(); From 5a5bb36274c2d97c28e6d2fc7d0a094f23cb678d Mon Sep 17 00:00:00 2001 From: AMashenkov Date: Fri, 10 Apr 2026 14:47:44 +0300 Subject: [PATCH 10/12] Fix PruneTableModifyRule result type --- .../apache/ignite/internal/sql/engine/ItDmlTest.java | 2 +- .../sql/engine/ItSqlUsesKeyValueDeleteTest.java | 4 ++-- .../sql/engine/rule/logical/PruneTableModifyRule.java | 10 +++++++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java index bacd1fc1a84..988249c5b67 100644 --- a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java +++ b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java @@ -1187,7 +1187,7 @@ public void insertFromSelectWithAlwaysFalseCondition() { assertQuery("INSERT INTO test2 SELECT id, val FROM test WHERE val > 1 AND val < 0") .matches(Matchers.not(containsSubPlan("TableModify"))) - .returns(0) // TODO https://issues.apache.org/jira/browse/IGNITE-23512: Long value is expected here. + .returns(0L) .check(); } diff --git a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSqlUsesKeyValueDeleteTest.java b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSqlUsesKeyValueDeleteTest.java index d0b5dc2f234..3664c79c38c 100644 --- a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSqlUsesKeyValueDeleteTest.java +++ b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSqlUsesKeyValueDeleteTest.java @@ -93,11 +93,11 @@ void lookupOnOutOfRangeKey() { sql(tx, "INSERT INTO simple_key VALUES (2147483647, 0), (-2147483648, 0);"); assertQuery((InternalTransaction) tx, "DELETE FROM simple_key WHERE id = 2147483648") - .returns(0) // TODO https://issues.apache.org/jira/browse/IGNITE-23512: Long value is expected here. + .returns(0L) .check(); assertQuery((InternalTransaction) tx, "DELETE FROM simple_key WHERE id = -2147483649") - .returns(0) // TODO https://issues.apache.org/jira/browse/IGNITE-23512: Long value is expected here. + .returns(0L) .check(); } finally { tx.rollback(); diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/PruneTableModifyRule.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/PruneTableModifyRule.java index 60ea0e6243e..49395e99b8d 100644 --- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/PruneTableModifyRule.java +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/PruneTableModifyRule.java @@ -18,6 +18,7 @@ package org.apache.ignite.internal.sql.engine.rule.logical; import java.util.Collections; +import java.util.List; import org.apache.calcite.plan.RelOptRule; import org.apache.calcite.plan.RelOptRuleCall; import org.apache.calcite.plan.RelRule; @@ -26,6 +27,8 @@ import org.apache.calcite.rel.core.TableModify; import org.apache.calcite.rel.core.Values; import org.apache.calcite.rel.rules.SubstitutionRule; +import org.apache.calcite.rex.RexLiteral; +import org.apache.ignite.internal.sql.engine.rex.IgniteRexBuilder; import org.apache.ignite.internal.sql.engine.rule.logical.PruneTableModifyRule.Config; import org.immutables.value.Value; @@ -48,7 +51,12 @@ private PruneTableModifyRule(PruneTableModifyRule.Config config) { @Override public void onMatch(RelOptRuleCall call) { TableModify singleRel = call.rel(0); - RelNode singleValue = call.builder().values(singleRel.getRowType(), 0L).build(); + // TODO https://issues.apache.org/jira/browse/IGNITE-23512: Default Calcite RexBuilder ignores field type and extract type from + // the given value. E.g. for zero value RexBuilder creates INT literal. Use simple way create `singleValue` after fixing the issue. + // RelNode singleValue = call.builder().values(singleRel.getRowType(), 0L).build(); + RexLiteral zeroLiteral = IgniteRexBuilder.INSTANCE.makeLiteral(0L, singleRel.getRowType().getFieldList().get(0).getType()); + RelNode singleValue = call.builder().values(List.of(List.of(zeroLiteral)), singleRel.getRowType()).build(); + RelTraitSet traits = singleRel.getTraitSet(); // propagate all traits (except convention) from the original singleRel if (singleValue.getConvention() != null) { From 7ba51628ae587e7c7b4edba0dcb7cf12c62143dc Mon Sep 17 00:00:00 2001 From: AMashenkov Date: Mon, 13 Apr 2026 10:43:05 +0300 Subject: [PATCH 11/12] styles --- .../internal/sql/engine/rule/logical/PruneTableModifyRule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/PruneTableModifyRule.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/PruneTableModifyRule.java index 49395e99b8d..e9b3045535e 100644 --- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/PruneTableModifyRule.java +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/PruneTableModifyRule.java @@ -56,7 +56,7 @@ private PruneTableModifyRule(PruneTableModifyRule.Config config) { // RelNode singleValue = call.builder().values(singleRel.getRowType(), 0L).build(); RexLiteral zeroLiteral = IgniteRexBuilder.INSTANCE.makeLiteral(0L, singleRel.getRowType().getFieldList().get(0).getType()); RelNode singleValue = call.builder().values(List.of(List.of(zeroLiteral)), singleRel.getRowType()).build(); - + RelTraitSet traits = singleRel.getTraitSet(); // propagate all traits (except convention) from the original singleRel if (singleValue.getConvention() != null) { From 412750f17b55d949603fdf04217a33014075ea12 Mon Sep 17 00:00:00 2001 From: AMashenkov Date: Tue, 14 Apr 2026 18:18:52 +0300 Subject: [PATCH 12/12] minor after review --- .../rule/logical/PruneTableModifyRule.java | 8 +------- .../mapping/test_partition_pruning.test | 16 ---------------- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/PruneTableModifyRule.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/PruneTableModifyRule.java index e9b3045535e..fe8164af5d3 100644 --- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/PruneTableModifyRule.java +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/PruneTableModifyRule.java @@ -22,7 +22,6 @@ import org.apache.calcite.plan.RelOptRule; import org.apache.calcite.plan.RelOptRuleCall; import org.apache.calcite.plan.RelRule; -import org.apache.calcite.plan.RelTraitSet; import org.apache.calcite.rel.RelNode; import org.apache.calcite.rel.core.TableModify; import org.apache.calcite.rel.core.Values; @@ -57,12 +56,7 @@ private PruneTableModifyRule(PruneTableModifyRule.Config config) { RexLiteral zeroLiteral = IgniteRexBuilder.INSTANCE.makeLiteral(0L, singleRel.getRowType().getFieldList().get(0).getType()); RelNode singleValue = call.builder().values(List.of(List.of(zeroLiteral)), singleRel.getRowType()).build(); - RelTraitSet traits = singleRel.getTraitSet(); - // propagate all traits (except convention) from the original singleRel - if (singleValue.getConvention() != null) { - traits = traits.replace(singleValue.getConvention()); - } - singleValue = singleValue.copy(traits, Collections.emptyList()); + singleValue = singleValue.copy(singleRel.getCluster().traitSet(), Collections.emptyList()); call.transformTo(singleValue); } diff --git a/modules/sql-engine/src/test/resources/mapping/test_partition_pruning.test b/modules/sql-engine/src/test/resources/mapping/test_partition_pruning.test index 1fd90eeff0b..febc16e2780 100644 --- a/modules/sql-engine/src/test/resources/mapping/test_partition_pruning.test +++ b/modules/sql-engine/src/test/resources/mapping/test_partition_pruning.test @@ -163,22 +163,6 @@ Fragment#4 fieldNames: [ID, C1, C2] est: (rows=1) --- -# Self join, different predicates that produce disjoint set of partitions -N1 -SELECT /*+ DISABLE_RULE('NestedLoopJoinConverter', 'HashJoinConverter', 'CorrelatedNestedLoopJoin') */ * - FROM t1_n1n2n3 as t1, t1_n1n2n3 as t2 - WHERE t1.id = t2.id and t1.id IN (1, 3) and t2.id IN (42, 44) ---- -Fragment#1 root - distribution: broadcast - executionNodes: [N1] - colocationGroup[-1]: {nodes=[N1], sourceIds=[-1], assignments={}, partitionsWithConsistencyTokens={N1=[]}} - tree: - Values - fieldNames: [ID, C1, C2, ID0, C10, C20] - tuples: [] - est: (rows=1) ---- # Correlated # Prune partitions from left arm statically, and pass meta to the right arm. # Same set of nodes.