You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by al...@apache.org on 2022/02/07 13:23:39 UTC

[ignite] branch sql-calcite updated: IGNITE-16414 Fix sorted index spool and sorted aggregate collation planning - Fixes #9800.

This is an automated email from the ASF dual-hosted git repository.

alexpl pushed a commit to branch sql-calcite
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/sql-calcite by this push:
     new 7dd1f63  IGNITE-16414 Fix sorted index spool and sorted aggregate collation planning - Fixes #9800.
7dd1f63 is described below

commit 7dd1f633cf1afaace122399cb56145e0ae724d9d
Author: Aleksey Plekhanov <pl...@gmail.com>
AuthorDate: Mon Feb 7 16:09:44 2022 +0300

    IGNITE-16414 Fix sorted index spool and sorted aggregate collation planning - Fixes #9800.
    
    Signed-off-by: Aleksey Plekhanov <pl...@gmail.com>
---
 .../calcite/rel/agg/IgniteSortAggregateBase.java   | 20 ++++-
 .../FilterSpoolMergeToSortedIndexSpoolRule.java    | 61 +++++++++++++--
 .../calcite/rule/SortAggregateConverterRule.java   |  7 +-
 .../query/calcite/schema/SchemaHolderImpl.java     | 12 +--
 .../processors/query/calcite/trait/TraitUtils.java | 22 +++++-
 .../processors/query/calcite/util/RexUtils.java    | 20 ++---
 .../query/calcite/planner/AbstractPlannerTest.java | 33 ++++++++
 .../planner/AggregateDistinctPlannerTest.java      |  5 +-
 .../calcite/planner/AggregatePlannerTest.java      | 12 ++-
 .../CorrelatedNestedLoopJoinPlannerTest.java       |  8 +-
 .../calcite/planner/HashAggregatePlannerTest.java  |  5 +-
 .../calcite/planner/HashIndexSpoolPlannerTest.java |  8 +-
 .../calcite/planner/JoinColocationPlannerTest.java | 13 ++--
 .../calcite/planner/LimitOffsetPlannerTest.java    | 24 +-----
 .../query/calcite/planner/PlannerTest.java         |  6 +-
 .../planner/ProjectFilterScanMergePlannerTest.java |  5 +-
 .../calcite/planner/SortAggregatePlannerTest.java  | 79 +++++++++++++++++--
 .../planner/SortedIndexSpoolPlannerTest.java       | 88 +++++++++++++++++++++-
 .../calcite/planner/StatisticsPlannerTest.java     | 12 ++-
 .../query/calcite/planner/TableDmlPlannerTest.java |  7 +-
 20 files changed, 329 insertions(+), 118 deletions(-)

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 79f8069..d3ba800 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
@@ -17,16 +17,15 @@
 
 package org.apache.ignite.internal.processors.query.calcite.rel.agg;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
-
 import com.google.common.collect.ImmutableList;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.util.ImmutableBitSet;
-import org.apache.calcite.util.ImmutableIntList;
 import org.apache.calcite.util.Pair;
 import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils;
 import org.apache.ignite.internal.processors.query.calcite.trait.TraitsAwareIgniteRel;
