From 02827da2cc8232192fb8008e539cd39822004d45 Mon Sep 17 00:00:00 2001 From: Aleksey Plekhanov Date: Wed, 4 Feb 2026 18:46:21 +0300 Subject: [PATCH 1/6] IGNITE-27738 SQL Calcite: Fix planner hang on multi-row values --- .../rel/agg/IgniteColocatedSortAggregate.java | 4 +- .../rel/agg/IgniteReduceSortAggregate.java | 4 +- .../query/calcite/trait/TraitUtils.java | 68 +++++++++++++++++-- .../integration/TableDmlIntegrationTest.java | 15 ++++ .../planner/JoinColocationPlannerTest.java | 28 ++++++++ 5 files changed, 109 insertions(+), 10 deletions(-) diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteColocatedSortAggregate.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteColocatedSortAggregate.java index 50488bf4d4fd7..8833643cbc8bb 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteColocatedSortAggregate.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteColocatedSortAggregate.java @@ -19,7 +19,6 @@ import java.util.List; import java.util.Objects; - import org.apache.calcite.plan.RelOptCluster; import org.apache.calcite.plan.RelOptCost; import org.apache.calcite.plan.RelOptPlanner; @@ -34,7 +33,6 @@ import org.apache.calcite.util.ImmutableBitSet; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRelVisitor; -import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils; /** * @@ -75,7 +73,7 @@ public IgniteColocatedSortAggregate(RelInput input) { @Override public Aggregate copy(RelTraitSet traitSet, RelNode input, ImmutableBitSet groupSet, List groupSets, List aggCalls) { return new IgniteColocatedSortAggregate( - getCluster(), traitSet, input, groupSet, groupSets, aggCalls, TraitUtils.collation(traitSet)); + getCluster(), traitSet, input, groupSet, groupSets, aggCalls, collation); } /** {@inheritDoc} */ diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteReduceSortAggregate.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteReduceSortAggregate.java index 632df8863a48d..c569a04f464b6 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteReduceSortAggregate.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteReduceSortAggregate.java @@ -19,7 +19,6 @@ import java.util.List; import java.util.Objects; - import org.apache.calcite.plan.RelOptCluster; import org.apache.calcite.plan.RelOptCost; import org.apache.calcite.plan.RelOptPlanner; @@ -36,7 +35,6 @@ import org.apache.ignite.internal.processors.query.calcite.metadata.cost.IgniteCostFactory; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRelVisitor; -import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils; /** * @@ -83,7 +81,7 @@ public IgniteReduceSortAggregate(RelInput input) { groupSets, aggCalls, rowType, - TraitUtils.collation(traitSet) + collation ); } diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/TraitUtils.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/TraitUtils.java index b007a97331549..ff27c6e41c649 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/TraitUtils.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/TraitUtils.java @@ -18,6 +18,8 @@ package org.apache.ignite.internal.processors.query.calcite.trait; import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.BitSet; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -29,6 +31,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import org.apache.calcite.linq4j.Ord; +import org.apache.calcite.plan.AbstractRelOptPlanner; import org.apache.calcite.plan.RelOptCluster; import org.apache.calcite.plan.RelOptPlanner; import org.apache.calcite.plan.RelOptRule; @@ -61,6 +64,7 @@ import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSort; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableSpool; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTrimExchange; +import org.apache.ignite.internal.processors.query.calcite.util.Commons; import org.apache.ignite.internal.util.typedef.F; import org.jetbrains.annotations.Nullable; @@ -167,6 +171,7 @@ else if (converter == RewindabilityTraitDef.INSTANCE) RelOptRule.convert( rel, rel.getTraitSet() + .replace(RewindabilityTrait.ONE_WAY) .replace(CorrelationTrait.UNCORRELATED) ), toTrait); @@ -419,15 +424,58 @@ public static Pair> passThrough(TraitsAwareIgnite assert traits.size() <= 1; + if (!traits.isEmpty() && traits.get(0).left.satisfies(requiredTraits)) { + // Return most relaxed parent traits. + return Pair.of(requiredTraits, traits.get(0).right); + } + return F.first(traits); } + /** */ + public static List removeDuplicates(List traits) { + BitSet duplicates = null; + + for (int i = 0; i < traits.size() - 1; i++) { + if (duplicates != null && duplicates.get(i)) + continue; + + for (int j = i + 1; j < traits.size(); j++) { + if (duplicates != null && duplicates.get(j)) + continue; + + // Return most strict child traits. + if (traits.get(i).satisfies(traits.get(j))) + (duplicates == null ? duplicates = new BitSet() : duplicates).set(j); + else if (traits.get(j).satisfies(traits.get(i))) { + (duplicates == null ? duplicates = new BitSet() : duplicates).set(i); + break; + } + } + } + + if (duplicates == null) + return traits; + + List newTraits = new ArrayList<>(traits.size() - duplicates.cardinality()); + + for (int i = 0; i < traits.size(); i++) { + if (!duplicates.get(i)) + newTraits.add(traits.get(i)); + } + + return newTraits; + } + /** */ public static List derive(TraitsAwareIgniteRel rel, List> inTraits) { assert !F.isEmpty(inTraits); RelTraitSet outTraits = rel.getCluster().traitSetOf(IgniteConvention.INSTANCE); - Set>> combinations = combinations(outTraits, inTraits); + + inTraits = Commons.transform(inTraits, TraitUtils::removeDuplicates); + + Set>> combinations = combinations(rel, outTraits, inTraits); if (combinations.isEmpty()) return ImmutableList.of(); @@ -448,14 +496,19 @@ private static List singletonListFromNullable(@Nullable T elem) { } /** */ - private static Set>> combinations(RelTraitSet outTraits, List> inTraits) { + private static Set>> combinations( + TraitsAwareIgniteRel rel, + RelTraitSet outTraits, + List> inTraits + ) { Set>> out = new HashSet<>(); - fillRecursive(outTraits, inTraits, out, new RelTraitSet[inTraits.size()], 0); + fillRecursive(rel, outTraits, inTraits, out, new RelTraitSet[inTraits.size()], 0); return out; } /** */ private static boolean fillRecursive( + TraitsAwareIgniteRel rel, RelTraitSet outTraits, List> inTraits, Set>> result, @@ -463,6 +516,13 @@ private static boolean fillRecursive( int idx ) throws ControlFlowException { boolean processed = false, last = idx == inTraits.size() - 1; + + if (last) { + assert rel.getCluster().getPlanner() instanceof AbstractRelOptPlanner; + + ((AbstractRelOptPlanner)rel.getCluster().getPlanner()).checkCancel(); + } + for (RelTraitSet t : inTraits.get(idx)) { assert t.getConvention() == IgniteConvention.INSTANCE; @@ -471,7 +531,7 @@ private static boolean fillRecursive( if (last) result.add(Pair.of(outTraits, ImmutableList.copyOf(combination))); - else if (!fillRecursive(outTraits, inTraits, result, combination, idx + 1)) + else if (!fillRecursive(rel, outTraits, inTraits, result, combination, idx + 1)) return false; } return processed; diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/TableDmlIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/TableDmlIntegrationTest.java index 4ef41473fa6d1..f94c45162fbaa 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/TableDmlIntegrationTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/TableDmlIntegrationTest.java @@ -23,6 +23,7 @@ import java.time.Duration; import java.time.Period; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.UUID; @@ -657,6 +658,20 @@ public void testInsertIncorrectDate() { assertThrows("INSERT INTO timestamp_t VALUES ('1900-1-1 00-00-00')", errType, errDate); } + /** */ + @Test + public void testInsertMultiRowValues() { + sql("CREATE TABLE test (id int, val int) WITH " + atomicity()); + + int rowsCnt = 50; + + String sql = "INSERT INTO test VALUES " + String.join(", ", Collections.nCopies(rowsCnt, "(?, ?)")); + + sql(sql, new Object[rowsCnt * 2]); + + assertQuery("SELECT * FROM test").resultSize(rowsCnt).check(); + } + /** */ private void checkDefaultValue(String sqlType, String sqlVal, Object expectedVal) { try { diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/JoinColocationPlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/JoinColocationPlannerTest.java index 5376a79e8bb29..0b06c0ddd75fc 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/JoinColocationPlannerTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/JoinColocationPlannerTest.java @@ -33,6 +33,7 @@ import org.apache.ignite.internal.util.typedef.internal.CU; import org.junit.Test; +import static org.apache.calcite.sql.type.SqlTypeName.INTEGER; import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.Assert.assertThat; @@ -209,4 +210,31 @@ public void joinBroadcastAggregateRehash() throws Exception { ) ); } + + /** + * Re-hashing right hand for merge join. + */ + @Test + public void joinMergeJoinAffinityRehash() throws Exception { + IgniteSchema schema = createSchema( + createTable("ORDERS", IgniteDistributions.affinity(0, "orders", "hash"), + "ID", INTEGER, "REGION", INTEGER) + .addIndex("ORDER_ID_IDX", 0), + createTable("ORDER_ITEMS", IgniteDistributions.affinity(0, "order_items", "hash"), + "ID", INTEGER, "ORDER_ID", INTEGER, "AMOUNT", INTEGER) + .addIndex("ORDER_ITEMS_ORDER_ID_IDX", 1) + ); + + String sql = "SELECT sum(amount)" + + " FROM order_items i JOIN orders o ON o.id=i.order_id" + + " WHERE o.region = ?"; + + assertPlan(sql, schema, + nodeOrAnyChild(isInstanceOf(IgniteMergeJoin.class)) + .and(hasChildThat(isIndexScan("ORDERS", "ORDER_ID_IDX"))) + .and(hasChildThat(isInstanceOf(IgniteExchange.class) + .and(hasDistribution(IgniteDistributions.affinity(0, "orders", "hash"))) + .and(hasChildThat(isIndexScan("ORDER_ITEMS", "ORDER_ITEMS_ORDER_ID_IDX"))))) + ); + } } From 39fd0ffbae631c294c466598242f4f033cc68ff2 Mon Sep 17 00:00:00 2001 From: Aleksey Plekhanov Date: Wed, 4 Feb 2026 23:57:50 +0300 Subject: [PATCH 2/6] IGNITE-27738 SQL Calcite: Fix planner hang on multi-row values --- .../query/calcite/rel/agg/IgniteMapSortAggregate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteMapSortAggregate.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteMapSortAggregate.java index 8efba40250cf9..d8ad5264495f1 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteMapSortAggregate.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteMapSortAggregate.java @@ -84,7 +84,7 @@ public IgniteMapSortAggregate(RelInput input) { List groupSets, List aggCalls) { return new IgniteMapSortAggregate( - getCluster(), traitSet, input, groupSet, groupSets, aggCalls, TraitUtils.collation(traitSet)); + getCluster(), traitSet, input, groupSet, groupSets, aggCalls, collation); } /** {@inheritDoc} */ From 0e0a3ad23556dec57bd29d874f85f652874fb377 Mon Sep 17 00:00:00 2001 From: Aleksey Plekhanov Date: Thu, 5 Feb 2026 00:16:45 +0300 Subject: [PATCH 3/6] IGNITE-27738 SQL Calcite: Fix planner hang on multi-row values --- .../calcite/rel/agg/IgniteColocatedSortAggregate.java | 2 +- .../query/calcite/rel/agg/IgniteMapSortAggregate.java | 5 +++-- .../query/calcite/rel/agg/IgniteReduceSortAggregate.java | 2 +- .../query/calcite/rel/agg/IgniteSortAggregateBase.java | 7 +++++++ 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteColocatedSortAggregate.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteColocatedSortAggregate.java index 8833643cbc8bb..81114cf506aaf 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteColocatedSortAggregate.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteColocatedSortAggregate.java @@ -73,7 +73,7 @@ public IgniteColocatedSortAggregate(RelInput input) { @Override public Aggregate copy(RelTraitSet traitSet, RelNode input, ImmutableBitSet groupSet, List groupSets, List aggCalls) { return new IgniteColocatedSortAggregate( - getCluster(), traitSet, input, groupSet, groupSets, aggCalls, collation); + getCluster(), traitSet, input, groupSet, groupSets, aggCalls, copyCollation(traitSet)); } /** {@inheritDoc} */ diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteMapSortAggregate.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteMapSortAggregate.java index d8ad5264495f1..e01a455e2d2de 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteMapSortAggregate.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteMapSortAggregate.java @@ -82,9 +82,10 @@ public IgniteMapSortAggregate(RelInput input) { RelNode input, ImmutableBitSet groupSet, List groupSets, - List aggCalls) { + List aggCalls + ) { return new IgniteMapSortAggregate( - getCluster(), traitSet, input, groupSet, groupSets, aggCalls, collation); + getCluster(), traitSet, input, groupSet, groupSets, aggCalls, copyCollation(traitSet)); } /** {@inheritDoc} */ diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteReduceSortAggregate.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteReduceSortAggregate.java index c569a04f464b6..330a9126d6dae 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteReduceSortAggregate.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteReduceSortAggregate.java @@ -81,7 +81,7 @@ public IgniteReduceSortAggregate(RelInput input) { groupSets, aggCalls, rowType, - collation + copyCollation(traitSet) ); } diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteSortAggregateBase.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteSortAggregateBase.java index d3ba800e7a4fc..31f4f4c53e15a 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteSortAggregateBase.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteSortAggregateBase.java @@ -87,4 +87,11 @@ interface IgniteSortAggregateBase extends TraitsAwareIgniteRel { inputTraits )); } + + /** */ + public default RelCollation copyCollation(RelTraitSet traitSet) { + RelCollation traitsCollation = TraitUtils.collation(traitSet); + + return collation().satisfies(traitsCollation) ? collation() : traitsCollation; + } } From ecbbf41660cfde52f2567eb43fe5c2c057c40e6d Mon Sep 17 00:00:00 2001 From: Aleksey Plekhanov Date: Thu, 5 Feb 2026 09:19:13 +0300 Subject: [PATCH 4/6] IGNITE-27738 SQL Calcite: Fix planner hang on multi-row values --- .../calcite/rel/agg/IgniteColocatedSortAggregate.java | 3 ++- .../query/calcite/rel/agg/IgniteMapSortAggregate.java | 2 +- .../query/calcite/rel/agg/IgniteReduceSortAggregate.java | 3 ++- .../query/calcite/rel/agg/IgniteSortAggregateBase.java | 7 ------- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteColocatedSortAggregate.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteColocatedSortAggregate.java index 81114cf506aaf..3ebab072bec88 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteColocatedSortAggregate.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteColocatedSortAggregate.java @@ -33,6 +33,7 @@ import org.apache.calcite.util.ImmutableBitSet; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRelVisitor; +import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils; /** * @@ -73,7 +74,7 @@ public IgniteColocatedSortAggregate(RelInput input) { @Override public Aggregate copy(RelTraitSet traitSet, RelNode input, ImmutableBitSet groupSet, List groupSets, List aggCalls) { return new IgniteColocatedSortAggregate( - getCluster(), traitSet, input, groupSet, groupSets, aggCalls, copyCollation(traitSet)); + getCluster(), traitSet, input, groupSet, groupSets, aggCalls, TraitUtils.collation(input.getTraitSet())); } /** {@inheritDoc} */ diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteMapSortAggregate.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteMapSortAggregate.java index e01a455e2d2de..fa74cec1bd8cc 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteMapSortAggregate.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteMapSortAggregate.java @@ -85,7 +85,7 @@ public IgniteMapSortAggregate(RelInput input) { List aggCalls ) { return new IgniteMapSortAggregate( - getCluster(), traitSet, input, groupSet, groupSets, aggCalls, copyCollation(traitSet)); + getCluster(), traitSet, input, groupSet, groupSets, aggCalls, TraitUtils.collation(input.getTraitSet())); } /** {@inheritDoc} */ diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteReduceSortAggregate.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteReduceSortAggregate.java index 330a9126d6dae..802cb48dd977d 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteReduceSortAggregate.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteReduceSortAggregate.java @@ -35,6 +35,7 @@ import org.apache.ignite.internal.processors.query.calcite.metadata.cost.IgniteCostFactory; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRelVisitor; +import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils; /** * @@ -81,7 +82,7 @@ public IgniteReduceSortAggregate(RelInput input) { groupSets, aggCalls, rowType, - copyCollation(traitSet) + TraitUtils.collation(sole(inputs).getTraitSet()) ); } diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteSortAggregateBase.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteSortAggregateBase.java index 31f4f4c53e15a..d3ba800e7a4fc 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteSortAggregateBase.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteSortAggregateBase.java @@ -87,11 +87,4 @@ interface IgniteSortAggregateBase extends TraitsAwareIgniteRel { inputTraits )); } - - /** */ - public default RelCollation copyCollation(RelTraitSet traitSet) { - RelCollation traitsCollation = TraitUtils.collation(traitSet); - - return collation().satisfies(traitsCollation) ? collation() : traitsCollation; - } } From 17db2a41016305fbfb1fedfa207c1e1bb8382c7f Mon Sep 17 00:00:00 2001 From: Aleksey Plekhanov Date: Thu, 5 Feb 2026 10:33:51 +0300 Subject: [PATCH 5/6] IGNITE-27738 SQL Calcite: Fix planner hang on multi-row values --- .../rel/agg/IgniteColocatedSortAggregate.java | 7 +- .../rel/agg/IgniteMapSortAggregate.java | 7 +- .../rel/agg/IgniteReduceSortAggregate.java | 7 +- .../SortAggregateIntegrationTest.java | 15 ++ .../planner/SortAggregatePlannerTest.java | 169 ++++++++++++------ 5 files changed, 145 insertions(+), 60 deletions(-) diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteColocatedSortAggregate.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteColocatedSortAggregate.java index 3ebab072bec88..3c85ddc89bc9c 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteColocatedSortAggregate.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteColocatedSortAggregate.java @@ -73,8 +73,13 @@ public IgniteColocatedSortAggregate(RelInput input) { /** {@inheritDoc} */ @Override public Aggregate copy(RelTraitSet traitSet, RelNode input, ImmutableBitSet groupSet, List groupSets, List aggCalls) { + RelCollation collation = TraitUtils.collation(input.getTraitSet()); + + assert collation.satisfies(TraitUtils.collation(traitSet)) + : "Unexpected collations: input=" + collation + ", traitSet=" + TraitUtils.collation(traitSet); + return new IgniteColocatedSortAggregate( - getCluster(), traitSet, input, groupSet, groupSets, aggCalls, TraitUtils.collation(input.getTraitSet())); + getCluster(), traitSet, input, groupSet, groupSets, aggCalls, collation); } /** {@inheritDoc} */ diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteMapSortAggregate.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteMapSortAggregate.java index fa74cec1bd8cc..15b37d76242fc 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteMapSortAggregate.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteMapSortAggregate.java @@ -84,8 +84,13 @@ public IgniteMapSortAggregate(RelInput input) { List groupSets, List aggCalls ) { + RelCollation collation = TraitUtils.collation(input.getTraitSet()); + + assert collation.satisfies(TraitUtils.collation(traitSet)) + : "Unexpected collations: input=" + collation + ", traitSet=" + TraitUtils.collation(traitSet); + return new IgniteMapSortAggregate( - getCluster(), traitSet, input, groupSet, groupSets, aggCalls, TraitUtils.collation(input.getTraitSet())); + getCluster(), traitSet, input, groupSet, groupSets, aggCalls, collation); } /** {@inheritDoc} */ diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteReduceSortAggregate.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteReduceSortAggregate.java index 802cb48dd977d..7d1700bcd398a 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteReduceSortAggregate.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/agg/IgniteReduceSortAggregate.java @@ -74,6 +74,11 @@ public IgniteReduceSortAggregate(RelInput input) { /** {@inheritDoc} */ @Override public RelNode copy(RelTraitSet traitSet, List inputs) { + RelCollation collation = TraitUtils.collation(sole(inputs).getTraitSet()); + + assert collation.satisfies(TraitUtils.collation(traitSet)) + : "Unexpected collations: input=" + collation + ", traitSet=" + TraitUtils.collation(traitSet); + return new IgniteReduceSortAggregate( getCluster(), traitSet, @@ -82,7 +87,7 @@ public IgniteReduceSortAggregate(RelInput input) { groupSets, aggCalls, rowType, - TraitUtils.collation(sole(inputs).getTraitSet()) + collation ); } diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SortAggregateIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SortAggregateIntegrationTest.java index 28d72a9472018..8d9f9cc85358d 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SortAggregateIntegrationTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SortAggregateIntegrationTest.java @@ -140,6 +140,21 @@ public void correctCollationsOnMapReduceSortAgg() throws InterruptedException { assertEquals(ROWS, cursors.size()); } + /** */ + @Test + public void testNullsReordering() { + sql("CREATE TABLE t(a INTEGER, b INTEGER) WITH " + atomicity()); + sql("INSERT INTO t VALUES (1, 1), (2, 2), (1, 3), (3, 4), (NULL, 1), (1, NULL)"); + + assertQuery("SELECT a, SUM(b), COUNT(b), COUNT(*) FROM t GROUP BY a ORDER BY a NULLS LAST") + .ordered() + .returns(1, 4L, 2L, 3L) + .returns(2, 2L, 1L, 1L) + .returns(3, 4L, 1L, 1L) + .returns(null, 1L, 1L, 1L) + .check(); + } + /** * @param c Cache. * @param rows Rows count. diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/SortAggregatePlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/SortAggregatePlannerTest.java index 9116092c0d7db..810982b5c4c65 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/SortAggregatePlannerTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/SortAggregatePlannerTest.java @@ -18,20 +18,24 @@ package org.apache.ignite.internal.processors.query.calcite.planner; import java.util.Arrays; +import java.util.function.Predicate; import org.apache.calcite.plan.RelOptPlanner; import org.apache.calcite.plan.RelOptUtil; +import org.apache.calcite.rel.RelCollation; import org.apache.calcite.rel.RelCollations; import org.apache.calcite.rel.RelFieldCollation; +import org.apache.calcite.rel.RelNode; import org.apache.calcite.rel.type.RelDataTypeFactory; import org.apache.ignite.internal.processors.query.calcite.metadata.ColocationGroup; import org.apache.ignite.internal.processors.query.calcite.prepare.MappingQueryContext; -import org.apache.ignite.internal.processors.query.calcite.rel.IgniteAggregate; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteCorrelatedNestedLoopJoin; +import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteLimit; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSort; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan; import org.apache.ignite.internal.processors.query.calcite.rel.agg.IgniteColocatedSortAggregate; +import org.apache.ignite.internal.processors.query.calcite.rel.agg.IgniteMapSortAggregate; import org.apache.ignite.internal.processors.query.calcite.rel.agg.IgniteReduceSortAggregate; import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema; import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution; @@ -43,6 +47,10 @@ import org.apache.ignite.testframework.GridTestUtils; import org.junit.Test; +import static org.apache.calcite.sql.type.SqlTypeName.INTEGER; +import static org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions.random; +import static org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions.single; + /** * */ @@ -208,7 +216,7 @@ public void collationPermuteMapReduce() throws Exception { @Test public void testEmptyCollationPassThroughLimit() throws Exception { IgniteSchema publicSchema = createSchema( - createTable("TEST", IgniteDistributions.single(), "A", Integer.class)); + createTable("TEST", single(), "A", Integer.class)); assertPlan("SELECT (SELECT test.a FROM test t ORDER BY 1 LIMIT 1) FROM test", publicSchema, hasChildThat(isInstanceOf(IgniteCorrelatedNestedLoopJoin.class) @@ -220,65 +228,112 @@ public void testEmptyCollationPassThroughLimit() throws Exception { /** */ @Test public void testCollationPassThrough() throws Exception { - IgniteSchema publicSchema = createSchema( - createTable("TEST", IgniteDistributions.single(), "A", Integer.class, "B", Integer.class)); - - // Sort order equals to grouping set. - assertPlan("SELECT a, b, COUNT(*) FROM test GROUP BY a, b ORDER BY a, b", publicSchema, - isInstanceOf(IgniteAggregate.class) - .and(input(isInstanceOf(IgniteSort.class) - .and(s -> s.collation().equals(TraitUtils.createCollation(F.asList(0, 1)))) - .and(input(isTableScan("TEST"))))), - HASH_AGG_RULES - ); + for (boolean colocated : F.asList(true, false)) { + log.info("Test colocated=" + colocated); - // Sort order equals to grouping set (permuted collation). - assertPlan("SELECT a, b, COUNT(*) FROM test GROUP BY a, b ORDER BY b, a", publicSchema, - isInstanceOf(IgniteAggregate.class) - .and(input(isInstanceOf(IgniteSort.class) - .and(s -> s.collation().equals(TraitUtils.createCollation(F.asList(1, 0)))) - .and(input(isTableScan("TEST"))))), - HASH_AGG_RULES - ); + String[] disabledRules = colocated ? HASH_AGG_RULES : + F.concat(HASH_AGG_RULES, "ColocatedSortAggregateConverterRule"); - // Sort order is a subset of grouping set. - assertPlan("SELECT a, b, COUNT(*) cnt FROM test GROUP BY a, b ORDER BY a", publicSchema, - isInstanceOf(IgniteAggregate.class) - .and(input(isInstanceOf(IgniteSort.class) - .and(s -> s.collation().equals(TraitUtils.createCollation(F.asList(0, 1)))) - .and(input(isTableScan("TEST"))))), - HASH_AGG_RULES - ); - - // Sort order is a subset of grouping set (permuted collation). - assertPlan("SELECT a, b, COUNT(*) cnt FROM test GROUP BY a, b ORDER BY b", publicSchema, - isInstanceOf(IgniteAggregate.class) - .and(input(isInstanceOf(IgniteSort.class) - .and(s -> s.collation().equals(TraitUtils.createCollation(F.asList(1, 0)))) - .and(input(isTableScan("TEST"))))), - HASH_AGG_RULES - ); + IgniteSchema publicSchema = createSchema( + createTable("TEST", colocated ? single() : random(), "A", INTEGER, "B", INTEGER)); - // Sort order is a superset of grouping set (additional sorting required). - assertPlan("SELECT a, b, COUNT(*) cnt FROM test GROUP BY a, b ORDER BY a, b, cnt", publicSchema, - isInstanceOf(IgniteSort.class) - .and(s -> s.collation().equals(TraitUtils.createCollation(F.asList(0, 1, 2)))) - .and(input(isInstanceOf(IgniteAggregate.class) - .and(input(isInstanceOf(IgniteSort.class) + // Sort order equals to grouping set. + assertPlan("SELECT a, b, COUNT(*) FROM test GROUP BY a, b ORDER BY a, b", publicSchema, + isAggregateWithCollation(colocated, TraitUtils.createCollation(F.asList(0, 1)), + input(isInstanceOf(IgniteSort.class) .and(s -> s.collation().equals(TraitUtils.createCollation(F.asList(0, 1)))) - .and(input(isTableScan("TEST"))))))), - HASH_AGG_RULES - ); - - // Sort order is not equals to grouping set (additional sorting required). - assertPlan("SELECT a, b, COUNT(*) cnt FROM test GROUP BY a, b ORDER BY cnt, b", publicSchema, - isInstanceOf(IgniteSort.class) - .and(s -> s.collation().equals(TraitUtils.createCollation(F.asList(2, 1)))) - .and(input(isInstanceOf(IgniteAggregate.class) - .and(input(isInstanceOf(IgniteSort.class) + .and(input(isTableScan("TEST"))))), + disabledRules + ); + + // Sort order equals to grouping set, but order is not default. + RelCollation expectedCollation = RelCollations.of( + TraitUtils.createFieldCollation(0, false), + TraitUtils.createFieldCollation(1, true) + ); + + assertPlan("SELECT a, b, COUNT(*) FROM test GROUP BY a, b ORDER BY a desc, b", publicSchema, + isAggregateWithCollation(colocated, expectedCollation, + input(isInstanceOf(IgniteSort.class) + .and(s -> s.collation().equals(expectedCollation)) + .and(input(isTableScan("TEST"))))), + disabledRules + ); + + // Sort order equals to grouping set (permuted collation). + assertPlan("SELECT a, b, COUNT(*) FROM test GROUP BY a, b ORDER BY b, a", publicSchema, + isAggregateWithCollation(colocated, TraitUtils.createCollation(F.asList(1, 0)), + input(isInstanceOf(IgniteSort.class) + .and(s -> s.collation().equals(TraitUtils.createCollation(F.asList(1, 0)))) + .and(input(isTableScan("TEST"))))), + disabledRules + ); + + // Sort order is a subset of grouping set. + assertPlan("SELECT a, b, COUNT(*) cnt FROM test GROUP BY a, b ORDER BY a", publicSchema, + isAggregateWithCollation(colocated, TraitUtils.createCollation(F.asList(0, 1)), + input(isInstanceOf(IgniteSort.class) .and(s -> s.collation().equals(TraitUtils.createCollation(F.asList(0, 1)))) - .and(input(isTableScan("TEST"))))))), - HASH_AGG_RULES - ); + .and(input(isTableScan("TEST"))))), + disabledRules + ); + + // Sort order is a subset of grouping set (permuted collation). + assertPlan("SELECT a, b, COUNT(*) cnt FROM test GROUP BY a, b ORDER BY b", publicSchema, + isAggregateWithCollation(colocated, TraitUtils.createCollation(F.asList(1, 0)), + input(isInstanceOf(IgniteSort.class) + .and(s -> s.collation().equals(TraitUtils.createCollation(F.asList(1, 0)))) + .and(input(isTableScan("TEST"))))), + disabledRules + ); + + // Sort order is a superset of grouping set (additional sorting required). + assertPlan("SELECT a, b, COUNT(*) cnt FROM test GROUP BY a, b ORDER BY a, b, cnt", publicSchema, + isInstanceOf(IgniteSort.class) + .and(s -> s.collation().equals(TraitUtils.createCollation(F.asList(0, 1, 2)))) + .and(input(isAggregateWithCollation(colocated, TraitUtils.createCollation(F.asList(0, 1)), + input(isInstanceOf(IgniteSort.class) + .and(s -> s.collation().equals(TraitUtils.createCollation(F.asList(0, 1)))) + .and(input(isTableScan("TEST"))))))), + disabledRules + ); + + // Sort order is not equals to grouping set (additional sorting required). + assertPlan("SELECT a, b, COUNT(*) cnt FROM test GROUP BY a, b ORDER BY cnt, b", publicSchema, + isInstanceOf(IgniteSort.class) + .and(s -> s.collation().equals(TraitUtils.createCollation(F.asList(2, 1)))) + .and(input(isAggregateWithCollation(colocated, TraitUtils.createCollation(F.asList(0, 1)), + input(isInstanceOf(IgniteSort.class) + .and(s -> s.collation().equals(TraitUtils.createCollation(F.asList(0, 1)))) + .and(input(isTableScan("TEST"))))))), + disabledRules + ); + } + } + + /** + * Predicate builder to check aggregate with collation. + */ + protected Predicate isAggregateWithCollation( + boolean colocated, + RelCollation collation, + Predicate predicate + ) { + if (colocated) { + return isInstanceOf(IgniteColocatedSortAggregate.class) + .and(a -> a.collation().satisfies(collation)) + .and(predicate); + } + else { + return isInstanceOf(IgniteReduceSortAggregate.class) + .and(a -> a.collation().satisfies(collation)) + .and(input(isInstanceOf(IgniteExchange.class) + .and(input(isInstanceOf(IgniteMapSortAggregate.class) + .and(a -> a.collation().satisfies(collation)) + .and(predicate) + )) + )); + } } + } From 15b6c4ec9b413042da36471eab5d986c877e4f13 Mon Sep 17 00:00:00 2001 From: Aleksey Plekhanov Date: Thu, 5 Feb 2026 10:36:39 +0300 Subject: [PATCH 6/6] IGNITE-27738 SQL Calcite: Fix planner hang on multi-row values --- .../query/calcite/planner/SortAggregatePlannerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/SortAggregatePlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/SortAggregatePlannerTest.java index 810982b5c4c65..2f0e13cc24c4a 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/SortAggregatePlannerTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/SortAggregatePlannerTest.java @@ -335,5 +335,4 @@ protected Predicate isAggregateWithCollation( )); } } - }