Index: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerPhase.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerPhase.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerPhase.java
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerPhase.java	(revision 422e993f1533ea0ad02168a34f87044fb4350e05)
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerPhase.java	(date 1639129915363)
@@ -212,8 +212,8 @@
 
                     LogicalOrToUnionRule.INSTANCE,
 
-                    CorrelatedNestedLoopJoinRule.INSTANCE,
-                    CorrelateToNestedLoopRule.INSTANCE,
+                    //CorrelatedNestedLoopJoinRule.INSTANCE,
+                    //CorrelateToNestedLoopRule.INSTANCE,
                     NestedLoopJoinConverterRule.INSTANCE,
                     MergeJoinConverterRule.INSTANCE,
 
Index: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/TraitUtils.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/TraitUtils.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/TraitUtils.java
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/TraitUtils.java	(revision 422e993f1533ea0ad02168a34f87044fb4350e05)
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/TraitUtils.java	(date 1639130282528)
@@ -116,8 +116,10 @@
             return convertCollation(planner, (RelCollation)toTrait, rel);
         else if (converter == DistributionTraitDef.INSTANCE)
             return convertDistribution(planner, (IgniteDistribution)toTrait, rel);
-        else if (converter == RewindabilityTraitDef.INSTANCE)
+        else if (converter == RewindabilityTraitDef.INSTANCE) {
+            IgniteTableSpool.CONVERT_TRAIT.incrementAndGet();
             return convertRewindability(planner, (RewindabilityTrait)toTrait, rel);
+        }
         else
             return convertOther(planner, converter, toTrait, rel);
     }
Index: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/AbstractIgniteJoin.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/AbstractIgniteJoin.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/AbstractIgniteJoin.java
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/AbstractIgniteJoin.java	(revision 422e993f1533ea0ad02168a34f87044fb4350e05)
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/AbstractIgniteJoin.java	(date 1639130871489)
@@ -181,14 +181,14 @@
             res.add(Pair.of(outTraits, ImmutableList.of(leftTraits, rightTraits)));
         }
 
-        leftTraits = left.replace(hash(joinInfo.leftKeys, DistributionFunction.hash()));
-        rightTraits = right.replace(hash(joinInfo.rightKeys, DistributionFunction.hash()));
-
-        outTraits = nodeTraits.replace(hash(joinInfo.leftKeys, DistributionFunction.hash()));
-        res.add(Pair.of(outTraits, ImmutableList.of(leftTraits, rightTraits)));
-
-        outTraits = nodeTraits.replace(hash(joinInfo.rightKeys, DistributionFunction.hash()));
-        res.add(Pair.of(outTraits, ImmutableList.of(leftTraits, rightTraits)));
+//        leftTraits = left.replace(hash(joinInfo.leftKeys, DistributionFunction.hash()));
+//        rightTraits = right.replace(hash(joinInfo.rightKeys, DistributionFunction.hash()));
+//
+//        outTraits = nodeTraits.replace(hash(joinInfo.leftKeys, DistributionFunction.hash()));
+//        res.add(Pair.of(outTraits, ImmutableList.of(leftTraits, rightTraits)));
+//
+//        outTraits = nodeTraits.replace(hash(joinInfo.rightKeys, DistributionFunction.hash()));
+//        res.add(Pair.of(outTraits, ImmutableList.of(leftTraits, rightTraits)));
 
         return ImmutableList.copyOf(res);
     }
Index: modules/core/src/test/config/log4j-test.xml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/modules/core/src/test/config/log4j-test.xml b/modules/core/src/test/config/log4j-test.xml
--- a/modules/core/src/test/config/log4j-test.xml	(revision 422e993f1533ea0ad02168a34f87044fb4350e05)
+++ b/modules/core/src/test/config/log4j-test.xml	(date 1639123040628)
@@ -145,6 +145,22 @@
         <level value="INFO"/>
     </category>
 