@@ -48,7 +47,22 @@ interface IgniteSortAggregateBase extends TraitsAwareIgniteRel {
     @Override default Pair<RelTraitSet, List<RelTraitSet>> passThroughCollation(
         RelTraitSet nodeTraits, List<RelTraitSet> inputTraits
     ) {
-        RelCollation collation = RelCollations.of(ImmutableIntList.copyOf(getGroupSet().asList()));
+        RelCollation required = TraitUtils.collation(nodeTraits);
+        ImmutableBitSet requiredKeys = ImmutableBitSet.of(required.getKeys());
+        RelCollation collation;
+
+        if (getGroupSet().contains(requiredKeys)) {
+            List<RelFieldCollation> newCollationFields = new ArrayList<>(getGroupSet().cardinality());
+            newCollationFields.addAll(required.getFieldCollations());
+
+            ImmutableBitSet keysLeft = getGroupSet().except(requiredKeys);
+
+            keysLeft.forEach(fieldIdx -> newCollationFields.add(TraitUtils.createFieldCollation(fieldIdx)));
+
+            collation = RelCollations.of(newCollationFields);
+        }
+        else
+            collation = TraitUtils.createCollation(getGroupSet().asList());
 
         return Pair.of(nodeTraits.replace(collation),
             ImmutableList.of(inputTraits.get(0).replace(collation)));
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/FilterSpoolMergeToSortedIndexSpoolRule.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/FilterSpoolMergeToSortedIndexSpoolRule.java
index d753fc0..024be7e 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/FilterSpoolMergeToSortedIndexSpoolRule.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/FilterSpoolMergeToSortedIndexSpoolRule.java
@@ -16,16 +16,22 @@
  */
 package org.apache.ignite.internal.processors.query.calcite.rule;
 
-import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
 import org.apache.calcite.plan.RelOptCluster;
 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.RelCollation;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Filter;
 import org.apache.calcite.rel.core.Spool;
+import org.apache.calcite.rex.RexNode;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSortedIndexSpool;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableSpool;
@@ -64,9 +70,11 @@ public class FilterSpoolMergeToSortedIndexSpoolRule extends RelRule<FilterSpoolM
 
         RelNode input = spool.getInput();
 
+        RelCollation inCollation = TraitUtils.collation(input);
+
         IndexConditions idxCond = RexUtils.buildSortedIndexConditions(
             cluster,
-            TraitUtils.collation(input),
+            inCollation,
             filter.getCondition(),
             spool.getRowType(),
             null
@@ -75,13 +83,52 @@ public class FilterSpoolMergeToSortedIndexSpoolRule extends RelRule<FilterSpoolM
         if (F.isEmpty(idxCond.lowerCondition()) && F.isEmpty(idxCond.upperCondition()))
             return;
 
-        RelCollation collation = TraitUtils.createCollation(ImmutableList.copyOf(idxCond.keys()));
-        
+        RelCollation traitCollation;
+        RelCollation searchCollation;
+
+        if (inCollation == null || inCollation.isDefault()) {
+            // Create collation by index condition.
+            List<RexNode> lowerBound = idxCond.lowerBound();
+            List<RexNode> upperBound = idxCond.upperBound();
+
+            assert lowerBound == null || upperBound == null || lowerBound.size() == upperBound.size();
+
+            int cardinality = lowerBound != null ? lowerBound.size() : upperBound.size();
+
+            List<Integer> equalsFields = new ArrayList<>(cardinality);
+            List<Integer> otherFields = new ArrayList<>(cardinality);
+
+            // First, add all equality filters to collation, then add other fields.
+            for (int i = 0; i < cardinality; i++) {
+                RexNode lowerNode = lowerBound != null ? lowerBound.get(i) : null;
+                RexNode upperNode = upperBound != null ? upperBound.get(i) : null;
+
+                if (RexUtils.isNotNull(lowerNode) || RexUtils.isNotNull(upperNode))
+                    (F.eq(lowerNode, upperNode) ? equalsFields : otherFields).add(i);
+            }
+
+            searchCollation = traitCollation = TraitUtils.createCollation(F.concat(true, equalsFields, otherFields));
+        }
+        else {
+            // Create search collation as a prefix of input collation.
+            traitCollation = inCollation;
+
+            Set<Integer> searchKeys = idxCond.keys();
+
+            List<RelFieldCollation> collationFields = inCollation.getFieldCollations().subList(0, searchKeys.size());
+
+            assert searchKeys.containsAll(collationFields.stream().map(RelFieldCollation::getFieldIndex)
+                .collect(Collectors.toSet())) : "Search condition should be a prefix of collation [searchKeys=" +
+                searchKeys + ", collation=" + inCollation + ']';
+
+            searchCollation = RelCollations.of(collationFields);
+        }
+
         RelNode res = new IgniteSortedIndexSpool(
             cluster,
-            trait.replace(collation),
-            convert(input, input.getTraitSet().replace(collation)),
-            collation,
+            trait.replace(traitCollation),
+            convert(input, input.getTraitSet().replace(traitCollation)),
+            searchCollation,
             filter.getCondition(),
             idxCond
         );
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/SortAggregateConverterRule.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/SortAggregateConverterRule.java
index ee82f83..207b909 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/SortAggregateConverterRule.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/SortAggregateConverterRule.java
@@ -23,16 +23,15 @@ import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.PhysicalNode;
 import org.apache.calcite.rel.RelCollation;
-import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.logical.LogicalAggregate;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
-import org.apache.calcite.util.ImmutableIntList;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
 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.rel.agg.IgniteSingleSortAggregate;
 import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
+import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils;
 import org.apache.ignite.internal.processors.query.calcite.util.HintUtils;
 import org.apache.ignite.internal.util.typedef.F;
 
@@ -71,7 +70,7 @@ public class SortAggregateConverterRule {
             RelOptCluster cluster = agg.getCluster();
             RelNode input = agg.getInput();
 
-            RelCollation collation = RelCollations.of(ImmutableIntList.copyOf(agg.getGroupSet().asList()));
+            RelCollation collation = TraitUtils.createCollation(agg.getGroupSet().asList());
 
             RelTraitSet inTrait = cluster.traitSetOf(IgniteConvention.INSTANCE)
                 .replace(collation)
@@ -112,7 +111,7 @@ public class SortAggregateConverterRule {
             RelOptCluster cluster = agg.getCluster();
             RelNode input = agg.getInput();
 
-            RelCollation collation = RelCollations.of(ImmutableIntList.copyOf(agg.getGroupSet().asList()));
+            RelCollation collation = TraitUtils.createCollation(agg.getGroupSet().asList());
 
             RelTraitSet inTrait = cluster.traitSetOf(IgniteConvention.INSTANCE).replace(collation);
             RelTraitSet outTrait = cluster.traitSetOf(IgniteConvention.INSTANCE).replace(collation);
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/SchemaHolderImpl.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/SchemaHolderImpl.java
index d2f92c3..69876e9 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/SchemaHolderImpl.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/SchemaHolderImpl.java
@@ -37,6 +37,7 @@ import org.apache.ignite.internal.processors.cache.GridCacheContextInfo;
 import org.apache.ignite.internal.processors.query.GridQueryIndexDescriptor;
 import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
 import org.apache.ignite.internal.processors.query.calcite.exec.exp.IgniteScalarFunction;
+import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils;
 import org.apache.ignite.internal.processors.query.calcite.util.AbstractService;
 import org.apache.ignite.internal.processors.query.schema.SchemaChangeListener;
 import org.apache.ignite.internal.processors.subscription.GridInternalSubscriptionProcessor;
@@ -245,9 +246,7 @@ public class SchemaHolderImpl extends AbstractService implements SchemaHolder, S
             boolean descending = idxDesc.descending(idxField);
             int fieldIdx = fieldDesc.fieldIndex();
 
-            collations.add(
-                createFieldCollation(fieldIdx, !descending)
-            );
+            collations.add(TraitUtils.createFieldCollation(fieldIdx, !descending));
         }
 
         return RelCollations.of(collations);
@@ -298,11 +297,4 @@ public class SchemaHolderImpl extends AbstractService implements SchemaHolder, S
         igniteSchemas.forEach(newCalciteSchema::add);
         calciteSchema = newCalciteSchema;
     }
-
-    /** */
-    private static RelFieldCollation createFieldCollation(int fieldIdx, boolean asc) {
-        return asc
-            ? new RelFieldCollation(fieldIdx, RelFieldCollation.Direction.ASCENDING, RelFieldCollation.NullDirection.FIRST)
-            : new RelFieldCollation(fieldIdx, RelFieldCollation.Direction.DESCENDING, RelFieldCollation.NullDirection.LAST);
-    }
 }
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 5fab4d4..9cc1014 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
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.processors.query.calcite.trait;
 
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -24,7 +25,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
-
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import org.apache.calcite.linq4j.Ord;
@@ -465,13 +465,29 @@ public class TraitUtils {
      * @param keys The keys to create collation from.
      * @return New collation.
      */
-    public static RelCollation createCollation(List<Integer> keys) {
+    public static RelCollation createCollation(Collection<Integer> keys) {
         return RelCollations.of(
-            keys.stream().map(RelFieldCollation::new).collect(Collectors.toList())
+            keys.stream().map(TraitUtils::createFieldCollation).collect(Collectors.toList())
         );
     }
 
     /**
+     * Creates field collation with default direction and nulls ordering.
+     */
+    public static RelFieldCollation createFieldCollation(int fieldIdx) {
+        return createFieldCollation(fieldIdx, true);
+    }
+
+    /**
+     * Creates field collation with default nulls ordering.
+     */
+    public static RelFieldCollation createFieldCollation(int fieldIdx, boolean asc) {
+        return asc
+            ? new RelFieldCollation(fieldIdx, RelFieldCollation.Direction.ASCENDING, RelFieldCollation.NullDirection.FIRST)
+            : new RelFieldCollation(fieldIdx, RelFieldCollation.Direction.DESCENDING, RelFieldCollation.NullDirection.LAST);
+    }
+
+    /**
      * Creates mapping from provided projects that maps a source column idx
      * to idx in a row after applying projections.
      *
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/RexUtils.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/RexUtils.java
index 759d125..77ee156 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/RexUtils.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/RexUtils.java
@@ -26,13 +26,11 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-
 import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptPredicateList;
 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.core.CorrelationId;
@@ -60,11 +58,11 @@ import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.util.ControlFlowException;
 import org.apache.calcite.util.ImmutableBitSet;
-import org.apache.calcite.util.ImmutableIntList;
 import org.apache.calcite.util.Litmus;
 import org.apache.calcite.util.Util;
 import org.apache.calcite.util.mapping.MappingType;
 import org.apache.calcite.util.mapping.Mappings;
+import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.jetbrains.annotations.Nullable;
@@ -182,8 +180,16 @@ public class RexUtils {
         List<RexNode> upper = new ArrayList<>();
 
         // Force collation for all fields of the condition.
-        if (collation == null || collation.isDefault())
-            collation = RelCollations.of(ImmutableIntList.copyOf(fieldsToPredicates.keySet()));
+        if (collation == null || collation.isDefault()) {
+            List<Integer> equalsFields = new ArrayList<>(fieldsToPredicates.size());
+            List<Integer> otherFields = new ArrayList<>(fieldsToPredicates.size());
+
+            // It's more effective to put equality conditions in the collation first.
+            fieldsToPredicates.forEach((idx, conds) ->
+                (F.exist(conds, call -> call.getOperator().getKind() == EQUALS) ? equalsFields : otherFields).add(idx));
+
+            collation = TraitUtils.createCollation(F.concat(true, equalsFields, otherFields));
+        }
 
         for (int i = 0; i < collation.getFieldCollations().size(); i++) {
             RelFieldCollation fc = collation.getFieldCollations().get(i);
@@ -237,10 +243,6 @@ public class RexUtils {
             if (bestLower == null && bestUpper == null)
                 break; // No bounds, so break the loop.
 
-            if (i > 0 && bestLower != bestUpper)
-                // Go behind the first index field only in the case of multiple "=" conditions on index fields.
-                break; // TODO https://issues.apache.org/jira/browse/IGNITE-13568
-
             if (bestLower != null && bestUpper != null) { // "x>5 AND x<10"
                 upper.add(bestUpper);
                 lower.add(bestLower);
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java
index e1ce9d3..8cc783b 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java
@@ -85,6 +85,7 @@ import org.apache.ignite.internal.processors.query.calcite.prepare.MappingQueryC
 import org.apache.ignite.internal.processors.query.calcite.prepare.PlannerHelper;
 import org.apache.ignite.internal.processors.query.calcite.prepare.PlanningContext;
 import org.apache.ignite.internal.processors.query.calcite.prepare.Splitter;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
 import org.apache.ignite.internal.processors.query.calcite.rel.logical.IgniteLogicalTableScan;
@@ -97,6 +98,7 @@ import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
 import org.apache.ignite.internal.processors.query.calcite.schema.IgniteStatisticsImpl;
 import org.apache.ignite.internal.processors.query.calcite.schema.ModifyTuple;
 import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
+import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils;
 import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
 import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeSystem;
 import org.apache.ignite.internal.processors.query.stat.ObjectStatisticsImpl;
@@ -496,6 +498,30 @@ public abstract class AbstractPlannerTest extends GridCommonAbstractTest {
     }
 
     /**
+     * Predicate builder for "Index scan with given name" condition.
+     */
+    protected <T extends RelNode> Predicate<IgniteIndexScan> isIndexScan(String tableName, String idxName) {
+        return isInstanceOf(IgniteIndexScan.class).and(
+            n -> {
+                String scanTableName = Util.last(n.getTable().getQualifiedName());
+
+                if (!tableName.equalsIgnoreCase(scanTableName)) {
+                    lastErrorMsg = "Unexpected table name [exp=" + tableName + ", act=" + scanTableName + ']';
+
+                    return false;
+                }
+
+                if (!idxName.equals(n.indexName())) {
+                    lastErrorMsg = "Unexpected index name [exp=" + idxName + ", act=" + n.indexName() + ']';
+
+                    return false;
+                }
+
+                return true;
+            });
+    }
+
+    /**
      * Predicate builder for "Any child satisfy predicate" condition.
      */
     protected <T extends RelNode> Predicate<RelNode> hasChildThat(Predicate<T> predicate) {
@@ -817,6 +843,13 @@ public abstract class AbstractPlannerTest extends GridCommonAbstractTest {
             return this;
         }
 
+        /** */
+        public TestTable addIndex(String name, int... keys) {
+            addIndex(TraitUtils.createCollation(Arrays.stream(keys).boxed().collect(Collectors.toList())), name);
+
+            return this;
+        }
+
         /** {@inheritDoc} */
         @Override public IgniteIndex getIndex(String idxName) {
             return indexes.get(idxName);
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AggregateDistinctPlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AggregateDistinctPlannerTest.java
index 8bef6fb..f587189 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AggregateDistinctPlannerTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AggregateDistinctPlannerTest.java
@@ -20,11 +20,8 @@ package org.apache.ignite.internal.processors.query.calcite.planner;
 import java.util.List;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
-
 import org.apache.calcite.plan.RelOptUtil;
-import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.sql.SqlExplainLevel;
-import org.apache.calcite.util.ImmutableIntList;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteAggregate;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
@@ -65,7 +62,7 @@ public class AggregateDistinctPlannerTest extends AbstractAggregatePlannerTest {
      */
     @Test
     public void mapReduceDistinctWithIndex() throws Exception {
-        TestTable tbl = createAffinityTable().addIndex(RelCollations.of(ImmutableIntList.of(1, 2)), "val0_val1");
+        TestTable tbl = createAffinityTable().addIndex("val0_val1", 1, 2);
 
         IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
 
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AggregatePlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AggregatePlannerTest.java
index b184826..0b50227 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AggregatePlannerTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AggregatePlannerTest.java
@@ -22,7 +22,6 @@ import java.util.List;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.calcite.plan.RelOptUtil;
-import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.AggregateCall;
 import org.apache.calcite.rel.core.Join;
@@ -30,7 +29,6 @@ import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.sql.SqlExplainLevel;
 import org.apache.calcite.sql.fun.SqlAvgAggFunction;
-import org.apache.calcite.util.ImmutableIntList;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteAggregate;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
@@ -74,7 +72,7 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
      */
     @Test
     public void singleWithoutIndex() throws Exception {
-        TestTable tbl = createBroadcastTable().addIndex(RelCollations.of(ImmutableIntList.of(1, 2)), "val0_val1");
+        TestTable tbl = createBroadcastTable().addIndex("val0_val1", 1, 2);
 
         IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
 
@@ -108,7 +106,7 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
      */
     @Test
     public void singleWithIndex() throws Exception {
-        TestTable tbl = createBroadcastTable().addIndex(RelCollations.of(ImmutableIntList.of(3, 4)), "grp0_grp1");
+        TestTable tbl = createBroadcastTable().addIndex("grp0_grp1", 3, 4);
 
         IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
 
@@ -185,7 +183,7 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
      */
     @Test
     public void distribution() throws Exception {
-        TestTable tbl = createAffinityTable().addIndex(RelCollations.of(ImmutableIntList.of(3)), "grp0");
+        TestTable tbl = createAffinityTable().addIndex("grp0", 3);
 
         IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
 
@@ -222,8 +220,8 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
     @Test
     public void expandDistinctAggregates() throws Exception {
         TestTable tbl = createAffinityTable()
-            .addIndex(RelCollations.of(ImmutableIntList.of(3, 1, 0)), "idx_val0")
-            .addIndex(RelCollations.of(ImmutableIntList.of(3, 2, 0)), "idx_val1");
+            .addIndex("idx_val0", 3, 1, 0)
+            .addIndex("idx_val1", 3, 2, 0);
 
         IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
 
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/CorrelatedNestedLoopJoinPlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/CorrelatedNestedLoopJoinPlannerTest.java
index 570fd8d..812ec10 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/CorrelatedNestedLoopJoinPlannerTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/CorrelatedNestedLoopJoinPlannerTest.java
@@ -19,11 +19,9 @@ package org.apache.ignite.internal.processors.query.calcite.planner;
 
 import java.util.List;
 import org.apache.calcite.plan.RelOptUtil;
-import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rex.RexFieldAccess;
 import org.apache.calcite.rex.RexNode;
-import org.apache.calcite.util.ImmutableIntList;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
 import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
@@ -74,7 +72,7 @@ public class CorrelatedNestedLoopJoinPlannerTest extends AbstractPlannerTest {
                     return IgniteDistributions.broadcast();
                 }
             }
-                .addIndex(RelCollations.of(ImmutableIntList.of(1, 0)), "t1_jid_idx")
+                .addIndex("t1_jid_idx", 1, 0)
         );
 
         String sql = "select * " +
@@ -136,7 +134,7 @@ public class CorrelatedNestedLoopJoinPlannerTest extends AbstractPlannerTest {
                     return IgniteDistributions.broadcast();
                 }
             }
-                .addIndex(RelCollations.of(ImmutableIntList.of(1, 0)), "t0_jid_idx")
+                .addIndex("t0_jid_idx", 1, 0)
         );
 
         publicSchema.addTable(
@@ -152,7 +150,7 @@ public class CorrelatedNestedLoopJoinPlannerTest extends AbstractPlannerTest {
                     return IgniteDistributions.broadcast();
                 }
             }
-                .addIndex(RelCollations.of(ImmutableIntList.of(1, 0)), "t1_jid_idx")
+                .addIndex("t1_jid_idx", 1, 0)
         );
 
         String sql = "select * " +
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/HashAggregatePlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/HashAggregatePlannerTest.java
index 716506f..96a83ae 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/HashAggregatePlannerTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/HashAggregatePlannerTest.java
@@ -18,13 +18,10 @@
 package org.apache.ignite.internal.processors.query.calcite.planner;
 
 import java.util.Arrays;
-
 import org.apache.calcite.plan.RelOptUtil;
-import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.sql.fun.SqlAvgAggFunction;
 import org.apache.calcite.sql.fun.SqlCountAggFunction;
-import org.apache.calcite.util.ImmutableIntList;
 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.IgniteRel;
@@ -109,7 +106,7 @@ public class HashAggregatePlannerTest extends AbstractAggregatePlannerTest {
      */
     @Test
     public void noGroupByAggregate() throws Exception {
-        TestTable tbl = createAffinityTable().addIndex(RelCollations.of(ImmutableIntList.of(1, 2)), "val0_val1");
+        TestTable tbl = createAffinityTable().addIndex("val0_val1", 1, 2);
 
         IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
 
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/HashIndexSpoolPlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/HashIndexSpoolPlannerTest.java
index ffa91b3..49cb57c 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/HashIndexSpoolPlannerTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/HashIndexSpoolPlannerTest.java
@@ -19,11 +19,9 @@ package org.apache.ignite.internal.processors.query.calcite.planner;
 
 import java.util.List;
 import org.apache.calcite.plan.RelOptUtil;
-import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rex.RexFieldAccess;
 import org.apache.calcite.rex.RexNode;
-import org.apache.calcite.util.ImmutableIntList;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteHashIndexSpool;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
 import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
@@ -59,7 +57,7 @@ public class HashIndexSpoolPlannerTest extends AbstractPlannerTest {
                     return IgniteDistributions.affinity(0, "T0", "hash");
                 }
             }
-                .addIndex(RelCollations.of(ImmutableIntList.of(1, 0)), "t0_jid_idx")
+                .addIndex("t0_jid_idx", 1, 0)
         );
 
         publicSchema.addTable(
@@ -75,7 +73,7 @@ public class HashIndexSpoolPlannerTest extends AbstractPlannerTest {
                     return IgniteDistributions.affinity(0, "T1", "hash");
                 }
             }
-                .addIndex(RelCollations.of(ImmutableIntList.of(1, 0)), "t1_jid_idx")
+                .addIndex("t1_jid_idx", 1, 0)
         );
 
         String sql = "select * " +
@@ -140,7 +138,7 @@ public class HashIndexSpoolPlannerTest extends AbstractPlannerTest {
                     return IgniteDistributions.affinity(0, "T1", "hash");
                 }
             }
-                .addIndex(RelCollations.of(ImmutableIntList.of(1, 0)), "t1_jid0_idx")
+                .addIndex("t1_jid0_idx", 1, 0)
         );
 
         String sql = "select * " +
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 c8076ec..053a827 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
@@ -19,7 +19,6 @@ package org.apache.ignite.internal.processors.query.calcite.planner;
 
 import java.util.List;
 import org.apache.calcite.plan.RelOptUtil;
-import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.util.ImmutableIntList;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
@@ -55,7 +54,7 @@ public class JoinColocationPlannerTest extends AbstractPlannerTest {
             "VAL", String.class
         );
 
-        tbl.addIndex(RelCollations.of(0), "PK");
+        tbl.addIndex("PK", 0);
 
         IgniteSchema schema = createSchema(tbl);
 
@@ -88,7 +87,7 @@ public class JoinColocationPlannerTest extends AbstractPlannerTest {
             "VAL", String.class
         );
 
-        tbl.addIndex(RelCollations.of(ImmutableIntList.of(0, 1)), "PK");
+        tbl.addIndex("PK", 0, 1);
 
         IgniteSchema schema = createSchema(tbl);
 
@@ -123,7 +122,7 @@ public class JoinColocationPlannerTest extends AbstractPlannerTest {
             "VAL", String.class
         );
 
-        complexTbl.addIndex(RelCollations.of(ImmutableIntList.of(0, 1)), "PK");
+        complexTbl.addIndex("PK", 0, 1);
 
         TestTable simpleTbl = createTable(
             "SIMPLE_TBL",
@@ -133,7 +132,7 @@ public class JoinColocationPlannerTest extends AbstractPlannerTest {
             "VAL", String.class
         );
 
-        simpleTbl.addIndex(RelCollations.of(0), "PK");
+        simpleTbl.addIndex("PK", 0);
 
         IgniteSchema schema = createSchema(complexTbl, simpleTbl);
 
@@ -172,7 +171,7 @@ public class JoinColocationPlannerTest extends AbstractPlannerTest {
             "VAL", String.class
         );
 
-        complexTblDirect.addIndex(RelCollations.of(ImmutableIntList.of(0, 1)), "PK");
+        complexTblDirect.addIndex("PK", 0, 1);
 
         TestTable complexTblIndirect = createTable(
             "COMPLEX_TBL_INDIRECT",
@@ -182,7 +181,7 @@ public class JoinColocationPlannerTest extends AbstractPlannerTest {
             "VAL", String.class
         );
 
-        complexTblIndirect.addIndex(RelCollations.of(ImmutableIntList.of(0, 1)), "PK");
+        complexTblIndirect.addIndex("PK", 0, 1);
 
         IgniteSchema schema = createSchema(complexTblDirect, complexTblIndirect);
 
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/LimitOffsetPlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/LimitOffsetPlannerTest.java
index 2528668..595f780 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/LimitOffsetPlannerTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/LimitOffsetPlannerTest.java
@@ -17,10 +17,6 @@
 
 package org.apache.ignite.internal.processors.query.calcite.planner;
 
-import java.util.Arrays;
-
-import org.apache.calcite.rel.RelCollations;
-import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.util.ImmutableIntList;
@@ -37,8 +33,6 @@ import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeSystem
 import org.apache.ignite.internal.util.typedef.F;
 import org.junit.Test;
 
-import static java.util.stream.Collectors.toList;
-
 /**
  * Planner test for LIMIT and OFFSET.
  */
@@ -187,22 +181,8 @@ public class LimitOffsetPlannerTest extends AbstractPlannerTest {
             }
         };
 
-        if (!F.isEmpty(indexedColumns)) {
-            table.addIndex(
-                RelCollations.of(
-                    Arrays.stream(indexedColumns)
-                        .mapToObj(
-                            idx -> new RelFieldCollation(
-                                idx,
-                                RelFieldCollation.Direction.ASCENDING,
-                                RelFieldCollation.NullDirection.FIRST
-                            )
-                        )
-                        .collect(toList())
-                ),
-                "test_idx"
-            );
-        }
+        if (!F.isEmpty(indexedColumns))
+            table.addIndex("test_idx", indexedColumns);
 
         return createSchema(table);
     }
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlannerTest.java
index 3f79059..4295224 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlannerTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlannerTest.java
@@ -27,7 +27,6 @@ import java.util.function.Function;
 import java.util.function.Predicate;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.plan.RelTraitSet;
-import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelRoot;
 import org.apache.calcite.rel.RelVisitor;
@@ -35,7 +34,6 @@ import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.schema.SchemaPlus;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.util.ImmutableBitSet;
-import org.apache.calcite.util.ImmutableIntList;
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
 import org.apache.ignite.internal.processors.query.calcite.QueryRegistryImpl;
 import org.apache.ignite.internal.processors.query.calcite.exec.ArrayRowHandler;
@@ -1232,7 +1230,7 @@ public class PlannerTest extends AbstractPlannerTest {
             }
         };
 
-        emp.addIndex(RelCollations.of(ImmutableIntList.of(1, 2)), "emp_idx");
+        emp.addIndex("emp_idx", 1, 2);
 
         TestTable dept = new TestTable(
             new RelDataTypeFactory.Builder(f)
@@ -1245,7 +1243,7 @@ public class PlannerTest extends AbstractPlannerTest {
             }
         };
 
-        dept.addIndex(RelCollations.of(ImmutableIntList.of(1, 0)), "dep_idx");
+        dept.addIndex("dep_idx", 1, 0);
 
         IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
 
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/ProjectFilterScanMergePlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/ProjectFilterScanMergePlannerTest.java
index 731bda6..225bbfc 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/ProjectFilterScanMergePlannerTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/ProjectFilterScanMergePlannerTest.java
@@ -17,7 +17,6 @@
 
 package org.apache.ignite.internal.processors.query.calcite.planner;
 
-import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.sql.type.SqlTypeName;
@@ -87,7 +86,7 @@ public class ProjectFilterScanMergePlannerTest extends AbstractPlannerTest {
     public void testProjectFilterMergeIndex() throws Exception {
         // Test project and filter merge into index scan.
         TestTable tbl = ((TestTable)publicSchema.getTable("TBL"));
-        tbl.addIndex(RelCollations.of(2), "IDX_C");
+        tbl.addIndex("IDX_C", 2);
 
         // Without index condition shift.
         assertPlan("SELECT a, b FROM tbl WHERE c = 0", publicSchema, isInstanceOf(IgniteIndexScan.class)
@@ -117,7 +116,7 @@ public class ProjectFilterScanMergePlannerTest extends AbstractPlannerTest {
     public void testIdentityFilterMergeIndex() throws Exception {
         // Test project and filter merge into index scan.
         TestTable tbl = ((TestTable)publicSchema.getTable("TBL"));
-        tbl.addIndex(RelCollations.of(2), "IDX_C");
+        tbl.addIndex("IDX_C", 2);
 
         // Without index condition shift.
         assertPlan("SELECT a, b, c FROM tbl WHERE c = 0", publicSchema, isInstanceOf(IgniteIndexScan.class)
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 1384de7..3259413 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
@@ -23,9 +23,9 @@ import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
-import org.apache.calcite.util.ImmutableIntList;
 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.IgniteLimit;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
@@ -36,8 +36,10 @@ import org.apache.ignite.internal.processors.query.calcite.rel.agg.IgniteSingleS
 import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
 import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
 import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
+import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils;
 import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
 import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeSystem;
+import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.testframework.GridTestUtils;
 import org.junit.Test;
 
@@ -51,7 +53,7 @@ public class SortAggregatePlannerTest extends AbstractAggregatePlannerTest {
      */
     @Test
     public void notApplicableForSortAggregate() {
-        TestTable tbl = createAffinityTable().addIndex(RelCollations.of(ImmutableIntList.of(1, 2)), "val0_val1");
+        TestTable tbl = createAffinityTable().addIndex("val0_val1", 1, 2);
 
         IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
 
@@ -73,7 +75,7 @@ public class SortAggregatePlannerTest extends AbstractAggregatePlannerTest {
     /** Checks if already sorted input exist and involved [Map|Reduce]SortAggregate */
     @Test
     public void testNoSortAppendingWithCorrectCollation() throws Exception {
-        RelFieldCollation coll = new RelFieldCollation(1, RelFieldCollation.Direction.DESCENDING);
+        RelFieldCollation coll = TraitUtils.createFieldCollation(1, false);
 
         TestTable tbl = createAffinityTable().addIndex(RelCollations.of(coll), "val0Idx");
 
@@ -120,7 +122,7 @@ public class SortAggregatePlannerTest extends AbstractAggregatePlannerTest {
                 return IgniteDistributions.broadcast();
             }
         }
-            .addIndex(RelCollations.of(ImmutableIntList.of(3, 4)), "grp0_1");
+            .addIndex("grp0_1", 3, 4);
 
         IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
 
@@ -174,7 +176,7 @@ public class SortAggregatePlannerTest extends AbstractAggregatePlannerTest {
                 return IgniteDistributions.affinity(0, "test", "hash");
             }
         }
-            .addIndex(RelCollations.of(ImmutableIntList.of(3, 4)), "grp0_1");
+            .addIndex("grp0_1", 3, 4);
 
         IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
 
@@ -200,7 +202,7 @@ public class SortAggregatePlannerTest extends AbstractAggregatePlannerTest {
 
     /** */
     @Test
-    public void testEmptyCollationPasshThroughLimit() throws Exception {
+    public void testEmptyCollationPassThroughLimit() throws Exception {
         IgniteSchema publicSchema = createSchema(
             createTable("TEST", IgniteDistributions.single(), "A", Integer.class));
 
@@ -210,4 +212,69 @@ public class SortAggregatePlannerTest extends AbstractAggregatePlannerTest {
                     .and(input(isInstanceOf(IgniteSort.class)))))))
         );
     }
+
+    /** */
+    @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"))))),
+            "HashSingleAggregateConverterRule", "HashMapReduceAggregateConverterRule"
+        );
+
+        // 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"))))),
+            "HashSingleAggregateConverterRule", "HashMapReduceAggregateConverterRule"
+        );
+
+        // 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"))))),
+            "HashSingleAggregateConverterRule", "HashMapReduceAggregateConverterRule"
+        );
+
+        // 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"))))),
+            "HashSingleAggregateConverterRule", "HashMapReduceAggregateConverterRule"
+        );
+
+        // 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)
+                        .and(s -> s.collation().equals(TraitUtils.createCollation(F.asList(0, 1))))
+                        .and(input(isTableScan("TEST"))))))),
+            "HashSingleAggregateConverterRule", "HashMapReduceAggregateConverterRule"
+        );
+
+        // 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(s -> s.collation().equals(TraitUtils.createCollation(F.asList(0, 1))))
+                        .and(input(isTableScan("TEST"))))))),
+            "HashSingleAggregateConverterRule", "HashMapReduceAggregateConverterRule"
+        );
+    }
 }
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/SortedIndexSpoolPlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/SortedIndexSpoolPlannerTest.java
index 1a1bfa4..096adfe 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/SortedIndexSpoolPlannerTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/SortedIndexSpoolPlannerTest.java
@@ -18,19 +18,22 @@
 package org.apache.ignite.internal.processors.query.calcite.planner;
 
 import java.util.List;
