You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by am...@apache.org on 2023/08/25 07:56:06 UTC

[ignite-3] branch main updated: IGNITE-20015 Sql. Introduce new distribution function for system views (#2455)

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

amashenkov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new bc17dfd795 IGNITE-20015 Sql. Introduce new distribution function for system views (#2455)
bc17dfd795 is described below

commit bc17dfd7955dd5565c0ca26d1a0bbf64bb0bc455
Author: Andrew V. Mashenkov <AM...@users.noreply.github.com>
AuthorDate: Fri Aug 25 10:56:00 2023 +0300

    IGNITE-20015 Sql. Introduce new distribution function for system views (#2455)
---
 modules/runner/build.gradle                        |   1 +
 modules/sql-engine/build.gradle                    |   2 +
 .../sql/engine/exec/DestinationFactory.java        |  12 +-
 .../sql/engine/exec/LogicalRelImplementor.java     |   2 +-
 .../internal/sql/engine/externalize/RelJson.java   |   5 +
 .../internal/sql/engine/trait/Destination.java     |   3 +-
 .../sql/engine/trait/DistributionFunction.java     |  24 ++
 .../ignite/internal/sql/engine/trait/Identity.java |  62 +++
 .../sql/engine/trait/IgniteDistributions.java      |  10 +
 .../exec/IdentityDistributionFunctionSelfTest.java | 136 +++++++
 .../planner/AbstractAggregatePlannerTest.java      | 419 ++++++++++++++++++++-
 .../sql/engine/planner/AggregatePlannerTest.java   | 307 ++++++++++++---
 .../planner/ColocatedHashAggregatePlannerTest.java | 218 ++++++++++-
 .../planner/ColocatedSortAggregatePlannerTest.java | 216 +++++++++--
 .../sql/engine/planner/DmlPlannerTest.java         |   6 +-
 .../planner/IdentityDistributionPlannerTest.java   | 200 ++++++++++
 .../planner/MapReduceHashAggregatePlannerTest.java | 138 ++++++-
 .../planner/MapReduceSortAggregatePlannerTest.java | 205 +++++++---
 .../sql/engine/planner/SetOpPlannerTest.java       | 192 +++++++++-
 .../internal/sql/engine/util/CursorUtils.java      |   0
 .../internal/sql/engine/util/MetadataMatcher.java  |   0
 .../sql/engine/util/NativeTypeWrapper.java         |   0
 .../internal/sql/engine/util/QueryChecker.java     |   0
 23 files changed, 1984 insertions(+), 174 deletions(-)

diff --git a/modules/runner/build.gradle b/modules/runner/build.gradle
index 1d11625efe..35795f4331 100644
--- a/modules/runner/build.gradle
+++ b/modules/runner/build.gradle
@@ -185,6 +185,7 @@ dependencies {
     testFixturesImplementation project(':ignite-schema')
     testFixturesImplementation testFixtures(project(':ignite-api'))
     testFixturesImplementation testFixtures(project(':ignite-core'))
+    testFixturesImplementation testFixtures(project(':ignite-sql-engine'))
     testFixturesImplementation testFixtures(project(':ignite-network'))
     testFixturesImplementation libs.jetbrains.annotations
     testFixturesImplementation libs.hamcrest.core
diff --git a/modules/sql-engine/build.gradle b/modules/sql-engine/build.gradle
index c7e33c78cf..0cffa3c8a4 100644
--- a/modules/sql-engine/build.gradle
+++ b/modules/sql-engine/build.gradle
@@ -92,5 +92,7 @@ dependencies {
     testFixturesImplementation project(':ignite-core')
     testFixturesImplementation project(':ignite-api')
     testFixturesImplementation project(':ignite-schema')
+    testFixturesImplementation testFixtures(project(':ignite-core'))
     testFixturesImplementation libs.jetbrains.annotations
+    testFixturesImplementation libs.hamcrest.core
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/DestinationFactory.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/DestinationFactory.java
index ff350add2a..d34dc34b5d 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/DestinationFactory.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/DestinationFactory.java
@@ -31,6 +31,7 @@ import org.apache.ignite.internal.sql.engine.trait.AllNodes;
 import org.apache.ignite.internal.sql.engine.trait.Destination;
 import org.apache.ignite.internal.sql.engine.trait.DistributionFunction;
 import org.apache.ignite.internal.sql.engine.trait.DistributionFunction.AffinityDistribution;
+import org.apache.ignite.internal.sql.engine.trait.Identity;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
 import org.apache.ignite.internal.sql.engine.trait.Partitioned;
 import org.apache.ignite.internal.sql.engine.trait.RandomNode;
@@ -43,16 +44,19 @@ import org.apache.ignite.internal.util.IgniteUtils;
  * physical representation - {@link Destination} function.
  */
 class DestinationFactory<RowT> {
+    private final RowHandler<RowT> rowHandler;
     private final HashFunctionFactory<RowT> hashFunctionFactory;
     private final ResolvedDependencies dependencies;
 
     /**
      * Constructor.
      *
+     * @param rowHandler Row handler.
      * @param hashFunctionFactory Hash-function factory required to resolve hash-based distributions.
      * @param dependencies Dependencies required to resolve row value dependent distributions.
      */
-    DestinationFactory(HashFunctionFactory<RowT> hashFunctionFactory, ResolvedDependencies dependencies) {
+    DestinationFactory(RowHandler<RowT> rowHandler, HashFunctionFactory<RowT> hashFunctionFactory, ResolvedDependencies dependencies) {
+        this.rowHandler = rowHandler;
         this.hashFunctionFactory = hashFunctionFactory;
         this.dependencies = dependencies;
     }
@@ -83,6 +87,12 @@ class DestinationFactory<RowT> {
             case HASH_DISTRIBUTED: {
                 ImmutableIntList keys = distribution.getKeys();
 
+                if ("identity".equals(function.name())) {
+                    assert !nullOrEmpty(group.nodeNames()) && !nullOrEmpty(keys) && keys.size() == 1;
+
+                    return new Identity<>(rowHandler, keys.get(0), group.nodeNames());
+                }
+
                 assert !nullOrEmpty(group.assignments()) && !nullOrEmpty(keys);
 
                 List<List<String>> assignments = Commons.transform(group.assignments(), v -> Commons.transform(v, NodeWithTerm::name));
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/LogicalRelImplementor.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/LogicalRelImplementor.java
index 9856df07f2..d894685874 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/LogicalRelImplementor.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/LogicalRelImplementor.java
@@ -158,7 +158,7 @@ public class LogicalRelImplementor<RowT> implements IgniteRelVisitor<Node<RowT>>
         this.resolvedDependencies = resolvedDependencies;
 
         expressionFactory = ctx.expressionFactory();
-        destinationFactory = new DestinationFactory<>(hashFuncFactory, resolvedDependencies);
+        destinationFactory = new DestinationFactory<>(ctx.rowHandler(), hashFuncFactory, resolvedDependencies);
     }
 
     /** {@inheritDoc} */
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/externalize/RelJson.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/externalize/RelJson.java
index f66116c2d2..e9353b4b10 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/externalize/RelJson.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/externalize/RelJson.java
@@ -676,6 +676,11 @@ class RelJson {
         List<Integer> keys = (List<Integer>) map.get("keys");
 
         switch (functionName) {
+            case "identity": {
+                assert keys.size() == 1;
+
+                return IgniteDistributions.identity(keys.get(0));
+            }
             case "hash":
                 return IgniteDistributions.hash(keys, DistributionFunction.hash());
             default: {
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/Destination.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/Destination.java
index 03f6c7323e..8a1d209d86 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/Destination.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/Destination.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.sql.engine.trait;
 
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -34,5 +35,5 @@ public interface Destination<RowT> {
     /**
      * Get all target nodes.
      */
-    List<String> targets();
+    Collection<String> targets();
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/DistributionFunction.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/DistributionFunction.java
index 598bc880e0..340c304b9c 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/DistributionFunction.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/DistributionFunction.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.sql.engine.trait;
 
 import java.util.Objects;
 import org.apache.calcite.rel.RelDistribution;
+import org.apache.calcite.rel.RelDistribution.Type;
 import org.apache.calcite.rel.RelNode;
 
 /**
@@ -104,6 +105,10 @@ public abstract class DistributionFunction {
         return HashDistribution.INSTANCE;
     }
 
+    public static DistributionFunction identity() {
+        return IdentityDistribution.INSTANCE;
+    }
+
     /**
      * Satisfy.
      * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
@@ -211,4 +216,23 @@ public abstract class DistributionFunction {
             return "affinity[tableId=" + tableId + ", zoneId=" + zoneId + ']';
         }
     }
+
+    /**
+     * Distribution function, which treats column's raw value as a destination.
+     */
+    public static final class IdentityDistribution extends DistributionFunction {
+        public static final DistributionFunction INSTANCE = new IdentityDistribution();
+
+        /** {@inheritDoc} */
+        @Override
+        public Type type() {
+            return Type.HASH_DISTRIBUTED;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        protected String name0() {
+            return "identity";
+        }
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/Identity.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/Identity.java
new file mode 100644
index 0000000000..3558580c8d
--- /dev/null
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/Identity.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.sql.engine.trait;
+
+import java.util.List;
+import java.util.Set;
+import org.apache.ignite.internal.sql.engine.exec.RowHandler;
+
+/**
+ * Destination, which uses a column raw value as a target.
+ */
+public class Identity<RowT> implements Destination<RowT> {
+    private final RowHandler<RowT> rowHandler;
+    private final Set<String> nodes;
+    private final int columnIndex;
+
+    /**
+     * Constructor.
+     *
+     * @param rowHandler Row handler.
+     * @param columnIndex Column index in a row, which used for affinity calculation.
+     * @param nodes Names of nodes, which are valid destinations.
+     */
+    public Identity(RowHandler<RowT> rowHandler, int columnIndex, List<String> nodes) {
+        this.rowHandler = rowHandler;
+        this.columnIndex = columnIndex;
+        this.nodes = Set.copyOf(nodes);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public List<String> targets(RowT row) {
+        Object node = rowHandler.get(columnIndex, row);
+
+        if (node instanceof String && nodes.contains(node)) {
+            return List.of((String) node);
+        }
+
+        throw new IllegalStateException("No target found.");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Set<String> targets() {
+        return nodes;
+    }
+}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/IgniteDistributions.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/IgniteDistributions.java
index 9f5a0c0057..599a291e56 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/IgniteDistributions.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/IgniteDistributions.java
@@ -109,6 +109,16 @@ public class IgniteDistributions {
         return canonize(new DistributionTrait(keys, function));
     }
 
+    /**
+     * Creates an identity distribution that calculates destinations based on a raw value of the row field.
+     *
+     * @param key Distribution key ordinal.
+     * @return Identity distribution.
+     */
+    public static IgniteDistribution identity(int key) {
+        return canonize(new DistributionTrait(List.of(key), DistributionFunction.identity()));
+    }
+
     /**
      * See {@link RelTraitDef#canonize(org.apache.calcite.plan.RelTrait)}.
      */
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/IdentityDistributionFunctionSelfTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/IdentityDistributionFunctionSelfTest.java
new file mode 100644
index 0000000000..2b46f736b7
--- /dev/null
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/IdentityDistributionFunctionSelfTest.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.sql.engine.exec;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+
+import java.util.Collection;
+import java.util.List;
+import org.apache.calcite.rel.RelDistribution.Type;
+import org.apache.ignite.internal.schema.NativeTypes;
+import org.apache.ignite.internal.sql.engine.exec.row.RowSchema;
+import org.apache.ignite.internal.sql.engine.metadata.ColocationGroup;
+import org.apache.ignite.internal.sql.engine.trait.Destination;
+import org.apache.ignite.internal.sql.engine.trait.DistributionFunction.IdentityDistribution;
+import org.apache.ignite.internal.sql.engine.trait.Identity;
+import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Identity distribution function self test.
+ */
+public class IdentityDistributionFunctionSelfTest {
+
+    private static final String NODE_1 = "node1";
+    private static final String NODE_2 = "node2";
+    private static final String NODE_3 = "node3";
+
+    private final RowHandler<Object[]> rowHandler = ArrayRowHandler.INSTANCE;
+
+    private final RowSchema rowSchema = RowSchema.builder()
+            .addField(NativeTypes.STRING)
+            .addField(NativeTypes.STRING)
+            .build();
+
+    private final ColocationGroup colocationGroup = ColocationGroup.forNodes(List.of(NODE_1, NODE_2, NODE_3));
+    private final DestinationFactory<Object[]> destinationFactory = new DestinationFactory<>(rowHandler, null, null);
+
+    @Test
+    public void identityDistributionTrait() {
+        IdentityDistribution function = new IdentityDistribution();
+
+        assertThat(function.type(), equalTo(Type.HASH_DISTRIBUTED));
+        assertThat(function.affinity(), is(false));
+        assertThat(function.name(), equalTo("identity"));
+    }
+
+    @Test
+    public void destinationTargets() {
+        Destination<Object[]> destination = destinationFactory.createDestination(IgniteDistributions.identity(0), colocationGroup);
+
+        assertThat(destination, instanceOf(Identity.class));
+
+        Collection<String> targets = destination.targets();
+
+        assertThat(targets.size(), equalTo(3));
+        assertThat(targets, containsInAnyOrder(NODE_1, NODE_2, NODE_3));
+    }
+
+    @Test
+    public void destinationRowTargets() {
+        Object[] row = rowHandler.factory(rowSchema).create(NODE_1, "Ne prikhodya v soznanie");
+
+        Destination<Object[]> destination0 = destinationFactory.createDestination(IgniteDistributions.identity(0), colocationGroup);
+        Destination<Object[]> destination1 = destinationFactory.createDestination(IgniteDistributions.identity(1), colocationGroup);
+
+        assertThat(destination0, instanceOf(Identity.class));
+
+        // Valid row has single target.
+        Collection<String> targets = destination0.targets(row);
+
+        assertThat(targets.size(), equalTo(1));
+        assertThat(targets, Matchers.contains(NODE_1));
+
+        // Validate column mapping
+        Object[] otherRow = rowHandler.factory(rowSchema).create("UNKNOWN", NODE_2);
+        targets = destination1.targets(otherRow);
+
+        assertThat(targets.size(), equalTo(1));
+        assertThat(targets, Matchers.contains(NODE_2));
+
+        // Check wrong mapping.
+        Assertions.assertThrows(IllegalStateException.class, () -> destination0.targets(otherRow));
+    }
+
+    @Test
+    public void destinationForInvalidRow() {
+        Destination<Object[]> destination = destinationFactory.createDestination(IgniteDistributions.identity(0), colocationGroup);
+
+        Object[] invalidRow1 = rowHandler.factory(rowSchema).create("UNKNOWN", NODE_2);
+        Assertions.assertThrows(IllegalStateException.class, () -> destination.targets(invalidRow1));
+
+        Object[] invalidRow2 = rowHandler.factory(rowSchema).create("", NODE_2);
+        Assertions.assertThrows(IllegalStateException.class, () -> destination.targets(invalidRow2));
+
+        Object[] invalidRow3 = rowHandler.factory(rowSchema).create(null, NODE_2);
+        Assertions.assertThrows(IllegalStateException.class, () -> destination.targets(invalidRow3));
+    }
+}
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractAggregatePlannerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractAggregatePlannerTest.java
index 6272403614..f268ac7e49 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractAggregatePlannerTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractAggregatePlannerTest.java
@@ -21,6 +21,7 @@ import static org.apache.ignite.internal.sql.engine.trait.IgniteDistributions.si
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.fail;
 
+import it.unimi.dsi.fastutil.ints.IntList;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.List;
@@ -37,6 +38,7 @@ import org.apache.ignite.internal.sql.engine.rel.agg.IgniteReduceAggregateBase;
 import org.apache.ignite.internal.sql.engine.schema.IgniteIndex.Collation;
 import org.apache.ignite.internal.sql.engine.schema.IgniteSchema;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
+import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
 import org.hamcrest.Matchers;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
@@ -85,6 +87,12 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution: hash(0)
          */
         CASE_1A("SELECT SUM(val0) FROM test", schema(hash())),
+        /**
+         * Query: SELECT SUM(val0) FROM test.
+         *
+         * <p>Distribution: identity(0)
+         */
+        CASE_1B("SELECT SUM(val0) FROM test", schema(identity())),
         /**
          * Query: SELECT SUM(DISTINCT val0) FROM test.
          *
@@ -97,6 +105,24 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution: hash(0)
          */
         CASE_2_1A("SELECT SUM(DISTINCT val0) FROM test", schema(hash())),
+        /**
+         * Query: SELECT SUM(DISTINCT val0) FROM test.
+         *
+         * <p>Distribution: identity(0)
+         */
+        CASE_2_1B("SELECT SUM(DISTINCT val0) FROM test", schema(identity())),
+        /**
+         * Query: SELECT SUM(DISTINCT val0) FROM test.
+         *
+         * <p>Distribution: hash(1)
+         */
+        CASE_2_1C("SELECT SUM(DISTINCT val0) FROM test", schema(hash(1))),
+        /**
+         * Query: SELECT SUM(DISTINCT val0) FROM test.
+         *
+         * <p>Distribution: identity(1)
+         */
+        CASE_2_1D("SELECT SUM(DISTINCT val0) FROM test", schema(identity(1))),
         /**
          * Query: SELECT COUNT(DISTINCT val0) FROM test.
          *
@@ -110,17 +136,23 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          */
         CASE_2_2A("SELECT COUNT(DISTINCT val0) FROM test", schema(hash())),
         /**
-         * Query: SELECT SUM(DISTINCT val0) FROM test.
+         * Query: SELECT COUNT(DISTINCT val0) FROM test.
          *
-         * <p>Distribution: single
+         * <p>Distribution: identity(0)
          */
-        CASE_2_3("SELECT SUM(DISTINCT val0) FROM test", schema(single())),
+        CASE_2_2B("SELECT COUNT(DISTINCT val0) FROM test", schema(identity())),
         /**
-         * Query: SELECT SUM(DISTINCT val0) FROM test.
+         * Query: SELECT COUNT(DISTINCT val0) FROM test.
          *
-         * <p>Distribution: hash(0)
+         * <p>Distribution: hash(1)
          */
-        CASE_2_3A("SELECT SUM(DISTINCT val0) FROM test", schema(hash())),
+        CASE_2_2C("SELECT COUNT(DISTINCT val0) FROM test", schema(hash(1))),
+        /**
+         * Query: SELECT COUNT(DISTINCT val0) FROM test.
+         *
+         * <p>Distribution: identity(1)
+         */
+        CASE_2_2D("SELECT COUNT(DISTINCT val0) FROM test", schema(identity(1))),
         /**
          * Query: SELECT MIN(DISTINCT val0) FROM test.
          *
@@ -133,6 +165,24 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution: hash(0)
          */
         CASE_3_1A("SELECT MIN(DISTINCT val0) FROM test", schema(hash())),
+        /**
+         * Query: SELECT MIN(DISTINCT val0) FROM test.
+         *
+         * <p>Distribution: identity(0)
+         */
+        CASE_3_1B("SELECT MIN(DISTINCT val0) FROM test", schema(identity())),
+        /**
+         * Query: SELECT MIN(DISTINCT val0) FROM test.
+         *
+         * <p>Distribution: hash(1)
+         */
+        CASE_3_1C("SELECT MIN(DISTINCT val0) FROM test", schema(hash(1))),
+        /**
+         * Query: SELECT MIN(DISTINCT val0) FROM test.
+         *
+         * <p>Distribution: identity(1)
+         */
+        CASE_3_1D("SELECT MIN(DISTINCT val0) FROM test", schema(identity(1))),
         /**
          * Query: SELECT MAX(DISTINCT val0) FROM test.
          *
@@ -145,6 +195,24 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution: hash(0)
          */
         CASE_3_2A("SELECT MAX(DISTINCT val0) FROM test", schema(hash())),
+        /**
+         * Query: SELECT MAX(DISTINCT val0) FROM test.
+         *
+         * <p>Distribution: identity(0)
+         */
+        CASE_3_2B("SELECT MAX(DISTINCT val0) FROM test", schema(identity())),
+        /**
+         * Query: SELECT MAX(DISTINCT val0) FROM test.
+         *
+         * <p>Distribution: hash(1)
+         */
+        CASE_3_2C("SELECT MAX(DISTINCT val0) FROM test", schema(hash(1))),
+        /**
+         * Query: SELECT MAX(DISTINCT val0) FROM test.
+         *
+         * <p>Distribution: identity(1)
+         */
+        CASE_3_2D("SELECT MAX(DISTINCT val0) FROM test", schema(identity(1))),
         /**
          * Query: SELECT SUM(val0) FROM test GROUP BY grp0.
          *
@@ -154,9 +222,27 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
         /**
          * Query: SELECT SUM(val0) FROM test GROUP BY grp0.
          *
-         * <p>Distribution hash
+         * <p>Distribution hash(0)
          */
         CASE_5A("SELECT SUM(val0) FROM test GROUP BY grp0", schema(hash())),
+        /**
+         * Query: SELECT SUM(val0) FROM test GROUP BY grp0.
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_5B("SELECT SUM(val0) FROM test GROUP BY grp0", schema(identity())),
+        /**
+         * Query: SELECT SUM(val0) FROM test GROUP BY grp0.
+         *
+         * <p>Distribution identity(3)
+         */
+        CASE_5C("SELECT SUM(val0) FROM test GROUP BY grp0", schema(hash(3))),
+        /**
+         * Query: SELECT SUM(val0) FROM test GROUP BY grp0.
+         *
+         * <p>Distribution identity(3)
+         */
+        CASE_5D("SELECT SUM(val0) FROM test GROUP BY grp0", schema(identity(3))),
         /**
          * Query: SELECT SUM(val0) FROM test GROUP BY grp1, grp0.
          *
@@ -169,6 +255,18 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution hash(0)
          */
         CASE_6A("SELECT SUM(val0) FROM test GROUP BY grp1, grp0", schema(hash())),
+        /**
+         * Query: SELECT SUM(val0) FROM test GROUP BY grp1, grp0.
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_6B("SELECT SUM(val0) FROM test GROUP BY grp1, grp0", schema(identity())),
+        /**
+         * Query: SELECT SUM(val0) FROM test GROUP BY grp1, grp0.
+         *
+         * <p>Distribution hash(3, 4)
+         */
+        CASE_6C("SELECT SUM(val0) FROM test GROUP BY grp1, grp0", schema(hash(3, 4))),
         /**
          * Query: SELECT COUNT(DISTINCT val0) FROM test GROUP BY grp0.
          *
@@ -181,6 +279,24 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution hash(0)
          */
         CASE_7_1A("SELECT COUNT(DISTINCT val0) FROM test GROUP BY grp0", schema(hash())),
+        /**
+         * Query: SELECT COUNT(DISTINCT val0) FROM test GROUP BY grp0.
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_7_1B("SELECT COUNT(DISTINCT val0) FROM test GROUP BY grp0", schema(identity())),
+        /**
+         * Query: SELECT COUNT(DISTINCT val0) FROM test GROUP BY grp0.
+         *
+         * <p>Distribution hash(3)
+         */
+        CASE_7_1C("SELECT COUNT(DISTINCT val0) FROM test GROUP BY grp0", schema(hash(3))),
+        /**
+         * Query: SELECT COUNT(DISTINCT val0) FROM test GROUP BY grp0.
+         *
+         * <p>Distribution identity(3)
+         */
+        CASE_7_1D("SELECT COUNT(DISTINCT val0) FROM test GROUP BY grp0", schema(identity(3))),
         /**
          * Query: SELECT grp0, COUNT(DISTINCT val0) as v1 FROM test GROUP BY grp0.
          *
@@ -193,6 +309,24 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution hash(0)
          */
         CASE_7_2A("SELECT grp0, COUNT(DISTINCT val0) as v1 FROM test GROUP BY grp0", schema(hash())),
+        /**
+         * Query: SELECT grp0, COUNT(DISTINCT val0) as v1 FROM test GROUP BY grp0.
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_7_2B("SELECT grp0, COUNT(DISTINCT val0) as v1 FROM test GROUP BY grp0", schema(identity())),
+        /**
+         * Query: SELECT grp0, COUNT(DISTINCT val0) as v1 FROM test GROUP BY grp0.
+         *
+         * <p>Distribution hash(3)
+         */
+        CASE_7_2C("SELECT grp0, COUNT(DISTINCT val0) as v1 FROM test GROUP BY grp0", schema(hash(3))),
+        /**
+         * Query: SELECT grp0, COUNT(DISTINCT val0) as v1 FROM test GROUP BY grp0.
+         *
+         * <p>Distribution identity(3)
+         */
+        CASE_7_2D("SELECT grp0, COUNT(DISTINCT val0) as v1 FROM test GROUP BY grp0", schema(identity(3))),
         /**
          * Query: SELECT SUM(DISTINCT val0) FROM test GROUP BY grp0.
          *
@@ -208,15 +342,21 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
         /**
          * Query: SELECT SUM(DISTINCT val0) FROM test GROUP BY grp0.
          *
-         * <p>Distribution single
+         * <p>Distribution identity(0)
          */
-        CASE_7_4("SELECT SUM(DISTINCT val0) FROM test GROUP BY grp0", schema(single())),
+        CASE_7_3B("SELECT SUM(DISTINCT val0) FROM test GROUP BY grp0", schema(identity())),
         /**
          * Query: SELECT SUM(DISTINCT val0) FROM test GROUP BY grp0.
          *
-         * <p>Distribution hash(0)
+         * <p>Distribution hash(3)
          */
-        CASE_7_4A("SELECT SUM(DISTINCT val0) FROM test GROUP BY grp0", schema(hash())),
+        CASE_7_3C("SELECT SUM(DISTINCT val0) FROM test GROUP BY grp0", schema(hash(3))),
+        /**
+         * Query: SELECT SUM(DISTINCT val0) FROM test GROUP BY grp0.
+         *
+         * <p>Distribution identity(3)
+         */
+        CASE_7_3D("SELECT SUM(DISTINCT val0) FROM test GROUP BY grp0", schema(identity(3))),
         /**
          * Query: SELECT MIN(DISTINCT val0) FROM test GROUP BY val1.
          *
@@ -229,6 +369,24 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution hash(0)
          */
         CASE_8_1A("SELECT MIN(DISTINCT val0) FROM test GROUP BY val1", schema(hash())),
+        /**
+         * Query: SELECT MIN(DISTINCT val0) FROM test GROUP BY val1.
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_8_1B("SELECT MIN(DISTINCT val0) FROM test GROUP BY val1", schema(identity())),
+        /**
+         * Query: SELECT MIN(DISTINCT val0) FROM test GROUP BY val1.
+         *
+         * <p>Distribution hash(2)
+         */
+        CASE_8_1C("SELECT MIN(DISTINCT val0) FROM test GROUP BY val1", schema(hash(2))),
+        /**
+         * Query: SELECT MIN(DISTINCT val0) FROM test GROUP BY val1.
+         *
+         * <p>Distribution identity(2)
+         */
+        CASE_8_1D("SELECT MIN(DISTINCT val0) FROM test GROUP BY val1", schema(identity(2))),
         /**
          * Query: SELECT MAX(DISTINCT val0) FROM test GROUP BY val1.
          *
@@ -241,6 +399,24 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution hash(0)
          */
         CASE_8_2A("SELECT MAX(DISTINCT val0) FROM test GROUP BY val1", schema(hash())),
+        /**
+         * Query: SELECT MAX(DISTINCT val0) FROM test GROUP BY val1.
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_8_2B("SELECT MAX(DISTINCT val0) FROM test GROUP BY val1", schema(identity())),
+        /**
+         * Query: SELECT MAX(DISTINCT val0) FROM test GROUP BY val1.
+         *
+         * <p>Distribution hash(2)
+         */
+        CASE_8_2C("SELECT MAX(DISTINCT val0) FROM test GROUP BY val1", schema(hash(2))),
+        /**
+         * Query: SELECT MAX(DISTINCT val0) FROM test GROUP BY val1.
+         *
+         * <p>Distribution identity(2)
+         */
+        CASE_8_2D("SELECT MAX(DISTINCT val0) FROM test GROUP BY val1", schema(identity(2))),
         /**
          * Query: SELECT SUM(val0) FROM test GROUP BY grp0.
          *
@@ -257,6 +433,30 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution hash(0)
          */
         CASE_9A("SELECT SUM(val0) FROM test GROUP BY grp0", schema(hash(), addSortIndex("grp0", "grp1"))),
+        /**
+         * Query: SELECT SUM(val0) FROM test GROUP BY grp0.
+         *
+         * <p>Index on (grp0, grp1)
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_9B("SELECT SUM(val0) FROM test GROUP BY grp0", schema(identity(), addSortIndex("grp0", "grp1"))),
+        /**
+         * Query: SELECT SUM(val0) FROM test GROUP BY grp0.
+         *
+         * <p>Index on (grp0, grp1)
+         *
+         * <p>Distribution hash(3)
+         */
+        CASE_9C("SELECT SUM(val0) FROM test GROUP BY grp0", schema(hash(3), addSortIndex("grp0", "grp1"))),
+        /**
+         * Query: SELECT SUM(val0) FROM test GROUP BY grp0.
+         *
+         * <p>Index on (grp0, grp1)
+         *
+         * <p>Distribution identity(3)
+         */
+        CASE_9D("SELECT SUM(val0) FROM test GROUP BY grp0", schema(identity(3), addSortIndex("grp0", "grp1"))),
         /**
          * Query: SELECT SUM(val0) FROM test GROUP BY grp0, grp1.
          *
@@ -269,6 +469,18 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution hash(0)
          */
         CASE_10A("SELECT SUM(val0) FROM test GROUP BY grp0, grp1", schema(hash(), addSortIndex("grp0", "grp1"))),
+        /**
+         * Query: SELECT SUM(val0) FROM test GROUP BY grp0, grp1.
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_10B("SELECT SUM(val0) FROM test GROUP BY grp0, grp1", schema(identity(), addSortIndex("grp0", "grp1"))),
+        /**
+         * Query: SELECT SUM(val0) FROM test GROUP BY grp0, grp1.
+         *
+         * <p>Distribution hash(3, 4)
+         */
+        CASE_10C("SELECT SUM(val0) FROM test GROUP BY grp0, grp1", schema(hash(3, 4), addSortIndex("grp0", "grp1"))),
         /**
          * Query: SELECT SUM(val0) FROM test GROUP BY grp1, grp0.
          *
@@ -281,6 +493,18 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution hash(0)
          */
         CASE_11A("SELECT SUM(val0) FROM test GROUP BY grp1, grp0", schema(hash(), addSortIndex("grp0", "grp1"))),
+        /**
+         * Query: SELECT SUM(val0) FROM test GROUP BY grp1, grp0.
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_11B("SELECT SUM(val0) FROM test GROUP BY grp1, grp0", schema(identity(), addSortIndex("grp0", "grp1"))),
+        /**
+         * Query: SELECT SUM(val0) FROM test GROUP BY grp1, grp0.
+         *
+         * <p>Distribution hash(4, 3)
+         */
+        CASE_11C("SELECT SUM(val0) FROM test GROUP BY grp1, grp0", schema(hash(4, 3), addSortIndex("grp0", "grp1"))),
         /**
          * Query: SELECT DISTINCT val0, val1 FROM test.
          *
@@ -297,6 +521,31 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution hash(0)
          */
         CASE_12A("SELECT DISTINCT val0, val1 FROM test", schema(hash(), addSortIndex("val0"))),
+        /**
+         * Query: SELECT DISTINCT val0, val1 FROM test.
+         *
+         * <p>Index on val0
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_12B("SELECT DISTINCT val0, val1 FROM test", schema(identity(), addSortIndex("val0"))),
+        /**
+         * Query: SELECT DISTINCT val0, val1 FROM test.
+         *
+         * <p>Index on val0
+         *
+         * <p>Distribution hash(1)
+         */
+        CASE_12C("SELECT DISTINCT val0, val1 FROM test", schema(hash(1), addSortIndex("val0"))),
+
+        /**
+         * Query: SELECT DISTINCT val0, val1 FROM test.
+         *
+         * <p>Index on val0
+         *
+         * <p>Distribution identity(1)
+         */
+        CASE_12D("SELECT DISTINCT val0, val1 FROM test", schema(identity(1), addSortIndex("val0"))),
         /**
          * Query: SELECT DISTINCT grp0, grp1 FROM test.
          *
@@ -313,6 +562,30 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution hash(0)
          */
         CASE_13A("SELECT DISTINCT grp0, grp1 FROM test", schema(hash(), addSortIndex("grp0", "grp1"))),
+        /**
+         * Query: SELECT DISTINCT grp0, grp1 FROM test.
+         *
+         * <p>Index on (grp0, grp1)
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_13B("SELECT DISTINCT grp0, grp1 FROM test", schema(identity(), addSortIndex("grp0", "grp1"))),
+        /**
+         * Query: SELECT DISTINCT grp0, grp1 FROM test.
+         *
+         * <p>Index on (grp0, grp1)
+         *
+         * <p>Distribution hash(3)
+         */
+        CASE_13C("SELECT DISTINCT grp0, grp1 FROM test", schema(hash(3), addSortIndex("grp0", "grp1"))),
+        /**
+         * Query: SELECT DISTINCT grp0, grp1 FROM test.
+         *
+         * <p>Index on (grp0, grp1)
+         *
+         * <p>Distribution identity(3)
+         */
+        CASE_13D("SELECT DISTINCT grp0, grp1 FROM test", schema(identity(3), addSortIndex("grp0", "grp1"))),
         /**
          * Query: SELECT val0 FROM test WHERE VAL1 = (SELECT SUM(val1) FROM test).
          *
@@ -325,6 +598,12 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution hash(0)
          */
         CASE_14A("SELECT val0 FROM test WHERE VAL1 = (SELECT SUM(val1) FROM test)", schema(hash())),
+        /**
+         * Query: SELECT val0 FROM test WHERE VAL1 = (SELECT SUM(val1) FROM test).
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_14B("SELECT val0 FROM test WHERE VAL1 = (SELECT SUM(val1) FROM test)", schema(identity())),
         /**
          * Query: SELECT val0 FROM test WHERE VAL1 = ANY(SELECT DISTINCT val1 FROM test).
          *
@@ -337,6 +616,12 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution hash(0)
          */
         CASE_15A("SELECT val0 FROM test WHERE VAL1 = ANY(SELECT DISTINCT val1 FROM test)", schema(hash())),
+        /**
+         * Query: SELECT val0 FROM test WHERE VAL1 = ANY(SELECT DISTINCT val1 FROM test).
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_15B("SELECT val0 FROM test WHERE VAL1 = ANY(SELECT DISTINCT val1 FROM test)", schema(identity())),
         /**
          * Query: SELECT ID FROM test WHERE VAL0 IN (SELECT VAL0 FROM test).
          *
@@ -353,6 +638,14 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution hash(0)
          */
         CASE_16A("SELECT ID FROM test WHERE VAL0 IN (SELECT VAL0 FROM test)", schema(hash(), indexByVal0Desc())),
+        /**
+         * Query: SELECT ID FROM test WHERE VAL0 IN (SELECT VAL0 FROM test).
+         *
+         * <p>Index on val0 DESC
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_16B("SELECT ID FROM test WHERE VAL0 IN (SELECT VAL0 FROM test)", schema(identity(), indexByVal0Desc())),
         /**
          * Query: SELECT (SELECT test.val0 FROM test t ORDER BY 1 LIMIT 1) FROM test.
          *
@@ -365,6 +658,12 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution hash(0)
          */
         CASE_17A("SELECT (SELECT test.val0 FROM test t ORDER BY 1 LIMIT 1) FROM test", schema(hash())),
+        /**
+         * Query: SELECT (SELECT test.val0 FROM test t ORDER BY 1 LIMIT 1) FROM test.
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_17B("SELECT (SELECT test.val0 FROM test t ORDER BY 1 LIMIT 1) FROM test", schema(identity())),
         /**
          * Query: SELECT val0, val1, COUNT(*) cnt FROM test GROUP BY val0, val1 ORDER BY val0, val1.
          *
@@ -377,6 +676,12 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution hash(0)
          */
         CASE_18_1A("SELECT val0, val1, COUNT(*) cnt FROM test GROUP BY val0, val1 ORDER BY val0, val1", schema(hash())),
+        /**
+         * Query: SELECT val0, val1, COUNT(*) cnt FROM test GROUP BY val0, val1 ORDER BY val0, val1.
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_18_1B("SELECT val0, val1, COUNT(*) cnt FROM test GROUP BY val0, val1 ORDER BY val0, val1", schema(identity())),
         /**
          * Query: SELECT val0, val1, COUNT(*) cnt FROM test GROUP BY val0, val1 ORDER BY val1, val0.
          *
@@ -389,6 +694,12 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution hash(0)
          */
         CASE_18_2A("SELECT val0, val1, COUNT(*) cnt FROM test GROUP BY val0, val1 ORDER BY val1, val0", schema(hash())),
+        /**
+         * Query: SELECT val0, val1, COUNT(*) cnt FROM test GROUP BY val0, val1 ORDER BY val1, val0.
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_18_2B("SELECT val0, val1, COUNT(*) cnt FROM test GROUP BY val0, val1 ORDER BY val1, val0", schema(identity())),
         /**
          * Query: SELECT val0, val1, COUNT(*) cnt FROM test GROUP BY val1, val0 ORDER BY val0, val1.
          *
@@ -401,7 +712,12 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution hash(0)
          */
         CASE_18_3A("SELECT val1, val0, COUNT(*) cnt FROM test GROUP BY val1, val0 ORDER BY val0, val1", schema(hash())),
-
+        /**
+         * Query: SELECT val0, val1, COUNT(*) cnt FROM test GROUP BY val1, val0 ORDER BY val0, val1.
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_18_3B("SELECT val1, val0, COUNT(*) cnt FROM test GROUP BY val1, val0 ORDER BY val0, val1", schema(identity())),
         /**
          * SELECT val0, val1, COUNT(*) cnt FROM test GROUP BY val0, val1 ORDER BY val0.
          *
@@ -414,6 +730,12 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution hash(0)
          */
         CASE_19_1A("SELECT val0, val1, COUNT(*) cnt FROM test GROUP BY val0, val1 ORDER BY val0", schema(hash())),
+        /**
+         * SELECT val0, val1, COUNT(*) cnt FROM test GROUP BY val0, val1 ORDER BY val0.
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_19_1B("SELECT val0, val1, COUNT(*) cnt FROM test GROUP BY val0, val1 ORDER BY val0", schema(identity())),
         /**
          * Query: SELECT val0, val1, COUNT(*) cnt FROM test GROUP BY val0, val1 ORDER BY val1.
          *
@@ -426,6 +748,12 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution hash(0)
          */
         CASE_19_2A("SELECT val0, val1, COUNT(*) cnt FROM test GROUP BY val0, val1 ORDER BY val1", schema(hash())),
+        /**
+         * Query: SELECT val0, val1, COUNT(*) cnt FROM test GROUP BY val0, val1 ORDER BY val1.
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_19_2B("SELECT val0, val1, COUNT(*) cnt FROM test GROUP BY val0, val1 ORDER BY val1", schema(identity())),
         /**
          * Query: SELECT val0, val1, COUNT(*) cnt FROM test GROUP BY val0, val1 ORDER BY val0, val1, cnt.
          *
@@ -438,6 +766,12 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          * <p>Distribution hash(0)
          */
         CASE_20A("SELECT val0, val1, COUNT(*) cnt FROM test GROUP BY val0, val1 ORDER BY val0, val1, cnt", schema(hash())),
+        /**
+         * Query: SELECT val0, val1, COUNT(*) cnt FROM test GROUP BY val0, val1 ORDER BY val0, val1, cnt.
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_20B("SELECT val0, val1, COUNT(*) cnt FROM test GROUP BY val0, val1 ORDER BY val0, val1, cnt", schema(identity())),
         /**
          * Query: SELECT /*+ EXPAND_DISTINCT_AGG *&#47; SUM(DISTINCT val0), SUM(DISTINCT val1) FROM test GROUP BY grp0.
          *
@@ -456,20 +790,63 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
          */
         CASE_21A("SELECT /*+ EXPAND_DISTINCT_AGG */ SUM(DISTINCT val0), SUM(DISTINCT val1) FROM test GROUP BY grp0",
                 schema(hash(), addSortIndex("grp0", "val0"), addSortIndex("grp0", "val1"))),
-
+        /**
+         * Query: SELECT /*+ EXPAND_DISTINCT_AGG *&#47; SUM(DISTINCT val0), SUM(DISTINCT val1) FROM test GROUP BY grp0.
+         *
+         * <p>Index on val0
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_21B("SELECT /*+ EXPAND_DISTINCT_AGG */ SUM(DISTINCT val0), SUM(DISTINCT val1) FROM test GROUP BY grp0",
+                        schema(identity(), addSortIndex("grp0", "val0"), addSortIndex("grp0", "val1"))),
         /**
          * Query: SELECT val0, COUNT(val1) FROM test GROUP BY val0.
          *
          * <p>Distribution hash(0)
          */
         CASE_22("SELECT val0, COUNT(val1) FROM test GROUP BY val0", schema(hash())),
-
+        /**
+         * Query: SELECT val0, COUNT(val1) FROM test GROUP BY val0.
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_22A("SELECT val0, COUNT(val1) FROM test GROUP BY val0", schema(identity())),
+        /**
+         * Query: SELECT val0, COUNT(val1) FROM test GROUP BY val0.
+         *
+         * <p>Distribution hash(1)
+         */
+        CASE_22B("SELECT val0, COUNT(val1) FROM test GROUP BY val0", schema(hash(1))),
+        /**
+         * Query: SELECT val0, COUNT(val1) FROM test GROUP BY val0.
+         *
+         * <p>Distribution identity(1)
+         */
+        CASE_22C("SELECT val0, COUNT(val1) FROM test GROUP BY val0", schema(identity(1))),
         /**
          * Query: SELECT val0, AVG(val1) FROM test GROUP BY val0.
          *
          * <p>Distribution hash(0)
          */
         CASE_23("SELECT val0, AVG(val1) FROM test GROUP BY val0", schema(hash())),
+        /**
+         * Query: SELECT val0, AVG(val1) FROM test GROUP BY val0.
+         *
+         * <p>Distribution identity(0)
+         */
+        CASE_23A("SELECT val0, AVG(val1) FROM test GROUP BY val0", schema(identity())),
+        /**
+         * Query: SELECT val0, AVG(val1) FROM test GROUP BY val0.
+         *
+         * <p>Distribution hash(1)
+         */
+        CASE_23B("SELECT val0, AVG(val1) FROM test GROUP BY val0", schema(hash(1))),
+        /**
+         * Query: SELECT val0, AVG(val1) FROM test GROUP BY val0.
+         *
+         * <p>Distribution identity(1)
+         */
+        CASE_23C("SELECT val0, AVG(val1) FROM test GROUP BY val0", schema(identity(1))),
         ;
 
         final String query;
@@ -551,7 +928,19 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
     }
 
     private static IgniteDistribution hash() {
-        return someAffinity();
+        return IgniteDistributions.affinity(0, nextTableId(), DEFAULT_ZONE_ID);
+    }
+
+    private static IgniteDistribution hash(int... keys) {
+        return IgniteDistributions.affinity(IntList.of(keys), nextTableId(), DEFAULT_ZONE_ID);
+    }
+
+    private static IgniteDistribution identity() {
+        return IgniteDistributions.identity(0);
+    }
+
+    private static IgniteDistribution identity(int key) {
+        return IgniteDistributions.identity(key);
     }
 
     private static UnaryOperator<TableBuilder> indexByVal0Desc() {
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AggregatePlannerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AggregatePlannerTest.java
index 23a79d620d..8842af8cfc 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AggregatePlannerTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AggregatePlannerTest.java
@@ -55,6 +55,7 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
     protected void simpleAggregate() throws Exception {
         checkSimpleAggSingle(TestCase.CASE_1);
         checkSimpleAggHash(TestCase.CASE_1A);
+        checkSimpleAggHash(TestCase.CASE_1B);
     }
 
     /**
@@ -66,11 +67,18 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
     public void distinctAggregate() throws Exception {
         checkDistinctAggSingle(TestCase.CASE_2_1);
         checkDistinctAggSingle(TestCase.CASE_2_2);
-        checkDistinctAggSingle(TestCase.CASE_2_3);
 
         checkDistinctAggHash(TestCase.CASE_2_1A);
         checkDistinctAggHash(TestCase.CASE_2_2A);
-        checkDistinctAggHash(TestCase.CASE_2_3A);
+
+        checkDistinctAggHash(TestCase.CASE_2_1B);
+        checkDistinctAggHash(TestCase.CASE_2_2B);
+
+        checkColocatedDistinctAggHash(TestCase.CASE_2_1C);
+        checkColocatedDistinctAggHash(TestCase.CASE_2_2C);
+
+        checkColocatedDistinctAggHash(TestCase.CASE_2_1D);
+        checkColocatedDistinctAggHash(TestCase.CASE_2_2D);
     }
 
     /**
@@ -85,6 +93,15 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
 
         checkSimpleAggHash(TestCase.CASE_3_1A);
         checkSimpleAggHash(TestCase.CASE_3_2A);
+
+        checkSimpleAggHash(TestCase.CASE_3_1B);
+        checkSimpleAggHash(TestCase.CASE_3_2B);
+
+        checkSimpleAggHash(TestCase.CASE_3_1C);
+        checkSimpleAggHash(TestCase.CASE_3_2C);
+
+        checkSimpleAggHash(TestCase.CASE_3_1D);
+        checkSimpleAggHash(TestCase.CASE_3_2D);
     }
 
     /**
@@ -96,8 +113,16 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
         checkSimpleAggWithGroupBySingle(TestCase.CASE_6);
 
         checkSimpleAggWithGroupByHash(TestCase.CASE_5A);
+        checkSimpleAggWithGroupByHash(TestCase.CASE_5B);
+
         //TODO replace with calls to test methods after https://issues.apache.org/jira/browse/IGNITE-20083 is resolved
         assumeRun("checkSimpleAggWithGroupByHash", TestCase.CASE_6A);
+        assumeRun("checkSimpleAggWithGroupByHash", TestCase.CASE_6B);
+
+        checkSimpleAggWithColocatedGroupByHash(TestCase.CASE_5C);
+        checkSimpleAggWithColocatedGroupByHash(TestCase.CASE_5D);
+
+        checkSimpleAggWithColocatedGroupByHash(TestCase.CASE_6C);
     }
 
     /**
@@ -110,12 +135,22 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
         checkDistinctAggWithGroupBySingle(TestCase.CASE_7_1);
         checkDistinctAggWithGroupBySingle(TestCase.CASE_7_2);
         checkDistinctAggWithGroupBySingle(TestCase.CASE_7_3);
-        checkDistinctAggWithGroupBySingle(TestCase.CASE_7_4);
 
         checkDistinctAggWithGroupByHash(TestCase.CASE_7_1A);
         checkDistinctAggWithGroupByHash(TestCase.CASE_7_2A);
         checkDistinctAggWithGroupByHash(TestCase.CASE_7_3A);
-        checkDistinctAggWithGroupByHash(TestCase.CASE_7_4A);
+
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_1B);
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_2B);
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_3B);
+
+        checkDistinctAggWithColocatedGroupByHash(TestCase.CASE_7_1C);
+        checkDistinctAggWithColocatedGroupByHash(TestCase.CASE_7_2C);
+        checkDistinctAggWithColocatedGroupByHash(TestCase.CASE_7_3C);
+
+        checkDistinctAggWithColocatedGroupByHash(TestCase.CASE_7_1D);
+        checkDistinctAggWithColocatedGroupByHash(TestCase.CASE_7_2D);
+        checkDistinctAggWithColocatedGroupByHash(TestCase.CASE_7_3D);
     }
 
     /**
@@ -130,6 +165,15 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
 
         checkSimpleAggWithGroupByHash(TestCase.CASE_8_1A);
         checkSimpleAggWithGroupByHash(TestCase.CASE_8_2A);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_8_1B);
+        checkSimpleAggWithGroupByHash(TestCase.CASE_8_2B);
+
+        checkSimpleAggWithColocatedGroupByHash(TestCase.CASE_8_1C);
+        checkSimpleAggWithColocatedGroupByHash(TestCase.CASE_8_2C);
+
+        checkSimpleAggWithColocatedGroupByHash(TestCase.CASE_8_1D);
+        checkSimpleAggWithColocatedGroupByHash(TestCase.CASE_8_2D);
     }
 
     /**
@@ -146,6 +190,16 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
         checkAggWithGroupByIndexColumnsSort(TestCase.CASE_9A);
         checkAggWithGroupByIndexColumnsSort(TestCase.CASE_10A);
         checkAggWithGroupByIndexColumnsSort(TestCase.CASE_11A);
+
+        checkAggWithGroupByIndexColumnsSort(TestCase.CASE_9B);
+        checkAggWithGroupByIndexColumnsSort(TestCase.CASE_10B);
+        checkAggWithGroupByIndexColumnsSort(TestCase.CASE_11B);
+
+        checkAggWithColocatedGroupByIndexColumnsHash(TestCase.CASE_9C);
+        checkAggWithColocatedGroupByIndexColumnsHash(TestCase.CASE_10C);
+        checkAggWithColocatedGroupByIndexColumnsHash(TestCase.CASE_11C);
+
+        checkAggWithColocatedGroupByIndexColumnsHash(TestCase.CASE_9D);
     }
 
     /**
@@ -155,15 +209,11 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
     public void distinctWithoutAggregate() throws Exception {
         checkGroupWithNoAggregateSingle(TestCase.CASE_12);
 
-        assertPlan(TestCase.CASE_12A,
-                nodeOrAnyChild(isInstanceOf(IgniteColocatedHashAggregate.class)
-                        .and(not(hasAggregate()))
-                        .and(hasGroups())
-                        .and(input(isInstanceOf(IgniteExchange.class)
-                                .and(input(isTableScan("TEST")))
-                        ))
-                )
-        );
+        checkGroupWithNoAggregateHash(TestCase.CASE_12A);
+        checkGroupWithNoAggregateHash(TestCase.CASE_12B);
+
+        checkColocatedGroupWithNoAggregateHash(TestCase.CASE_12C);
+        checkColocatedGroupWithNoAggregateHash(TestCase.CASE_12D);
     }
 
     /**
@@ -171,25 +221,13 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
      */
     @Test
     public void distinctWithoutAggregateUseIndex() throws Exception {
-        assertPlan(TestCase.CASE_13,
-                nodeOrAnyChild(isInstanceOf(IgniteColocatedSortAggregate.class)
-                        .and(not(hasAggregate()))
-                        .and(hasGroups())
-                        .and(input(isIndexScan("TEST", "idx_grp0_grp1")))
-                ));
+        checkGroupWithNoAggregateUseIndexSingle(TestCase.CASE_13);
 
-        assertPlan(TestCase.CASE_13A,
-                nodeOrAnyChild(isInstanceOf(IgniteReduceSortAggregate.class)
-                        .and(not(hasAggregate()))
-                        .and(hasGroups())
-                        .and(input(isInstanceOf(IgniteExchange.class)
-                                .and(input(isInstanceOf(IgniteMapSortAggregate.class)
-                                        .and(not(hasAggregate()))
-                                        .and(hasGroups())
-                                        .and(input(isIndexScan("TEST", "idx_grp0_grp1")))
-                                ))
-                        ))
-                ));
+        checkGroupWithNoAggregateUseIndexHash(TestCase.CASE_13A);
+        checkGroupWithNoAggregateUseIndexHash(TestCase.CASE_13B);
+
+        checkColocatedGroupWithNoAggregateUseIndexHash(TestCase.CASE_13C);
+        checkColocatedGroupWithNoAggregateUseIndexHash(TestCase.CASE_13D);
     }
 
     /**
@@ -199,6 +237,7 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
     public void subqueryWithAggregateInWhereClause() throws Exception {
         checkSimpleAggSingle(TestCase.CASE_14);
         checkSimpleAggHash(TestCase.CASE_14A);
+        checkSimpleAggHash(TestCase.CASE_14B);
     }
 
     /**
@@ -216,6 +255,14 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
                                 .and(input(isTableScan("TEST")))
                         ))
                 ));
+        assertPlan(TestCase.CASE_15B,
+                nodeOrAnyChild(isInstanceOf(IgniteColocatedHashAggregate.class)
+                        .and(not(hasAggregate()))
+                        .and(hasGroups())
+                        .and(input(isInstanceOf(IgniteExchange.class)
+                                .and(input(isTableScan("TEST")))
+                        ))
+                ));
     }
 
     /**
@@ -241,6 +288,15 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
                         ))
                 ),
                 additionalRulesToDisable);
+        assertPlan(TestCase.CASE_16B,
+                nodeOrAnyChild(isInstanceOf(IgniteReduceSortAggregate.class)
+                        .and(input(isInstanceOf(IgniteExchange.class)
+                                .and(input(isInstanceOf(IgniteMapSortAggregate.class)
+                                        .and(input(isIndexScan("TEST", "idx_val0")))
+                                ))
+                        ))
+                ),
+                additionalRulesToDisable);
     }
 
     /**
@@ -271,6 +327,18 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
                                 ))
                         ))
                 ));
+        assertPlan(TestCase.CASE_17B,
+                hasChildThat(isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)
+                        .and(input(1, isInstanceOf(IgniteColocatedHashAggregate.class)
+                                .and(input(isInstanceOf(IgniteLimit.class)
+                                        .and(input(isInstanceOf(IgniteExchange.class)
+                                                .and(input(isInstanceOf(IgniteSort.class)
+                                                        .and(input(isTableScan("TEST")))
+                                                ))
+                                        ))
+                                ))
+                        ))
+                ));
     }
 
     /**
@@ -285,6 +353,10 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
         checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_1A, TraitUtils.createCollation(List.of(0, 1)));
         checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_2A, TraitUtils.createCollation(List.of(1, 0)));
         checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_3A, TraitUtils.createCollation(List.of(1, 0)));
+
+        checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_1B, TraitUtils.createCollation(List.of(0, 1)));
+        checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_2B, TraitUtils.createCollation(List.of(1, 0)));
+        checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_3B, TraitUtils.createCollation(List.of(1, 0)));
     }
 
     /**
@@ -297,6 +369,9 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
 
         checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_19_1A, TraitUtils.createCollation(List.of(0, 1)));
         checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_19_2A, TraitUtils.createCollation(List.of(1, 0)));
+
+        checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_19_1B, TraitUtils.createCollation(List.of(0, 1)));
+        checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_19_2B, TraitUtils.createCollation(List.of(1, 0)));
     }
 
     /**
@@ -321,6 +396,15 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
                                 ))
                         ))
         );
+        assertPlan(TestCase.CASE_20B,
+                isInstanceOf(IgniteSort.class)
+                        .and(s -> s.collation().equals(TraitUtils.createCollation(List.of(0, 1, 2))))
+                        .and(input(isInstanceOf(IgniteColocatedHashAggregate.class)
+                                .and(input(isInstanceOf(IgniteExchange.class)
+                                        .and(input(isTableScan("TEST")))
+                                ))
+                        ))
+        );
     }
 
 
@@ -368,20 +452,22 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
                 .and(input(0, subtreePredicate))
                 .and(input(1, subtreePredicate))
         ));
+        assertPlan(TestCase.CASE_21B, nodeOrAnyChild(isInstanceOf(IgniteMergeJoin.class)
+                .and(input(0, subtreePredicate))
+                .and(input(1, subtreePredicate))
+        ));
     }
 
     /** Validate that we choose two-phase COUNT aggregate for COUNT by default. */
     @Test
     public void countAgg() throws Exception {
-        Predicate<AggregateCall> countMap = (a) -> {
-            return Objects.equals(a.getAggregation().getName(), "COUNT") && a.getArgList().equals(List.of(1));
-        };
+        Predicate<AggregateCall> countMap = (a) ->
+                Objects.equals(a.getAggregation().getName(), "COUNT") && a.getArgList().equals(List.of(1));
 
-        Predicate<AggregateCall> countReduce = (a) -> {
-            return Objects.equals(a.getAggregation().getName(), "$SUM0") && a.getArgList().equals(List.of(1));
-        };
+        Predicate<AggregateCall> countReduce = (a) ->
+                Objects.equals(a.getAggregation().getName(), "$SUM0") && a.getArgList().equals(List.of(1));
 
-        assertPlan(TestCase.CASE_22, hasChildThat(isInstanceOf(IgniteReduceHashAggregate.class)
+        Predicate<RelNode> nonColocatedGroupBy = hasChildThat(isInstanceOf(IgniteReduceHashAggregate.class)
                 .and(in -> hasAggregates(countReduce).test(in.getAggregateCalls()))
                 .and(input(isInstanceOf(IgniteExchange.class)
                         .and(input(isInstanceOf(IgniteMapHashAggregate.class)
@@ -389,20 +475,44 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
                                         .and(input(isTableScan("TEST")))
                                 )
                         ))
-                )));
+                ));
+
+        Predicate<IgniteExchange> colocatedGroupBy = isInstanceOf(IgniteExchange.class)
+                .and(hasDistribution(IgniteDistributions.single()))
+                .and(input(isInstanceOf(IgniteColocatedHashAggregate.class)
+                        .and(in -> hasAggregates(countMap).test(in.getAggCallList()))
+                        .and(input(isTableScan("TEST")))
+                ));
+
+        assertPlan(TestCase.CASE_22, nonColocatedGroupBy);
+        assertPlan(TestCase.CASE_22A, nonColocatedGroupBy);
+
+        assertPlan(TestCase.CASE_22B, colocatedGroupBy);
+        assertPlan(TestCase.CASE_22C, colocatedGroupBy);
     }
 
     /** Validate that we choose single phase AVG aggregate for AVG by default. */
     @Test
     public void avgAgg() throws Exception {
-        Predicate<AggregateCall> countMap = (a) -> {
-            return Objects.equals(a.getAggregation().getName(), "AVG") && a.getArgList().equals(List.of(1));
-        };
+        Predicate<AggregateCall> countMap = (a) -> Objects.equals(a.getAggregation().getName(), "AVG") && a.getArgList().equals(List.of(1));
 
-        assertPlan(TestCase.CASE_23, isInstanceOf(IgniteColocatedHashAggregate.class)
+        Predicate<IgniteColocatedHashAggregate> nonColocated = isInstanceOf(IgniteColocatedHashAggregate.class)
                 .and(in -> hasAggregates(countMap).test(in.getAggCallList()))
                 .and(input(isInstanceOf(IgniteExchange.class)
-                        .and(hasDistribution(IgniteDistributions.single())))));
+                        .and(hasDistribution(IgniteDistributions.single()))));
+
+        Predicate<IgniteExchange> colocatedGroupBy = isInstanceOf(IgniteExchange.class)
+                .and(hasDistribution(IgniteDistributions.single()))
+                .and(input(isInstanceOf(IgniteColocatedHashAggregate.class)
+                        .and(in -> hasAggregates(countMap).test(in.getAggCallList()))
+                        .and(input(isTableScan("TEST")))
+                ));
+
+        assertPlan(TestCase.CASE_23, nonColocated);
+        assertPlan(TestCase.CASE_23A, nonColocated);
+
+        assertPlan(TestCase.CASE_23B, colocatedGroupBy);
+        assertPlan(TestCase.CASE_23C, colocatedGroupBy);
     }
 
     private void checkSimpleAggSingle(TestCase testCase) throws Exception {
@@ -452,6 +562,18 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
         );
     }
 
+    private void checkSimpleAggWithColocatedGroupByHash(TestCase testCase) throws Exception {
+        assertPlan(testCase,
+                nodeOrAnyChild(isInstanceOf(IgniteExchange.class)
+                        .and(nodeOrAnyChild(isInstanceOf(IgniteColocatedHashAggregate.class)
+                                .and(hasAggregate())
+                                .and(hasGroups())
+                                .and(input(isTableScan("TEST")))
+                        ))
+                )
+        );
+    }
+
     private void checkDistinctAggSingle(TestCase testCase) throws Exception {
         assertPlan(testCase,
                 isInstanceOf(IgniteColocatedHashAggregate.class)
@@ -478,6 +600,22 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
         );
     }
 
+    private void checkColocatedDistinctAggHash(TestCase testCase) throws Exception {
+        assertPlan(testCase,
+                nodeOrAnyChild(isInstanceOf(IgniteReduceSortAggregate.class)
+                        .and(input(isInstanceOf(IgniteExchange.class)
+                                .and(input(isInstanceOf(IgniteMapSortAggregate.class)
+                                        .and(hasAggregate())
+                                        .and(not(hasDistinctAggregate()))
+                                        .and(input(isInstanceOf(IgniteColocatedHashAggregate.class)
+                                                .and(hasGroups())
+                                                .and(input(isTableScan("TEST")))
+                                        ))
+                                ))
+                        ))
+                ));
+    }
+
     private void checkDistinctAggWithGroupBySingle(TestCase testCase) throws Exception {
         assertPlan(testCase,
                 nodeOrAnyChild(isInstanceOf(IgniteColocatedHashAggregate.class)
@@ -500,16 +638,16 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
         );
     }
 
-    private void checkSimpleAggWithGroupByHashColocatedFromExchange(TestCase testCase) throws Exception {
+    private void checkDistinctAggWithColocatedGroupByHash(TestCase testCase) throws Exception {
         assertPlan(testCase,
-                nodeOrAnyChild(isInstanceOf(IgniteColocatedHashAggregate.class)
-                        .and(hasAggregate())
-                        .and(hasGroups())
-                        .and(input(isInstanceOf(IgniteExchange.class)
+                nodeOrAnyChild(isInstanceOf(IgniteExchange.class)
+                        .and(nodeOrAnyChild(isInstanceOf(IgniteColocatedHashAggregate.class)
+                                .and(hasAggregate())
+                                .and(hasGroups())
+                                .and(hasDistinctAggregate())
                                 .and(input(isTableScan("TEST")))
                         ))
-                )
-        );
+                ));
     }
 
     private void checkAggWithGroupByIndexColumnsSingle(TestCase testCase) throws Exception {
@@ -534,6 +672,16 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
         );
     }
 
+    private void checkAggWithColocatedGroupByIndexColumnsHash(TestCase testCase) throws Exception {
+        assertPlan(testCase,
+                nodeOrAnyChild(isInstanceOf(IgniteExchange.class)
+                        .and(nodeOrAnyChild(isInstanceOf(IgniteColocatedSortAggregate.class)
+                                .and(hasAggregate())
+                                .and(input(isIndexScan("TEST", "idx_grp0_grp1")))
+                        ))
+                ));
+    }
+
     private void checkGroupWithNoAggregateSingle(TestCase testCase) throws Exception {
         assertPlan(testCase,
                 nodeOrAnyChild(isInstanceOf(IgniteColocatedHashAggregate.class)
@@ -543,6 +691,63 @@ public class AggregatePlannerTest extends AbstractAggregatePlannerTest {
                 ));
     }
 
+    private void checkGroupWithNoAggregateHash(TestCase testCase) throws Exception {
+        assertPlan(testCase,
+                nodeOrAnyChild(isInstanceOf(IgniteColocatedHashAggregate.class)
+                        .and(not(hasAggregate()))
+                        .and(hasGroups())
+                        .and(input(isInstanceOf(IgniteExchange.class)
+                                .and(input(isTableScan("TEST")))
+                        ))
+                ));
+    }
+
+    private void checkColocatedGroupWithNoAggregateHash(TestCase testCase) throws Exception {
+        assertPlan(testCase,
+                nodeOrAnyChild(isInstanceOf(IgniteExchange.class)
+                        .and(input(isInstanceOf(IgniteColocatedHashAggregate.class)
+                                .and(not(hasAggregate()))
+                                .and(hasGroups())
+                                .and(input(isTableScan("TEST")))
+                        ))
+                ));
+    }
+
+    private void checkGroupWithNoAggregateUseIndexSingle(TestCase testCase) throws Exception {
+        assertPlan(testCase,
+                nodeOrAnyChild(isInstanceOf(IgniteColocatedSortAggregate.class)
+                        .and(not(hasAggregate()))
+                        .and(hasGroups())
+                        .and(input(isIndexScan("TEST", "idx_grp0_grp1")))
+                ));
+    }
+
+    private void checkGroupWithNoAggregateUseIndexHash(TestCase testCase) throws Exception {
+        assertPlan(testCase,
+                nodeOrAnyChild(isInstanceOf(IgniteReduceSortAggregate.class)
+                        .and(not(hasAggregate()))
+                        .and(hasGroups())
+                        .and(input(isInstanceOf(IgniteExchange.class)
+                                .and(input(isInstanceOf(IgniteMapSortAggregate.class)
+                                        .and(not(hasAggregate()))
+                                        .and(hasGroups())
+                                        .and(input(isIndexScan("TEST", "idx_grp0_grp1")))
+                                ))
+                        ))
+                ));
+    }
+
+    private void checkColocatedGroupWithNoAggregateUseIndexHash(TestCase testCase) throws Exception {
+        assertPlan(testCase,
+                nodeOrAnyChild(isInstanceOf(IgniteExchange.class)
+                        .and(input(isInstanceOf(IgniteColocatedSortAggregate.class)
+                                .and(not(hasAggregate()))
+                                .and(hasGroups())
+                                .and(input(isIndexScan("TEST", "idx_grp0_grp1")))
+                        ))
+                ));
+    }
+
     private void checkGroupsWithOrderByGroupColumnsSingle(TestCase testCase, RelCollation collation) throws Exception {
         assertPlan(testCase,
                 isInstanceOf(IgniteColocatedSortAggregate.class)
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ColocatedHashAggregatePlannerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ColocatedHashAggregatePlannerTest.java
index aff3899acd..54fe13c0bb 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ColocatedHashAggregatePlannerTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ColocatedHashAggregatePlannerTest.java
@@ -57,6 +57,7 @@ public class ColocatedHashAggregatePlannerTest extends AbstractAggregatePlannerT
     protected void simpleAggregate() throws Exception {
         checkSimpleAggSingle(TestCase.CASE_1);
         checkSimpleAggHash(TestCase.CASE_1A);
+        checkSimpleAggHash(TestCase.CASE_1B);
     }
 
     /**
@@ -68,11 +69,18 @@ public class ColocatedHashAggregatePlannerTest extends AbstractAggregatePlannerT
     public void distinctAggregate() throws Exception {
         checkDistinctAggSingle(TestCase.CASE_2_1);
         checkDistinctAggSingle(TestCase.CASE_2_2);
-        checkDistinctAggSingle(TestCase.CASE_2_3);
 
         checkDistinctAggHash(TestCase.CASE_2_1A);
         checkDistinctAggHash(TestCase.CASE_2_2A);
-        checkDistinctAggHash(TestCase.CASE_2_3A);
+
+        checkDistinctAggHash(TestCase.CASE_2_1B);
+        checkDistinctAggHash(TestCase.CASE_2_2B);
+
+        checkColocatedDistinctAggHash(TestCase.CASE_2_1C);
+        checkColocatedDistinctAggHash(TestCase.CASE_2_2C);
+
+        checkColocatedDistinctAggHash(TestCase.CASE_2_1D);
+        checkColocatedDistinctAggHash(TestCase.CASE_2_2D);
     }
 
     /**
@@ -87,6 +95,15 @@ public class ColocatedHashAggregatePlannerTest extends AbstractAggregatePlannerT
 
         checkSimpleAggHash(TestCase.CASE_3_1A);
         checkSimpleAggHash(TestCase.CASE_3_2A);
+
+        checkSimpleAggHash(TestCase.CASE_3_1B);
+        checkSimpleAggHash(TestCase.CASE_3_2B);
+
+        checkSimpleAggHash(TestCase.CASE_3_1C);
+        checkSimpleAggHash(TestCase.CASE_3_2C);
+
+        checkSimpleAggHash(TestCase.CASE_3_1D);
+        checkSimpleAggHash(TestCase.CASE_3_2D);
     }
 
     /**
@@ -99,6 +116,14 @@ public class ColocatedHashAggregatePlannerTest extends AbstractAggregatePlannerT
 
         checkSimpleAggWithGroupByHash(TestCase.CASE_5A);
         checkSimpleAggWithGroupByHash(TestCase.CASE_6A);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_5B);
+        checkSimpleAggWithGroupByHash(TestCase.CASE_6B);
+
+        checkSimpleAggWithColocatedGroupBy(TestCase.CASE_5C);
+        checkSimpleAggWithColocatedGroupBy(TestCase.CASE_6C);
+
+        checkSimpleAggWithColocatedGroupBy(TestCase.CASE_5D);
     }
 
     /**
@@ -111,12 +136,22 @@ public class ColocatedHashAggregatePlannerTest extends AbstractAggregatePlannerT
         checkDistinctAggWithGroupBySingle(TestCase.CASE_7_1);
         checkDistinctAggWithGroupBySingle(TestCase.CASE_7_2);
         checkDistinctAggWithGroupBySingle(TestCase.CASE_7_3);
-        checkDistinctAggWithGroupBySingle(TestCase.CASE_7_4);
 
         checkDistinctAggWithGroupByHash(TestCase.CASE_7_1A);
         checkDistinctAggWithGroupByHash(TestCase.CASE_7_2A);
         checkDistinctAggWithGroupByHash(TestCase.CASE_7_3A);
-        checkDistinctAggWithGroupByHash(TestCase.CASE_7_4A);
+
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_1B);
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_2B);
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_3B);
+
+        checkDistinctAggWithColocatedGroupByHash(TestCase.CASE_7_1C);
+        checkDistinctAggWithColocatedGroupByHash(TestCase.CASE_7_2C);
+        checkDistinctAggWithColocatedGroupByHash(TestCase.CASE_7_3C);
+
+        checkDistinctAggWithColocatedGroupByHash(TestCase.CASE_7_1D);
+        checkDistinctAggWithColocatedGroupByHash(TestCase.CASE_7_2D);
+        checkDistinctAggWithColocatedGroupByHash(TestCase.CASE_7_3D);
     }
 
     /**
@@ -131,6 +166,15 @@ public class ColocatedHashAggregatePlannerTest extends AbstractAggregatePlannerT
 
         checkSimpleAggWithGroupByHash(TestCase.CASE_8_1A);
         checkSimpleAggWithGroupByHash(TestCase.CASE_8_2A);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_8_1B);
+        checkSimpleAggWithGroupByHash(TestCase.CASE_8_2B);
+
+        checkSimpleAggWithColocatedGroupBy(TestCase.CASE_8_1C);
+        checkSimpleAggWithColocatedGroupBy(TestCase.CASE_8_2C);
+
+        checkSimpleAggWithColocatedGroupBy(TestCase.CASE_8_1D);
+        checkSimpleAggWithColocatedGroupBy(TestCase.CASE_8_2D);
     }
 
     /**
@@ -147,6 +191,16 @@ public class ColocatedHashAggregatePlannerTest extends AbstractAggregatePlannerT
         checkAggWithGroupByIndexColumnsHash(TestCase.CASE_9A);
         checkAggWithGroupByIndexColumnsHash(TestCase.CASE_10A);
         checkAggWithGroupByIndexColumnsHash(TestCase.CASE_11A);
+
+        checkAggWithGroupByIndexColumnsHash(TestCase.CASE_9B);
+        checkAggWithGroupByIndexColumnsHash(TestCase.CASE_10B);
+        checkAggWithGroupByIndexColumnsHash(TestCase.CASE_11B);
+
+        checkAggWithColocatedGroupByIndexColumnsHash(TestCase.CASE_9C);
+        checkAggWithColocatedGroupByIndexColumnsHash(TestCase.CASE_10C);
+        checkAggWithColocatedGroupByIndexColumnsHash(TestCase.CASE_11C);
+
+        checkAggWithColocatedGroupByIndexColumnsHash(TestCase.CASE_9D);
     }
 
     /**
@@ -156,15 +210,17 @@ public class ColocatedHashAggregatePlannerTest extends AbstractAggregatePlannerT
     public void distinctWithoutAggregate() throws Exception {
         checkGroupWithNoAggregateSingle(TestCase.CASE_12);
         checkGroupWithNoAggregateHash(TestCase.CASE_12A);
-    }
+        checkGroupWithNoAggregateHash(TestCase.CASE_12B);
+
+        checkColocatedGroupWithNoAggregateHash(TestCase.CASE_12C);
+        checkColocatedGroupWithNoAggregateHash(TestCase.CASE_12D);
 
-    /**
-     * Validates a plan uses index for a query with DISTINCT and without aggregation functions.
-     */
-    @Test
-    public void distinctWithoutAggregateUseIndex() throws Exception {
         checkGroupWithNoAggregateSingle(TestCase.CASE_13);
         checkGroupWithNoAggregateHash(TestCase.CASE_13A);
+        checkGroupWithNoAggregateHash(TestCase.CASE_13B);
+
+        checkColocatedGroupWithNoAggregateHash(TestCase.CASE_13C);
+        checkColocatedGroupWithNoAggregateHash(TestCase.CASE_13D);
     }
 
     /**
@@ -174,6 +230,7 @@ public class ColocatedHashAggregatePlannerTest extends AbstractAggregatePlannerT
     public void subqueryWithAggregateInWhereClause() throws Exception {
         checkSimpleAggSingle(TestCase.CASE_14);
         checkSimpleAggHash(TestCase.CASE_14A);
+        checkSimpleAggHash(TestCase.CASE_14B);
     }
 
     /**
@@ -183,6 +240,7 @@ public class ColocatedHashAggregatePlannerTest extends AbstractAggregatePlannerT
     public void distinctAggregateInWhereClause() throws Exception {
         checkGroupWithNoAggregateSingle(TestCase.CASE_15);
         checkGroupWithNoAggregateHash(TestCase.CASE_15A);
+        checkGroupWithNoAggregateHash(TestCase.CASE_15B);
     }
 
     /**
@@ -211,6 +269,16 @@ public class ColocatedHashAggregatePlannerTest extends AbstractAggregatePlannerT
                 ),
                 ArrayUtils.concat(disableRules, additionalRulesToDisable)
         );
+        assertPlan(TestCase.CASE_16B,
+                nodeOrAnyChild(isInstanceOf(IgniteSort.class)
+                        .and(input(isInstanceOf(IgniteColocatedHashAggregate.class)
+                                .and(input(isInstanceOf(IgniteExchange.class)
+                                        .and(input(isTableScan("TEST")))
+                                ))
+                        ))
+                ),
+                ArrayUtils.concat(disableRules, additionalRulesToDisable)
+        );
     }
 
     /**
@@ -245,6 +313,20 @@ public class ColocatedHashAggregatePlannerTest extends AbstractAggregatePlannerT
                 ),
                 disableRules
         );
+        assertPlan(TestCase.CASE_17B,
+                hasChildThat(isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)
+                        .and(input(1, isInstanceOf(IgniteColocatedHashAggregate.class)
+                                .and(input(isInstanceOf(IgniteLimit.class)
+                                        .and(input(isInstanceOf(IgniteExchange.class)
+                                                .and(input(isInstanceOf(IgniteSort.class)
+                                                        .and(input(isTableScan("TEST")))
+                                                ))
+                                        ))
+                                ))
+                        ))
+                ),
+                disableRules
+        );
     }
 
     /**
@@ -259,6 +341,10 @@ public class ColocatedHashAggregatePlannerTest extends AbstractAggregatePlannerT
         checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_1A, TraitUtils.createCollation(List.of(0, 1)));
         checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_2A, TraitUtils.createCollation(List.of(1, 0)));
         checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_3A, TraitUtils.createCollation(List.of(1, 0)));
+
+        checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_1B, TraitUtils.createCollation(List.of(0, 1)));
+        checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_2B, TraitUtils.createCollation(List.of(1, 0)));
+        checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_3B, TraitUtils.createCollation(List.of(1, 0)));
     }
 
     /**
@@ -273,6 +359,10 @@ public class ColocatedHashAggregatePlannerTest extends AbstractAggregatePlannerT
         checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_19_1A, TraitUtils.createCollation(List.of(0)));
         checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_19_2A, TraitUtils.createCollation(List.of(1)));
         checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_20A, TraitUtils.createCollation(List.of(0, 1, 2)));
+
+        checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_19_1B, TraitUtils.createCollation(List.of(0)));
+        checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_19_2B, TraitUtils.createCollation(List.of(1)));
+        checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_20B, TraitUtils.createCollation(List.of(0, 1, 2)));
     }
 
     /**
@@ -313,6 +403,10 @@ public class ColocatedHashAggregatePlannerTest extends AbstractAggregatePlannerT
                 .and(input(0, subtreePredicate))
                 .and(input(1, subtreePredicate))
         ), disableRules);
+        assertPlan(TestCase.CASE_21B, nodeOrAnyChild(isInstanceOf(IgniteMergeJoin.class)
+                .and(input(0, subtreePredicate))
+                .and(input(1, subtreePredicate))
+        ), disableRules);
     }
 
     /**
@@ -320,14 +414,24 @@ public class ColocatedHashAggregatePlannerTest extends AbstractAggregatePlannerT
      */
     @Test
     public void testCountAgg() throws Exception {
-        Predicate<AggregateCall> countMap = (a) -> {
-            return Objects.equals(a.getAggregation().getName(), "COUNT") && a.getArgList().equals(List.of(1));
-        };
+        Predicate<AggregateCall> countMap = (a) ->
+                Objects.equals(a.getAggregation().getName(), "COUNT") && a.getArgList().equals(List.of(1));
 
-        assertPlan(TestCase.CASE_22, isInstanceOf(IgniteColocatedHashAggregate.class)
+        Predicate<IgniteColocatedHashAggregate> nonColocatedGroupBy = isInstanceOf(IgniteColocatedHashAggregate.class)
                 .and(in -> hasAggregates(countMap).test(in.getAggCallList()))
                 .and(input(isInstanceOf(IgniteExchange.class)
-                        .and(hasDistribution(IgniteDistributions.single())))), disableRules);
+                        .and(hasDistribution(IgniteDistributions.single()))));
+
+        assertPlan(TestCase.CASE_22, nonColocatedGroupBy, disableRules);
+        assertPlan(TestCase.CASE_22A, nonColocatedGroupBy, disableRules);
+
+        Predicate<IgniteExchange> colocatedGroupBy = isInstanceOf(IgniteExchange.class)
+                .and(hasDistribution(IgniteDistributions.single()))
+                .and(input(isInstanceOf(IgniteColocatedHashAggregate.class)
+                        .and(in -> hasAggregates(countMap).test(in.getAggCallList()))));
+
+        assertPlan(TestCase.CASE_22B, colocatedGroupBy, disableRules);
+        assertPlan(TestCase.CASE_22C, colocatedGroupBy, disableRules);
     }
 
     /**
@@ -335,14 +439,24 @@ public class ColocatedHashAggregatePlannerTest extends AbstractAggregatePlannerT
      */
     @Test
     public void testAvgAgg() throws Exception {
-        Predicate<AggregateCall> countMap = (a) -> {
-            return Objects.equals(a.getAggregation().getName(), "AVG") && a.getArgList().equals(List.of(1));
-        };
+        Predicate<AggregateCall> countMap = (a) -> Objects.equals(a.getAggregation().getName(), "AVG") && a.getArgList().equals(List.of(1));
 
-        assertPlan(TestCase.CASE_23, isInstanceOf(IgniteColocatedHashAggregate.class)
+        Predicate<IgniteColocatedHashAggregate> nonColocatedGroupBy = isInstanceOf(IgniteColocatedHashAggregate.class)
                 .and(in -> hasAggregates(countMap).test(in.getAggCallList()))
                 .and(input(isInstanceOf(IgniteExchange.class)
-                        .and(hasDistribution(IgniteDistributions.single())))), disableRules);
+                        .and(hasDistribution(IgniteDistributions.single()))));
+
+        assertPlan(TestCase.CASE_23, nonColocatedGroupBy, disableRules);
+        assertPlan(TestCase.CASE_23A, nonColocatedGroupBy, disableRules);
+
+        Predicate<IgniteExchange> colocatedGroupBy = isInstanceOf(IgniteExchange.class)
+                .and(hasDistribution(IgniteDistributions.single()))
+                .and(input(isInstanceOf(IgniteColocatedHashAggregate.class)
+                        .and(in -> hasAggregates(countMap).test(in.getAggCallList()))));
+
+        assertPlan(TestCase.CASE_23B, colocatedGroupBy, disableRules);
+        assertPlan(TestCase.CASE_23C, colocatedGroupBy, disableRules);
+
     }
 
     private void checkSimpleAggSingle(TestCase testCase) throws Exception {
@@ -390,6 +504,31 @@ public class ColocatedHashAggregatePlannerTest extends AbstractAggregatePlannerT
         );
     }
 
+    private void checkSimpleAggWithColocatedGroupBy(TestCase testCase) throws Exception {
+        assertPlan(testCase,
+                nodeOrAnyChild(isInstanceOf(IgniteExchange.class)
+                        .and(nodeOrAnyChild(isInstanceOf(IgniteColocatedHashAggregate.class)
+                                .and(hasAggregate())
+                                .and(hasGroups())
+                                .and(input(isTableScan("TEST")))
+                        ))),
+                disableRules
+        );
+    }
+
+    private void checkDistinctAggWithColocatedGroupByHash(TestCase testCase) throws Exception {
+        assertPlan(testCase,
+                nodeOrAnyChild(isInstanceOf(IgniteExchange.class)
+                        .and(nodeOrAnyChild(isInstanceOf(IgniteColocatedHashAggregate.class)
+                                .and(hasAggregate())
+                                .and(hasGroups())
+                                .and(hasDistinctAggregate())
+                                .and(input(isTableScan("TEST")))
+                        ))),
+                disableRules
+        );
+    }
+
     private void checkDistinctAggSingle(TestCase testCase) throws Exception {
         assertPlan(testCase,
                 isInstanceOf(IgniteColocatedHashAggregate.class)
@@ -418,6 +557,21 @@ public class ColocatedHashAggregatePlannerTest extends AbstractAggregatePlannerT
         );
     }
 
+    private void checkColocatedDistinctAggHash(TestCase testCase) throws Exception {
+        assertPlan(testCase,
+                isInstanceOf(IgniteColocatedHashAggregate.class)
+                        .and(hasAggregate())
+                        .and(not(hasDistinctAggregate()))
+                        .and(input(isInstanceOf(IgniteExchange.class)
+                                .and(input(isInstanceOf(IgniteColocatedHashAggregate.class)
+                                        .and(hasGroups())
+                                        .and(input(isTableScan("TEST")))
+                                ))
+                        )),
+                disableRules
+        );
+    }
+
     private void checkDistinctAggWithGroupBySingle(TestCase testCase) throws Exception {
         assertPlan(testCase,
                 nodeOrAnyChild(isInstanceOf(IgniteColocatedHashAggregate.class)
@@ -464,6 +618,18 @@ public class ColocatedHashAggregatePlannerTest extends AbstractAggregatePlannerT
         );
     }
 
+    private void checkAggWithColocatedGroupByIndexColumnsHash(TestCase testCase) throws Exception {
+        assertPlan(testCase,
+                nodeOrAnyChild(isInstanceOf(IgniteExchange.class)
+                        .and(nodeOrAnyChild(isInstanceOf(IgniteColocatedHashAggregate.class)
+                                .and(hasAggregate())
+                                .and(input(isTableScan("TEST")))
+                        ))
+                ),
+                disableRules
+        );
+    }
+
     private void checkGroupWithNoAggregateSingle(TestCase testCase) throws Exception {
         assertPlan(testCase,
                 nodeOrAnyChild(isInstanceOf(IgniteColocatedHashAggregate.class)
@@ -488,6 +654,18 @@ public class ColocatedHashAggregatePlannerTest extends AbstractAggregatePlannerT
         );
     }
 
+    private void checkColocatedGroupWithNoAggregateHash(TestCase testCase) throws Exception {
+        assertPlan(testCase,
+                nodeOrAnyChild(isInstanceOf(IgniteExchange.class)
+                        .and(input(isInstanceOf(IgniteColocatedHashAggregate.class)
+                                .and(not(hasAggregate()))
+                                .and(hasGroups())
+                                .and(input(isTableScan("TEST")))
+                        ))
+                ),
+                disableRules
+        );
+    }
 
     private void checkGroupsWithOrderByGroupColumnsSingle(TestCase testCase, RelCollation collation) throws Exception {
         assertPlan(testCase,
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ColocatedSortAggregatePlannerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ColocatedSortAggregatePlannerTest.java
index 1a84130156..4c66544ec5 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ColocatedSortAggregatePlannerTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ColocatedSortAggregatePlannerTest.java
@@ -56,6 +56,7 @@ public class ColocatedSortAggregatePlannerTest extends AbstractAggregatePlannerT
     protected void simpleAggregate() throws Exception {
         checkSimpleAggSingle(TestCase.CASE_1);
         checkSimpleAggHash(TestCase.CASE_1A);
+        checkSimpleAggHash(TestCase.CASE_1B);
     }
 
     /**
@@ -67,11 +68,18 @@ public class ColocatedSortAggregatePlannerTest extends AbstractAggregatePlannerT
     public void distinctAggregate() throws Exception {
         checkDistinctAggSingle(TestCase.CASE_2_1);
         checkDistinctAggSingle(TestCase.CASE_2_2);
-        checkDistinctAggSingle(TestCase.CASE_2_3);
 
         checkDistinctAggHash(TestCase.CASE_2_1A);
         checkDistinctAggHash(TestCase.CASE_2_2A);
-        checkDistinctAggHash(TestCase.CASE_2_3A);
+
+        checkDistinctAggHash(TestCase.CASE_2_1B);
+        checkDistinctAggHash(TestCase.CASE_2_2B);
+
+        checkDistinctAggHash(TestCase.CASE_2_1C);
+        checkDistinctAggHash(TestCase.CASE_2_2C);
+
+        checkDistinctAggHash(TestCase.CASE_2_1D);
+        checkDistinctAggHash(TestCase.CASE_2_2D);
     }
 
     /**
@@ -86,6 +94,15 @@ public class ColocatedSortAggregatePlannerTest extends AbstractAggregatePlannerT
 
         checkSimpleAggHash(TestCase.CASE_3_1A);
         checkSimpleAggHash(TestCase.CASE_3_2A);
+
+        checkSimpleAggHash(TestCase.CASE_3_1B);
+        checkSimpleAggHash(TestCase.CASE_3_2B);
+
+        checkSimpleAggHash(TestCase.CASE_3_1C);
+        checkSimpleAggHash(TestCase.CASE_3_2C);
+
+        checkSimpleAggHash(TestCase.CASE_3_1D);
+        checkSimpleAggHash(TestCase.CASE_3_2D);
     }
 
     /**
@@ -98,6 +115,14 @@ public class ColocatedSortAggregatePlannerTest extends AbstractAggregatePlannerT
 
         checkSimpleAggWithGroupByHash(TestCase.CASE_5A);
         checkSimpleAggWithGroupByHash(TestCase.CASE_6A);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_5B);
+        checkSimpleAggWithGroupByHash(TestCase.CASE_6B);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_5C);
+        checkSimpleAggWithGroupByHash(TestCase.CASE_6C);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_5D);
     }
 
     /**
@@ -110,12 +135,22 @@ public class ColocatedSortAggregatePlannerTest extends AbstractAggregatePlannerT
         checkDistinctAggWithGroupBySingle(TestCase.CASE_7_1);
         checkDistinctAggWithGroupBySingle(TestCase.CASE_7_2);
         checkDistinctAggWithGroupBySingle(TestCase.CASE_7_3);
-        checkDistinctAggWithGroupBySingle(TestCase.CASE_7_4);
 
         checkDistinctAggWithGroupByHash(TestCase.CASE_7_1A);
         checkDistinctAggWithGroupByHash(TestCase.CASE_7_2A);
         checkDistinctAggWithGroupByHash(TestCase.CASE_7_3A);
-        checkDistinctAggWithGroupByHash(TestCase.CASE_7_4A);
+
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_1B);
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_2B);
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_3B);
+
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_1C);
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_2C);
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_3C);
+
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_1D);
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_2D);
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_3D);
     }
 
     /**
@@ -130,6 +165,15 @@ public class ColocatedSortAggregatePlannerTest extends AbstractAggregatePlannerT
 
         checkSimpleAggWithGroupByHash(TestCase.CASE_8_1A);
         checkSimpleAggWithGroupByHash(TestCase.CASE_8_2A);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_8_1B);
+        checkSimpleAggWithGroupByHash(TestCase.CASE_8_2B);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_8_1C);
+        checkSimpleAggWithGroupByHash(TestCase.CASE_8_2C);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_8_1D);
+        checkSimpleAggWithGroupByHash(TestCase.CASE_8_2D);
     }
 
     /**
@@ -146,6 +190,16 @@ public class ColocatedSortAggregatePlannerTest extends AbstractAggregatePlannerT
         checkAggWithGroupByIndexColumnsHash(TestCase.CASE_9A);
         checkAggWithGroupByIndexColumnsHash(TestCase.CASE_10A);
         checkAggWithGroupByIndexColumnsHash(TestCase.CASE_11A);
+
+        checkAggWithGroupByIndexColumnsHash(TestCase.CASE_9B);
+        checkAggWithGroupByIndexColumnsHash(TestCase.CASE_10B);
+        checkAggWithGroupByIndexColumnsHash(TestCase.CASE_11B);
+
+        checkAggWithColocatedGroupByIndexColumnsHash(TestCase.CASE_9C);
+        checkAggWithColocatedGroupByIndexColumnsHash(TestCase.CASE_10C);
+        checkAggWithColocatedGroupByIndexColumnsHash(TestCase.CASE_11C);
+
+        checkAggWithColocatedGroupByIndexColumnsHash(TestCase.CASE_9D);
     }
 
     /**
@@ -156,6 +210,10 @@ public class ColocatedSortAggregatePlannerTest extends AbstractAggregatePlannerT
         checkGroupWithNoAggregateSingle(TestCase.CASE_12);
 
         checkGroupWithNoAggregateHash(TestCase.CASE_12A);
+        checkGroupWithNoAggregateHash(TestCase.CASE_12B);
+
+        checkGroupWithNoAggregateHash(TestCase.CASE_12C);
+        checkGroupWithNoAggregateHash(TestCase.CASE_12D);
     }
 
     /**
@@ -163,25 +221,13 @@ public class ColocatedSortAggregatePlannerTest extends AbstractAggregatePlannerT
      */
     @Test
     public void distinctWithoutAggregateUseIndex() throws Exception {
-        assertPlan(TestCase.CASE_13,
-                nodeOrAnyChild(isInstanceOf(IgniteColocatedSortAggregate.class)
-                        .and(not(hasAggregate()))
-                        .and(hasGroups())
-                        .and(input(isIndexScan("TEST", "idx_grp0_grp1")))
-                ),
-                disableRules
-        );
+        checkGroupWithNoAggregateUseIndexSingle(TestCase.CASE_13);
 
-        assertPlan(TestCase.CASE_13A,
-                nodeOrAnyChild(isInstanceOf(IgniteColocatedSortAggregate.class)
-                        .and(not(hasAggregate()))
-                        .and(hasGroups())
-                        .and(input(isInstanceOf(IgniteExchange.class)
-                                .and(input(isIndexScan("TEST", "idx_grp0_grp1")))
-                        ))
-                ),
-                disableRules
-        );
+        checkGroupWithNoAggregateUseIndexHash(TestCase.CASE_13A);
+        checkGroupWithNoAggregateUseIndexHash(TestCase.CASE_13B);
+
+        checkColocatedGroupWithNoAggregateUseIndexHash(TestCase.CASE_13C);
+        checkColocatedGroupWithNoAggregateUseIndexHash(TestCase.CASE_13D);
     }
 
     /**
@@ -191,6 +237,7 @@ public class ColocatedSortAggregatePlannerTest extends AbstractAggregatePlannerT
     public void subqueryWithAggregateInWhereClause() throws Exception {
         checkSimpleAggSingle(TestCase.CASE_14);
         checkSimpleAggHash(TestCase.CASE_14A);
+        checkSimpleAggHash(TestCase.CASE_14B);
     }
 
     /**
@@ -200,6 +247,7 @@ public class ColocatedSortAggregatePlannerTest extends AbstractAggregatePlannerT
     public void distinctAggregateInWhereClause() throws Exception {
         checkGroupWithNoAggregateSingle(TestCase.CASE_15);
         checkGroupWithNoAggregateHash(TestCase.CASE_15A);
+        checkGroupWithNoAggregateHash(TestCase.CASE_15B);
     }
 
     /**
@@ -227,6 +275,16 @@ public class ColocatedSortAggregatePlannerTest extends AbstractAggregatePlannerT
                         ),
                 ArrayUtils.concat(disableRules, additionalRulesToDisable)
         );
+        assertPlan(TestCase.CASE_16B,
+                not(nodeOrAnyChild(isInstanceOf(IgniteSort.class)))
+                        .and(nodeOrAnyChild(input(1, isInstanceOf(IgniteColocatedSortAggregate.class)
+                                        .and(input(isInstanceOf(IgniteExchange.class)
+                                                .and(input(isIndexScan("TEST", "idx_val0")))
+                                        ))
+                                ))
+                        ),
+                ArrayUtils.concat(disableRules, additionalRulesToDisable)
+        );
     }
 
     /**
@@ -261,6 +319,20 @@ public class ColocatedSortAggregatePlannerTest extends AbstractAggregatePlannerT
                 ),
                 disableRules
         );
+        assertPlan(TestCase.CASE_17B,
+                hasChildThat(isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)
+                        .and(input(1, isInstanceOf(IgniteColocatedSortAggregate.class)
+                                .and(input(isInstanceOf(IgniteLimit.class)
+                                        .and(input(isInstanceOf(IgniteExchange.class)
+                                                .and(input(isInstanceOf(IgniteSort.class)
+                                                        .and(input(isTableScan("TEST")))
+                                                ))
+                                        ))
+                                ))
+                        ))
+                ),
+                disableRules
+        );
     }
 
     /**
@@ -275,6 +347,10 @@ public class ColocatedSortAggregatePlannerTest extends AbstractAggregatePlannerT
         checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_1A, TraitUtils.createCollation(List.of(0, 1)));
         checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_2A, TraitUtils.createCollation(List.of(1, 0)));
         checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_3A, TraitUtils.createCollation(List.of(1, 0)));
+
+        checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_1B, TraitUtils.createCollation(List.of(0, 1)));
+        checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_2B, TraitUtils.createCollation(List.of(1, 0)));
+        checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_3B, TraitUtils.createCollation(List.of(1, 0)));
     }
 
     /**
@@ -287,6 +363,9 @@ public class ColocatedSortAggregatePlannerTest extends AbstractAggregatePlannerT
 
         checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_19_1A, TraitUtils.createCollation(List.of(0, 1)));
         checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_19_2A, TraitUtils.createCollation(List.of(1, 0)));
+
+        checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_19_1B, TraitUtils.createCollation(List.of(0, 1)));
+        checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_19_2B, TraitUtils.createCollation(List.of(1, 0)));
     }
 
     /**
@@ -297,6 +376,7 @@ public class ColocatedSortAggregatePlannerTest extends AbstractAggregatePlannerT
         checkGroupsWithOrderByAndGroupByWithAdditionalSortingSingle(TestCase.CASE_20);
 
         checkGroupsWithOrderByAndGroupByWithAdditionalSortingHash(TestCase.CASE_20A);
+        checkGroupsWithOrderByAndGroupByWithAdditionalSortingHash(TestCase.CASE_20B);
     }
 
 
@@ -338,6 +418,10 @@ public class ColocatedSortAggregatePlannerTest extends AbstractAggregatePlannerT
                 .and(input(0, subtreePredicate))
                 .and(input(1, subtreePredicate))
         ), disableRules);
+        assertPlan(TestCase.CASE_21B, nodeOrAnyChild(isInstanceOf(IgniteMergeJoin.class)
+                .and(input(0, subtreePredicate))
+                .and(input(1, subtreePredicate))
+        ), disableRules);
     }
 
     /**
@@ -345,15 +429,18 @@ public class ColocatedSortAggregatePlannerTest extends AbstractAggregatePlannerT
      */
     @Test
     public void testCountAgg() throws Exception {
-        Predicate<AggregateCall> countMap = (a) -> {
-            String aggName = a.getAggregation().getName();
-            return Objects.equals(aggName, "COUNT") && a.getArgList().equals(List.of(1));
-        };
+        Predicate<AggregateCall> countMap = (a) ->
+                Objects.equals(a.getAggregation().getName(), "COUNT") && a.getArgList().equals(List.of(1));
 
-        assertPlan(TestCase.CASE_22, isInstanceOf(IgniteColocatedSortAggregate.class)
+        Predicate<IgniteColocatedSortAggregate> checkPlan = isInstanceOf(IgniteColocatedSortAggregate.class)
                 .and(in -> hasAggregates(countMap).test(in.getAggCallList()))
                 .and(input(isInstanceOf(IgniteExchange.class)
-                        .and(hasDistribution(IgniteDistributions.single())))), disableRules);
+                        .and(hasDistribution(IgniteDistributions.single()))));
+
+        assertPlan(TestCase.CASE_22, checkPlan, disableRules);
+        assertPlan(TestCase.CASE_22A, checkPlan, disableRules);
+        assertPlan(TestCase.CASE_22B, checkPlan, disableRules);
+        assertPlan(TestCase.CASE_22C, checkPlan, disableRules);
     }
 
     /**
@@ -361,14 +448,17 @@ public class ColocatedSortAggregatePlannerTest extends AbstractAggregatePlannerT
      */
     @Test
     public void testAvgAgg() throws Exception {
-        Predicate<AggregateCall> countMap = (a) -> {
-            return Objects.equals(a.getAggregation().getName(), "AVG") && a.getArgList().equals(List.of(1));
-        };
+        Predicate<AggregateCall> countMap = (a) -> Objects.equals(a.getAggregation().getName(), "AVG") && a.getArgList().equals(List.of(1));
 
-        assertPlan(TestCase.CASE_23, isInstanceOf(IgniteColocatedSortAggregate.class)
+        Predicate<IgniteColocatedSortAggregate> checkPlan = isInstanceOf(IgniteColocatedSortAggregate.class)
                 .and(in -> hasAggregates(countMap).test(in.getAggCallList()))
                 .and(input(isInstanceOf(IgniteExchange.class)
-                        .and(hasDistribution(IgniteDistributions.single())))), disableRules);
+                        .and(hasDistribution(IgniteDistributions.single()))));
+
+        assertPlan(TestCase.CASE_23, checkPlan, disableRules);
+        assertPlan(TestCase.CASE_23A, checkPlan, disableRules);
+        assertPlan(TestCase.CASE_23B, checkPlan, disableRules);
+        assertPlan(TestCase.CASE_23C, checkPlan, disableRules);
     }
 
     private void checkSimpleAggSingle(TestCase testCase) throws Exception {
@@ -495,6 +585,18 @@ public class ColocatedSortAggregatePlannerTest extends AbstractAggregatePlannerT
         );
     }
 
+    private void checkAggWithColocatedGroupByIndexColumnsHash(TestCase testCase) throws Exception {
+        assertPlan(testCase,
+                nodeOrAnyChild(isInstanceOf(IgniteExchange.class)
+                        .and(nodeOrAnyChild(isInstanceOf(IgniteColocatedSortAggregate.class)
+                                .and(hasAggregate())
+                                .and(input(isIndexScan("TEST", "idx_grp0_grp1")))
+                        ))
+                ),
+                disableRules
+        );
+    }
+
     private void checkGroupWithNoAggregateSingle(TestCase testCase) throws Exception {
         assertPlan(testCase,
                 nodeOrAnyChild(isInstanceOf(IgniteColocatedSortAggregate.class)
@@ -508,6 +610,43 @@ public class ColocatedSortAggregatePlannerTest extends AbstractAggregatePlannerT
         );
     }
 
+    private void checkGroupWithNoAggregateUseIndexSingle(TestCase testCase) throws Exception {
+        assertPlan(testCase,
+                nodeOrAnyChild(isInstanceOf(IgniteColocatedSortAggregate.class)
+                        .and(not(hasAggregate()))
+                        .and(hasGroups())
+                        .and(input(isIndexScan("TEST", "idx_grp0_grp1")))
+                ),
+                disableRules
+        );
+    }
+
+    private void checkGroupWithNoAggregateUseIndexHash(TestCase testCase) throws Exception {
+        assertPlan(testCase,
+                nodeOrAnyChild(isInstanceOf(IgniteColocatedSortAggregate.class)
+                        .and(not(hasAggregate()))
+                        .and(hasGroups())
+                        .and(input(isInstanceOf(IgniteExchange.class)
+                                .and(input(isIndexScan("TEST", "idx_grp0_grp1")))
+                        ))
+                ),
+                disableRules
+        );
+    }
+
+    private void checkColocatedGroupWithNoAggregateUseIndexHash(TestCase testCase) throws Exception {
+        assertPlan(testCase,
+                nodeOrAnyChild(isInstanceOf(IgniteExchange.class)
+                        .and(input(isInstanceOf(IgniteColocatedSortAggregate.class)
+                                .and(not(hasAggregate()))
+                                .and(hasGroups())
+                                .and(input(isIndexScan("TEST", "idx_grp0_grp1")))
+                        ))
+                ),
+                disableRules
+        );
+    }
+
     private void checkGroupWithNoAggregateHash(TestCase testCase) throws Exception {
         assertPlan(testCase,
                 nodeOrAnyChild(isInstanceOf(IgniteColocatedSortAggregate.class)
@@ -523,6 +662,17 @@ public class ColocatedSortAggregatePlannerTest extends AbstractAggregatePlannerT
         );
     }
 
+    private void checkGroupWi2thNoAggregateSingle(TestCase testCase) throws Exception {
+        assertPlan(testCase,
+                nodeOrAnyChild(isInstanceOf(IgniteColocatedSortAggregate.class)
+                        .and(not(hasAggregate()))
+                        .and(hasGroups())
+                        .and(input(isIndexScan("TEST", "idx_grp0_grp1")))
+                ),
+                disableRules
+        );
+    }
+
     private void checkGroupsWithOrderByGroupColumnsSingle(TestCase testCase, RelCollation collation) throws Exception {
         assertPlan(testCase,
                 isInstanceOf(IgniteColocatedSortAggregate.class)
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/DmlPlannerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/DmlPlannerTest.java
index 06040d9b14..ae2db70e4f 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/DmlPlannerTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/DmlPlannerTest.java
@@ -173,7 +173,8 @@ public class DmlPlannerTest extends AbstractPlannerTest {
         return Stream.of(
                 IgniteDistributions.single(),
                 IgniteDistributions.hash(List.of(0, 1)),
-                IgniteDistributions.affinity(0, 2, "0")
+                IgniteDistributions.affinity(0, 2, "0"),
+                IgniteDistributions.identity(0)
         );
     }
 
@@ -188,7 +189,8 @@ public class DmlPlannerTest extends AbstractPlannerTest {
                 IgniteDistributions.affinity(1, 2, "0"),
                 IgniteDistributions.affinity(3, 2, "0"),
                 IgniteDistributions.affinity(List.of(1, 3), 2, "0"),
-                IgniteDistributions.affinity(List.of(3, 1), 2, "0")
+                IgniteDistributions.affinity(List.of(3, 1), 2, "0"),
+                IgniteDistributions.identity(1)
         );
     }
 
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/IdentityDistributionPlannerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/IdentityDistributionPlannerTest.java
new file mode 100644
index 0000000000..71344346cc
--- /dev/null
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/IdentityDistributionPlannerTest.java
@@ -0,0 +1,200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.sql.engine.planner;
+
+import static org.apache.ignite.internal.sql.engine.trait.IgniteDistributions.single;
+
+import org.apache.ignite.internal.schema.NativeTypes;
+import org.apache.ignite.internal.sql.engine.framework.TestBuilders;
+import org.apache.ignite.internal.sql.engine.framework.TestTable;
+import org.apache.ignite.internal.sql.engine.rel.IgniteExchange;
+import org.apache.ignite.internal.sql.engine.rel.IgniteIndexScan;
+import org.apache.ignite.internal.sql.engine.rel.IgniteMergeJoin;
+import org.apache.ignite.internal.sql.engine.rel.IgniteTrimExchange;
+import org.apache.ignite.internal.sql.engine.schema.IgniteIndex.Collation;
+import org.apache.ignite.internal.sql.engine.schema.IgniteSchema;
+import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
+import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test suite to verify join colocation when using
+ * {@link org.apache.ignite.internal.sql.engine.trait.DistributionFunction.IdentityDistribution} function.
+ */
+public class IdentityDistributionPlannerTest extends AbstractPlannerTest {
+
+    /**
+     * Join of the tables with same affinity is expected to be colocated.
+     */
+    @Test
+    public void joinSameTableIdentity() throws Exception {
+        TestTable tbl1 = simpleTable("TEST_TBL1", DEFAULT_TBL_SIZE, IgniteDistributions.identity(0));
+        TestTable tbl2 = simpleTable("TEST_TBL2", DEFAULT_TBL_SIZE, IgniteDistributions.identity(0));
+
+        IgniteSchema schema = createSchema(tbl1, tbl2);
+
+        String sql = "select count(*) "
+                + "from TEST_TBL1 t1 "
+                + "join TEST_TBL2 t2 on t1.id = t2.id";
+
+        assertPlan(sql, schema, nodeOrAnyChild(isInstanceOf(IgniteExchange.class)
+                .and(nodeOrAnyChild(isInstanceOf(IgniteMergeJoin.class)
+                        .and(hasDistribution(IgniteDistributions.identity(0)))
+                        .and(input(0, isInstanceOf(IgniteIndexScan.class)))
+                        .and(input(1, isInstanceOf(IgniteIndexScan.class)))
+                ))
+        ));
+    }
+
+    /**
+     * Re-hashing based on affinity.
+     */
+    @Test
+    public void joinAffinityTableWithIdentityTable() throws Exception {
+        IgniteDistribution affinityDistribution = IgniteDistributions.affinity(0, nextTableId(), 0);
+
+        TestTable tbl1 = simpleTable("TEST_TBL1", 2 * DEFAULT_TBL_SIZE, IgniteDistributions.identity(0));
+        TestTable tbl2 = simpleTable("TEST_TBL2", DEFAULT_TBL_SIZE, affinityDistribution);
+
+        IgniteSchema schema = createSchema(tbl1, tbl2);
+
+        String sql = "select count(*) "
+                + "from TEST_TBL1 t1 "
+                + "join TEST_TBL2 t2 on t1.id = t2.id";
+
+        assertPlan(sql, schema, nodeOrAnyChild(isInstanceOf(IgniteExchange.class)
+                .and(hasDistribution(single()))
+                .and(nodeOrAnyChild(isInstanceOf(IgniteMergeJoin.class)
+                        .and(hasDistribution(IgniteDistributions.identity(0)))
+                        .and(input(0, isInstanceOf(IgniteIndexScan.class)))
+                        .and(input(1, isInstanceOf(IgniteExchange.class)
+                                .and(input(isInstanceOf(IgniteIndexScan.class)))
+                        ))
+                ))
+        ));
+    }
+
+    /**
+     * Re-hashing based on identity function.
+     */
+    @Test
+    public void joinIndentityTableWithAffinityTable() throws Exception {
+        IgniteDistribution affinityDistribution = IgniteDistributions.affinity(0, nextTableId(), 0);
+
+        TestTable tbl1 = simpleTable("TEST_TBL1", DEFAULT_TBL_SIZE, IgniteDistributions.identity(0));
+        TestTable tbl2 = simpleTable("TEST_TBL2", 2 * DEFAULT_TBL_SIZE, affinityDistribution);
+
+        IgniteSchema schema = createSchema(tbl1, tbl2);
+
+        String sql = "select count(*) "
+                + "from TEST_TBL1 t1 "
+                + "join TEST_TBL2 t2 on t1.id = t2.id";
+
+        assertPlan(sql, schema, nodeOrAnyChild(isInstanceOf(IgniteExchange.class)
+                .and(hasDistribution(single()))
+                .and(nodeOrAnyChild(isInstanceOf(IgniteMergeJoin.class)
+                        .and(hasDistribution(affinityDistribution))
+                        .and(input(0, isInstanceOf(IgniteExchange.class)
+                                .and(input(isInstanceOf(IgniteIndexScan.class)))
+                        ))
+                        .and(input(1, isInstanceOf(IgniteIndexScan.class)))
+                ))
+        ));
+    }
+
+    /**
+     * Join of the tables with same affinity is expected to be colocated.
+     */
+    @Test
+    public void joinTableWithDifferentIdentity() throws Exception {
+        TestTable tbl1 = complexTable("TEST_TBL1", DEFAULT_TBL_SIZE, IgniteDistributions.identity(0));
+        TestTable tbl2 = complexTable("TEST_TBL2", DEFAULT_TBL_SIZE, IgniteDistributions.identity(1));
+
+        IgniteSchema schema = createSchema(tbl1, tbl2);
+
+        String sql = "select count(*) "
+                + "from TEST_TBL1 t1 "
+                + "join TEST_TBL2 t2 on t1.id1 = t2.id2";
+
+        assertPlan(sql, schema, nodeOrAnyChild(isInstanceOf(IgniteExchange.class)
+                .and(nodeOrAnyChild(isInstanceOf(IgniteMergeJoin.class)
+                        .and(hasDistribution(IgniteDistributions.identity(0)))
+                        .and(input(0, isInstanceOf(IgniteIndexScan.class)))
+                        .and(input(1, isInstanceOf(IgniteIndexScan.class)))
+                ))
+        ));
+    }
+
+    /**
+     * Join of the tables with identity and broadcast distribution is expected to be colocated.
+     */
+    @Test
+    public void joinTableWithIdentityAndBroadcastDistributions() throws Exception {
+        TestTable tbl1 = complexTable("TEST_TBL1", DEFAULT_TBL_SIZE, IgniteDistributions.identity(0));
+        TestTable tbl2 = complexTable("TEST_TBL2", DEFAULT_TBL_SIZE, IgniteDistributions.broadcast());
+
+        IgniteSchema schema = createSchema(tbl1, tbl2);
+
+        String sql = "select count(*) "
+                + "from TEST_TBL1 t1 "
+                + "join TEST_TBL2 t2 on t1.id1 = t2.id1";
+
+        assertPlan(sql, schema, nodeOrAnyChild(isInstanceOf(IgniteExchange.class)
+                .and(nodeOrAnyChild(isInstanceOf(IgniteMergeJoin.class)
+                        .and(hasDistribution(IgniteDistributions.identity(0)))
+                        .and(input(0, isInstanceOf(IgniteIndexScan.class)))
+                        .and(input(1, isInstanceOf(IgniteTrimExchange.class)
+                                .and(input(isInstanceOf(IgniteIndexScan.class)))
+                        ))
+                ))
+        ));
+    }
+
+    private static TestTable simpleTable(String tableName, int size, IgniteDistribution distribution) {
+        return TestBuilders.table()
+                .name(tableName)
+                .size(size)
+                .distribution(distribution)
+                .addColumn("ID", NativeTypes.STRING)
+                .addColumn("VAL", NativeTypes.STRING)
+                .sortedIndex()
+                .name("PK")
+                .addColumn("ID", Collation.ASC_NULLS_LAST)
+                .end()
+                .build();
+    }
+
+    private static TestTable complexTable(String tableName, int size, IgniteDistribution distribution) {
+        return TestBuilders.table()
+                .name(tableName)
+                .size(size)
+                .distribution(distribution)
+                .addColumn("ID1", NativeTypes.STRING)
+                .addColumn("ID2", NativeTypes.STRING)
+                .addColumn("VAL", NativeTypes.STRING)
+                .sortedIndex()
+                .name("IDX1")
+                .addColumn("ID1", Collation.ASC_NULLS_LAST)
+                .end()
+                .sortedIndex()
+                .name("IDX2")
+                .addColumn("ID2", Collation.ASC_NULLS_LAST)
+                .end()
+                .build();
+    }
+}
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/MapReduceHashAggregatePlannerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/MapReduceHashAggregatePlannerTest.java
index 2d7472757b..a525b719eb 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/MapReduceHashAggregatePlannerTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/MapReduceHashAggregatePlannerTest.java
@@ -61,6 +61,7 @@ public class MapReduceHashAggregatePlannerTest extends AbstractAggregatePlannerT
     protected void simpleAggregate() throws Exception {
         checkSimpleAggSingle(TestCase.CASE_1);
         checkSimpleAggHash(TestCase.CASE_1A);
+        checkSimpleAggHash(TestCase.CASE_1B);
     }
 
     /**
@@ -72,11 +73,18 @@ public class MapReduceHashAggregatePlannerTest extends AbstractAggregatePlannerT
     public void distinctAggregate() throws Exception {
         checkDistinctAggSingle(TestCase.CASE_2_1);
         checkDistinctAggSingle(TestCase.CASE_2_2);
-        checkDistinctAggSingle(TestCase.CASE_2_3);
 
         checkDistinctAggHash(TestCase.CASE_2_1A);
         checkDistinctAggHash(TestCase.CASE_2_2A);
-        checkDistinctAggHash(TestCase.CASE_2_3A);
+
+        checkDistinctAggHash(TestCase.CASE_2_1B);
+        checkDistinctAggHash(TestCase.CASE_2_2B);
+
+        checkDistinctAggHash(TestCase.CASE_2_1C);
+        checkDistinctAggHash(TestCase.CASE_2_2C);
+
+        checkDistinctAggHash(TestCase.CASE_2_1D);
+        checkDistinctAggHash(TestCase.CASE_2_2D);
     }
 
     /**
@@ -91,6 +99,15 @@ public class MapReduceHashAggregatePlannerTest extends AbstractAggregatePlannerT
 
         checkSimpleAggHash(TestCase.CASE_3_1A);
         checkSimpleAggHash(TestCase.CASE_3_2A);
+
+        checkSimpleAggHash(TestCase.CASE_3_1B);
+        checkSimpleAggHash(TestCase.CASE_3_2B);
+
+        checkSimpleAggHash(TestCase.CASE_3_1C);
+        checkSimpleAggHash(TestCase.CASE_3_2C);
+
+        checkSimpleAggHash(TestCase.CASE_3_1D);
+        checkSimpleAggHash(TestCase.CASE_3_2D);
     }
 
     /**
@@ -103,6 +120,14 @@ public class MapReduceHashAggregatePlannerTest extends AbstractAggregatePlannerT
 
         checkSimpleAggWithGroupByHash(TestCase.CASE_5A);
         checkSimpleAggWithGroupByHash(TestCase.CASE_6A);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_5B);
+        checkSimpleAggWithGroupByHash(TestCase.CASE_6B);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_5C);
+        checkSimpleAggWithGroupByHash(TestCase.CASE_6C);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_5D);
     }
 
     /**
@@ -115,13 +140,23 @@ public class MapReduceHashAggregatePlannerTest extends AbstractAggregatePlannerT
         checkDistinctAggWithGroupBySingle(TestCase.CASE_7_1);
         checkDistinctAggWithGroupBySingle(TestCase.CASE_7_2);
         checkDistinctAggWithGroupBySingle(TestCase.CASE_7_3);
-        checkDistinctAggWithGroupBySingle(TestCase.CASE_7_4);
 
         //TODO replace with calls to test methods after https://issues.apache.org/jira/browse/IGNITE-20083 is fixed
         assumeRun("checkDistinctAggWithGroupByHash", TestCase.CASE_7_1A);
         assumeRun("checkDistinctAggWithGroupByHash", TestCase.CASE_7_2A);
         assumeRun("checkDistinctAggWithGroupByHash", TestCase.CASE_7_3A);
-        assumeRun("checkDistinctAggWithGroupByHash", TestCase.CASE_7_4A);
+
+        assumeRun("checkDistinctAggWithGroupByHash", TestCase.CASE_7_1B);
+        assumeRun("checkDistinctAggWithGroupByHash", TestCase.CASE_7_2B);
+        assumeRun("checkDistinctAggWithGroupByHash", TestCase.CASE_7_3B);
+
+        assumeRun("checkDistinctAggWithColocatedGroupByHash", TestCase.CASE_7_1C);
+        assumeRun("checkDistinctAggWithColocatedGroupByHash", TestCase.CASE_7_2C);
+        assumeRun("checkDistinctAggWithColocatedGroupByHash", TestCase.CASE_7_3C);
+
+        assumeRun("checkDistinctAggWithColocatedGroupByHash", TestCase.CASE_7_1D);
+        assumeRun("checkDistinctAggWithColocatedGroupByHash", TestCase.CASE_7_2D);
+        assumeRun("checkDistinctAggWithColocatedGroupByHash", TestCase.CASE_7_3D);
     }
 
     /**
@@ -136,6 +171,15 @@ public class MapReduceHashAggregatePlannerTest extends AbstractAggregatePlannerT
 
         checkSimpleAggWithGroupByHash(TestCase.CASE_8_1A);
         checkSimpleAggWithGroupByHash(TestCase.CASE_8_2A);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_8_1B);
+        checkSimpleAggWithGroupByHash(TestCase.CASE_8_2B);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_8_1C);
+        checkSimpleAggWithGroupByHash(TestCase.CASE_8_2C);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_8_1D);
+        checkSimpleAggWithGroupByHash(TestCase.CASE_8_2D);
     }
 
     /**
@@ -152,6 +196,16 @@ public class MapReduceHashAggregatePlannerTest extends AbstractAggregatePlannerT
         checkAggWithGroupByIndexColumnsHash(TestCase.CASE_9A);
         checkAggWithGroupByIndexColumnsHash(TestCase.CASE_10A);
         checkAggWithGroupByIndexColumnsHash(TestCase.CASE_11A);
+
+        checkAggWithGroupByIndexColumnsHash(TestCase.CASE_9B);
+        checkAggWithGroupByIndexColumnsHash(TestCase.CASE_10B);
+        checkAggWithGroupByIndexColumnsHash(TestCase.CASE_11B);
+
+        checkAggWithGroupByIndexColumnsHash(TestCase.CASE_9C);
+        checkAggWithGroupByIndexColumnsHash(TestCase.CASE_10C);
+        checkAggWithGroupByIndexColumnsHash(TestCase.CASE_11C);
+
+        checkAggWithGroupByIndexColumnsHash(TestCase.CASE_9D);
     }
 
     /**
@@ -162,6 +216,9 @@ public class MapReduceHashAggregatePlannerTest extends AbstractAggregatePlannerT
         checkGroupWithNoAggregateSingle(TestCase.CASE_12);
         //TODO replace with calls to test methods after https://issues.apache.org/jira/browse/IGNITE-20083 is resolved
         assumeRun("checkGroupWithNoAggregateHash", TestCase.CASE_12A);
+        assumeRun("checkGroupWithNoAggregateHash", TestCase.CASE_12B);
+        assumeRun("checkGroupWithNoAggregateHash", TestCase.CASE_12C);
+        assumeRun("checkGroupWithNoAggregateHash", TestCase.CASE_12D);
     }
 
     /**
@@ -172,6 +229,9 @@ public class MapReduceHashAggregatePlannerTest extends AbstractAggregatePlannerT
         checkGroupWithNoAggregateSingle(TestCase.CASE_13);
         //TODO replace with calls to test methods after https://issues.apache.org/jira/browse/IGNITE-20083 is resolved
         assumeRun("checkGroupWithNoAggregateHash", TestCase.CASE_13A);
+        assumeRun("checkGroupWithNoAggregateHash", TestCase.CASE_13B);
+        assumeRun("checkGroupWithNoAggregateHash", TestCase.CASE_13C);
+        assumeRun("checkGroupWithNoAggregateHash", TestCase.CASE_13D);
     }
 
     /**
@@ -181,6 +241,7 @@ public class MapReduceHashAggregatePlannerTest extends AbstractAggregatePlannerT
     public void subqueryWithAggregateInWhereClause() throws Exception {
         checkSimpleAggSingle(TestCase.CASE_14);
         checkSimpleAggHash(TestCase.CASE_14A);
+        checkSimpleAggHash(TestCase.CASE_14B);
     }
 
     /**
@@ -190,6 +251,7 @@ public class MapReduceHashAggregatePlannerTest extends AbstractAggregatePlannerT
     public void distinctAggregateInWhereClause() throws Exception {
         checkGroupWithNoAggregateSingle(TestCase.CASE_15);
         checkGroupWithNoAggregateHash(TestCase.CASE_15A);
+        checkGroupWithNoAggregateHash(TestCase.CASE_15B);
     }
 
     /**
@@ -222,6 +284,18 @@ public class MapReduceHashAggregatePlannerTest extends AbstractAggregatePlannerT
                 ),
                 ArrayUtils.concat(disableRules, additionalRulesToDisable)
         );
+        assertPlan(TestCase.CASE_16B,
+                nodeOrAnyChild(isInstanceOf(IgniteSort.class)
+                        .and(input(isInstanceOf(IgniteReduceHashAggregate.class)
+                                .and(input(isInstanceOf(IgniteExchange.class)
+                                        .and(input(isInstanceOf(IgniteMapHashAggregate.class)
+                                                .and(input(isTableScan("TEST")))
+                                        ))
+                                ))
+                        ))
+                ),
+                ArrayUtils.concat(disableRules, additionalRulesToDisable)
+        );
     }
 
     /**
@@ -260,6 +334,22 @@ public class MapReduceHashAggregatePlannerTest extends AbstractAggregatePlannerT
                 ),
                 disableRules
         );
+        assertPlan(TestCase.CASE_17B,
+                hasChildThat(isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)
+                        .and(input(1, isInstanceOf(IgniteReduceHashAggregate.class)
+                                .and(input(isInstanceOf(IgniteMapHashAggregate.class)
+                                        .and(input(isInstanceOf(IgniteLimit.class)
+                                                .and(input(isInstanceOf(IgniteExchange.class)
+                                                        .and(input(isInstanceOf(IgniteSort.class)
+                                                                .and(input(isTableScan("TEST")))
+                                                        ))
+                                                ))
+                                        ))
+                                ))
+                        ))
+                ),
+                disableRules
+        );
     }
 
     /**
@@ -274,6 +364,10 @@ public class MapReduceHashAggregatePlannerTest extends AbstractAggregatePlannerT
         checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_1A, TraitUtils.createCollation(List.of(0, 1)));
         checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_2A, TraitUtils.createCollation(List.of(1, 0)));
         checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_3A, TraitUtils.createCollation(List.of(1, 0)));
+
+        checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_1B, TraitUtils.createCollation(List.of(0, 1)));
+        checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_2B, TraitUtils.createCollation(List.of(1, 0)));
+        checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_18_3B, TraitUtils.createCollation(List.of(1, 0)));
     }
 
     /**
@@ -288,6 +382,10 @@ public class MapReduceHashAggregatePlannerTest extends AbstractAggregatePlannerT
         checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_19_1A, TraitUtils.createCollation(List.of(0)));
         checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_19_2A, TraitUtils.createCollation(List.of(1)));
         checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_20A, TraitUtils.createCollation(List.of(0, 1, 2)));
+
+        checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_19_1B, TraitUtils.createCollation(List.of(0)));
+        checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_19_2B, TraitUtils.createCollation(List.of(1)));
+        checkGroupsWithOrderByGroupColumnsHash(TestCase.CASE_20B, TraitUtils.createCollation(List.of(0, 1, 2)));
     }
 
     /**
@@ -340,6 +438,7 @@ public class MapReduceHashAggregatePlannerTest extends AbstractAggregatePlannerT
 
         //TODO Replace with uncommented code after https://issues.apache.org/jira/browse/IGNITE-20083 is resolved
         assumeRun("", TestCase.CASE_21A);
+        assumeRun("", TestCase.CASE_21B);
         /*
         assertPlan(TestCase.CASE_21A, nodeOrAnyChild(isInstanceOf(IgniteMergeJoin.class)
                 .and(input(0, subtreePredicate))
@@ -363,7 +462,7 @@ public class MapReduceHashAggregatePlannerTest extends AbstractAggregatePlannerT
             return Objects.equals(aggName, "$SUM0") && a.getArgList().equals(List.of(1));
         };
 
-        assertPlan(TestCase.CASE_22, hasChildThat(isInstanceOf(IgniteReduceHashAggregate.class)
+        Predicate<RelNode> nonColocated = hasChildThat(isInstanceOf(IgniteReduceHashAggregate.class)
                 .and(in -> hasAggregates(countReduce).test(in.getAggregateCalls()))
                 .and(input(isInstanceOf(IgniteExchange.class)
                         .and(input(isInstanceOf(IgniteMapHashAggregate.class)
@@ -371,7 +470,23 @@ public class MapReduceHashAggregatePlannerTest extends AbstractAggregatePlannerT
                                         .and(input(isTableScan("TEST")))
                                 )
                         ))
-                )), disableRules);
+                ));
+
+        assertPlan(TestCase.CASE_22, nonColocated, disableRules);
+        assertPlan(TestCase.CASE_22A, nonColocated, disableRules);
+
+        Predicate<RelNode> colocated = hasChildThat(isInstanceOf(IgniteReduceHashAggregate.class)
+                .and(in -> hasAggregates(countReduce).test(in.getAggregateCalls()))
+                .and(input(isInstanceOf(IgniteExchange.class)
+                        .and(input(isInstanceOf(IgniteMapHashAggregate.class)
+                                        .and(in -> hasAggregates(countMap).test(in.getAggCallList()))
+                                        .and(input(isTableScan("TEST")))
+                                )
+                        ))
+                ));
+
+        assertPlan(TestCase.CASE_22B, colocated, disableRules);
+        assertPlan(TestCase.CASE_22C, colocated, disableRules);
     }
 
     /**
@@ -382,7 +497,18 @@ public class MapReduceHashAggregatePlannerTest extends AbstractAggregatePlannerT
     public void testAvgAgg() {
         RuntimeException e = assertThrows(RuntimeException.class,
                 () -> assertPlan(TestCase.CASE_23, isInstanceOf(IgniteRel.class), disableRules));
+        assertThat(e.getMessage(), containsString("There are not enough rules to produce a node with desired properties"));
+
+        e = assertThrows(RuntimeException.class,
+                () -> assertPlan(TestCase.CASE_23A, isInstanceOf(IgniteRel.class), disableRules));
+        assertThat(e.getMessage(), containsString("There are not enough rules to produce a node with desired properties"));
+
+        e = assertThrows(RuntimeException.class,
+                () -> assertPlan(TestCase.CASE_23B, isInstanceOf(IgniteRel.class), disableRules));
+        assertThat(e.getMessage(), containsString("There are not enough rules to produce a node with desired properties"));
 
+        e = assertThrows(RuntimeException.class,
+                () -> assertPlan(TestCase.CASE_23C, isInstanceOf(IgniteRel.class), disableRules));
         assertThat(e.getMessage(), containsString("There are not enough rules to produce a node with desired properties"));
     }
 
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/MapReduceSortAggregatePlannerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/MapReduceSortAggregatePlannerTest.java
index d31d8c4ce6..a02267ab11 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/MapReduceSortAggregatePlannerTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/MapReduceSortAggregatePlannerTest.java
@@ -28,7 +28,6 @@ import java.util.function.Predicate;
 import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.AggregateCall;
-import org.apache.calcite.sql.SqlAggFunction;
 import org.apache.ignite.internal.sql.engine.rel.IgniteCorrelatedNestedLoopJoin;
 import org.apache.ignite.internal.sql.engine.rel.IgniteExchange;
 import org.apache.ignite.internal.sql.engine.rel.IgniteLimit;
@@ -62,6 +61,7 @@ public class MapReduceSortAggregatePlannerTest extends AbstractAggregatePlannerT
     protected void simpleAggregate() throws Exception {
         checkSimpleAggSingle(TestCase.CASE_1);
         checkSimpleAggHash(TestCase.CASE_1A);
+        checkSimpleAggHash(TestCase.CASE_1B);
     }
 
     /**
@@ -73,11 +73,18 @@ public class MapReduceSortAggregatePlannerTest extends AbstractAggregatePlannerT
     public void distinctAggregate() throws Exception {
         checkDistinctAggSingle(TestCase.CASE_2_1);
         checkDistinctAggSingle(TestCase.CASE_2_2);
-        checkDistinctAggSingle(TestCase.CASE_2_3);
 
         checkDistinctAggHash(TestCase.CASE_2_1A);
         checkDistinctAggHash(TestCase.CASE_2_2A);
-        checkDistinctAggHash(TestCase.CASE_2_3A);
+
+        checkDistinctAggHash(TestCase.CASE_2_1B);
+        checkDistinctAggHash(TestCase.CASE_2_2B);
+
+        checkDistinctAggHash(TestCase.CASE_2_1C);
+        checkDistinctAggHash(TestCase.CASE_2_2C);
+
+        checkDistinctAggHash(TestCase.CASE_2_1D);
+        checkDistinctAggHash(TestCase.CASE_2_2D);
     }
 
     /**
@@ -92,6 +99,15 @@ public class MapReduceSortAggregatePlannerTest extends AbstractAggregatePlannerT
 
         checkSimpleAggHash(TestCase.CASE_3_1A);
         checkSimpleAggHash(TestCase.CASE_3_2A);
+
+        checkSimpleAggHash(TestCase.CASE_3_1B);
+        checkSimpleAggHash(TestCase.CASE_3_2B);
+
+        checkSimpleAggHash(TestCase.CASE_3_1C);
+        checkSimpleAggHash(TestCase.CASE_3_2C);
+
+        checkSimpleAggHash(TestCase.CASE_3_1D);
+        checkSimpleAggHash(TestCase.CASE_3_2D);
     }
 
     /**
@@ -104,6 +120,14 @@ public class MapReduceSortAggregatePlannerTest extends AbstractAggregatePlannerT
 
         checkSimpleAggWithGroupByHash(TestCase.CASE_5A);
         checkSimpleAggWithGroupByHash(TestCase.CASE_6A);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_5B);
+        checkSimpleAggWithGroupByHash(TestCase.CASE_6B);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_5C);
+        checkSimpleAggWithGroupByHash(TestCase.CASE_6C);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_5D);
     }
 
     /**
@@ -116,12 +140,22 @@ public class MapReduceSortAggregatePlannerTest extends AbstractAggregatePlannerT
         checkDistinctAggWithGroupBySingle(TestCase.CASE_7_1);
         checkDistinctAggWithGroupBySingle(TestCase.CASE_7_2);
         checkDistinctAggWithGroupBySingle(TestCase.CASE_7_3);
-        checkDistinctAggWithGroupBySingle(TestCase.CASE_7_4);
 
         checkDistinctAggWithGroupByHash(TestCase.CASE_7_1A);
         checkDistinctAggWithGroupByHash(TestCase.CASE_7_2A);
         checkDistinctAggWithGroupByHash(TestCase.CASE_7_3A);
-        checkDistinctAggWithGroupByHash(TestCase.CASE_7_4A);
+
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_1B);
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_2B);
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_3B);
+
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_1C);
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_2C);
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_3C);
+
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_1D);
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_2D);
+        checkDistinctAggWithGroupByHash(TestCase.CASE_7_3D);
     }
 
     /**
@@ -136,6 +170,15 @@ public class MapReduceSortAggregatePlannerTest extends AbstractAggregatePlannerT
 
         checkSimpleAggWithGroupByHash(TestCase.CASE_8_1A);
         checkSimpleAggWithGroupByHash(TestCase.CASE_8_2A);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_8_1B);
+        checkSimpleAggWithGroupByHash(TestCase.CASE_8_2B);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_8_1C);
+        checkSimpleAggWithGroupByHash(TestCase.CASE_8_2C);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_8_1D);
+        checkSimpleAggWithGroupByHash(TestCase.CASE_8_2D);
     }
 
     /**
@@ -152,6 +195,16 @@ public class MapReduceSortAggregatePlannerTest extends AbstractAggregatePlannerT
         checkAggWithGroupByIndexColumnsHash(TestCase.CASE_9A);
         checkAggWithGroupByIndexColumnsHash(TestCase.CASE_10A);
         checkAggWithGroupByIndexColumnsHash(TestCase.CASE_11A);
+
+        checkAggWithGroupByIndexColumnsHash(TestCase.CASE_9B);
+        checkAggWithGroupByIndexColumnsHash(TestCase.CASE_10B);
+        checkAggWithGroupByIndexColumnsHash(TestCase.CASE_11B);
+
+        checkAggWithGroupByIndexColumnsHash(TestCase.CASE_9C);
+        checkAggWithGroupByIndexColumnsHash(TestCase.CASE_10C);
+        checkAggWithGroupByIndexColumnsHash(TestCase.CASE_11C);
+
+        checkAggWithGroupByIndexColumnsHash(TestCase.CASE_9D);
     }
 
     /**
@@ -162,6 +215,9 @@ public class MapReduceSortAggregatePlannerTest extends AbstractAggregatePlannerT
         checkGroupWithNoAggregateSingle(TestCase.CASE_12);
 
         checkGroupWithNoAggregateHash(TestCase.CASE_12A);
+        checkGroupWithNoAggregateHash(TestCase.CASE_12B);
+        checkGroupWithNoAggregateHash(TestCase.CASE_12C);
+        checkGroupWithNoAggregateHash(TestCase.CASE_12D);
     }
 
     /**
@@ -169,33 +225,13 @@ public class MapReduceSortAggregatePlannerTest extends AbstractAggregatePlannerT
      */
     @Test
     public void distinctWithoutAggregateUseIndex() throws Exception {
-        assertPlan(TestCase.CASE_13,
-                nodeOrAnyChild(isInstanceOf(IgniteReduceSortAggregate.class)
-                        .and(not(hasAggregate()))
-                        .and(hasGroups())
-                        .and(input(isInstanceOf(IgniteMapSortAggregate.class)
-                                .and(not(hasAggregate()))
-                                .and(hasGroups())
-                                .and(input(isIndexScan("TEST", "idx_grp0_grp1")))
-                        ))
-                ),
-                disableRules
-        );
+        checkGroupWithNoAggregateUseIndexSingle(TestCase.CASE_13);
 
-        assertPlan(TestCase.CASE_13A,
-                nodeOrAnyChild(isInstanceOf(IgniteReduceSortAggregate.class)
-                        .and(not(hasAggregate()))
-                        .and(hasGroups())
-                        .and(input(isInstanceOf(IgniteExchange.class)
-                                .and(input(isInstanceOf(IgniteMapSortAggregate.class)
-                                        .and(not(hasAggregate()))
-                                        .and(hasGroups())
-                                        .and(input(isIndexScan("TEST", "idx_grp0_grp1")))
-                                ))
-                        ))
-                ),
-                disableRules
-        );
+        checkGroupWithNoAggregateUseIndexHash(TestCase.CASE_13A);
+        checkGroupWithNoAggregateUseIndexHash(TestCase.CASE_13B);
+
+        checkGroupWithNoAggregateUseIndexHash(TestCase.CASE_13C);
+        checkGroupWithNoAggregateUseIndexHash(TestCase.CASE_13D);
     }
 
     /**
@@ -205,6 +241,7 @@ public class MapReduceSortAggregatePlannerTest extends AbstractAggregatePlannerT
     public void subqueryWithAggregateInWhereClause() throws Exception {
         checkSimpleAggSingle(TestCase.CASE_14);
         checkSimpleAggHash(TestCase.CASE_14A);
+        checkSimpleAggHash(TestCase.CASE_14B);
     }
 
     /**
@@ -214,6 +251,7 @@ public class MapReduceSortAggregatePlannerTest extends AbstractAggregatePlannerT
     public void distinctAggregateInWhereClause() throws Exception {
         checkGroupWithNoAggregateSingle(TestCase.CASE_15);
         checkGroupWithNoAggregateHash(TestCase.CASE_15A);
+        checkGroupWithNoAggregateHash(TestCase.CASE_15B);
     }
 
     /**
@@ -242,6 +280,16 @@ public class MapReduceSortAggregatePlannerTest extends AbstractAggregatePlannerT
                 ),
                 ArrayUtils.concat(disableRules, additionalRulesToDisable)
         );
+        assertPlan(TestCase.CASE_16B,
+                nodeOrAnyChild(isInstanceOf(IgniteReduceSortAggregate.class)
+                        .and(input(isInstanceOf(IgniteExchange.class)
+                                .and(input(isInstanceOf(IgniteMapSortAggregate.class)
+                                        .and(input(isIndexScan("TEST", "idx_val0")))
+                                ))
+                        ))
+                ),
+                ArrayUtils.concat(disableRules, additionalRulesToDisable)
+        );
     }
 
     /**
@@ -280,6 +328,22 @@ public class MapReduceSortAggregatePlannerTest extends AbstractAggregatePlannerT
                 ),
                 disableRules
         );
+        assertPlan(TestCase.CASE_17B,
+                hasChildThat(isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)
+                        .and(input(1, isInstanceOf(IgniteReduceSortAggregate.class)
+                                .and(input(isInstanceOf(IgniteMapSortAggregate.class)
+                                        .and(input(isInstanceOf(IgniteLimit.class)
+                                                .and(input(isInstanceOf(IgniteExchange.class)
+                                                        .and(input(isInstanceOf(IgniteSort.class)
+                                                                .and(input(isTableScan("TEST")))
+                                                        ))
+                                                ))
+                                        ))
+                                ))
+                        ))
+                ),
+                disableRules
+        );
     }
 
     /**
@@ -294,6 +358,10 @@ public class MapReduceSortAggregatePlannerTest extends AbstractAggregatePlannerT
         checkGroupsWithOrderBySubsetOfGroupColumnsHash(TestCase.CASE_18_1A, TraitUtils.createCollation(List.of(0, 1)));
         checkGroupsWithOrderBySubsetOfGroupColumnsHash(TestCase.CASE_18_2A, TraitUtils.createCollation(List.of(1, 0)));
         checkGroupsWithOrderBySubsetOfGroupColumnsHash(TestCase.CASE_18_3A, TraitUtils.createCollation(List.of(1, 0)));
+
+        checkGroupsWithOrderBySubsetOfGroupColumnsHash(TestCase.CASE_18_1B, TraitUtils.createCollation(List.of(0, 1)));
+        checkGroupsWithOrderBySubsetOfGroupColumnsHash(TestCase.CASE_18_2B, TraitUtils.createCollation(List.of(1, 0)));
+        checkGroupsWithOrderBySubsetOfGroupColumnsHash(TestCase.CASE_18_3B, TraitUtils.createCollation(List.of(1, 0)));
     }
 
     /**
@@ -306,6 +374,9 @@ public class MapReduceSortAggregatePlannerTest extends AbstractAggregatePlannerT
 
         checkGroupsWithOrderBySubsetOfGroupColumnsHash(TestCase.CASE_19_1A, TraitUtils.createCollation(List.of(0, 1)));
         checkGroupsWithOrderBySubsetOfGroupColumnsHash(TestCase.CASE_19_2A, TraitUtils.createCollation(List.of(1, 0)));
+
+        checkGroupsWithOrderBySubsetOfGroupColumnsHash(TestCase.CASE_19_1B, TraitUtils.createCollation(List.of(0, 1)));
+        checkGroupsWithOrderBySubsetOfGroupColumnsHash(TestCase.CASE_19_2B, TraitUtils.createCollation(List.of(1, 0)));
     }
 
     /**
@@ -316,6 +387,7 @@ public class MapReduceSortAggregatePlannerTest extends AbstractAggregatePlannerT
         checkGroupsWithGroupBySubsetOfSortColumnsSingle(TestCase.CASE_20, TraitUtils.createCollation(List.of(0, 1)));
 
         checkGroupsWithGroupBySubsetOfSortColumnsHash(TestCase.CASE_20A, TraitUtils.createCollation(List.of(0, 1)));
+        checkGroupsWithGroupBySubsetOfSortColumnsHash(TestCase.CASE_20B, TraitUtils.createCollation(List.of(0, 1)));
     }
 
     /**
@@ -368,6 +440,10 @@ public class MapReduceSortAggregatePlannerTest extends AbstractAggregatePlannerT
                 .and(input(0, subtreePredicate))
                 .and(input(1, subtreePredicate))
         ), disableRules);
+        assertPlan(TestCase.CASE_21B, nodeOrAnyChild(isInstanceOf(IgniteMergeJoin.class)
+                .and(input(0, subtreePredicate))
+                .and(input(1, subtreePredicate))
+        ), disableRules);
     }
 
     /**
@@ -375,35 +451,46 @@ public class MapReduceSortAggregatePlannerTest extends AbstractAggregatePlannerT
      */
     @Test
     public void twoPhaseCountAgg() throws Exception {
-        Predicate<AggregateCall> countMap = (a) -> {
-            SqlAggFunction aggName = a.getAggregation();
-            return Objects.equals(aggName.getName(), "COUNT") && a.getArgList().equals(List.of(1));
-        };
+        Predicate<AggregateCall> countMap = (a) ->
+                Objects.equals(a.getAggregation().getName(), "COUNT") && a.getArgList().equals(List.of(1));
 
-        Predicate<AggregateCall> countReduce = (a) -> {
-            SqlAggFunction aggName = a.getAggregation();
-            return Objects.equals(aggName.getName(), "$SUM0") && a.getArgList().equals(List.of(1));
-        };
+        Predicate<AggregateCall> countReduce = (a) ->
+                Objects.equals(a.getAggregation().getName(), "$SUM0") && a.getArgList().equals(List.of(1));
 
-        assertPlan(TestCase.CASE_22, hasChildThat(isInstanceOf(IgniteReduceSortAggregate.class)
+        Predicate<RelNode> nonColocated = hasChildThat(isInstanceOf(IgniteReduceSortAggregate.class)
                 .and(in -> hasAggregates(countReduce).test(in.getAggregateCalls()))
                 .and(input(isInstanceOf(IgniteExchange.class)
                         .and(input(isInstanceOf(IgniteMapSortAggregate.class)
                                         .and(in -> hasAggregates(countMap).test(in.getAggCallList()))
                                 )
                         ))
-                )), disableRules);
+                ));
+
+        assertPlan(TestCase.CASE_22, nonColocated, disableRules);
+        assertPlan(TestCase.CASE_22A, nonColocated, disableRules);
+        assertPlan(TestCase.CASE_22B, nonColocated, disableRules);
+        assertPlan(TestCase.CASE_22C, nonColocated, disableRules);
     }
 
     /**
-     * Validates that AVG can not be used as two phase mode.
-     * Should be fixed with TODO https://issues.apache.org/jira/browse/IGNITE-20009
+     * Validates that AVG can not be used as two phase mode. Should be fixed with TODO https://issues.apache.org/jira/browse/IGNITE-20009
      */
     @Test
     public void testAvgAgg() {
         RuntimeException e = assertThrows(RuntimeException.class,
                 () -> assertPlan(TestCase.CASE_23, isInstanceOf(IgniteRel.class), disableRules));
+        assertThat(e.getMessage(), containsString("There are not enough rules to produce a node with desired properties"));
 
+        e = assertThrows(RuntimeException.class,
+                () -> assertPlan(TestCase.CASE_23A, isInstanceOf(IgniteRel.class), disableRules));
+        assertThat(e.getMessage(), containsString("There are not enough rules to produce a node with desired properties"));
+
+        e = assertThrows(RuntimeException.class,
+                () -> assertPlan(TestCase.CASE_23B, isInstanceOf(IgniteRel.class), disableRules));
+        assertThat(e.getMessage(), containsString("There are not enough rules to produce a node with desired properties"));
+
+        e = assertThrows(RuntimeException.class,
+                () -> assertPlan(TestCase.CASE_23C, isInstanceOf(IgniteRel.class), disableRules));
         assertThat(e.getMessage(), containsString("There are not enough rules to produce a node with desired properties"));
     }
 
@@ -627,6 +714,38 @@ public class MapReduceSortAggregatePlannerTest extends AbstractAggregatePlannerT
         );
     }
 
+    private void checkGroupWithNoAggregateUseIndexSingle(TestCase testCase) throws Exception {
+        assertPlan(testCase,
+                nodeOrAnyChild(isInstanceOf(IgniteReduceSortAggregate.class)
+                        .and(not(hasAggregate()))
+                        .and(hasGroups())
+                        .and(input(isInstanceOf(IgniteMapSortAggregate.class)
+                                .and(not(hasAggregate()))
+                                .and(hasGroups())
+                                .and(input(isIndexScan("TEST", "idx_grp0_grp1")))
+                        ))
+                ),
+                disableRules
+        );
+    }
+
+    private void checkGroupWithNoAggregateUseIndexHash(TestCase testCase) throws Exception {
+        assertPlan(testCase,
+                nodeOrAnyChild(isInstanceOf(IgniteReduceSortAggregate.class)
+                        .and(not(hasAggregate()))
+                        .and(hasGroups())
+                        .and(input(isInstanceOf(IgniteExchange.class)
+                                .and(input(isInstanceOf(IgniteMapSortAggregate.class)
+                                        .and(not(hasAggregate()))
+                                        .and(hasGroups())
+                                        .and(input(isIndexScan("TEST", "idx_grp0_grp1")))
+                                ))
+                        ))
+                ),
+                disableRules
+        );
+    }
+
     private void checkGroupsWithOrderBySubsetOfGroupColumnsSingle(TestCase testCase, RelCollation collation) throws Exception {
         assertPlan(testCase,
                 nodeOrAnyChild(isInstanceOf(IgniteReduceSortAggregate.class)
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/SetOpPlannerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/SetOpPlannerTest.java
index 71307b59bd..519f8b7dad 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/SetOpPlannerTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/SetOpPlannerTest.java
@@ -68,7 +68,10 @@ public class SetOpPlannerTest extends AbstractPlannerTest {
                 createTable("HASH_TBL1", IgniteDistributions.hash(List.of(0))),
                 createTable("AFFINITY_TBL2", IgniteDistributions.affinity(0, nextTableId(), DEFAULT_ZONE_ID)),
                 createTable("AFFINITY_TBL3", IgniteDistributions.affinity(1, nextTableId(), DEFAULT_ZONE_ID)),
-                createTable("AFFINITY_TBL4", IgniteDistributions.affinity(0, nextTableId(), DEFAULT_ZONE_ID + 1))
+                createTable("AFFINITY_TBL4", IgniteDistributions.affinity(0, nextTableId(), DEFAULT_ZONE_ID + 1)),
+                createTable("IDENTITY_TBL1", IgniteDistributions.identity(0)),
+                createTable("IDENTITY_TBL2", IgniteDistributions.identity(0)),
+                createTable("IDENTITY_TBL3", IgniteDistributions.identity(1))
         );
     }
 
@@ -229,6 +232,25 @@ public class SetOpPlannerTest extends AbstractPlannerTest {
         );
     }
 
+    /**
+     * Tests SET operations on two tables with single and identity distribution.
+     *
+     * <p>{@link Type#SINGLETON Single} distribution cannot be colocated with identity distribution.
+     *
+     * @throws Exception If failed.
+     */
+    @ParameterizedTest
+    @EnumSource
+    public void testSetOpSingleAndIdentity(SetOp setOp) throws Exception {
+        String sql = "SELECT * FROM single_tbl1 "
+                + setOp(setOp)
+                + "SELECT * FROM identity_tbl1 ";
+
+        assertPlan(sql, publicSchema, isInstanceOf(setOp.colocated)
+                .and(hasDistribution(IgniteDistributions.single()))
+                .and(input(0, isTableScan("single_tbl1")))
+                .and(input(1, hasChildThat(isTableScan("identity_tbl1")))));
+    }
 
     /**
      * Tests SET operations on tables with the same affinity distribution.
@@ -479,6 +501,23 @@ public class SetOpPlannerTest extends AbstractPlannerTest {
                         .and(input(3, isTableScan("affinity_tbl2")))
                 ))
         );
+
+        sql = "SELECT * FROM random_tbl1 "
+                + setOpAll(setOp)
+                + "SELECT * FROM random_tbl2 "
+                + setOpAll(setOp)
+                + "SELECT * FROM identity_tbl1 "
+                + setOpAll(setOp)
+                + "SELECT * FROM identity_tbl2 ";
+
+        assertPlan(sql, publicSchema, isInstanceOf(setOp.reduce).and(IgniteSetOp::all)
+                .and(hasChildThat(isInstanceOf(setOp.map)
+                        .and(input(0, isTableScan("random_tbl1")))
+                        .and(input(1, isTableScan("random_tbl2")))
+                        .and(input(2, isTableScan("identity_tbl1")))
+                        .and(input(3, isTableScan("identity_tbl2")))
+                ))
+        );
     }
 
     /**
@@ -502,6 +541,157 @@ public class SetOpPlannerTest extends AbstractPlannerTest {
                         .and(input(2, isTableScan("affinity_tbl1")))
                 ))
         );
+
+        sql = "SELECT * FROM random_tbl1 "
+                + setOpAll(setOp)
+                + "SELECT * FROM random_tbl2 "
+                + setOp(setOp)
+                + "SELECT * FROM identity_tbl1 ";
+
+        assertPlan(sql, publicSchema, isInstanceOf(setOp.reduce).and(n -> !n.all())
+                .and(hasChildThat(isInstanceOf(setOp.map)
+                        .and(input(0, isTableScan("random_tbl1")))
+                        .and(input(1, isTableScan("random_tbl2")))
+                        .and(input(2, isTableScan("identity_tbl1")))
+                ))
+        );
+    }
+
+    /**
+     * Tests SET operations on tables with the same identity distribution.
+     *
+     * <p>The operation is considered colocated because the tables are
+     * compared against the corresponding collocation columns.
+     *
+     * @throws Exception If failed.
+     */
+    @ParameterizedTest
+    @EnumSource
+    public void testSetOpIdentity(SetOp setOp) throws Exception {
+        String sql = "SELECT * FROM identity_tbl1 "
+                + setOp(setOp)
+                + "SELECT * FROM identity_tbl2 ";
+
+        assertPlan(sql, publicSchema, isInstanceOf(IgniteExchange.class)
+                .and(input(isInstanceOf(setOp.colocated)
+                        .and(hasDistribution(IgniteDistributions.identity(0)))
+                        .and(input(0, isTableScan("identity_tbl1")))
+                        .and(input(1, isTableScan("identity_tbl2")))
+                ))
+        );
+    }
+
+    /**
+     * Tests SET operations on two tables with identity and broadcast distribution.
+     *
+     * <p>The operation is considered colocated because {@link Type#BROADCAST_DISTRIBUTED broadcast}
+     * distribution satisfies any other distribution.
+     *
+     * @throws Exception If failed.
+     */
+    @ParameterizedTest
+    @EnumSource
+    public void testSetOpIdentityAndBroadcast(SetOp setOp) throws Exception {
+        String sql = "SELECT * FROM identity_tbl1 "
+                + setOp(setOp)
+                + "SELECT * FROM broadcast_tbl1 ";
+
+        assertPlan(sql, publicSchema, isInstanceOf(IgniteExchange.class)
+                .and(input(isInstanceOf(setOp.colocated)
+                        .and(hasDistribution(IgniteDistributions.identity(0)))
+                        .and(input(0, isTableScan("identity_tbl1")))
+                        .and(input(1, isInstanceOf(IgniteTrimExchange.class)
+                                .and(input(isTableScan("broadcast_tbl1")))
+                        ))
+                ))
+        );
+    }
+
+    /**
+     * Tests SET operations on tables with different identity distribution.
+     *
+     * <p>Different identity distributions cannot be colocated.
+     *
+     * @throws Exception If failed.
+     */
+    @ParameterizedTest
+    @EnumSource
+    public void testSetOpNonColocatedIdentity(SetOp setOp) throws Exception {
+        String sql = "SELECT * FROM identity_tbl1 "
+                + setOp(setOp)
+                + "SELECT * FROM identity_tbl3 ";
+
+        assertPlan(sql, publicSchema, isInstanceOf(setOp.reduce)
+                .and(hasChildThat(isInstanceOf(setOp.map)
+                        .and(input(0, isTableScan("identity_tbl1")))
+                        .and(input(1, isTableScan("identity_tbl3")))
+                ))
+        );
+    }
+
+    /**
+     * Tests SET operations on two tables with affinity and identity distribution.
+     *
+     * <p>Affinity distribution can not be colocated with identity distribution.
+     *
+     * @throws Exception If failed.
+     */
+    @ParameterizedTest
+    @EnumSource
+    public void testSetOpIdentityAndAffinity(SetOp setOp) throws Exception {
+        String sql = "SELECT * FROM identity_tbl1 "
+                + setOp(setOp)
+                + "SELECT * FROM affinity_tbl1 ";
+
+
+        assertPlan(sql, publicSchema, isInstanceOf(setOp.reduce)
+                .and(hasChildThat(isInstanceOf(setOp.map)
+                        .and(input(0, isTableScan("identity_tbl1")))
+                        .and(input(1, isTableScan("affinity_tbl1")))
+                ))
+        );
+
+        sql = "SELECT * FROM affinity_tbl1 "
+                + setOp(setOp)
+                + "SELECT * FROM identity_tbl1 ";
+
+
+        assertPlan(sql, publicSchema, isInstanceOf(setOp.reduce)
+                .and(hasChildThat(isInstanceOf(setOp.map)
+                        .and(input(0, isTableScan("affinity_tbl1")))
+                        .and(input(1, isTableScan("identity_tbl1")))
+                ))
+        );
+    }
+
+    /**
+     * Tests two SET operations (nested and outer) on two tables with the same identity distribution.
+     *
+     * <p>Nested operation is considered colocated because the tables are compared against the corresponding collocation columns.
+     * Outer operation considered colocated because the result of nested operation must have the distribution of one of the participating
+     * tables.
+     *
+     * @throws Exception If failed.
+     */
+    @ParameterizedTest
+    @EnumSource
+    public void testSetOpIdentityNested(SetOp setOp) throws Exception {
+        String sql = "SELECT * FROM identity_tbl2 " + setOp(setOp) + "("
+                + "   SELECT * FROM identity_tbl1 "
+                + setOp(setOp)
+                + "   SELECT * FROM identity_tbl2"
+                + ")";
+
+        assertPlan(sql, publicSchema, isInstanceOf(IgniteExchange.class)
+                        .and(input(isInstanceOf(setOp.colocated)
+                                .and(input(0, isTableScan("identity_tbl2")))
+                                .and(input(1, isInstanceOf(setOp.colocated)
+                                        .and(input(0, isTableScan("identity_tbl1")))
+                                        .and(input(1, isTableScan("identity_tbl2")))
+                                ))
+                        )),
+                "MinusMergeRule", "IntersectMergeRule"
+        );
     }
 
     private String setOp(SetOp setOp) {
diff --git a/modules/runner/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/CursorUtils.java b/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/CursorUtils.java
similarity index 100%
rename from modules/runner/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/CursorUtils.java
rename to modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/CursorUtils.java
diff --git a/modules/runner/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/MetadataMatcher.java b/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/MetadataMatcher.java
similarity index 100%
rename from modules/runner/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/MetadataMatcher.java
rename to modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/MetadataMatcher.java
diff --git a/modules/runner/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/NativeTypeWrapper.java b/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/NativeTypeWrapper.java
similarity index 100%
rename from modules/runner/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/NativeTypeWrapper.java
rename to modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/NativeTypeWrapper.java
diff --git a/modules/runner/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java b/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java
similarity index 100%
rename from modules/runner/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java
rename to modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java