+<!--    <category name="org.apache.calcite.plan.volcano.task">-->
+<!--        <level value="DEBUG"/>-->
+<!--    </category>-->
+
+    <category name="org.apache.calcite.plan.AbstractRelOptPlanner.rule_execution_summary">
+        <level value="DEBUG"/>
+    </category>
+
+    <category name="org.apache.calcite.plan.AbstractRelOptPlanner">
+        <level value="DEBUG"/>
+    </category>
+
+    <category name="org.apache.calcite.sql2rel">
+        <level value="DEBUG"/>
+    </category>
+
     <!-- Default settings. -->
     <root>
         <!-- Print at info by default. -->
Index: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/AbstractIgniteConverterRule.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/AbstractIgniteConverterRule.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/AbstractIgniteConverterRule.java
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/AbstractIgniteConverterRule.java	(revision 422e993f1533ea0ad02168a34f87044fb4350e05)
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/AbstractIgniteConverterRule.java	(date 1639048252609)
@@ -19,10 +19,14 @@
 
 import org.apache.calcite.plan.Convention;
 import org.apache.calcite.plan.RelOptPlanner;
+import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.rel.PhysicalNode;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.convert.ConverterRule;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.sql.SqlExplainFormat;
+import org.apache.calcite.sql.SqlExplainLevel;
+import org.apache.calcite.util.trace.CalciteTrace;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
 
 /** */
