You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@ignite.apache.org by GitBox <gi...@apache.org> on 2021/06/01 15:07:39 UTC

[GitHub] [ignite] zstan opened a new pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

zstan opened a new pull request #9143:
URL: https://github.com/apache/ignite/pull/9143


   Thank you for submitting the pull request to the Apache Ignite.
   
   In order to streamline the review of the contribution 
   we ask you to ensure the following steps have been taken:
   
   ### The Contribution Checklist
   - [ ] There is a single JIRA ticket related to the pull request. 
   - [ ] The web-link to the pull request is attached to the JIRA ticket.
   - [ ] The JIRA ticket has the _Patch Available_ state.
   - [ ] The pull request body describes changes that have been made. 
   The description explains _WHAT_ and _WHY_ was made instead of _HOW_.
   - [ ] The pull request title is treated as the final commit message. 
   The following pattern must be used: `IGNITE-XXXX Change summary` where `XXXX` - number of JIRA issue.
   - [ ] A reviewer has been mentioned through the JIRA comments 
   (see [the Maintainers list](https://cwiki.apache.org/confluence/display/IGNITE/How+to+Contribute#HowtoContribute-ReviewProcessandMaintainers)) 
   - [ ] The pull request has been checked by the Teamcity Bot and 
   the `green visa` attached to the JIRA ticket (see [TC.Bot: Check PR](https://mtcga.gridgain.com/prs.html))
   
   ### Notes
   - [How to Contribute](https://cwiki.apache.org/confluence/display/IGNITE/How+to+Contribute)
   - [Coding abbreviation rules](https://cwiki.apache.org/confluence/display/IGNITE/Abbreviation+Rules)
   - [Coding Guidelines](https://cwiki.apache.org/confluence/display/IGNITE/Coding+Guidelines)
   - [Apache Ignite Teamcity Bot](https://cwiki.apache.org/confluence/display/IGNITE/Apache+Ignite+Teamcity+Bot)
   
   If you need any help, please email dev@ignite.apache.org or ask anу advice on http://asf.slack.com _#ignite_ channel.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] zstan commented on a change in pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
zstan commented on a change in pull request #9143:
URL: https://github.com/apache/ignite/pull/9143#discussion_r659914520



##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMergeJoin.java
##########
@@ -173,24 +184,34 @@ else if (isPrefix(rightCollation.getKeys(), joinInfo.rightKeys))// preserve righ
         Map<Integer, Integer> rightToLeft = joinInfo.pairs().stream()
             .collect(Collectors.toMap(p -> p.target, p -> p.source));
 
-        List<Integer> collationLeftPrj = new ArrayList<>();
+        List<Integer> collationLeftPrj = new ArrayList<>(collation.getKeys().size());
 
         for (Integer c : collation.getKeys()) {
             collationLeftPrj.add(
                 c >= rightOff ? rightToLeft.get(c - rightOff) : c

Review comment:
       i extend tests but have no reproducer for this assumption ...




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] korlov42 commented on a change in pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
korlov42 commented on a change in pull request #9143:
URL: https://github.com/apache/ignite/pull/9143#discussion_r680949704



##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMergeJoin.java
##########
@@ -162,79 +176,297 @@ else if (isPrefix(rightCollation.getKeys(), joinInfo.rightKeys))// preserve righ
 
     /** {@inheritDoc} */
     @Override public Pair<RelTraitSet, List<RelTraitSet>> passThroughCollation(
-        RelTraitSet nodeTraits,
+        RelTraitSet required,
         List<RelTraitSet> inputTraits
     ) {
-        RelCollation collation = TraitUtils.collation(nodeTraits);
-        RelTraitSet left = inputTraits.get(0), right = inputTraits.get(1);
+        // Required collation keys can be subset or superset of merge join keys.
+        RelCollation collation = TraitUtils.collation(required);
+        int leftInputFieldCount = left.getRowType().getFieldCount();
 
-        int rightOff = this.left.getRowType().getFieldCount();
+        List<Integer> reqKeys = RelCollations.ordinals(collation);
+        List<Integer> leftKeys = joinInfo.leftKeys.toIntegerList();
+        List<Integer> rightKeys =
+            joinInfo.rightKeys.incr(leftInputFieldCount).toIntegerList();
 
-        Map<Integer, Integer> rightToLeft = joinInfo.pairs().stream()
-            .collect(Collectors.toMap(p -> p.target, p -> p.source));
+        ImmutableBitSet reqKeySet = ImmutableBitSet.of(reqKeys);
+        ImmutableBitSet leftKeySet = ImmutableBitSet.of(joinInfo.leftKeys);
+        ImmutableBitSet rightKeySet = ImmutableBitSet.of(joinInfo.rightKeys)
+            .shift(leftInputFieldCount);
 
-        List<Integer> collationLeftPrj = new ArrayList<>();
+        if (reqKeySet.equals(leftKeySet)) {
+            // if sort keys equal to left join keys, we can pass through all collations directly.
+            Pair<RelTraitSet, List<RelTraitSet>> transformedCollations = transformCollations(required, leftInputFieldCount);
 
-        for (Integer c : collation.getKeys()) {
-            collationLeftPrj.add(
-                c >= rightOff ? rightToLeft.get(c - rightOff) : c
-            );
+            if (transformedCollations != null)
+                return transformedCollations;
+
+            Mappings.TargetMapping mapping = buildProjectionMapping(true);
+            RelCollation rightCollation = collation.apply(mapping);
+
+            if (debugOutput)
+                System.err.println("1 " + TraitUtils.collation(required) + " l: " +
+                    TraitUtils.collation(required) + " r: " + rightCollation);
+
+            return Pair.of(
+                required, ImmutableList.of(required, required.replace(rightCollation)));

Review comment:
       you can't just pass required traits for every input




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] korlov42 commented on a change in pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
korlov42 commented on a change in pull request #9143:
URL: https://github.com/apache/ignite/pull/9143#discussion_r699378865



##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/AbstractIgniteJoin.java
##########
@@ -90,7 +90,7 @@ protected AbstractIgniteJoin(RelOptCluster cluster, RelTraitSet traitSet, RelNod
         // TODO set NullDirection.LAST for insufficient fields instead of erasing collation.

Review comment:
       I've removed this, since it was outdated




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] zstan commented on a change in pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
zstan commented on a change in pull request #9143:
URL: https://github.com/apache/ignite/pull/9143#discussion_r659914520



##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMergeJoin.java
##########
@@ -173,24 +184,34 @@ else if (isPrefix(rightCollation.getKeys(), joinInfo.rightKeys))// preserve righ
         Map<Integer, Integer> rightToLeft = joinInfo.pairs().stream()
             .collect(Collectors.toMap(p -> p.target, p -> p.source));
 
-        List<Integer> collationLeftPrj = new ArrayList<>();
+        List<Integer> collationLeftPrj = new ArrayList<>(collation.getKeys().size());
 
         for (Integer c : collation.getKeys()) {
             collationLeftPrj.add(
                 c >= rightOff ? rightToLeft.get(c - rightOff) : c

Review comment:
       i extend tests but have no reproducer for this assumption ...

##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMergeJoin.java
##########
@@ -173,24 +184,34 @@ else if (isPrefix(rightCollation.getKeys(), joinInfo.rightKeys))// preserve righ
         Map<Integer, Integer> rightToLeft = joinInfo.pairs().stream()
             .collect(Collectors.toMap(p -> p.target, p -> p.source));
 
-        List<Integer> collationLeftPrj = new ArrayList<>();
+        List<Integer> collationLeftPrj = new ArrayList<>(collation.getKeys().size());
 
         for (Integer c : collation.getKeys()) {
             collationLeftPrj.add(
                 c >= rightOff ? rightToLeft.get(c - rightOff) : c
             );
         }
 
-        boolean preserveNodeCollation = false;
+        boolean preserveNodeCollation = isPrefix(collationLeftPrj, joinInfo.leftKeys);
 
         List<Integer> newLeftCollation, newRightCollation;
 
         Map<Integer, Integer> leftToRight = joinInfo.pairs().stream()
             .collect(Collectors.toMap(p -> p.source, p -> p.target));
 
-        if (isPrefix(collationLeftPrj, joinInfo.leftKeys)) { // preserve collation
-            newLeftCollation = new ArrayList<>();
-            newRightCollation = new ArrayList<>();
+        if (joinType == RIGHT || joinType == JoinRelType.FULL) {
+            for (RelFieldCollation field : collation.getFieldCollations()) {
+                if (RelFieldCollation.NullDirection.LAST != field.nullDirection) {
+                    preserveNodeCollation = false;
+                    break;
+                }
+            }
+        }
+
+        // we can`t preserve collation in case of full/right joins.
+        if (preserveNodeCollation) { // preserve collation
+            newLeftCollation = new ArrayList<>(collation.getKeys().size());
+            newRightCollation = new ArrayList<>(collation.getKeys().size());
 
             int ind = 0;
             for (Integer c : collation.getKeys()) {

Review comment:
       i extend tests but they still ok.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] zstan commented on a change in pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
zstan commented on a change in pull request #9143:
URL: https://github.com/apache/ignite/pull/9143#discussion_r659914801



##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMergeJoin.java
##########
@@ -173,24 +184,34 @@ else if (isPrefix(rightCollation.getKeys(), joinInfo.rightKeys))// preserve righ
         Map<Integer, Integer> rightToLeft = joinInfo.pairs().stream()
             .collect(Collectors.toMap(p -> p.target, p -> p.source));
 
-        List<Integer> collationLeftPrj = new ArrayList<>();
+        List<Integer> collationLeftPrj = new ArrayList<>(collation.getKeys().size());
 
         for (Integer c : collation.getKeys()) {
             collationLeftPrj.add(
                 c >= rightOff ? rightToLeft.get(c - rightOff) : c
             );
         }
 
-        boolean preserveNodeCollation = false;
+        boolean preserveNodeCollation = isPrefix(collationLeftPrj, joinInfo.leftKeys);
 
         List<Integer> newLeftCollation, newRightCollation;
 
         Map<Integer, Integer> leftToRight = joinInfo.pairs().stream()
             .collect(Collectors.toMap(p -> p.source, p -> p.target));
 
-        if (isPrefix(collationLeftPrj, joinInfo.leftKeys)) { // preserve collation
-            newLeftCollation = new ArrayList<>();
-            newRightCollation = new ArrayList<>();
+        if (joinType == RIGHT || joinType == JoinRelType.FULL) {
+            for (RelFieldCollation field : collation.getFieldCollations()) {
+                if (RelFieldCollation.NullDirection.LAST != field.nullDirection) {
+                    preserveNodeCollation = false;
+                    break;
+                }
+            }
+        }
+
+        // we can`t preserve collation in case of full/right joins.
+        if (preserveNodeCollation) { // preserve collation
+            newLeftCollation = new ArrayList<>(collation.getKeys().size());
+            newRightCollation = new ArrayList<>(collation.getKeys().size());
 
             int ind = 0;
             for (Integer c : collation.getKeys()) {

Review comment:
       i extend tests but they still ok.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] tledkov-gridgain commented on a change in pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
tledkov-gridgain commented on a change in pull request #9143:
URL: https://github.com/apache/ignite/pull/9143#discussion_r687637844



##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdCollation.java
##########
@@ -548,9 +548,9 @@ public int compare(List<RexLiteral> o1, List<RexLiteral> o2) {
             case FULL:
                 for (RelCollation collation : leftCollations) {
                     for (RelFieldCollation field : collation.getFieldCollations()) {
-                        if (!(RelFieldCollation.NullDirection.LAST == field.nullDirection)) {
+                        if (!(RelFieldCollation.NullDirection.LAST.nullComparison ==

Review comment:
       If any method `enumerable<NodeName>` is called by Ignite optimizer?
   Should they are removed?

##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdCollation.java
##########
@@ -548,9 +548,9 @@ public int compare(List<RexLiteral> o1, List<RexLiteral> o2) {
             case FULL:
                 for (RelCollation collation : leftCollations) {
                     for (RelFieldCollation field : collation.getFieldCollations()) {
-                        if (!(RelFieldCollation.NullDirection.LAST == field.nullDirection)) {
+                        if (!(RelFieldCollation.NullDirection.LAST.nullComparison ==

Review comment:
       Is any method `enumerable<NodeName>` is called by Ignite optimizer?
   Should they are removed?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] tledkov-gridgain commented on a change in pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
tledkov-gridgain commented on a change in pull request #9143:
URL: https://github.com/apache/ignite/pull/9143#discussion_r687657904



##########
File path: modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/MergeJoinPlannerTest.java
##########
@@ -0,0 +1,2799 @@
+/*
+ * 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.processors.query.calcite.planner;
+
+import java.util.List;
+
+import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.RelFieldCollation;
+import org.apache.calcite.rel.core.Join;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSort;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
+import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
+import org.junit.Test;
+
+import static org.apache.calcite.rel.RelFieldCollation.Direction.ASCENDING;
+import static org.apache.calcite.rel.RelFieldCollation.Direction.DESCENDING;
+
+/** MergeJoin planner test. */
+public class MergeJoinPlannerTest extends AbstractPlannerTest {
+    /** Only MergeJoin encourage. */
+    private static final String[] DISABLED_RULES = {
+        "NestedLoopJoinConverter",
+        "CorrelatedNestedLoopJoin",
+        "FilterSpoolMergeRule",
+        "JoinCommuteRule"
+    };
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation consisted of join keys of left table should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft1() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1, LEFT_T.c2";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation consisted of join keys of left table should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft2() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1 DESC, LEFT_T.c2 DESC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, DESCENDING),
+            new RelFieldCollation(1, DESCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation consisted of join keys of left table should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft3() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1 DESC, LEFT_T.c2 ASC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, DESCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation consisted of join keys of left table should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft4() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1 ASC NULLS LAST, LEFT_T.c2 ASC NULLS LAST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation consisted of join keys of left table should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft5() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1 ASC NULLS FIRST, LEFT_T.c2 ASC NULLS LAST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is superset of join keys should be propagated as is,
+     * if it doesn't include fields from right table.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft6() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1, LEFT_T.c2, LEFT_T.c3";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            sortNodes.get(0).collation()
+        );
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortNodes.get(1).collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is superset of join keys should be propagated as is,
+     * if it doesn't include fields from right table.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft7() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1 DESC, LEFT_T.c2 DESC, LEFT_T.c3 DESC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, DESCENDING),
+                new RelFieldCollation(2, DESCENDING)
+            ),
+            sortNodes.get(0).collation()
+        );
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, DESCENDING)
+            ),
+            sortNodes.get(1).collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is superset of join keys should be propagated as is,
+     * if it doesn't include fields from right table.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft8() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1 DESC, LEFT_T.c2 ASC, LEFT_T.c3 DESC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, DESCENDING)
+            ),
+            sortNodes.get(0).collation()
+        );
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortNodes.get(1).collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is superset of join keys should be propagated as is,
+     * if it doesn't include fields from right table.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft9() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1 ASC NULLS LAST, LEFT_T.c2 ASC NULLS LAST, LEFT_T.c3 ASC NULLS LAST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            sortNodes.get(0).collation()
+        );
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortNodes.get(1).collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is superset of join keys should be propagated as is,
+     * if it doesn't include fields from right table.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft10() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1 ASC NULLS FIRST, LEFT_T.c2 ASC NULLS LAST, LEFT_T.c3 ASC NULLS FIRST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING, RelFieldCollation.NullDirection.FIRST)
+            ),
+            sortNodes.get(0).collation()
+        );
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortNodes.get(1).collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is superset of join keys, but its prefix
+     * contains columns outside of join keys, can't be propagated.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft11() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c3, LEFT_T.c2, LEFT_T.c1";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        IgniteSort topSortNode = sortOnTopOfJoin(rel);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(2, ASCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(0, ASCENDING)
+            ),
+            topSortNode.collation()
+        );
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortNodes.get(0).collation()
+        );
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortNodes.get(1).collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is subset of join keys should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft12() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 " +
+            " order by LEFT_T.c1, LEFT_T.c2";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING),
+            new RelFieldCollation(2, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is subset of join keys should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft13() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 " +
+            " order by LEFT_T.c1 DESC, LEFT_T.c2 DESC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, DESCENDING),
+            new RelFieldCollation(1, DESCENDING),
+            new RelFieldCollation(2, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is subset of join keys should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft14() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 " +
+            " order by LEFT_T.c1 DESC, LEFT_T.c2 ASC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, DESCENDING),
+            new RelFieldCollation(1, ASCENDING),
+            new RelFieldCollation(2, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is subset of join keys should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft15() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 " +
+            " order by LEFT_T.c1 ASC NULLS LAST, LEFT_T.c2 ASC NULLS LAST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING),
+            new RelFieldCollation(2, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is subset of join keys should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft16() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 " +
+            " order by LEFT_T.c1 ASC NULLS FIRST, LEFT_T.c2 ASC NULLS LAST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+            new RelFieldCollation(1, ASCENDING),
+            new RelFieldCollation(2, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation consisted of join keys only should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByRight1() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1, RIGHT_T.c2";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation consisted of join keys only should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByRight2() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1 DESC, RIGHT_T.c2 DESC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, DESCENDING),
+            new RelFieldCollation(1, DESCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation consisted of join keys only should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByRight3() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1 DESC, RIGHT_T.c2 ASC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, DESCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation consisted of join keys only should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByRight4() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1 ASC NULLS LAST, RIGHT_T.c2 ASC NULLS LAST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation consisted of join keys only should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByRight5() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1 ASC NULLS FIRST, RIGHT_T.c2 ASC NULLS LAST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * Any collation that contains column from right table which is not part
+     * of join keys can't be propagated.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByRight6() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1, RIGHT_T.c2, RIGHT_T.c3";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        IgniteSort topSortNode = sortOnTopOfJoin(rel);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(3, ASCENDING),
+                new RelFieldCollation(4, ASCENDING),
+                new RelFieldCollation(5, ASCENDING)
+            ),
+            topSortNode.collation()
+        );
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expectedBottomCollation = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expectedBottomCollation, sortNodes.get(0).collation());
+        assertEquals(expectedBottomCollation, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is subset of join keys should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByRight7() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 " +
+            " order by RIGHT_T.c1, RIGHT_T.c2";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING),
+            new RelFieldCollation(2, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is subset of join keys should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByRight8() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 " +
+            " order by RIGHT_T.c1 DESC, RIGHT_T.c2 DESC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, DESCENDING),
+            new RelFieldCollation(1, DESCENDING),
+            new RelFieldCollation(2, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is subset of join keys should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByRight9() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 " +
+            " order by RIGHT_T.c1 DESC, RIGHT_T.c2 ASC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, DESCENDING),
+            new RelFieldCollation(1, ASCENDING),
+            new RelFieldCollation(2, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is subset of join keys should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByRight10() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 " +
+            " order by RIGHT_T.c1 ASC NULLS LAST, RIGHT_T.c2 ASC NULLS LAST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING),
+            new RelFieldCollation(2, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is subset of join keys should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByRight11() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 " +
+            " order by RIGHT_T.c1 ASC NULLS FIRST, RIGHT_T.c2 ASC NULLS LAST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+            new RelFieldCollation(1, ASCENDING),
+            new RelFieldCollation(2, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of right join, collation consisted of left keys can't be propagated.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testRightPassThroughOrderByLeft1() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            " right join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1, LEFT_T.c2";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        IgniteSort topSortNode = sortOnTopOfJoin(rel);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            topSortNode.collation()
+        );
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of right join, collation that is superset of join keys can't be propagated.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testRightPassThroughOrderByLeft2() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            " right join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1, LEFT_T.c2, LEFT_T.c3";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        IgniteSort topSortNode = sortOnTopOfJoin(rel);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            topSortNode.collation()
+        );
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of right join, collation consisted of join keys only should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testRightPassThroughOrderByRight1() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            " right join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1, RIGHT_T.c2";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of right join, collation consisted of join keys only should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testRightPassThroughOrderByRight2() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            " right join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1 DESC, RIGHT_T.c2 DESC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, DESCENDING),
+            new RelFieldCollation(1, DESCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of right join, collation consisted of join keys only should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testRightPassThroughOrderByRight3() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            " right join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1 DESC, RIGHT_T.c2 ASC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, DESCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of right join, collation consisted of join keys only should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testRightPassThroughOrderByRight4() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            " right join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1 ASC NULLS LAST, RIGHT_T.c2 ASC NULLS LAST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of right join, collation consisted of join keys only should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testRightPassThroughOrderByRight5() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            " right join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1 ASC NULLS FIRST, RIGHT_T.c2 ASC NULLS LAST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of left join, collation consisted of join keys of right table can't be propagated.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testLeftPassThroughOrderByRight1() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  left join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1, RIGHT_T.c2";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(3, ASCENDING),
+                new RelFieldCollation(4, ASCENDING)
+            ),
+            sortOnTopOfJoin(rel).collation()
+        );
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of left join, collation consisted of join keys of right table can't be propagated.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testLeftPassThroughOrderByRight2() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  left join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 " +
+            " order by RIGHT_T.c1, RIGHT_T.c2";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(3, ASCENDING),
+                new RelFieldCollation(4, ASCENDING)
+            ),
+            sortOnTopOfJoin(rel).collation()
+        );
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING),
+            new RelFieldCollation(2, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of full join, any collation can't be propagated.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testFullPassThroughOrderByLeft1() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  full join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1, LEFT_T.c2";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfJoin(rel).collation()
+        );
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of full join, any collation can't be propagated.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testFullPassThroughOrderByRight1() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  full join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1, RIGHT_T.c2";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(3, ASCENDING),
+                new RelFieldCollation(4, ASCENDING)
+            ),
+            sortOnTopOfJoin(rel).collation()
+        );
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset or equal to join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft1() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "LEFT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset or equal to join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft2() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, DESCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "LEFT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, DESCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset or equal to join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft3() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "LEFT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset or equal to join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft4() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.LAST),
+                new RelFieldCollation(1, ASCENDING, RelFieldCollation.NullDirection.LAST)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "LEFT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset or equal to join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft5() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "LEFT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset or equal to join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft6() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "LEFT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset or equal to join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft7() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, DESCENDING),
+                new RelFieldCollation(2, DESCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "LEFT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, DESCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset or equal to join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft8() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "LEFT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset or equal, but its prefix contains columns
+     * outside of join keys, can't be derived.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft9() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(2, ASCENDING),
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "LEFT_T").collation()
+        );
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset or equal to join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft10() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "LEFT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is subset of join keys, is not suitable.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft11() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "LEFT_T").collation()
+        );
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset of join keys and has a common prefix could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft12() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1, LEFT_T.c2, LEFT_T.c3";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        assertNull(sortOnTopOfScan(rel, "LEFT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset of join keys, and there is no a common prefix,
+     * could not be derived.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft13() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(2, ASCENDING),
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c3, LEFT_T.c1, LEFT_T.c2";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(2, ASCENDING),
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfJoin(rel).collation()
+        );
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortOnTopOfScan(rel, "LEFT_T").collation());
+        assertEquals(expected, sortOnTopOfScan(rel, "RIGHT_T").collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, right collation that consists of join keys only could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveRight1() throws Exception {
+        TestTable right = createTable("RIGHT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        right.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            right
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "RIGHT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "LEFT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, right collation that consists of join keys only could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveRight2() throws Exception {
+        TestTable right = createTable("RIGHT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        right.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, DESCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            right
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "RIGHT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, DESCENDING)
+            ),
+            sortOnTopOfScan(rel, "LEFT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, right collation that consists of join keys only could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveRight3() throws Exception {
+        TestTable right = createTable("RIGHT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        right.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            right
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "RIGHT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "LEFT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, right collation that consists of join keys only could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveRight4() throws Exception {
+        TestTable right = createTable("RIGHT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        right.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            right
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "RIGHT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "LEFT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, right collation that consists of join keys only could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveRight5() throws Exception {
+        TestTable right = createTable("RIGHT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        right.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            right
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "RIGHT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "LEFT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, right collation that is superset of join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveRight6() throws Exception {
+        TestTable right = createTable("RIGHT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        right.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            right
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "RIGHT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "LEFT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, right collation that is superset of join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveRight7() throws Exception {
+        TestTable right = createTable("RIGHT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        right.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, DESCENDING),
+                new RelFieldCollation(2, DESCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            right
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "RIGHT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, DESCENDING)
+            ),
+            sortOnTopOfScan(rel, "LEFT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, right collation that is superset of join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveRight8() throws Exception {
+        TestTable right = createTable("RIGHT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        right.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            right
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "RIGHT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "LEFT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, right collation that is superset of join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveRight9() throws Exception {
+        TestTable right = createTable("RIGHT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        right.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            right
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "RIGHT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "LEFT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, right collation that is superset of join keys could be derived as is.

Review comment:
       Looks like the comment doesn't describe the test case completely.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] korlov42 commented on a change in pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
korlov42 commented on a change in pull request #9143:
URL: https://github.com/apache/ignite/pull/9143#discussion_r684158253



##########
File path: modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessorTest.java
##########
@@ -481,6 +495,310 @@ private void populateTables() throws InterruptedException {
         awaitPartitionMapExchange(true, true, null);
     }
 
+    /** */
+    @Test
+    public void testSingleOrdering() throws IgniteInterruptedCheckedException {

Review comment:
       Let's move all the tests to the to a separate JoinIntegrationTest class




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] korlov42 commented on a change in pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
korlov42 commented on a change in pull request #9143:
URL: https://github.com/apache/ignite/pull/9143#discussion_r699378865



##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/AbstractIgniteJoin.java
##########
@@ -90,7 +90,7 @@ protected AbstractIgniteJoin(RelOptCluster cluster, RelTraitSet traitSet, RelNod
         // TODO set NullDirection.LAST for insufficient fields instead of erasing collation.

Review comment:
       I've removed this, since it was outdated




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] tledkov-gridgain merged pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
tledkov-gridgain merged pull request #9143:
URL: https://github.com/apache/ignite/pull/9143


   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] korlov42 commented on a change in pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
korlov42 commented on a change in pull request #9143:
URL: https://github.com/apache/ignite/pull/9143#discussion_r695756692



##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMergeJoin.java
##########
@@ -162,74 +166,82 @@ else if (isPrefix(rightCollation.getKeys(), joinInfo.rightKeys))// preserve righ
 
     /** {@inheritDoc} */
     @Override public Pair<RelTraitSet, List<RelTraitSet>> passThroughCollation(
-        RelTraitSet nodeTraits,
+        RelTraitSet required,
         List<RelTraitSet> inputTraits
     ) {
-        RelCollation collation = TraitUtils.collation(nodeTraits);
-        RelTraitSet left = inputTraits.get(0), right = inputTraits.get(1);
-
-        int rightOff = this.left.getRowType().getFieldCount();
-
-        Map<Integer, Integer> rightToLeft = joinInfo.pairs().stream()
-            .collect(Collectors.toMap(p -> p.target, p -> p.source));
+        RelCollation collation = TraitUtils.collation(required);
+        RelTraitSet left = inputTraits.get(0);
+        RelTraitSet right = inputTraits.get(1);
 
-        List<Integer> collationLeftPrj = new ArrayList<>();
-
-        for (Integer c : collation.getKeys()) {
-            collationLeftPrj.add(
-                c >= rightOff ? rightToLeft.get(c - rightOff) : c
-            );
-        }
+        if (joinType == FULL)
+            return passThroughDefaultCollation(required, left, right);
 
-        boolean preserveNodeCollation = false;
+        int leftInputFieldCount = this.left.getRowType().getFieldCount();
 
-        List<Integer> newLeftCollation, newRightCollation;
+        List<Integer> reqKeys = RelCollations.ordinals(collation);
+        List<Integer> leftKeys = joinInfo.leftKeys.toIntegerList();
+        List<Integer> rightKeys = joinInfo.rightKeys.incr(leftInputFieldCount).toIntegerList();
 
-        Map<Integer, Integer> leftToRight = joinInfo.pairs().stream()
-            .collect(Collectors.toMap(p -> p.source, p -> p.target));
+        ImmutableBitSet reqKeySet = ImmutableBitSet.of(reqKeys);
+        ImmutableBitSet leftKeySet = ImmutableBitSet.of(joinInfo.leftKeys);
+        ImmutableBitSet rightKeySet = ImmutableBitSet.of(rightKeys);
 
-        if (isPrefix(collationLeftPrj, joinInfo.leftKeys)) { // preserve collation
-            newLeftCollation = new ArrayList<>();
-            newRightCollation = new ArrayList<>();
+        RelCollation nodeCollation;
+        RelCollation leftCollation;
+        RelCollation rightCollation;
 
-            int ind = 0;
-            for (Integer c : collation.getKeys()) {
-                if (c < rightOff) {
-                    newLeftCollation.add(c);
+        if (reqKeySet.equals(leftKeySet)) {
+            if (joinType == RIGHT)
+                return passThroughDefaultCollation(required, left, right);
 
-                    if (ind < joinInfo.leftKeys.size())
-                        newRightCollation.add(leftToRight.get(c));
-                }
-                else {
-                    c -= rightOff;
-                    newRightCollation.add(c);
-
-                    if (ind < joinInfo.leftKeys.size())
-                        newLeftCollation.add(rightToLeft.get(c));
-                }
-
-                ind++;
-            }
-
-            preserveNodeCollation = true;
+            nodeCollation = collation;
+            leftCollation = collation;
+            rightCollation = collation.apply(buildTransposeMapping(true));
         }
-        else { // generate new collations
-            newLeftCollation = maxPrefix(collationLeftPrj, joinInfo.leftKeys);
-
-            Set<Integer> tail = new HashSet<>(joinInfo.leftKeys);
-
-            tail.removeAll(newLeftCollation);
-
-            newLeftCollation.addAll(tail);
+        else if (containsOrderless(leftKeys, collation)) {
+            if (joinType == RIGHT)
+                return passThroughDefaultCollation(required, left, right);
+
+            // if sort keys are subset of left join keys, we can extend collations to make sure all join
+            // keys are sorted.
+            nodeCollation = collation;
+            leftCollation = extendCollation(collation, leftKeys);
+            rightCollation = leftCollation.apply(buildTransposeMapping(true));
+        }
+        else if (containsOrderless(collation, leftKeys) && reqKeys.stream().allMatch(i -> i < leftInputFieldCount)) {
+            if (joinType == RIGHT)
+                return passThroughDefaultCollation(required, left, right);
+
+            // if sort keys are superset of left join keys, and left join keys is prefix of sort keys
+            // (order not matter), also sort keys are all from left join input.
+            nodeCollation = collation;
+            leftCollation = collation;
+            rightCollation = leftCollation.apply(buildTransposeMapping(true));
+        }
+        else if (reqKeySet.equals(rightKeySet)) {
+            if (joinType == LEFT)
+                return passThroughDefaultCollation(required, left, right);
 
-            newRightCollation = newLeftCollation.stream().map(leftToRight::get).collect(Collectors.toList());
+            nodeCollation = collation;
+            rightCollation = RelCollations.shift(collation, -leftInputFieldCount);
+            leftCollation = rightCollation.apply(buildTransposeMapping(false));
         }
+        else if (containsOrderless(rightKeys, collation)) {
+            if (joinType == LEFT)
+                return passThroughDefaultCollation(required, left, right);
 
-        RelCollation leftCollation = createCollation(newLeftCollation);
-        RelCollation rightCollation = createCollation(newRightCollation);
+            nodeCollation = collation;
+            rightCollation = RelCollations.shift(extendCollation(collation, rightKeys), -leftInputFieldCount);
+            leftCollation = rightCollation.apply(buildTransposeMapping(false));
+        }
+        else {
+            nodeCollation = EMPTY;

Review comment:
       fixed

##########
File path: modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/MergeJoinPlannerTest.java
##########
@@ -0,0 +1,2799 @@
+/*
+ * 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.processors.query.calcite.planner;
+
+import java.util.List;
+
+import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.RelFieldCollation;
+import org.apache.calcite.rel.core.Join;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSort;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
+import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
+import org.junit.Test;
+
+import static org.apache.calcite.rel.RelFieldCollation.Direction.ASCENDING;
+import static org.apache.calcite.rel.RelFieldCollation.Direction.DESCENDING;
+
+/** MergeJoin planner test. */
+public class MergeJoinPlannerTest extends AbstractPlannerTest {
+    /** Only MergeJoin encourage. */
+    private static final String[] DISABLED_RULES = {
+        "NestedLoopJoinConverter",
+        "CorrelatedNestedLoopJoin",
+        "FilterSpoolMergeRule",
+        "JoinCommuteRule"
+    };
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation consisted of join keys of left table should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft1() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1, LEFT_T.c2";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation consisted of join keys of left table should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft2() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1 DESC, LEFT_T.c2 DESC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, DESCENDING),
+            new RelFieldCollation(1, DESCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation consisted of join keys of left table should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft3() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1 DESC, LEFT_T.c2 ASC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, DESCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation consisted of join keys of left table should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft4() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1 ASC NULLS LAST, LEFT_T.c2 ASC NULLS LAST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation consisted of join keys of left table should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft5() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1 ASC NULLS FIRST, LEFT_T.c2 ASC NULLS LAST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is superset of join keys should be propagated as is,
+     * if it doesn't include fields from right table.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft6() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1, LEFT_T.c2, LEFT_T.c3";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            sortNodes.get(0).collation()
+        );
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortNodes.get(1).collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is superset of join keys should be propagated as is,
+     * if it doesn't include fields from right table.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft7() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1 DESC, LEFT_T.c2 DESC, LEFT_T.c3 DESC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, DESCENDING),
+                new RelFieldCollation(2, DESCENDING)
+            ),
+            sortNodes.get(0).collation()
+        );
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, DESCENDING)
+            ),
+            sortNodes.get(1).collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is superset of join keys should be propagated as is,
+     * if it doesn't include fields from right table.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft8() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1 DESC, LEFT_T.c2 ASC, LEFT_T.c3 DESC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, DESCENDING)
+            ),
+            sortNodes.get(0).collation()
+        );
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortNodes.get(1).collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is superset of join keys should be propagated as is,
+     * if it doesn't include fields from right table.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft9() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1 ASC NULLS LAST, LEFT_T.c2 ASC NULLS LAST, LEFT_T.c3 ASC NULLS LAST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            sortNodes.get(0).collation()
+        );
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortNodes.get(1).collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is superset of join keys should be propagated as is,
+     * if it doesn't include fields from right table.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft10() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1 ASC NULLS FIRST, LEFT_T.c2 ASC NULLS LAST, LEFT_T.c3 ASC NULLS FIRST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING, RelFieldCollation.NullDirection.FIRST)
+            ),
+            sortNodes.get(0).collation()
+        );
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortNodes.get(1).collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is superset of join keys, but its prefix
+     * contains columns outside of join keys, can't be propagated.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft11() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c3, LEFT_T.c2, LEFT_T.c1";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        IgniteSort topSortNode = sortOnTopOfJoin(rel);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(2, ASCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(0, ASCENDING)
+            ),
+            topSortNode.collation()
+        );
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortNodes.get(0).collation()
+        );
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortNodes.get(1).collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is subset of join keys should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft12() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 " +
+            " order by LEFT_T.c1, LEFT_T.c2";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING),
+            new RelFieldCollation(2, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is subset of join keys should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft13() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 " +
+            " order by LEFT_T.c1 DESC, LEFT_T.c2 DESC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, DESCENDING),
+            new RelFieldCollation(1, DESCENDING),
+            new RelFieldCollation(2, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is subset of join keys should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft14() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 " +
+            " order by LEFT_T.c1 DESC, LEFT_T.c2 ASC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, DESCENDING),
+            new RelFieldCollation(1, ASCENDING),
+            new RelFieldCollation(2, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is subset of join keys should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft15() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 " +
+            " order by LEFT_T.c1 ASC NULLS LAST, LEFT_T.c2 ASC NULLS LAST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING),
+            new RelFieldCollation(2, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is subset of join keys should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByLeft16() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 " +
+            " order by LEFT_T.c1 ASC NULLS FIRST, LEFT_T.c2 ASC NULLS LAST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+            new RelFieldCollation(1, ASCENDING),
+            new RelFieldCollation(2, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation consisted of join keys only should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByRight1() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1, RIGHT_T.c2";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation consisted of join keys only should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByRight2() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1 DESC, RIGHT_T.c2 DESC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, DESCENDING),
+            new RelFieldCollation(1, DESCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation consisted of join keys only should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByRight3() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1 DESC, RIGHT_T.c2 ASC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, DESCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation consisted of join keys only should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByRight4() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1 ASC NULLS LAST, RIGHT_T.c2 ASC NULLS LAST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation consisted of join keys only should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByRight5() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1 ASC NULLS FIRST, RIGHT_T.c2 ASC NULLS LAST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * Any collation that contains column from right table which is not part
+     * of join keys can't be propagated.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByRight6() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1, RIGHT_T.c2, RIGHT_T.c3";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        IgniteSort topSortNode = sortOnTopOfJoin(rel);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(3, ASCENDING),
+                new RelFieldCollation(4, ASCENDING),
+                new RelFieldCollation(5, ASCENDING)
+            ),
+            topSortNode.collation()
+        );
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expectedBottomCollation = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expectedBottomCollation, sortNodes.get(0).collation());
+        assertEquals(expectedBottomCollation, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is subset of join keys should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByRight7() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 " +
+            " order by RIGHT_T.c1, RIGHT_T.c2";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING),
+            new RelFieldCollation(2, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is subset of join keys should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByRight8() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 " +
+            " order by RIGHT_T.c1 DESC, RIGHT_T.c2 DESC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, DESCENDING),
+            new RelFieldCollation(1, DESCENDING),
+            new RelFieldCollation(2, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is subset of join keys should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByRight9() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 " +
+            " order by RIGHT_T.c1 DESC, RIGHT_T.c2 ASC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, DESCENDING),
+            new RelFieldCollation(1, ASCENDING),
+            new RelFieldCollation(2, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is subset of join keys should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByRight10() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 " +
+            " order by RIGHT_T.c1 ASC NULLS LAST, RIGHT_T.c2 ASC NULLS LAST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING),
+            new RelFieldCollation(2, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, collation that is subset of join keys should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerPassThroughOrderByRight11() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 " +
+            " order by RIGHT_T.c1 ASC NULLS FIRST, RIGHT_T.c2 ASC NULLS LAST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+            new RelFieldCollation(1, ASCENDING),
+            new RelFieldCollation(2, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of right join, collation consisted of left keys can't be propagated.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testRightPassThroughOrderByLeft1() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            " right join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1, LEFT_T.c2";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        IgniteSort topSortNode = sortOnTopOfJoin(rel);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            topSortNode.collation()
+        );
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of right join, collation that is superset of join keys can't be propagated.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testRightPassThroughOrderByLeft2() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            " right join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1, LEFT_T.c2, LEFT_T.c3";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        IgniteSort topSortNode = sortOnTopOfJoin(rel);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            topSortNode.collation()
+        );
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of right join, collation consisted of join keys only should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testRightPassThroughOrderByRight1() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            " right join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1, RIGHT_T.c2";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of right join, collation consisted of join keys only should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testRightPassThroughOrderByRight2() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            " right join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1 DESC, RIGHT_T.c2 DESC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, DESCENDING),
+            new RelFieldCollation(1, DESCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of right join, collation consisted of join keys only should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testRightPassThroughOrderByRight3() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            " right join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1 DESC, RIGHT_T.c2 ASC";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, DESCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of right join, collation consisted of join keys only should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testRightPassThroughOrderByRight4() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            " right join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1 ASC NULLS LAST, RIGHT_T.c2 ASC NULLS LAST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of right join, collation consisted of join keys only should be propagated as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testRightPassThroughOrderByRight5() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            " right join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1 ASC NULLS FIRST, RIGHT_T.c2 ASC NULLS LAST";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of left join, collation consisted of join keys of right table can't be propagated.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testLeftPassThroughOrderByRight1() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  left join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1, RIGHT_T.c2";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(3, ASCENDING),
+                new RelFieldCollation(4, ASCENDING)
+            ),
+            sortOnTopOfJoin(rel).collation()
+        );
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of left join, collation consisted of join keys of right table can't be propagated.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testLeftPassThroughOrderByRight2() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  left join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 " +
+            " order by RIGHT_T.c1, RIGHT_T.c2";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(3, ASCENDING),
+                new RelFieldCollation(4, ASCENDING)
+            ),
+            sortOnTopOfJoin(rel).collation()
+        );
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING),
+            new RelFieldCollation(2, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of full join, any collation can't be propagated.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testFullPassThroughOrderByLeft1() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  full join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1, LEFT_T.c2";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfJoin(rel).collation()
+        );
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of full join, any collation can't be propagated.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testFullPassThroughOrderByRight1() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  full join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by RIGHT_T.c1, RIGHT_T.c2";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(3, ASCENDING),
+                new RelFieldCollation(4, ASCENDING)
+            ),
+            sortOnTopOfJoin(rel).collation()
+        );
+
+        List<IgniteSort> sortNodes = sortOnTopOfScan(rel);
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortNodes.get(0).collation());
+        assertEquals(expected, sortNodes.get(1).collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset or equal to join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft1() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "LEFT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset or equal to join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft2() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, DESCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "LEFT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, DESCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset or equal to join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft3() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "LEFT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset or equal to join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft4() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.LAST),
+                new RelFieldCollation(1, ASCENDING, RelFieldCollation.NullDirection.LAST)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "LEFT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset or equal to join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft5() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "LEFT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset or equal to join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft6() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "LEFT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset or equal to join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft7() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, DESCENDING),
+                new RelFieldCollation(2, DESCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "LEFT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, DESCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset or equal to join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft8() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "LEFT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset or equal, but its prefix contains columns
+     * outside of join keys, can't be derived.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft9() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(2, ASCENDING),
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "LEFT_T").collation()
+        );
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset or equal to join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft10() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "LEFT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is subset of join keys, is not suitable.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft11() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            "   and LEFT_T.c3 = RIGHT_T.c3 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "LEFT_T").collation()
+        );
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset of join keys and has a common prefix could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft12() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c1, LEFT_T.c2, LEFT_T.c3";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfJoin(rel));
+
+        assertNull(sortOnTopOfScan(rel, "LEFT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "RIGHT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, left collation that is superset of join keys, and there is no a common prefix,
+     * could not be derived.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveLeft13() throws Exception {
+        TestTable left = createTable("LEFT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        left.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(2, ASCENDING),
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            left,
+            createTable("RIGHT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class)
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 " +
+            " order by LEFT_T.c3, LEFT_T.c1, LEFT_T.c2";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(2, ASCENDING),
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfJoin(rel).collation()
+        );
+
+        RelCollation expected = RelCollations.of(
+            new RelFieldCollation(0, ASCENDING),
+            new RelFieldCollation(1, ASCENDING)
+        );
+
+        assertEquals(expected, sortOnTopOfScan(rel, "LEFT_T").collation());
+        assertEquals(expected, sortOnTopOfScan(rel, "RIGHT_T").collation());
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, right collation that consists of join keys only could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveRight1() throws Exception {
+        TestTable right = createTable("RIGHT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        right.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            right
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "RIGHT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "LEFT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, right collation that consists of join keys only could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveRight2() throws Exception {
+        TestTable right = createTable("RIGHT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        right.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, DESCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            right
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "RIGHT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, DESCENDING)
+            ),
+            sortOnTopOfScan(rel, "LEFT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, right collation that consists of join keys only could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveRight3() throws Exception {
+        TestTable right = createTable("RIGHT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        right.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            right
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "RIGHT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "LEFT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, right collation that consists of join keys only could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveRight4() throws Exception {
+        TestTable right = createTable("RIGHT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        right.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            right
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "RIGHT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "LEFT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, right collation that consists of join keys only could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveRight5() throws Exception {
+        TestTable right = createTable("RIGHT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        right.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            right
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "RIGHT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "LEFT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, right collation that is superset of join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveRight6() throws Exception {
+        TestTable right = createTable("RIGHT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        right.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            right
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "RIGHT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "LEFT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, right collation that is superset of join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveRight7() throws Exception {
+        TestTable right = createTable("RIGHT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        right.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, DESCENDING),
+                new RelFieldCollation(2, DESCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            right
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "RIGHT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, DESCENDING)
+            ),
+            sortOnTopOfScan(rel, "LEFT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, right collation that is superset of join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveRight8() throws Exception {
+        TestTable right = createTable("RIGHT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        right.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            right
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "RIGHT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, DESCENDING),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "LEFT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, right collation that is superset of join keys could be derived as is.
+     *
+     * @throws Exception In case of any unexpected error.
+     */
+    @Test
+    public void testInnerDerivePreserveRight9() throws Exception {
+        TestTable right = createTable("RIGHT_T", IgniteDistributions.single(),
+            "C1", Integer.class, "C2", Integer.class, "C3", Integer.class);
+
+        right.addIndex(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+                new RelFieldCollation(1, ASCENDING),
+                new RelFieldCollation(2, ASCENDING)
+            ),
+            "idx"
+        );
+
+        IgniteSchema schema = createSchema(
+            createTable("LEFT_T", IgniteDistributions.single(), "C1", Integer.class, "C2", Integer.class, "C3", Integer.class),
+            right
+        );
+
+        String sql = "" +
+            "select * " +
+            "  from LEFT_T " +
+            "  join RIGHT_T " +
+            "    on LEFT_T.c1 = RIGHT_T.c1 " +
+            "   and LEFT_T.c2 = RIGHT_T.c2 ";
+
+        IgniteRel rel = physicalPlan(sql, schema, DISABLED_RULES);
+
+        assertNull(sortOnTopOfScan(rel, "RIGHT_T"));
+        assertEquals(
+            RelCollations.of(
+                new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.FIRST),
+                new RelFieldCollation(1, ASCENDING)
+            ),
+            sortOnTopOfScan(rel, "LEFT_T").collation()
+        );
+    }
+
+    /**
+     * Test verifies the collation propagation from a parent node.
+     *
+     * In case of inner join, right collation that is superset of join keys could be derived as is.

Review comment:
       fixed

##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdCollation.java
##########
@@ -548,9 +548,9 @@ public int compare(List<RexLiteral> o1, List<RexLiteral> o2) {
             case FULL:
                 for (RelCollation collation : leftCollations) {
                     for (RelFieldCollation field : collation.getFieldCollations()) {
-                        if (!(RelFieldCollation.NullDirection.LAST == field.nullDirection)) {
+                        if (!(RelFieldCollation.NullDirection.LAST.nullComparison ==

Review comment:
       Cleaned up this mess a bit

##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/SchemaHolderImpl.java
##########
@@ -247,8 +248,11 @@ private static Object affinityIdentity(CacheConfiguration<?, ?> ccfg) {
             boolean descending = idxDesc.descending(idxField);
             int fieldIdx = fieldDesc.fieldIndex();
 
-            RelFieldCollation collation = new RelFieldCollation(fieldIdx,
-                descending ? RelFieldCollation.Direction.DESCENDING : RelFieldCollation.Direction.ASCENDING);
+            RelFieldCollation collation = new RelFieldCollation(
+                fieldIdx,
+                descending ? RelFieldCollation.Direction.DESCENDING : RelFieldCollation.Direction.ASCENDING,
+                RelFieldCollation.NullDirection.FIRST

Review comment:
       good catch, folks! Problem fixed, new tests added, `defaultNullCollation` changed to `LOW`




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] zstan commented on a change in pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
zstan commented on a change in pull request #9143:
URL: https://github.com/apache/ignite/pull/9143#discussion_r693909452



##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMergeJoin.java
##########
@@ -162,74 +166,82 @@ else if (isPrefix(rightCollation.getKeys(), joinInfo.rightKeys))// preserve righ
 
     /** {@inheritDoc} */
     @Override public Pair<RelTraitSet, List<RelTraitSet>> passThroughCollation(
-        RelTraitSet nodeTraits,
+        RelTraitSet required,
         List<RelTraitSet> inputTraits
     ) {
-        RelCollation collation = TraitUtils.collation(nodeTraits);
-        RelTraitSet left = inputTraits.get(0), right = inputTraits.get(1);
-
-        int rightOff = this.left.getRowType().getFieldCount();
-
-        Map<Integer, Integer> rightToLeft = joinInfo.pairs().stream()
-            .collect(Collectors.toMap(p -> p.target, p -> p.source));
+        RelCollation collation = TraitUtils.collation(required);
+        RelTraitSet left = inputTraits.get(0);
+        RelTraitSet right = inputTraits.get(1);
 
-        List<Integer> collationLeftPrj = new ArrayList<>();
-
-        for (Integer c : collation.getKeys()) {
-            collationLeftPrj.add(
-                c >= rightOff ? rightToLeft.get(c - rightOff) : c
-            );
-        }
+        if (joinType == FULL)
+            return passThroughDefaultCollation(required, left, right);
 
-        boolean preserveNodeCollation = false;
+        int leftInputFieldCount = this.left.getRowType().getFieldCount();
 
-        List<Integer> newLeftCollation, newRightCollation;
+        List<Integer> reqKeys = RelCollations.ordinals(collation);
+        List<Integer> leftKeys = joinInfo.leftKeys.toIntegerList();
+        List<Integer> rightKeys = joinInfo.rightKeys.incr(leftInputFieldCount).toIntegerList();
 
-        Map<Integer, Integer> leftToRight = joinInfo.pairs().stream()
-            .collect(Collectors.toMap(p -> p.source, p -> p.target));
+        ImmutableBitSet reqKeySet = ImmutableBitSet.of(reqKeys);
+        ImmutableBitSet leftKeySet = ImmutableBitSet.of(joinInfo.leftKeys);
+        ImmutableBitSet rightKeySet = ImmutableBitSet.of(rightKeys);
 
-        if (isPrefix(collationLeftPrj, joinInfo.leftKeys)) { // preserve collation
-            newLeftCollation = new ArrayList<>();
-            newRightCollation = new ArrayList<>();
+        RelCollation nodeCollation;
+        RelCollation leftCollation;
+        RelCollation rightCollation;
 
-            int ind = 0;
-            for (Integer c : collation.getKeys()) {
-                if (c < rightOff) {
-                    newLeftCollation.add(c);
+        if (reqKeySet.equals(leftKeySet)) {
+            if (joinType == RIGHT)
+                return passThroughDefaultCollation(required, left, right);
 
-                    if (ind < joinInfo.leftKeys.size())
-                        newRightCollation.add(leftToRight.get(c));
-                }
-                else {
-                    c -= rightOff;
-                    newRightCollation.add(c);
-
-                    if (ind < joinInfo.leftKeys.size())
-                        newLeftCollation.add(rightToLeft.get(c));
-                }
-
-                ind++;
-            }
-
-            preserveNodeCollation = true;
+            nodeCollation = collation;
+            leftCollation = collation;
+            rightCollation = collation.apply(buildTransposeMapping(true));
         }
-        else { // generate new collations
-            newLeftCollation = maxPrefix(collationLeftPrj, joinInfo.leftKeys);
-
-            Set<Integer> tail = new HashSet<>(joinInfo.leftKeys);
-
-            tail.removeAll(newLeftCollation);
-
-            newLeftCollation.addAll(tail);
+        else if (containsOrderless(leftKeys, collation)) {
+            if (joinType == RIGHT)
+                return passThroughDefaultCollation(required, left, right);
+
+            // if sort keys are subset of left join keys, we can extend collations to make sure all join
+            // keys are sorted.
+            nodeCollation = collation;
+            leftCollation = extendCollation(collation, leftKeys);
+            rightCollation = leftCollation.apply(buildTransposeMapping(true));
+        }
+        else if (containsOrderless(collation, leftKeys) && reqKeys.stream().allMatch(i -> i < leftInputFieldCount)) {
+            if (joinType == RIGHT)
+                return passThroughDefaultCollation(required, left, right);
+
+            // if sort keys are superset of left join keys, and left join keys is prefix of sort keys
+            // (order not matter), also sort keys are all from left join input.
+            nodeCollation = collation;
+            leftCollation = collation;
+            rightCollation = leftCollation.apply(buildTransposeMapping(true));
+        }
+        else if (reqKeySet.equals(rightKeySet)) {
+            if (joinType == LEFT)
+                return passThroughDefaultCollation(required, left, right);
 
-            newRightCollation = newLeftCollation.stream().map(leftToRight::get).collect(Collectors.toList());
+            nodeCollation = collation;
+            rightCollation = RelCollations.shift(collation, -leftInputFieldCount);
+            leftCollation = rightCollation.apply(buildTransposeMapping(false));
         }
+        else if (containsOrderless(rightKeys, collation)) {
+            if (joinType == LEFT)
+                return passThroughDefaultCollation(required, left, right);
 
-        RelCollation leftCollation = createCollation(newLeftCollation);
-        RelCollation rightCollation = createCollation(newRightCollation);
+            nodeCollation = collation;
+            rightCollation = RelCollations.shift(extendCollation(collation, rightKeys), -leftInputFieldCount);
+            leftCollation = rightCollation.apply(buildTransposeMapping(false));
+        }
+        else {
+            nodeCollation = EMPTY;

Review comment:
       +1




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] korlov42 commented on a change in pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
korlov42 commented on a change in pull request #9143:
URL: https://github.com/apache/ignite/pull/9143#discussion_r649187505



##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMergeJoin.java
##########
@@ -173,24 +184,34 @@ else if (isPrefix(rightCollation.getKeys(), joinInfo.rightKeys))// preserve righ
         Map<Integer, Integer> rightToLeft = joinInfo.pairs().stream()
             .collect(Collectors.toMap(p -> p.target, p -> p.source));
 
-        List<Integer> collationLeftPrj = new ArrayList<>();
+        List<Integer> collationLeftPrj = new ArrayList<>(collation.getKeys().size());
 
         for (Integer c : collation.getKeys()) {
             collationLeftPrj.add(
                 c >= rightOff ? rightToLeft.get(c - rightOff) : c
             );
         }
 
-        boolean preserveNodeCollation = false;
+        boolean preserveNodeCollation = isPrefix(collationLeftPrj, joinInfo.leftKeys);
 
         List<Integer> newLeftCollation, newRightCollation;
 
         Map<Integer, Integer> leftToRight = joinInfo.pairs().stream()
             .collect(Collectors.toMap(p -> p.source, p -> p.target));
 
-        if (isPrefix(collationLeftPrj, joinInfo.leftKeys)) { // preserve collation
-            newLeftCollation = new ArrayList<>();
-            newRightCollation = new ArrayList<>();
+        if (joinType == RIGHT || joinType == JoinRelType.FULL) {
+            for (RelFieldCollation field : collation.getFieldCollations()) {
+                if (RelFieldCollation.NullDirection.LAST != field.nullDirection) {
+                    preserveNodeCollation = false;
+                    break;
+                }
+            }
+        }
+
+        // we can`t preserve collation in case of full/right joins.
+        if (preserveNodeCollation) { // preserve collation
+            newLeftCollation = new ArrayList<>(collation.getKeys().size());
+            newRightCollation = new ArrayList<>(collation.getKeys().size());
 
             int ind = 0;
             for (Integer c : collation.getKeys()) {

Review comment:
       this piece of code is faulty too. Let's extend the test coverage for merge join with different types of collations (asc, desc, nulls last, nulls first) and fix all collation propagation related problems within this PR




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] alex-plekhanov commented on a change in pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
alex-plekhanov commented on a change in pull request #9143:
URL: https://github.com/apache/ignite/pull/9143#discussion_r690144708



##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/SchemaHolderImpl.java
##########
@@ -247,8 +248,11 @@ private static Object affinityIdentity(CacheConfiguration<?, ?> ccfg) {
             boolean descending = idxDesc.descending(idxField);
             int fieldIdx = fieldDesc.fieldIndex();
 
-            RelFieldCollation collation = new RelFieldCollation(fieldIdx,
-                descending ? RelFieldCollation.Direction.DESCENDING : RelFieldCollation.Direction.ASCENDING);
+            RelFieldCollation collation = new RelFieldCollation(
+                fieldIdx,
+                descending ? RelFieldCollation.Direction.DESCENDING : RelFieldCollation.Direction.ASCENDING,
+                RelFieldCollation.NullDirection.FIRST

Review comment:
       We have `NULLS LAST` for `DESC` order. 
   
   Also test for this should be added. For example, I've tested it with `CalciteBasicSecondaryIndexIntegrationTest` class, added a new index `CITY DESC`, put some nulls in `CITY` field and checked query `SELECT * FROM Developer ORDER BY city DESC nulls first`: currently it returns index scan without additional sort node and returns wrong nulls order.
   
   Also, shouldn't we change our `defaultNullCollation` to `LOW` to fit our indexes? Currently, if the user creates an index and then uses `order by` in a query by indexed columns, by default index will not be used without nulls first/nulls last clause. This can be confusing for some customers, perhaps behavior for index and `ORDER BY` should be consistent. In the current H2 engine, we have nulls ordering policy similar to calcites `LOW`.

##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdCollation.java
##########
@@ -548,9 +548,9 @@ public int compare(List<RexLiteral> o1, List<RexLiteral> o2) {
             case FULL:
                 for (RelCollation collation : leftCollations) {
                     for (RelFieldCollation field : collation.getFieldCollations()) {
-                        if (!(RelFieldCollation.NullDirection.LAST == field.nullDirection)) {
+                        if (!(RelFieldCollation.NullDirection.LAST.nullComparison ==

Review comment:
       Looks like these class contains a lot of unused code, this method unused too (applicable only for enumerable convention, but is not used even for enumerable convention, instead methods from `RelMdCollation` used)




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] korlov42 commented on a change in pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
korlov42 commented on a change in pull request #9143:
URL: https://github.com/apache/ignite/pull/9143#discussion_r680949704



##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMergeJoin.java
##########
@@ -162,79 +176,297 @@ else if (isPrefix(rightCollation.getKeys(), joinInfo.rightKeys))// preserve righ
 
     /** {@inheritDoc} */
     @Override public Pair<RelTraitSet, List<RelTraitSet>> passThroughCollation(
-        RelTraitSet nodeTraits,
+        RelTraitSet required,
         List<RelTraitSet> inputTraits
     ) {
-        RelCollation collation = TraitUtils.collation(nodeTraits);
-        RelTraitSet left = inputTraits.get(0), right = inputTraits.get(1);
+        // Required collation keys can be subset or superset of merge join keys.
+        RelCollation collation = TraitUtils.collation(required);
+        int leftInputFieldCount = left.getRowType().getFieldCount();
 
-        int rightOff = this.left.getRowType().getFieldCount();
+        List<Integer> reqKeys = RelCollations.ordinals(collation);
+        List<Integer> leftKeys = joinInfo.leftKeys.toIntegerList();
+        List<Integer> rightKeys =
+            joinInfo.rightKeys.incr(leftInputFieldCount).toIntegerList();
 
-        Map<Integer, Integer> rightToLeft = joinInfo.pairs().stream()
-            .collect(Collectors.toMap(p -> p.target, p -> p.source));
+        ImmutableBitSet reqKeySet = ImmutableBitSet.of(reqKeys);
+        ImmutableBitSet leftKeySet = ImmutableBitSet.of(joinInfo.leftKeys);
+        ImmutableBitSet rightKeySet = ImmutableBitSet.of(joinInfo.rightKeys)
+            .shift(leftInputFieldCount);
 
-        List<Integer> collationLeftPrj = new ArrayList<>();
+        if (reqKeySet.equals(leftKeySet)) {
+            // if sort keys equal to left join keys, we can pass through all collations directly.
+            Pair<RelTraitSet, List<RelTraitSet>> transformedCollations = transformCollations(required, leftInputFieldCount);
 
-        for (Integer c : collation.getKeys()) {
-            collationLeftPrj.add(
-                c >= rightOff ? rightToLeft.get(c - rightOff) : c
-            );
+            if (transformedCollations != null)
+                return transformedCollations;
+
+            Mappings.TargetMapping mapping = buildProjectionMapping(true);
+            RelCollation rightCollation = collation.apply(mapping);
+
+            if (debugOutput)
+                System.err.println("1 " + TraitUtils.collation(required) + " l: " +
+                    TraitUtils.collation(required) + " r: " + rightCollation);
+
+            return Pair.of(
+                required, ImmutableList.of(required, required.replace(rightCollation)));

Review comment:
       you can't just pass required traits for every input




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] alex-plekhanov commented on a change in pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
alex-plekhanov commented on a change in pull request #9143:
URL: https://github.com/apache/ignite/pull/9143#discussion_r691058170



##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMergeJoin.java
##########
@@ -162,74 +166,82 @@ else if (isPrefix(rightCollation.getKeys(), joinInfo.rightKeys))// preserve righ
 
     /** {@inheritDoc} */
     @Override public Pair<RelTraitSet, List<RelTraitSet>> passThroughCollation(
-        RelTraitSet nodeTraits,
+        RelTraitSet required,
         List<RelTraitSet> inputTraits
     ) {
-        RelCollation collation = TraitUtils.collation(nodeTraits);
-        RelTraitSet left = inputTraits.get(0), right = inputTraits.get(1);
-
-        int rightOff = this.left.getRowType().getFieldCount();
-
-        Map<Integer, Integer> rightToLeft = joinInfo.pairs().stream()
-            .collect(Collectors.toMap(p -> p.target, p -> p.source));
+        RelCollation collation = TraitUtils.collation(required);
+        RelTraitSet left = inputTraits.get(0);
+        RelTraitSet right = inputTraits.get(1);
 
-        List<Integer> collationLeftPrj = new ArrayList<>();
-
-        for (Integer c : collation.getKeys()) {
-            collationLeftPrj.add(
-                c >= rightOff ? rightToLeft.get(c - rightOff) : c
-            );
-        }
+        if (joinType == FULL)
+            return passThroughDefaultCollation(required, left, right);
 
-        boolean preserveNodeCollation = false;
+        int leftInputFieldCount = this.left.getRowType().getFieldCount();
 
-        List<Integer> newLeftCollation, newRightCollation;
+        List<Integer> reqKeys = RelCollations.ordinals(collation);
+        List<Integer> leftKeys = joinInfo.leftKeys.toIntegerList();
+        List<Integer> rightKeys = joinInfo.rightKeys.incr(leftInputFieldCount).toIntegerList();
 
-        Map<Integer, Integer> leftToRight = joinInfo.pairs().stream()
-            .collect(Collectors.toMap(p -> p.source, p -> p.target));
+        ImmutableBitSet reqKeySet = ImmutableBitSet.of(reqKeys);
+        ImmutableBitSet leftKeySet = ImmutableBitSet.of(joinInfo.leftKeys);
+        ImmutableBitSet rightKeySet = ImmutableBitSet.of(rightKeys);
 
-        if (isPrefix(collationLeftPrj, joinInfo.leftKeys)) { // preserve collation
-            newLeftCollation = new ArrayList<>();
-            newRightCollation = new ArrayList<>();
+        RelCollation nodeCollation;
+        RelCollation leftCollation;
+        RelCollation rightCollation;
 
-            int ind = 0;
-            for (Integer c : collation.getKeys()) {
-                if (c < rightOff) {
-                    newLeftCollation.add(c);
+        if (reqKeySet.equals(leftKeySet)) {
+            if (joinType == RIGHT)
+                return passThroughDefaultCollation(required, left, right);
 
-                    if (ind < joinInfo.leftKeys.size())
-                        newRightCollation.add(leftToRight.get(c));
-                }
-                else {
-                    c -= rightOff;
-                    newRightCollation.add(c);
-
-                    if (ind < joinInfo.leftKeys.size())
-                        newLeftCollation.add(rightToLeft.get(c));
-                }
-
-                ind++;
-            }
-
-            preserveNodeCollation = true;
+            nodeCollation = collation;
+            leftCollation = collation;
+            rightCollation = collation.apply(buildTransposeMapping(true));
         }
-        else { // generate new collations
-            newLeftCollation = maxPrefix(collationLeftPrj, joinInfo.leftKeys);
-
-            Set<Integer> tail = new HashSet<>(joinInfo.leftKeys);
-
-            tail.removeAll(newLeftCollation);
-
-            newLeftCollation.addAll(tail);
+        else if (containsOrderless(leftKeys, collation)) {
+            if (joinType == RIGHT)
+                return passThroughDefaultCollation(required, left, right);
+
+            // if sort keys are subset of left join keys, we can extend collations to make sure all join
+            // keys are sorted.
+            nodeCollation = collation;
+            leftCollation = extendCollation(collation, leftKeys);
+            rightCollation = leftCollation.apply(buildTransposeMapping(true));
+        }
+        else if (containsOrderless(collation, leftKeys) && reqKeys.stream().allMatch(i -> i < leftInputFieldCount)) {
+            if (joinType == RIGHT)
+                return passThroughDefaultCollation(required, left, right);
+
+            // if sort keys are superset of left join keys, and left join keys is prefix of sort keys
+            // (order not matter), also sort keys are all from left join input.
+            nodeCollation = collation;
+            leftCollation = collation;
+            rightCollation = leftCollation.apply(buildTransposeMapping(true));
+        }
+        else if (reqKeySet.equals(rightKeySet)) {
+            if (joinType == LEFT)
+                return passThroughDefaultCollation(required, left, right);
 
-            newRightCollation = newLeftCollation.stream().map(leftToRight::get).collect(Collectors.toList());
+            nodeCollation = collation;
+            rightCollation = RelCollations.shift(collation, -leftInputFieldCount);
+            leftCollation = rightCollation.apply(buildTransposeMapping(false));
         }
+        else if (containsOrderless(rightKeys, collation)) {
+            if (joinType == LEFT)
+                return passThroughDefaultCollation(required, left, right);
 
-        RelCollation leftCollation = createCollation(newLeftCollation);
-        RelCollation rightCollation = createCollation(newRightCollation);
+            nodeCollation = collation;
+            rightCollation = RelCollations.shift(extendCollation(collation, rightKeys), -leftInputFieldCount);
+            leftCollation = rightCollation.apply(buildTransposeMapping(false));
+        }
+        else {
+            nodeCollation = EMPTY;

Review comment:
       `return passThroughDefaultCollation(...)`?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] korlov42 commented on a change in pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
korlov42 commented on a change in pull request #9143:
URL: https://github.com/apache/ignite/pull/9143#discussion_r680948859



##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMergeJoin.java
##########
@@ -63,6 +71,9 @@
      */
     private final RelCollation rightCollation;
 
+    /** Debug purpose logging. */
+    private boolean debugOutput;

Review comment:
       please remove debug output 




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] tledkov-gridgain commented on a change in pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
tledkov-gridgain commented on a change in pull request #9143:
URL: https://github.com/apache/ignite/pull/9143#discussion_r699339206



##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/AbstractIgniteJoin.java
##########
@@ -90,7 +90,7 @@ protected AbstractIgniteJoin(RelOptCluster cluster, RelTraitSet traitSet, RelNod
         // TODO set NullDirection.LAST for insufficient fields instead of erasing collation.

Review comment:
       TODO without issue. Please create the issue and add to the comment.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] korlov42 commented on a change in pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
korlov42 commented on a change in pull request #9143:
URL: https://github.com/apache/ignite/pull/9143#discussion_r649174881



##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMergeJoin.java
##########
@@ -173,24 +184,34 @@ else if (isPrefix(rightCollation.getKeys(), joinInfo.rightKeys))// preserve righ
         Map<Integer, Integer> rightToLeft = joinInfo.pairs().stream()
             .collect(Collectors.toMap(p -> p.target, p -> p.source));
 
-        List<Integer> collationLeftPrj = new ArrayList<>();
+        List<Integer> collationLeftPrj = new ArrayList<>(collation.getKeys().size());
 
         for (Integer c : collation.getKeys()) {
             collationLeftPrj.add(
                 c >= rightOff ? rightToLeft.get(c - rightOff) : c

Review comment:
       looks like there is another bug: we can't preserve collation if required collation contains right table's field outside of join pairs 




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] tledkov-gridgain merged pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
tledkov-gridgain merged pull request #9143:
URL: https://github.com/apache/ignite/pull/9143


   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] tledkov-gridgain commented on a change in pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
tledkov-gridgain commented on a change in pull request #9143:
URL: https://github.com/apache/ignite/pull/9143#discussion_r687567800



##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/SchemaHolderImpl.java
##########
@@ -247,8 +248,11 @@ private static Object affinityIdentity(CacheConfiguration<?, ?> ccfg) {
             boolean descending = idxDesc.descending(idxField);
             int fieldIdx = fieldDesc.fieldIndex();
 
-            RelFieldCollation collation = new RelFieldCollation(fieldIdx,
-                descending ? RelFieldCollation.Direction.DESCENDING : RelFieldCollation.Direction.ASCENDING);
+            RelFieldCollation collation = new RelFieldCollation(
+                fieldIdx,
+                descending ? RelFieldCollation.Direction.DESCENDING : RelFieldCollation.Direction.ASCENDING,
+                RelFieldCollation.NullDirection.FIRST

Review comment:
       Is the `NullDirection.FIRST` applicable both for ASC & DESC orders?
   Maybe we have to use inverse of the `RelFieldCollation.Direction.defaultNullDirection()` or our own implementation of such method.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] tledkov-gridgain commented on a change in pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
tledkov-gridgain commented on a change in pull request #9143:
URL: https://github.com/apache/ignite/pull/9143#discussion_r699339206



##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/AbstractIgniteJoin.java
##########
@@ -90,7 +90,7 @@ protected AbstractIgniteJoin(RelOptCluster cluster, RelTraitSet traitSet, RelNod
         // TODO set NullDirection.LAST for insufficient fields instead of erasing collation.

Review comment:
       TODO without issue. Please create the issue and add to the comment.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] korlov42 commented on a change in pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
korlov42 commented on a change in pull request #9143:
URL: https://github.com/apache/ignite/pull/9143#discussion_r699378865



##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/AbstractIgniteJoin.java
##########
@@ -90,7 +90,7 @@ protected AbstractIgniteJoin(RelOptCluster cluster, RelTraitSet traitSet, RelNod
         // TODO set NullDirection.LAST for insufficient fields instead of erasing collation.

Review comment:
       I've removed this, since it was outdated




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] alex-plekhanov commented on a change in pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
alex-plekhanov commented on a change in pull request #9143:
URL: https://github.com/apache/ignite/pull/9143#discussion_r691058170



##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMergeJoin.java
##########
@@ -162,74 +166,82 @@ else if (isPrefix(rightCollation.getKeys(), joinInfo.rightKeys))// preserve righ
 
     /** {@inheritDoc} */
     @Override public Pair<RelTraitSet, List<RelTraitSet>> passThroughCollation(
-        RelTraitSet nodeTraits,
+        RelTraitSet required,
         List<RelTraitSet> inputTraits
     ) {
-        RelCollation collation = TraitUtils.collation(nodeTraits);
-        RelTraitSet left = inputTraits.get(0), right = inputTraits.get(1);
-
-        int rightOff = this.left.getRowType().getFieldCount();
-
-        Map<Integer, Integer> rightToLeft = joinInfo.pairs().stream()
-            .collect(Collectors.toMap(p -> p.target, p -> p.source));
+        RelCollation collation = TraitUtils.collation(required);
+        RelTraitSet left = inputTraits.get(0);
+        RelTraitSet right = inputTraits.get(1);
 
-        List<Integer> collationLeftPrj = new ArrayList<>();
-
-        for (Integer c : collation.getKeys()) {
-            collationLeftPrj.add(
-                c >= rightOff ? rightToLeft.get(c - rightOff) : c
-            );
-        }
+        if (joinType == FULL)
+            return passThroughDefaultCollation(required, left, right);
 
-        boolean preserveNodeCollation = false;
+        int leftInputFieldCount = this.left.getRowType().getFieldCount();
 
-        List<Integer> newLeftCollation, newRightCollation;
+        List<Integer> reqKeys = RelCollations.ordinals(collation);
+        List<Integer> leftKeys = joinInfo.leftKeys.toIntegerList();
+        List<Integer> rightKeys = joinInfo.rightKeys.incr(leftInputFieldCount).toIntegerList();
 
-        Map<Integer, Integer> leftToRight = joinInfo.pairs().stream()
-            .collect(Collectors.toMap(p -> p.source, p -> p.target));
+        ImmutableBitSet reqKeySet = ImmutableBitSet.of(reqKeys);
+        ImmutableBitSet leftKeySet = ImmutableBitSet.of(joinInfo.leftKeys);
+        ImmutableBitSet rightKeySet = ImmutableBitSet.of(rightKeys);
 
-        if (isPrefix(collationLeftPrj, joinInfo.leftKeys)) { // preserve collation
-            newLeftCollation = new ArrayList<>();
-            newRightCollation = new ArrayList<>();
+        RelCollation nodeCollation;
+        RelCollation leftCollation;
+        RelCollation rightCollation;
 
-            int ind = 0;
-            for (Integer c : collation.getKeys()) {
-                if (c < rightOff) {
-                    newLeftCollation.add(c);
+        if (reqKeySet.equals(leftKeySet)) {
+            if (joinType == RIGHT)
+                return passThroughDefaultCollation(required, left, right);
 
-                    if (ind < joinInfo.leftKeys.size())
-                        newRightCollation.add(leftToRight.get(c));
-                }
-                else {
-                    c -= rightOff;
-                    newRightCollation.add(c);
-
-                    if (ind < joinInfo.leftKeys.size())
-                        newLeftCollation.add(rightToLeft.get(c));
-                }
-
-                ind++;
-            }
-
-            preserveNodeCollation = true;
+            nodeCollation = collation;
+            leftCollation = collation;
+            rightCollation = collation.apply(buildTransposeMapping(true));
         }
-        else { // generate new collations
-            newLeftCollation = maxPrefix(collationLeftPrj, joinInfo.leftKeys);
-
-            Set<Integer> tail = new HashSet<>(joinInfo.leftKeys);
-
-            tail.removeAll(newLeftCollation);
-
-            newLeftCollation.addAll(tail);
+        else if (containsOrderless(leftKeys, collation)) {
+            if (joinType == RIGHT)
+                return passThroughDefaultCollation(required, left, right);
+
+            // if sort keys are subset of left join keys, we can extend collations to make sure all join
+            // keys are sorted.
+            nodeCollation = collation;
+            leftCollation = extendCollation(collation, leftKeys);
+            rightCollation = leftCollation.apply(buildTransposeMapping(true));
+        }
+        else if (containsOrderless(collation, leftKeys) && reqKeys.stream().allMatch(i -> i < leftInputFieldCount)) {
+            if (joinType == RIGHT)
+                return passThroughDefaultCollation(required, left, right);
+
+            // if sort keys are superset of left join keys, and left join keys is prefix of sort keys
+            // (order not matter), also sort keys are all from left join input.
+            nodeCollation = collation;
+            leftCollation = collation;
+            rightCollation = leftCollation.apply(buildTransposeMapping(true));
+        }
+        else if (reqKeySet.equals(rightKeySet)) {
+            if (joinType == LEFT)
+                return passThroughDefaultCollation(required, left, right);
 
-            newRightCollation = newLeftCollation.stream().map(leftToRight::get).collect(Collectors.toList());
+            nodeCollation = collation;
+            rightCollation = RelCollations.shift(collation, -leftInputFieldCount);
+            leftCollation = rightCollation.apply(buildTransposeMapping(false));
         }
+        else if (containsOrderless(rightKeys, collation)) {
+            if (joinType == LEFT)
+                return passThroughDefaultCollation(required, left, right);
 
-        RelCollation leftCollation = createCollation(newLeftCollation);
-        RelCollation rightCollation = createCollation(newRightCollation);
+            nodeCollation = collation;
+            rightCollation = RelCollations.shift(extendCollation(collation, rightKeys), -leftInputFieldCount);
+            leftCollation = rightCollation.apply(buildTransposeMapping(false));
+        }
+        else {
+            nodeCollation = EMPTY;

Review comment:
       `return passThroughDefaultCollation(...)`?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite] tledkov-gridgain commented on a change in pull request #9143: IGNITE-14808 RIGHT|FULL Join operations are lost nulls sort ordering.

Posted by GitBox <gi...@apache.org>.
tledkov-gridgain commented on a change in pull request #9143:
URL: https://github.com/apache/ignite/pull/9143#discussion_r699339206



##########
File path: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/AbstractIgniteJoin.java
##########
@@ -90,7 +90,7 @@ protected AbstractIgniteJoin(RelOptCluster cluster, RelTraitSet traitSet, RelNod
         // TODO set NullDirection.LAST for insufficient fields instead of erasing collation.

Review comment:
       TODO without issue. Please create the issue and add to the comment.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org