+import java.util.stream.IntStream;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rex.RexFieldAccess;
 import org.apache.calcite.rex.RexNode;
-import org.apache.calcite.util.ImmutableIntList;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteCorrelatedNestedLoopJoin;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSortedIndexSpool;
 import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
 import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
 import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
+import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils;
 import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
 import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeSystem;
+import org.junit.Ignore;
 import org.junit.Test;
 
 /**
@@ -59,7 +62,7 @@ public class SortedIndexSpoolPlannerTest extends AbstractPlannerTest {
                     return IgniteDistributions.affinity(0, "T0", "hash");
                 }
             }
-                .addIndex(RelCollations.of(ImmutableIntList.of(1, 0)), "t0_jid_idx")
+                .addIndex("t0_jid_idx", 1, 0)
         );
 
         publicSchema.addTable(
@@ -75,7 +78,7 @@ public class SortedIndexSpoolPlannerTest extends AbstractPlannerTest {
                     return IgniteDistributions.affinity(0, "T1", "hash");
                 }
             }
-                .addIndex(RelCollations.of(ImmutableIntList.of(1, 0)), "t1_jid_idx")
+                .addIndex("t1_jid_idx", 1, 0)
         );
 
         String sql = "select * " +
@@ -150,7 +153,7 @@ public class SortedIndexSpoolPlannerTest extends AbstractPlannerTest {
                     return IgniteDistributions.affinity(0, "T1", "hash");
                 }
             }
-                .addIndex(RelCollations.of(ImmutableIntList.of(1, 0)), "t1_jid0_idx")
+                .addIndex("t1_jid0_idx", 1, 0)
         );
 
         String sql = "select * " +
@@ -189,4 +192,81 @@ public class SortedIndexSpoolPlannerTest extends AbstractPlannerTest {
         assertNull(uBound.get(2));
         assertNull(uBound.get(3));
     }
+
+    /**
+     * Check colocated fields with DESC ordering.
+     */
+    @Test
+    public void testDescFields() throws Exception {
+        IgniteSchema publicSchema = createSchema(
+            createTable("T0", 10, IgniteDistributions.affinity(0, "T0", "hash"),
+                "ID", Integer.class, "JID", Integer.class, "VAL", String.class)
+                .addIndex("t0_jid_idx", 1),
+            createTable("T1", 100, IgniteDistributions.affinity(0, "T1", "hash"),
+                "ID", Integer.class, "JID", Integer.class, "VAL", String.class)
+                .addIndex(RelCollations.of(TraitUtils.createFieldCollation(1, false)), "t1_jid_idx")
+        );
+
+        String sql = "select * " +
+            "from t0 " +
+            "join t1 on t1.jid < t0.jid";
+
+        assertPlan(sql, publicSchema,
+            isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)
+                .and(input(1, isInstanceOf(IgniteSortedIndexSpool.class)
+                    .and(spool -> {
+                        List<RexNode> lBound = spool.indexCondition().lowerBound();
+
+                        // Condition is LESS_THEN, but we have DESC field and condition should be in lower bound
+                        // instead of upper bound.
+                        assertNotNull(lBound);
+                        assertEquals(3, lBound.size());
+
+                        assertNull(lBound.get(0));
+                        assertTrue(lBound.get(1) instanceof RexFieldAccess);
+                        assertNull(lBound.get(2));
+
+                        List<RexNode> uBound = spool.indexCondition().upperBound();
+
+                        assertNull(uBound);
+
+                        return true;
+                    })
+                    .and(hasChildThat(isIndexScan("T1", "t1_jid_idx")))
+                )),
+            "MergeJoinConverter", "NestedLoopJoinConverter", "FilterSpoolMergeToHashIndexSpoolRule"
+        );
+    }
+
+    /**
+     * Check sorted spool without input collation.
+     */
+    @Test
+    @Ignore("https://issues.apache.org/jira/browse/IGNITE-16430")
+    public void testRestoreCollation() throws Exception {
+        IgniteSchema publicSchema = createSchema(
+            createTable("T0", 100, IgniteDistributions.random(),
+                "I0", Integer.class, "I1", Integer.class, "I2", Integer.class, "I3", Integer.class),
+            createTable("T1", 10000, IgniteDistributions.random(),
+                "I0", Integer.class, "I1", Integer.class, "I2", Integer.class, "I3", Integer.class)
+        );
+
+        for (int i = 0; i < 4; i++) {
+            final int equalIdx = i;
+
+            String[] conds = IntStream.range(0, 4)
+                .mapToObj(idx -> "t0.i" + idx + ((idx == equalIdx) ? "=" : ">") + "t1.i" + idx)
+                .toArray(String[]::new);
+
+            String sql = "select * from t0 join t1 on " + String.join(" and ", conds);
+
+            assertPlan(sql, publicSchema,
+                hasChildThat(isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)
+                    .and(input(1, isInstanceOf(IgniteSortedIndexSpool.class)
+                        .and(spool -> spool.collation().getFieldCollations().get(0).getFieldIndex() == equalIdx)
+                    ))),
+                "MergeJoinConverter", "NestedLoopJoinConverter", "FilterSpoolMergeToHashIndexSpoolRule"
+            );
+        }
+    }
 }
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/StatisticsPlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/StatisticsPlannerTest.java
index d37925f..7ff7171 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/StatisticsPlannerTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/StatisticsPlannerTest.java
@@ -24,11 +24,9 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Set;
-import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rel.type.RelDataTypeField;
-import org.apache.calcite.util.ImmutableIntList;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
 import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