@@ -40,7 +44,14 @@
 
     /** {@inheritDoc} */
     @Override public final RelNode convert(RelNode rel) {
-        return convert(rel.getCluster().getPlanner(), rel.getCluster().getMetadataQuery(), (T)rel);
+//        CalciteTrace.getSqlToRelTracer().error(
+//            RelOptUtil.dumpPlan(this.getClass().getName() + " input: ", rel,
+//                SqlExplainFormat.TEXT, SqlExplainLevel.EXPPLAN_ATTRIBUTES) );
+        RelNode res = convert(rel.getCluster().getPlanner(), rel.getCluster().getMetadataQuery(), (T)rel);
+//        CalciteTrace.getSqlToRelTracer().error(
+//            RelOptUtil.dumpPlan(this.getClass().getName() + " output: ", res,
+//                SqlExplainFormat.TEXT, SqlExplainLevel.EXPPLAN_ATTRIBUTES) );
+        return res;
     }
 
     /**
Index: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgnitePlanner.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgnitePlanner.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgnitePlanner.java
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgnitePlanner.java	(revision 422e993f1533ea0ad02168a34f87044fb4350e05)
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgnitePlanner.java	(date 1639050963886)
@@ -60,12 +60,15 @@
 import org.apache.calcite.tools.RuleSets;
 import org.apache.calcite.tools.ValidationException;
 import org.apache.calcite.util.Pair;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
 import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
 import org.apache.ignite.internal.processors.query.IgniteSQLException;
 import org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMetadata;
 import org.apache.ignite.internal.processors.query.calcite.metadata.RelMetadataQueryEx;
 import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
 import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.internal.util.IgniteUtils;
 
 /**
  * Query planer.
@@ -284,6 +287,23 @@
         return w.toString();
     }
 
+    /** */
+    public String dumpRulesAttemptInfo() {
+        StringWriter w = new StringWriter();
+
+        Object ruleAttemtpsListener = IgniteUtils.field(planner, "ruleAttemptsListener");
+
+        if (ruleAttemtpsListener != null) {
+            try {
+                return IgniteUtils.invoke(null, ruleAttemtpsListener, "dump");
+            }
+            catch (IgniteCheckedException e) {
+                throw new IgniteException(e);
+            }
+        }
+        return "";
+    }
+
     /** */
     private SqlValidator validator() {
         if (validator == null)
Index: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/RewindabilityTraitDef.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/RewindabilityTraitDef.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/RewindabilityTraitDef.java
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/RewindabilityTraitDef.java	(revision 422e993f1533ea0ad02168a34f87044fb4350e05)
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/RewindabilityTraitDef.java	(date 1639131288833)
@@ -20,6 +20,7 @@
 import org.apache.calcite.plan.RelOptPlanner;
 import org.apache.calcite.plan.RelTraitDef;
 import org.apache.calcite.rel.RelNode;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableSpool;
 
 /** */
 public class RewindabilityTraitDef extends RelTraitDef<RewindabilityTrait> {
@@ -38,6 +39,7 @@
 
     /** {@inheritDoc} */
     @Override public RelNode convert(RelOptPlanner planner, RelNode rel, RewindabilityTrait toTrait, boolean allowInfiniteCostConverters) {
+        IgniteTableSpool.REWINDABILITY_TRAIT_DEF.incrementAndGet();
         return TraitUtils.convertRewindability(planner, toTrait, rel);
     }
 
@@ -48,6 +50,6 @@
 
     /** {@inheritDoc} */
     @Override public RewindabilityTrait getDefault() {
-        return RewindabilityTrait.ONE_WAY;
+        return RewindabilityTrait.REWINDABLE;
     }
 }
Index: modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTableSpool.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTableSpool.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTableSpool.java
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTableSpool.java	(revision 422e993f1533ea0ad02168a34f87044fb4350e05)
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTableSpool.java	(date 1639126085466)
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.processors.query.calcite.rel;
 
 import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptCost;
 import org.apache.calcite.plan.RelOptPlanner;
@@ -33,6 +34,10 @@
  * Relational operator that returns the contents of a table.
  */
 public class IgniteTableSpool extends Spool implements IgniteRel {
+    public static AtomicInteger REWINDABILITY_TRAIT_DEF = new AtomicInteger(0);
+
+    public static AtomicInteger CONVERT_TRAIT = new AtomicInteger(0);
+
     /** */
     public IgniteTableSpool(
         RelOptCluster cluster,
Index: modules/core/src/test/java/org/apache/ignite/testframework/GridTestUtils.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/modules/core/src/test/java/org/apache/ignite/testframework/GridTestUtils.java b/modules/core/src/test/java/org/apache/ignite/testframework/GridTestUtils.java
--- a/modules/core/src/test/java/org/apache/ignite/testframework/GridTestUtils.java	(revision 422e993f1533ea0ad02168a34f87044fb4350e05)
+++ b/modules/core/src/test/java/org/apache/ignite/testframework/GridTestUtils.java	(date 1639051357690)
@@ -139,7 +139,7 @@
     public static final long DFLT_BUSYWAIT_SLEEP_INTERVAL = 200;
 
     /** */
-    public static final long DFLT_TEST_TIMEOUT = 5 * 60 * 1000;
+    public static final long DFLT_TEST_TIMEOUT = 5 * 60000 * 1000;
 
     /** */
     static final String ALPHABETH = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890_";
Index: modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlannerTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlannerTest.java
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlannerTest.java	(revision 422e993f1533ea0ad02168a34f87044fb4350e05)
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlannerTest.java	(date 1639126085458)
@@ -25,6 +25,7 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Function;
 import java.util.function.Predicate;
+import org.apache.calcite.plan.AbstractRelOptPlanner;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelCollations;
@@ -64,6 +65,7 @@
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableSpool;
 import org.apache.ignite.internal.processors.query.calcite.schema.IgniteIndex;
 import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
 import org.apache.ignite.internal.processors.query.calcite.trait.CorrelationTrait;
@@ -73,6 +75,7 @@
 import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeSystem;
 import org.apache.ignite.internal.processors.query.calcite.util.Commons;
 import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.testframework.GridTestUtils;
 import org.apache.ignite.testframework.junits.GridTestKernalContext;
 import org.apache.ignite.thread.IgniteStripedThreadPoolExecutor;
 import org.junit.Assert;
@@ -1207,6 +1210,136 @@
                     "    IgniteTableScan(table=[[PUBLIC, DEPT]], filters=[=(CAST(+($t0, $cor2.DEPTNO)):INTEGER, 2)])\n",
                 RelOptUtil.toString(phys));
 
+            checkSplitAndSerialization(phys, publicSchema);
+        }
+    }
+
+    @Test
+    public void testMultipleJoin() throws Exception {
+        IgniteTypeFactory f = new IgniteTypeFactory(IgniteTypeSystem.INSTANCE);
+
+        TestTable t1 = new TestTable(
+            new RelDataTypeFactory.Builder(f)
+                .add("A", f.createJavaType(Integer.class))
+                .add("B", f.createJavaType(Integer.class))
+                .build()) {
+
+            @Override public IgniteDistribution distribution() {
+                return IgniteDistributions.broadcast();
+            }
+        };
+
+
+        TestTable t2 = new TestTable(
+            new RelDataTypeFactory.Builder(f)
+                .add("A", f.createJavaType(Integer.class))
+                .add("C", f.createJavaType(Integer.class))
+                .build()) {
+
+            @Override public IgniteDistribution distribution() {
+                return IgniteDistributions.broadcast();
+            }
+        };
+
+
+
+        TestTable t3 = new TestTable(
+            new RelDataTypeFactory.Builder(f)
+                .add("B", f.createJavaType(Integer.class))
+                .add("C", f.createJavaType(Integer.class))
+                .build()) {
+
+            @Override public IgniteDistribution distribution() {
+                return IgniteDistributions.broadcast();
+            }
+        };
+
+        TestTable t4 = new TestTable(
+            new RelDataTypeFactory.Builder(f)
+                .add("A", f.createJavaType(Integer.class))
+                .add("C", f.createJavaType(Integer.class))
+                .build()) {
+
+            @Override public IgniteDistribution distribution() {
+                return IgniteDistributions.broadcast();
+            }
+        };
+
+        IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
+
+        publicSchema.addTable("T1", t1);
+        publicSchema.addTable("T2", t2);
+        publicSchema.addTable("T3", t3);
+        publicSchema.addTable("T4", t4);
+
+        SchemaPlus schema = createRootSchema(false)
+            .add("PUBLIC", publicSchema);
+
+        String sql = "SELECT * FROM T1 JOIN T2 ON T1.A = T2.A JOIN T3 ON T3.B = T1.B AND T3.C = T2.C JOIN T4 ON T4.C = T3.C AND T4.A = T1.A";
+
+        PlanningContext ctx = PlanningContext.builder()
+            .parentContext(BaseQueryContext.builder()
+                .frameworkConfig(newConfigBuilder(FRAMEWORK_CONFIG)
+                    .defaultSchema(schema)
+                    .costFactory(new IgniteCostFactory(1, 100, 1, 1))
+                    .build())
+                .logger(log)
+                .build()
+            )
+            .query(sql)
+            .build();
+
+        RelRoot relRoot;
+
+        try (IgnitePlanner planner = ctx.planner()) {
+            assertNotNull(planner);
+
+            String qry = ctx.query();
+
+            assertNotNull(qry);
+
+            // Parse
+            SqlNode sqlNode = planner.parse(qry);
+
+            // Validate
+            sqlNode = planner.validate(sqlNode);
+
+            // Convert to Relational operators graph
+            relRoot = planner.rel(sqlNode);
+
+            RelNode rel = relRoot.rel;
+
+            assertNotNull(rel);
+
+            // Transformation chain
+            RelTraitSet desired = rel.getCluster().traitSet()
+                .replace(IgniteConvention.INSTANCE)
+                .replace(IgniteDistributions.single())
+                .replace(CorrelationTrait.UNCORRELATED)
+                .simplify();
+
+            GridTestUtils.runAsync(() -> {
+               while(true) {
+                   log().error(planner.dumpRulesAttemptInfo());
+                   log().error("Count of IgniteTableSpool from RewindTraitDef "
+                       + IgniteTableSpool.REWINDABILITY_TRAIT_DEF.get());
+
+                   log().error("Count of IgniteTableSpool from TraitUtils "
+                       + IgniteTableSpool.CONVERT_TRAIT.get());
+
+                   try {
+                       Thread.sleep(1000);
+                   }
+                   catch (InterruptedException e) {
+                       break;
+                   }
+               }
+            });
+
+            IgniteRel phys = planner.transform(PlannerPhase.OPTIMIZATION, desired, rel);
+
+            assertNotNull(phys);
+
             checkSplitAndSerialization(phys, publicSchema);
         }
     }