@@ -123,7 +121,7 @@ public class StatisticsPlannerTest extends AbstractPlannerTest {
         tbl1 = new TestTable(tbl1rt)
             .setDistribution(IgniteDistributions.affinity(0, "TBL1", "hash"));
 
-        tbl1.addIndex(RelCollations.of(0), "PK");
+        tbl1.addIndex("PK", 0);
 
         for (RelDataTypeField field : tbl1rt.getFieldList()) {
             if (field.getIndex() == 0)
@@ -132,12 +130,12 @@ public class StatisticsPlannerTest extends AbstractPlannerTest {
             int idx = field.getIndex();
             String name = getIdxName(1, field.getName().toUpperCase());
 
-            tbl1.addIndex(RelCollations.of(idx), name);
+            tbl1.addIndex(name, idx);
         }
 
         tbl4 = new TestTable(tbl1rt)
             .setDistribution(IgniteDistributions.affinity(0, "TBL4", "hash"));
-        tbl4.addIndex(RelCollations.of(0), "PK");
+        tbl4.addIndex("PK", 0);
 
         for (RelDataTypeField field : tbl1rt.getFieldList()) {
             if (field.getIndex() == 0)
@@ -146,10 +144,10 @@ public class StatisticsPlannerTest extends AbstractPlannerTest {
             int idx = field.getIndex();
             String name = getIdxName(4, field.getName().toUpperCase());
 
-            tbl4.addIndex(RelCollations.of(idx), name);
+            tbl4.addIndex(name, idx);
         }
 
-        tbl4.addIndex(RelCollations.of(ImmutableIntList.of(6, 7)), "TBL4_SHORT_LONG");
+        tbl4.addIndex("TBL4_SHORT_LONG", 6, 7);
 
         HashMap<String, ColumnStatistics> colStat1 = new HashMap<>();
         colStat1.put("T1C1INT", new ColumnStatistics(ValueInt.get(1), ValueInt.get(1000),
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/TableDmlPlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/TableDmlPlannerTest.java
index 9d84ef7..d08f5a7 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/TableDmlPlannerTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/TableDmlPlannerTest.java
@@ -18,7 +18,6 @@
 package org.apache.ignite.internal.processors.query.calcite.planner;
 
 import org.apache.calcite.plan.RelOptUtil;
-import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Spool;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
@@ -74,7 +73,7 @@ public class TableDmlPlannerTest extends AbstractPlannerTest {
     public void insertCachesIndexScan() throws Exception {
         TestTable tbl = createTable("TEST", IgniteDistributions.random(), "VAL", Integer.class);
 
-        tbl.addIndex(RelCollations.of(0), "IDX");
+        tbl.addIndex("IDX", 0);
 
         IgniteSchema schema = createSchema(tbl);
 
@@ -125,7 +124,7 @@ public class TableDmlPlannerTest extends AbstractPlannerTest {
     public void updateNotCachesNonDependentIndexScan() throws Exception {
         TestTable tbl = createTable("TEST", IgniteDistributions.random(), "VAL", Integer.class, "IDX_VAL", Integer.class);
 
-        tbl.addIndex(RelCollations.of(1), "IDX");
+        tbl.addIndex("IDX", 1);
 
         IgniteSchema schema = createSchema(tbl);
 
@@ -148,7 +147,7 @@ public class TableDmlPlannerTest extends AbstractPlannerTest {
     public void updateCachesDependentIndexScan() throws Exception {
         TestTable tbl = createTable("TEST", IgniteDistributions.random(), "VAL", Integer.class);
 
-        tbl.addIndex(RelCollations.of(0), "IDX");
+        tbl.addIndex("IDX", 0);
 
         IgniteSchema schema = createSchema(tbl);