You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by al...@apache.org on 2021/05/12 13:15:28 UTC

[ignite] branch sql-calcite updated: IGNITE-14542 Table functions, system_range() function - Fixes #9085.

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

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


The following commit(s) were added to refs/heads/sql-calcite by this push:
     new 89a47c7  IGNITE-14542 Table functions, system_range() function - Fixes #9085.
89a47c7 is described below

commit 89a47c785e3a4bd207ee6d48e1582458e708c9e8
Author: Aleksey Plekhanov <pl...@gmail.com>
AuthorDate: Wed May 12 16:05:30 2021 +0300

    IGNITE-14542 Table functions, system_range() function - Fixes #9085.
    
    Signed-off-by: Aleksey Plekhanov <pl...@gmail.com>
---
 .../query/calcite/exec/LogicalRelImplementor.java  |  21 ++-
 .../query/calcite/exec/TableFunctionScan.java      |  46 ++++++
 .../calcite/exec/exp/ExpressionFactoryImpl.java    |   4 +
 .../exec/rel/CorrelatedNestedLoopJoinNode.java     |  42 ++++-
 .../query/calcite/fun/IgniteSqlFunctions.java      | 129 ++++++++++++++-
 .../calcite/metadata/IgniteMdFragmentMapping.java  |   8 +
 .../processors/query/calcite/prepare/Cloner.java   |   6 +
 .../query/calcite/prepare/IgniteRelShuttle.java    |   6 +
 .../query/calcite/prepare/PlannerPhase.java        |   6 +-
 .../processors/query/calcite/rel/IgniteFilter.java |   2 +-
 .../query/calcite/rel/IgniteRelVisitor.java        |   5 +
 .../query/calcite/rel/IgniteTableFunctionScan.java |  75 +++++++++
 .../calcite/rule/CorrelateToNestedLoopRule.java    | 123 +++++++++++++++
 .../calcite/rule/CorrelatedNestedLoopJoinRule.java |   2 +-
 .../rule/TableFunctionScanConverterRule.java       |  63 ++++++++
 .../query/calcite/trait/CorrelationTrait.java      |   7 +-
 .../query/calcite/trait/CorrelationTraitDef.java   |   2 +-
 .../CalciteErrorHandlilngIntegrationTest.java      |   4 +-
 .../query/calcite/CalciteQueryProcessorTest.java   |   2 +-
 .../processors/query/calcite/FunctionsTest.java    | 101 +++++++++++-
 .../query/calcite/exec/rel/ExecutionTest.java      |  70 +++++----
 .../query/calcite/planner/AbstractPlannerTest.java | 131 +++++++++++++++-
 .../query/calcite/planner/ExceptPlannerTest.java   | 173 +++------------------
 .../query/calcite/planner/TableFunctionTest.java   | 110 +++++++++++++
 .../apache/ignite/testsuites/PlannerTestSuite.java |   4 +-
 .../aggregates/test_approx_quantile.test_ignore    |   1 -
 .../test_approximate_distinct_count.test_ignore    |   1 -
 .../test_group_by_many_groups.test_slow_ignore     |   3 +-
 .../sql/aggregate/aggregates/test_perfect_ht.test  |  28 ++++
 .../aggregates/test_perfect_ht.test_ignore         |  17 +-
 .../test_string_agg_many_groups.test_slow_ignore   |   1 -
 .../{test_sum.test_ignore => test_sum.test}        |  11 +-
 .../sql/aggregate/aggregates/test_sum.test_ignore  |  10 +-
 33 files changed, 986 insertions(+), 228 deletions(-)

diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java
index 873a2f0..15a1d6f 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java
@@ -24,7 +24,6 @@ import java.util.UUID;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
-
 import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.JoinRelType;
@@ -72,6 +71,7 @@ import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRelVisitor;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSender;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSort;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSortedIndexSpool;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableFunctionScan;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableModify;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableSpool;
@@ -107,7 +107,7 @@ import static org.apache.ignite.internal.processors.query.calcite.util.TypeUtils
 @SuppressWarnings("TypeMayBeWeakened")
 public class LogicalRelImplementor<Row> implements IgniteRelVisitor<Node<Row>> {
     /** */
-    public static final String CNLJ_SUPPORTS_ONLY_INNER_ASSERTION_MSG = "only INNER join supported by IgniteCorrelatedNestedLoop";
+    public static final String CNLJ_NOT_SUPPORTED_JOIN_ASSERTION_MSG = "only INNER and LEFT join supported by IgniteCorrelatedNestedLoop";
 
     /** */
     private final ExecutionContext<Row> ctx;
@@ -237,9 +237,11 @@ public class LogicalRelImplementor<Row> implements IgniteRelVisitor<Node<Row>> {
         RelDataType rowType = combinedRowType(ctx.getTypeFactory(), leftType, rightType);
         Predicate<Row> cond = expressionFactory.predicate(rel.getCondition(), rowType);
 
-        assert rel.getJoinType() == JoinRelType.INNER : CNLJ_SUPPORTS_ONLY_INNER_ASSERTION_MSG;
+        assert rel.getJoinType() == JoinRelType.INNER || rel.getJoinType() == JoinRelType.LEFT
+            : CNLJ_NOT_SUPPORTED_JOIN_ASSERTION_MSG;
 
-        Node<Row> node = new CorrelatedNestedLoopJoinNode<>(ctx, outType, cond, rel.getVariablesSet());
+        Node<Row> node = new CorrelatedNestedLoopJoinNode<>(ctx, outType, cond, rel.getVariablesSet(),
+            rel.getJoinType());
 
         Node<Row> leftInput = visit(rel.getLeft());
         Node<Row> rightInput = visit(rel.getRight());
@@ -459,6 +461,17 @@ public class LogicalRelImplementor<Row> implements IgniteRelVisitor<Node<Row>> {
     }
 
     /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteTableFunctionScan rel) {
+        Supplier<Iterable<Object[]>> dataSupplier = expressionFactory.execute(rel.getCall());
+
+        RelDataType rowType = rel.getRowType();
+
+        RowFactory<Row> rowFactory = ctx.rowHandler().factory(ctx.getTypeFactory(), rowType);
+
+        return new ScanNode<>(ctx, rowType, new TableFunctionScan<>(dataSupplier, rowFactory));
+    }
+
+    /** {@inheritDoc} */
     @Override public Node<Row> visit(IgniteTableModify rel) {
         switch (rel.getOperation()) {
             case INSERT:
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableFunctionScan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableFunctionScan.java
new file mode 100644
index 0000000..a853184
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableFunctionScan.java
@@ -0,0 +1,46 @@
+/*
+ * 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.exec;
+
+import java.util.Iterator;
+import java.util.function.Supplier;
+import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler.RowFactory;
+import org.apache.ignite.internal.util.typedef.F;
+
+/** */
+public class TableFunctionScan<Row> implements Iterable<Row> {
+    /** */
+    private final Supplier<Iterable<Object[]>> dataSupplier;
+
+    /** */
+    private final RowFactory<Row> rowFactory;
+
+    /** */
+    public TableFunctionScan(
+        Supplier<Iterable<Object[]>> dataSupplier,
+        RowFactory<Row> rowFactory
+    ) {
+        this.dataSupplier = dataSupplier;
+        this.rowFactory = rowFactory;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Iterator<Row> iterator() {
+        return F.iterator(dataSupplier.get(), rowFactory::create, true);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactoryImpl.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactoryImpl.java
index b54a26f..4da32f9 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactoryImpl.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactoryImpl.java
@@ -32,6 +32,7 @@ import java.util.stream.Collectors;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Ordering;
 import com.google.common.primitives.Primitives;
+import org.apache.calcite.DataContext;
 import org.apache.calcite.adapter.enumerable.EnumUtils;
 import org.apache.calcite.adapter.enumerable.RexToLixTranslator;
 import org.apache.calcite.adapter.enumerable.RexToLixTranslator.InputGetter;
@@ -283,6 +284,9 @@ public class ExpressionFactoryImpl<Row> implements ExpressionFactory<Row> {
         ParameterExpression out_ =
             Expressions.parameter(Object.class, "out");
 
+        builder.add(
+            Expressions.declare(Modifier.FINAL, DataContext.ROOT, Expressions.convert_(ctx_, DataContext.class)));
+
         Expression hnd_ = builder.append("hnd",
             Expressions.call(ctx_,
                 IgniteMethod.CONTEXT_ROW_HANDLER.method()));
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/CorrelatedNestedLoopJoinNode.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/CorrelatedNestedLoopJoinNode.java
index 72c8de5..035f021 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/CorrelatedNestedLoopJoinNode.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/CorrelatedNestedLoopJoinNode.java
@@ -18,12 +18,14 @@
 package org.apache.ignite.internal.processors.query.calcite.exec.rel;
 
 import java.util.ArrayList;
+import java.util.BitSet;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Predicate;
 
 import org.apache.calcite.rel.core.CorrelationId;
+import org.apache.calcite.rel.core.JoinRelType;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
 import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler;
@@ -40,6 +42,9 @@ public class CorrelatedNestedLoopJoinNode<Row> extends AbstractNode<Row> {
     private final List<CorrelationId> correlationIds;
 
     /** */
+    private final JoinRelType joinType;
+
+    /** */
     private final RowHandler<Row> handler;
 
     /** */
@@ -49,6 +54,9 @@ public class CorrelatedNestedLoopJoinNode<Row> extends AbstractNode<Row> {
     private final int rightInBufferSize;
 
     /** */
+    private final BitSet leftMatched = new BitSet();
+
+    /** */
     private int requested;
 
     /** */
@@ -70,6 +78,9 @@ public class CorrelatedNestedLoopJoinNode<Row> extends AbstractNode<Row> {
     private int rightIdx;
 
     /** */
+    private Row rightEmptyRow;
+
+    /** */
     private State state = State.INITIAL;
 
     /** */
@@ -81,13 +92,15 @@ public class CorrelatedNestedLoopJoinNode<Row> extends AbstractNode<Row> {
      * @param ctx Execution context.
      * @param cond Join expression.
      */
-    public CorrelatedNestedLoopJoinNode(ExecutionContext<Row> ctx, RelDataType rowType, Predicate<Row> cond, Set<CorrelationId> correlationIds) {
+    public CorrelatedNestedLoopJoinNode(ExecutionContext<Row> ctx, RelDataType rowType, Predicate<Row> cond,
+        Set<CorrelationId> correlationIds, JoinRelType joinType) {
         super(ctx, rowType);
 
         assert !F.isEmpty(correlationIds);
 
         this.cond = cond;
         this.correlationIds = new ArrayList<>(correlationIds);
+        this.joinType = joinType;
 
         leftInBufferSize = correlationIds.size();
         rightInBufferSize = IN_BUFFER_SIZE;
@@ -359,13 +372,15 @@ public class CorrelatedNestedLoopJoinNode<Row> extends AbstractNode<Row> {
 
                     Row row = handler.concat(leftInBuf.get(leftIdx), rightInBuf.get(rightIdx));
 
-                    leftIdx++;
-
                     if (cond.test(row)) {
+                        leftMatched.set(leftIdx);
+
                         requested--;
 
                         downstream().push(row);
                     }
+
+                    leftIdx++;
                 }
 
                 if (leftIdx == leftInBuf.size())
@@ -390,9 +405,30 @@ public class CorrelatedNestedLoopJoinNode<Row> extends AbstractNode<Row> {
                 return;
             }
 
+            if (joinType == JoinRelType.LEFT && !F.isEmpty(leftInBuf)) {
+                if (rightEmptyRow == null)
+                    rightEmptyRow = handler.factory(context().getTypeFactory(), rightSource().rowType()).create();
+
+                int notMatchedIdx = leftMatched.nextClearBit(0);
+
+                while (requested > 0 && notMatchedIdx < leftInBuf.size()) {
+                    downstream().push(handler.concat(leftInBuf.get(notMatchedIdx), rightEmptyRow));
+
+                    requested--;
+
+                    leftMatched.set(notMatchedIdx);
+
+                    notMatchedIdx = leftMatched.nextClearBit(notMatchedIdx + 1);
+                }
+
+                if (requested == 0 && notMatchedIdx < leftInBuf.size())
+                    return; // Some rows required to be pushed, wait for request.
+            }
+
             if (waitingLeft == 0) {
                 rightInBuf = null;
                 leftInBuf = null;
+                leftMatched.clear();
 
                 state = State.FILLING_LEFT;
 
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/fun/IgniteSqlFunctions.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/fun/IgniteSqlFunctions.java
index 8f3174b..7c914ba 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/fun/IgniteSqlFunctions.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/fun/IgniteSqlFunctions.java
@@ -16,12 +16,28 @@
  */
 package org.apache.ignite.internal.processors.query.calcite.fun;
 
+import org.apache.calcite.DataContext;
+import org.apache.calcite.config.CalciteConnectionConfig;
+import org.apache.calcite.linq4j.AbstractEnumerable;
+import org.apache.calcite.linq4j.Enumerable;
+import org.apache.calcite.linq4j.Enumerator;
+import org.apache.calcite.linq4j.Linq4j;
 import org.apache.calcite.linq4j.function.Strict;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.schema.ScannableTable;
+import org.apache.calcite.schema.Schema;
+import org.apache.calcite.schema.Statistic;
+import org.apache.calcite.schema.Statistics;
+import org.apache.calcite.sql.SqlCall;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 /**
  * Ignite SQL functions.
  */
-public class IgniteSqlFunctions  {
+public class IgniteSqlFunctions {
     /**
      * Default constructor.
      */
@@ -29,9 +45,120 @@ public class IgniteSqlFunctions  {
         // No-op.
     }
 
+    /** SQL SYSTEM_RANGE(start, end) table function. */
+    public static ScannableTable system_range(Object rangeStart, Object rangeEnd) {
+        return new RangeTable(rangeStart, rangeEnd, 1L);
+    }
+
+    /** SQL SYSTEM_RANGE(start, end, increment) table function. */
+    public static ScannableTable system_range(Object rangeStart, Object rangeEnd, Object increment) {
+        return new RangeTable(rangeStart, rangeEnd, increment);
+    }
+
     /** SQL LENGTH(string) function. */
     @Strict
     public static int length(String str) {
         return str.length();
     }
+
+    /** */
+    private static class RangeTable implements ScannableTable {
+        /** Start of the range. */
+        private final Object rangeStart;
+
+        /** End of the range. */
+        private final Object rangeEnd;
+
+        /** Increment. */
+        private final Object increment;
+
+        /**
+         * Note: {@code Object} arguments required here due to:
+         * 1. {@code NULL} arguments need to be supported, so we can't use {@code long} arguments type.
+         * 2. {@code Integer} and other numeric classes can be converted to {@code long} type by java, but can't be
+         *      converted to {@code Long} type, so we can't use {@code Long} arguments type either.
+         * Instead, we accept {@code Object} arguments type and try to convert valid types to {@code long}.
+         */
+        RangeTable(Object rangeStart, Object rangeEnd, Object increment) {
+            this.rangeStart = rangeStart;
+            this.rangeEnd = rangeEnd;
+            this.increment = increment;
+        }
+
+        /** {@inheritDoc} */
+        @Override public RelDataType getRowType(RelDataTypeFactory typeFactory) {
+            return typeFactory.builder().add("X", SqlTypeName.BIGINT).build();
+        }
+
+        /** {@inheritDoc} */
+        @Override public Enumerable<@Nullable Object[]> scan(DataContext root) {
+            if (rangeStart == null || rangeEnd == null || increment == null)
+                return Linq4j.emptyEnumerable();
+
+            long rangeStart = convertToLongArg(this.rangeStart, "rangeStart");
+            long rangeEnd = convertToLongArg(this.rangeEnd, "rangeEnd");
+            long increment = convertToLongArg(this.increment, "increment");
+
+            if (increment == 0L)
+                throw new IllegalArgumentException("Increment can't be 0");
+
+            return new AbstractEnumerable<@Nullable Object[]>() {
+                @Override public Enumerator<@Nullable Object[]> enumerator() {
+                    return new Enumerator<Object[]>() {
+                        long cur = rangeStart - increment;
+
+                        @Override public Object[] current() {
+                            return new Object[] { cur };
+                        }
+
+                        @Override public boolean moveNext() {
+                            cur += increment;
+
+                            return increment > 0L ? cur <= rangeEnd : cur >= rangeEnd;
+                        }
+
+                        @Override public void reset() {
+                            cur = rangeStart - increment;
+                        }
+
+                        @Override public void close() {
+                            // No-op.
+                        }
+                    };
+                }
+            };
+        }
+
+        /** */
+        private long convertToLongArg(Object val, String name) {
+            if (val instanceof Byte || val instanceof Short || val instanceof Integer || val instanceof Long)
+                return ((Number)val).longValue();
+
+            throw new IllegalArgumentException("Unsupported argument type [arg=" + name +
+                ", type=" + val.getClass().getSimpleName() + ']');
+        }
+
+        /** {@inheritDoc} */
+        @Override public Statistic getStatistic() {
+            // We can't get access to this method from physical node on planning phase and Calcite dosn't use it either,
+            // so we can return any value here, it can't be used.
+            return Statistics.UNKNOWN;
+        }
+
+        /** {@inheritDoc} */
+        @Override public Schema.TableType getJdbcTableType() {
+            return Schema.TableType.TABLE;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean isRolledUp(String column) {
+            return false;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean rolledUpColumnValidInsideAgg(String column, SqlCall call,
+            SqlNode parent, CalciteConnectionConfig cfg) {
+            return true;
+        }
+    }
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdFragmentMapping.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdFragmentMapping.java
index b8ed40f..e16ce58 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdFragmentMapping.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdFragmentMapping.java
@@ -34,6 +34,7 @@ import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteReceiver;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableFunctionScan;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTrimExchange;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteValues;
@@ -205,6 +206,13 @@ public class IgniteMdFragmentMapping implements MetadataHandler<FragmentMappingM
     }
 
     /**
+     * See {@link IgniteMdFragmentMapping#fragmentMapping(RelNode, RelMetadataQuery)}
+     */
+    public FragmentMapping fragmentMapping(IgniteTableFunctionScan rel, RelMetadataQuery mq) {
+        return FragmentMapping.create();
+    }
+
+    /**
      * Fragment info calculation entry point.
      * @param rel Root node of a calculated fragment.
      * @param mq Metadata query instance.
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/Cloner.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/Cloner.java
index 2a8b410..e5f88c4 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/Cloner.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/Cloner.java
@@ -34,6 +34,7 @@ import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRelVisitor;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSender;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSort;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSortedIndexSpool;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableFunctionScan;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableModify;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableSpool;
@@ -244,6 +245,11 @@ public class Cloner implements IgniteRelVisitor<IgniteRel> {
     }
 
     /** {@inheritDoc} */
+    @Override public IgniteRel visit(IgniteTableFunctionScan rel) {
+        return rel.clone(cluster, F.asList());
+    }
+
+    /** {@inheritDoc} */
     @Override public IgniteRel visit(IgniteRel rel) {
         return rel.accept(this);
     }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteRelShuttle.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteRelShuttle.java
index 6397b30..8f5aec2 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteRelShuttle.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteRelShuttle.java
@@ -34,6 +34,7 @@ import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRelVisitor;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSender;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSort;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSortedIndexSpool;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableFunctionScan;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableModify;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableSpool;
@@ -198,6 +199,11 @@ public class IgniteRelShuttle implements IgniteRelVisitor<IgniteRel> {
         return processNode(rel);
     }
 
+    /** {@inheritDoc} */
+    @Override public IgniteRel visit(IgniteTableFunctionScan rel) {
+        return processNode(rel);
+    }
+
     /**
      * Visits all children of a parent.
      */
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
index ccfce87..244b6bb 100644
--- 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
@@ -39,6 +39,7 @@ import org.apache.calcite.rel.rules.SortRemoveRule;
 import org.apache.calcite.tools.Program;
 import org.apache.calcite.tools.RuleSet;
 import org.apache.calcite.tools.RuleSets;
+import org.apache.ignite.internal.processors.query.calcite.rule.CorrelateToNestedLoopRule;
 import org.apache.ignite.internal.processors.query.calcite.rule.CorrelatedNestedLoopJoinRule;
 import org.apache.ignite.internal.processors.query.calcite.rule.FilterConverterRule;
 import org.apache.ignite.internal.processors.query.calcite.rule.FilterSpoolMergeToHashIndexSpoolRule;
@@ -51,6 +52,7 @@ import org.apache.ignite.internal.processors.query.calcite.rule.NestedLoopJoinCo
 import org.apache.ignite.internal.processors.query.calcite.rule.ProjectConverterRule;
 import org.apache.ignite.internal.processors.query.calcite.rule.SortAggregateConverterRule;
 import org.apache.ignite.internal.processors.query.calcite.rule.SortConverterRule;
+import org.apache.ignite.internal.processors.query.calcite.rule.TableFunctionScanConverterRule;
 import org.apache.ignite.internal.processors.query.calcite.rule.TableModifyConverterRule;
 import org.apache.ignite.internal.processors.query.calcite.rule.UnionConverterRule;
 import org.apache.ignite.internal.processors.query.calcite.rule.ValuesConverterRule;
@@ -160,6 +162,7 @@ public enum PlannerPhase {
                     LogicalOrToUnionRule.INSTANCE,
 
                     CorrelatedNestedLoopJoinRule.INSTANCE,
+                    CorrelateToNestedLoopRule.INSTANCE,
                     NestedLoopJoinConverterRule.INSTANCE,
                     MergeJoinConverterRule.INSTANCE,
 
@@ -180,7 +183,8 @@ public enum PlannerPhase {
                     UnionConverterRule.INSTANCE,
                     SortConverterRule.INSTANCE,
                     FilterSpoolMergeToSortedIndexSpoolRule.INSTANCE,
-                    FilterSpoolMergeToHashIndexSpoolRule.INSTANCE
+                    FilterSpoolMergeToHashIndexSpoolRule.INSTANCE,
+                    TableFunctionScanConverterRule.INSTANCE
                 )
             );
         }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteFilter.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteFilter.java
index cdf08d5..9e7425b 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteFilter.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteFilter.java
@@ -109,7 +109,7 @@ public class IgniteFilter extends Filter implements TraitsAwareIgniteRel {
         CorrelationTrait correlation = TraitUtils.correlation(nodeTraits);
 
         if (corrSet.isEmpty() || correlation.correlationIds().containsAll(corrSet))
-            return Pair.of(nodeTraits, ImmutableList.of(inTraits.get(0)));
+            return Pair.of(nodeTraits, ImmutableList.of(inTraits.get(0).replace(correlation)));
 
         return null;
     }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteRelVisitor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteRelVisitor.java
index cab5c5f..95e5f2f 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteRelVisitor.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteRelVisitor.java
@@ -172,6 +172,11 @@ public interface IgniteRelVisitor<T> {
     T visit(IgniteReduceMinus rel);
 
     /**
+     * See {@link IgniteRelVisitor#visit(IgniteRel)}
+     */
+    T visit(IgniteTableFunctionScan rel);
+
+    /**
      * Visits a relational node and calculates a result on the basis of node meta information.
      * @param rel Relational node.
      * @return Visit result.
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTableFunctionScan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTableFunctionScan.java
new file mode 100644
index 0000000..0e0fb43
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTableFunctionScan.java
@@ -0,0 +1,75 @@
+/*
+ * 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.rel;
+
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Set;
+import com.google.common.collect.ImmutableList;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.TableFunctionScan;
+import org.apache.calcite.rel.metadata.RelColumnMapping;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexNode;
+import org.apache.ignite.internal.util.typedef.F;
+
+/**
+ * Relational operator for table function scan.
+ */
+public class IgniteTableFunctionScan extends TableFunctionScan implements IgniteRel {
+    /** Default estimate row count. */
+    private static final int ESTIMATE_ROW_COUNT = 100;
+
+    /**
+     * Creates a TableFunctionScan.
+     */
+    public IgniteTableFunctionScan(
+        RelOptCluster cluster,
+        RelTraitSet traits,
+        RexNode call,
+        RelDataType rowType
+    ) {
+        super(cluster, traits, ImmutableList.of(), call, null, rowType, null);
+    }
+
+    /** {@inheritDoc} */
+    @Override public IgniteRel clone(RelOptCluster cluster, List<IgniteRel> inputs) {
+        return new IgniteTableFunctionScan(cluster, getTraitSet(), getCall(), getRowType());
+    }
+
+    /** {@inheritDoc} */
+    @Override public <T> T accept(IgniteRelVisitor<T> visitor) {
+        return visitor.visit(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public TableFunctionScan copy(RelTraitSet traitSet, List<RelNode> inputs, RexNode rexCall,
+        Type elementType, RelDataType rowType, Set<RelColumnMapping> columnMappings) {
+        assert F.isEmpty(inputs);
+
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override public double estimateRowCount(RelMetadataQuery mq) {
+        return ESTIMATE_ROW_COUNT;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/CorrelateToNestedLoopRule.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/CorrelateToNestedLoopRule.java
new file mode 100644
index 0000000..0c31678
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/CorrelateToNestedLoopRule.java
@@ -0,0 +1,123 @@
+/*
+ * 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.rule;
+
+import java.util.Collections;
+import java.util.Set;
+import org.apache.calcite.plan.Convention;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelOptRule;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelTrait;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.convert.ConverterRule;
+import org.apache.calcite.rel.core.Correlate;
+import org.apache.calcite.rel.core.CorrelationId;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.logical.LogicalCorrelate;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteCorrelatedNestedLoopJoin;
+import org.apache.ignite.internal.processors.query.calcite.trait.CorrelationTrait;
+import org.apache.ignite.internal.processors.query.calcite.trait.RewindabilityTrait;
+
+/**
+ * Rule to convert LogicalCorrelate operator to IgniteCorrelatedNestedLoopJoin.
+ *
+ * LogicalCorrelate operators are created during sub-query rewriting. In most cases LogicalCorrelate can be further
+ * converted to other logical operators if decorrelation is enabled, but in some cases query can't
+ * be decorrelated (when table function is used for example), this rule is required to support such cases.
+ */
+public class CorrelateToNestedLoopRule extends ConverterRule {
+    /** */
+    public static final RelOptRule INSTANCE = Config.DEFAULT.toRule();
+
+    /** */
+    public CorrelateToNestedLoopRule(Config cfg) {
+        super(cfg);
+    }
+
+    /** {@inheritDoc} */
+    @Override public RelNode convert(RelNode rel) {
+        throw new IllegalStateException("Should not be called");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onMatch(RelOptRuleCall call) {
+        LogicalCorrelate rel = call.rel(0);
+
+        final RelOptCluster cluster = rel.getCluster();
+
+        final Set<CorrelationId> correlationIds = Collections.singleton(rel.getCorrelationId());
+        CorrelationTrait corrTrait = CorrelationTrait.correlations(correlationIds);
+
+        RelTraitSet outTraits = cluster.traitSetOf(IgniteConvention.INSTANCE);
+        RelTraitSet leftInTraits = cluster.traitSetOf(IgniteConvention.INSTANCE);
+        RelTraitSet rightInTraits = cluster.traitSetOf(IgniteConvention.INSTANCE)
+            .replace(RewindabilityTrait.REWINDABLE)
+            .replace(corrTrait);
+
+        RelNode left = convert(rel.getLeft(), leftInTraits);
+        RelNode right = convert(rel.getRight(), rightInTraits);
+
+        call.transformTo(
+            new IgniteCorrelatedNestedLoopJoin(
+                cluster,
+                outTraits,
+                left,
+                right,
+                cluster.getRexBuilder().makeLiteral(true),
+                correlationIds,
+                rel.getJoinType()
+            )
+        );
+    }
+
+    /** */
+    @SuppressWarnings("ClassNameSameAsAncestorName")
+    public interface Config extends ConverterRule.Config {
+        /** */
+        Config DEFAULT = ConverterRule.Config.INSTANCE
+            .withDescription("CorrelateToNestedLoopRule")
+            .withRelBuilderFactory(RelFactories.LOGICAL_BUILDER)
+            .as(Config.class)
+            .withConversion(LogicalCorrelate.class, Convention.NONE, IgniteConvention.INSTANCE);
+
+        /** */
+        default Config withConversion(Class<? extends Correlate> clazz, RelTrait in, RelTrait out) {
+            return withInTrait(in)
+                .withOutTrait(out)
+                .withOperandSupplier(b ->
+                    b.operand(clazz).predicate(CorrelateToNestedLoopRule::preMatch).convert(in))
+                .as(Config.class);
+        }
+
+        /** {@inheritDoc} */
+        @Override default CorrelateToNestedLoopRule toRule() {
+            return new CorrelateToNestedLoopRule(this);
+        }
+    }
+
+    /** */
+    private static boolean preMatch(Correlate corr) {
+        // Only these join types are currently supported by IgniteCorrelatedNestedLoopJoin
+        // and only these types are used to rewrite sub-query.
+        return corr.getJoinType() == JoinRelType.INNER || corr.getJoinType() == JoinRelType.LEFT;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/CorrelatedNestedLoopJoinRule.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/CorrelatedNestedLoopJoinRule.java
index d035404..a1e2c66 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/CorrelatedNestedLoopJoinRule.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/CorrelatedNestedLoopJoinRule.java
@@ -187,6 +187,6 @@ public class CorrelatedNestedLoopJoinRule extends ConverterRule {
 
     /** */
     private static boolean preMatch(Join join) {
-        return join.getJoinType() == JoinRelType.INNER; // TODO LEFT, SEMI, ANTI
+        return join.getJoinType() == JoinRelType.INNER || join.getJoinType() == JoinRelType.LEFT; // TODO SEMI, ANTI
     }
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/TableFunctionScanConverterRule.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/TableFunctionScanConverterRule.java
new file mode 100644
index 0000000..4a21c94
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/TableFunctionScanConverterRule.java
@@ -0,0 +1,63 @@
+/*
+ * 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.rule;
+
+import java.util.Set;
+import org.apache.calcite.plan.RelOptPlanner;
+import org.apache.calcite.plan.RelOptRule;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.PhysicalNode;
+import org.apache.calcite.rel.core.CorrelationId;
+import org.apache.calcite.rel.logical.LogicalTableFunctionScan;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableFunctionScan;
+import org.apache.ignite.internal.processors.query.calcite.trait.CorrelationTrait;
+import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
+import org.apache.ignite.internal.processors.query.calcite.trait.RewindabilityTrait;
+import org.apache.ignite.internal.processors.query.calcite.util.RexUtils;
+import org.apache.ignite.internal.util.typedef.F;
+
+/**
+ * Rule to convert a {@link LogicalTableFunctionScan} to an {@link IgniteTableFunctionScan}.
+ */
+public class TableFunctionScanConverterRule extends AbstractIgniteConverterRule<LogicalTableFunctionScan> {
+    /** */
+    public static final RelOptRule INSTANCE = new TableFunctionScanConverterRule();
+
+    /** Default constructor. */
+    private TableFunctionScanConverterRule() {
+        super(LogicalTableFunctionScan.class, "TableFunctionScanConverterRule");
+    }
+
+    /** {@inheritDoc} */
+    @Override protected PhysicalNode convert(RelOptPlanner planner, RelMetadataQuery mq, LogicalTableFunctionScan rel) {
+        assert F.isEmpty(rel.getInputs());
+
+        RelTraitSet traitSet = rel.getTraitSet()
+            .replace(IgniteConvention.INSTANCE)
+            .replace(RewindabilityTrait.REWINDABLE)
+            .replace(IgniteDistributions.broadcast());
+
+        Set<CorrelationId> corrIds = RexUtils.extractCorrelationIds(rel.getCall());
+
+        if (!corrIds.isEmpty())
+            traitSet = traitSet.replace(CorrelationTrait.correlations(corrIds));
+
+        return new IgniteTableFunctionScan(rel.getCluster(), traitSet, rel.getCall(), rel.getRowType());
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/CorrelationTrait.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/CorrelationTrait.java
index 5c88ddf..523a701 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/CorrelationTrait.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/CorrelationTrait.java
@@ -92,7 +92,12 @@ public class CorrelationTrait implements RelTrait {
         if (trait == this || this == UNCORRELATED)
             return true;
 
-        return equals(trait);
+        if (!(trait instanceof CorrelationTrait))
+            return false;
+
+        CorrelationTrait other = (CorrelationTrait) trait;
+
+        return other.correlated() && other.correlationIds().containsAll(correlationIds());
     }
 
     /** {@inheritDoc} */
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/CorrelationTraitDef.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/CorrelationTraitDef.java
index de53642..9e0a020 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/CorrelationTraitDef.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/CorrelationTraitDef.java
@@ -43,7 +43,7 @@ public class CorrelationTraitDef extends RelTraitDef<CorrelationTrait> {
 
     /** {@inheritDoc} */
     @Override public boolean canConvert(RelOptPlanner planner, CorrelationTrait fromTrait, CorrelationTrait toTrait) {
-        return false;
+        return fromTrait.satisfies(toTrait);
     }
 
     /** {@inheritDoc} */
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/CalciteErrorHandlilngIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/CalciteErrorHandlilngIntegrationTest.java
index 82e52a2..bbe1592 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/CalciteErrorHandlilngIntegrationTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/CalciteErrorHandlilngIntegrationTest.java
@@ -49,7 +49,7 @@ import org.junit.After;
 import org.junit.Test;
 
 import static org.apache.ignite.IgniteSystemProperties.IGNITE_EXPERIMENTAL_SQL_ENGINE;
-import static org.apache.ignite.internal.processors.query.calcite.exec.LogicalRelImplementor.CNLJ_SUPPORTS_ONLY_INNER_ASSERTION_MSG;
+import static org.apache.ignite.internal.processors.query.calcite.exec.LogicalRelImplementor.CNLJ_NOT_SUPPORTED_JOIN_ASSERTION_MSG;
 
 /** */
 @WithSystemProperty(key = "calcite.debug", value = "false")
@@ -98,7 +98,7 @@ public class CalciteErrorHandlilngIntegrationTest extends GridCommonAbstractTest
         String sql = "select /*+ DISABLE_RULE('NestedLoopJoinConverter', 'MergeJoinConverter') */ t1.id from test t1, test t2 where t1.id = t2.id";
 
         Throwable t = GridTestUtils.assertThrowsWithCause(() -> sql(client, sql), AssertionError.class);
-        assertEquals(CNLJ_SUPPORTS_ONLY_INNER_ASSERTION_MSG, t.getCause().getMessage());
+        assertEquals(CNLJ_NOT_SUPPORTED_JOIN_ASSERTION_MSG, t.getCause().getMessage());
     }
 
     /**
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessorTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessorTest.java
index 9b0195b..c80118a 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessorTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessorTest.java
@@ -913,7 +913,7 @@ public class CalciteQueryProcessorTest extends GridCommonAbstractTest {
 
         employer.putAll(ImmutableMap.of(
             keysNode0.get(0), new Employer("Igor", 10d),
-            keysNode0.get(1), new Employer("Roman", 20d) ,
+            keysNode0.get(1), new Employer("Roman", 20d),
             keysNode1.get(0), new Employer("Nikolay", 30d)
         ));
 
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/FunctionsTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/FunctionsTest.java
index 851f11f..f200904 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/FunctionsTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/FunctionsTest.java
@@ -17,9 +17,13 @@
 
 package org.apache.ignite.internal.processors.query.calcite;
 
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.configuration.CacheConfiguration;
 import org.apache.ignite.internal.IgniteEx;
 import org.apache.ignite.internal.processors.query.QueryEngine;
 import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.testframework.GridTestUtils;
 import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
 import org.junit.Test;
 
@@ -32,19 +36,110 @@ public class FunctionsTest extends GridCommonAbstractTest {
 
     /** {@inheritDoc} */
     @Override protected void beforeTestsStarted() throws Exception {
-        IgniteEx grid = startGrid();
+        Ignite grid = startGridsMultiThreaded(3);
 
-        qryEngine = Commons.lookupComponent(grid.context(), QueryEngine.class);
+        qryEngine = Commons.lookupComponent(((IgniteEx)grid).context(), QueryEngine.class);
     }
 
     /** */
     @Test
-    public void testLength() throws Exception {
+    public void testLength() {
         checkQuery("SELECT LENGTH('TEST')").returns(4).check();
         checkQuery("SELECT LENGTH(NULL)").returns(new Object[] { null }).check();
     }
 
     /** */
+    @Test
+    public void testRange() {
+        checkQuery("SELECT * FROM table(system_range(1, 4))")
+            .returns(1L)
+            .returns(2L)
+            .returns(3L)
+            .returns(4L)
+            .check();
+
+        checkQuery("SELECT * FROM table(system_range(1, 4, 2))")
+            .returns(1L)
+            .returns(3L)
+            .check();
+
+        checkQuery("SELECT * FROM table(system_range(4, 1, -1))")
+            .returns(4L)
+            .returns(3L)
+            .returns(2L)
+            .returns(1L)
+            .check();
+
+        checkQuery("SELECT * FROM table(system_range(4, 1, -2))")
+            .returns(4L)
+            .returns(2L)
+            .check();
+
+        assertEquals(0, qryEngine.query(null, "PUBLIC",
+            "SELECT * FROM table(system_range(4, 1))").get(0).getAll().size());
+
+        assertEquals(0, qryEngine.query(null, "PUBLIC",
+            "SELECT * FROM table(system_range(null, 1))").get(0).getAll().size());
+
+        GridTestUtils.assertThrowsAnyCause(log, () -> qryEngine.query(null, "PUBLIC",
+            "SELECT * FROM table(system_range(1, 1, 0))").get(0).getAll(), IllegalArgumentException.class,
+            "Increment can't be 0");
+    }
+
+    /** */
+    @Test
+    public void testRangeWithCache() throws Exception {
+        IgniteCache<Integer, Integer> cache = grid(0).getOrCreateCache(
+            new CacheConfiguration<Integer, Integer>("test")
+                .setBackups(1)
+                .setIndexedTypes(Integer.class, Integer.class)
+        );
+
+        for (int i = 0; i < 100; i++)
+            cache.put(i, i);
+
+        awaitPartitionMapExchange();
+
+        // Correlated INNER join.
+        checkQuery("SELECT t._val FROM \"test\".Integer t WHERE t._val < 5 AND " +
+            "t._key in (SELECT x FROM table(system_range(t._val, t._val))) ")
+            .returns(0)
+            .returns(1)
+            .returns(2)
+            .returns(3)
+            .returns(4)
+            .check();
+
+        // Correlated LEFT joins.
+        checkQuery("SELECT t._val FROM \"test\".Integer t WHERE t._val < 5 AND " +
+            "EXISTS (SELECT x FROM table(system_range(t._val, t._val)) WHERE mod(x, 2) = 0) ")
+            .returns(0)
+            .returns(2)
+            .returns(4)
+            .check();
+
+        checkQuery("SELECT t._val FROM \"test\".Integer t WHERE t._val < 5 AND " +
+            "NOT EXISTS (SELECT x FROM table(system_range(t._val, t._val)) WHERE mod(x, 2) = 0) ")
+            .returns(1)
+            .returns(3)
+            .check();
+
+        assertEquals(0, qryEngine.query(null, "PUBLIC",
+            "SELECT t._val FROM \"test\".Integer t WHERE " +
+                "EXISTS (SELECT x FROM table(system_range(t._val, null))) ").get(0).getAll().size());
+
+        // Non-correlated join.
+        checkQuery("SELECT t._val FROM \"test\".Integer t JOIN table(system_range(1, 50)) as r ON t._key = r.x " +
+            "WHERE mod(r.x, 10) = 0")
+            .returns(10)
+            .returns(20)
+            .returns(30)
+            .returns(40)
+            .returns(50)
+            .check();
+    }
+
+    /** */
     private QueryChecker checkQuery(String qry) {
         return new QueryChecker(qry) {
             @Override protected QueryEngine getEngine() {
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/ExecutionTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/ExecutionTest.java
index bf2cff0..6bd51c6 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/ExecutionTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/ExecutionTest.java
@@ -21,9 +21,9 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.UUID;
-
 import com.google.common.collect.ImmutableSet;
 import org.apache.calcite.rel.core.CorrelationId;
+import org.apache.calcite.rel.core.JoinRelType;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.IgniteInterruptedCheckedException;
@@ -461,44 +461,50 @@ public class ExecutionTest extends AbstractExecutionTest {
         int[] leftSizes = {1, 99, 100, 101, 512, 513, 2000};
         int[] rightSizes = {1, 99, 100, 101, 512, 513, 2000};
         int[] rightBufSizes = {1, 100, 512};
+        JoinRelType[] joinTypes = {INNER, LEFT};
 
-        for (int rightBufSize : rightBufSizes) {
-            for (int leftSize : leftSizes) {
-                for (int rightSize : rightSizes) {
-                    log.info("Check: rightBufSize=" + rightBufSize + ", leftSize=" + leftSize + ", rightSize=" + rightSize);
+        for (JoinRelType joinType : joinTypes) {
+            for (int rightBufSize : rightBufSizes) {
+                for (int leftSize : leftSizes) {
+                    for (int rightSize : rightSizes) {
+                        log.info("Check: joinType=" + joinType + ", rightBufSize=" + rightBufSize +
+                            ", leftSize=" + leftSize + ", rightSize=" + rightSize);
 
-                    ScanNode<Object[]> left = new ScanNode<>(ctx, rowType, new TestTable(leftSize, rowType));
-                    ScanNode<Object[]> right = new ScanNode<>(ctx, rowType, new TestTable(rightSize, rowType));
+                        ScanNode<Object[]> left = new ScanNode<>(ctx, rowType, new TestTable(leftSize, rowType));
+                        ScanNode<Object[]> right = new ScanNode<>(ctx, rowType, new TestTable(rightSize, rowType));
 
-                    RelDataType joinRowType = TypeUtils.createRowType(
-                        tf,
-                        int.class, String.class, int.class,
-                        int.class, String.class, int.class);
+                        RelDataType joinRowType = TypeUtils.createRowType(
+                            tf,
+                            int.class, String.class, int.class,
+                            int.class, String.class, int.class);
 
-                    CorrelatedNestedLoopJoinNode<Object[]> join = new CorrelatedNestedLoopJoinNode<>(
-                        ctx,
-                        joinRowType,
-                        r -> r[0].equals(r[3]),
-                        ImmutableSet.of(new CorrelationId(0)));
+                        CorrelatedNestedLoopJoinNode<Object[]> join = new CorrelatedNestedLoopJoinNode<>(
+                            ctx,
+                            joinRowType,
+                            r -> r[0].equals(r[3]),
+                            ImmutableSet.of(new CorrelationId(0)),
+                            joinType
+                        );
 
-                    GridTestUtils.setFieldValue(join, "rightInBufferSize", rightBufSize);
+                        GridTestUtils.setFieldValue(join, "rightInBufferSize", rightBufSize);
 
-                    join.register(Arrays.asList(left, right));
+                        join.register(Arrays.asList(left, right));
 
-                    RootNode<Object[]> root = new RootNode<>(ctx, joinRowType);
-                    root.register(join);
+                        RootNode<Object[]> root = new RootNode<>(ctx, joinRowType);
+                        root.register(join);
 
-                    int cnt = 0;
-                    while (root.hasNext()) {
-                        root.next();
+                        int cnt = 0;
+                        while (root.hasNext()) {
+                            root.next();
 
-                        cnt++;
-                    }
+                            cnt++;
+                        }
 
-                    assertEquals(
-                        "Invalid result size. [left=" + leftSize + ", right=" + rightSize + ", results=" + cnt,
-                        min(leftSize, rightSize),
-                        cnt);
+                        assertEquals(
+                            "Invalid result size. [left=" + leftSize + ", right=" + rightSize + ", results=" + cnt,
+                            joinType == INNER ? min(leftSize, rightSize) : leftSize,
+                            cnt);
+                    }
                 }
             }
         }
@@ -604,10 +610,8 @@ public class ExecutionTest extends AbstractExecutionTest {
         watchDog.interrupt();
     }
 
-    /**
-     *
-     */
-    protected Object[] row(Object... fields) {
+    /** {@inheritDoc} */
+    @Override protected Object[] row(Object... fields) {
         return fields;
     }
 
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java
index 03a92bb..f8fa2f5 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java
@@ -54,6 +54,7 @@ import org.apache.calcite.schema.Schema;
 import org.apache.calcite.schema.SchemaPlus;
 import org.apache.calcite.schema.Statistic;
 import org.apache.calcite.sql.SqlCall;
+import org.apache.calcite.sql.SqlExplainLevel;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.ignite.cluster.ClusterNode;
@@ -75,6 +76,7 @@ import org.apache.ignite.internal.processors.query.calcite.prepare.PlanningConte
 import org.apache.ignite.internal.processors.query.calcite.prepare.Splitter;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
 import org.apache.ignite.internal.processors.query.calcite.rel.logical.IgniteLogicalIndexScan;
 import org.apache.ignite.internal.processors.query.calcite.rel.logical.IgniteLogicalTableScan;
 import org.apache.ignite.internal.processors.query.calcite.schema.IgniteIndex;
@@ -107,7 +109,7 @@ import static org.apache.ignite.internal.processors.query.calcite.externalize.Re
  *
  */
 //@WithSystemProperty(key = "calcite.debug", value = "true")
-@SuppressWarnings({"TooBroadScope", "FieldCanBeLocal", "TypeMayBeWeakened", "unchecked"})
+@SuppressWarnings({"TooBroadScope", "FieldCanBeLocal", "TypeMayBeWeakened"})
 public abstract class AbstractPlannerTest extends GridCommonAbstractTest {
     /** */
     protected List<UUID> nodes;
@@ -118,6 +120,9 @@ public abstract class AbstractPlannerTest extends GridCommonAbstractTest {
     /** */
     protected volatile Throwable lastE;
 
+    /** Last error message. */
+    private String lastErrorMsg;
+
     /** */
     @Before
     public void setup() {
@@ -139,6 +144,7 @@ public abstract class AbstractPlannerTest extends GridCommonAbstractTest {
 
     /** */
     interface TestVisitor {
+        /** */
         public void visit(RelNode node, int ordinal, RelNode parent);
     }
 
@@ -195,7 +201,7 @@ public abstract class AbstractPlannerTest extends GridCommonAbstractTest {
 
     /** */
     public static <T extends RelNode> Predicate<RelNode> byClass(Class<T> cls) {
-        return node -> cls.isInstance(node);
+        return cls::isInstance;
     }
 
     /** */
@@ -375,7 +381,7 @@ public abstract class AbstractPlannerTest extends GridCommonAbstractTest {
         }
 
         List<RelNode> expectedRels = fragments.stream()
-            .map(f -> f.root())
+            .map(Fragment::root)
             .collect(Collectors.toList());
 
         assertEquals("Invalid deserialization fragments count", expectedRels.size(), deserializedNodes.size());
@@ -437,6 +443,125 @@ public abstract class AbstractPlannerTest extends GridCommonAbstractTest {
     }
 
     /** */
+    protected static void createTable(IgniteSchema schema, String name, RelDataType type, IgniteDistribution distr,
+        List<List<UUID>> assignment) {
+        TestTable table = new TestTable(type) {
+            @Override public ColocationGroup colocationGroup(PlanningContext ctx) {
+                if (F.isEmpty(assignment))
+                    return super.colocationGroup(ctx);
+                else
+                    return ColocationGroup.forAssignments(assignment);
+            }
+
+            @Override public IgniteDistribution distribution() {
+                return distr;
+            }
+
+            @Override public String name() {
+                return name;
+            }
+        };
+
+        schema.addTable(name, table);
+    }
+
+    /** */
+    protected <T extends RelNode> void assertPlan(String sql, IgniteSchema schema, Predicate<T> predicate,
+        String... disabledRules) throws Exception {
+        IgniteRel plan = physicalPlan(sql, schema, disabledRules);
+
+        if (!predicate.test((T)plan)) {
+            String invalidPlanMsg = "Invalid plan (" + lastErrorMsg + "):\n" +
+                RelOptUtil.toString(plan, SqlExplainLevel.ALL_ATTRIBUTES);
+
+            fail(invalidPlanMsg);
+        }
+    }
+
+    /**
+     * Predicate builder for "Instance of class" condition.
+     */
+    protected <T extends RelNode> Predicate<T> isInstanceOf(Class<T> cls) {
+        return node -> {
+            if (cls.isInstance(node))
+                return true;
+
+            lastErrorMsg = "Unexpected node class [node=" + node + ", cls=" + cls.getSimpleName() + ']';
+
+            return false;
+        };
+    }
+
+    /**
+     * Predicate builder for "Table scan with given name" condition.
+     */
+    protected <T extends RelNode> Predicate<IgniteTableScan> isTableScan(String tableName) {
+        return isInstanceOf(IgniteTableScan.class).and(
+            n -> {
+                String scanTableName = n.getTable().unwrap(TestTable.class).name();
+
+                if (tableName.equalsIgnoreCase(scanTableName))
+                    return true;
+
+                lastErrorMsg = "Unexpected table name [exp=" + tableName + ", act=" + scanTableName + ']';
+
+                return false;
+            });
+    }
+
+    /**
+     * Predicate builder for "Any child satisfy predicate" condition.
+     */
+    protected <T extends RelNode> Predicate<RelNode> hasChildThat(Predicate<T> predicate) {
+        return new Predicate<RelNode>() {
+            public boolean checkRecursively(RelNode node) {
+                if (predicate.test((T)node))
+                    return true;
+
+                for (RelNode input : node.getInputs()) {
+                    if (checkRecursively(input))
+                        return true;
+                }
+
+                return false;
+            }
+
+            @Override public boolean test(RelNode node) {
+                for (RelNode input : node.getInputs()) {
+                    if (checkRecursively(input))
+                        return true;
+                }
+
+                lastErrorMsg = "Not found child for defined condition [node=" + node + ']';
+
+                return false;
+            }
+        };
+    }
+
+    /**
+     * Predicate builder for "Current node or any child satisfy predicate" condition.
+     */
+    protected Predicate<RelNode> nodeOrAnyChild(Predicate<? extends RelNode> predicate) {
+        return (Predicate<RelNode>)predicate.or(hasChildThat(predicate));
+    }
+
+    /**
+     * Predicate builder for "Input with given index satisfy predicate" condition.
+     */
+    protected <T extends RelNode> Predicate<RelNode> input(int idx, Predicate<T> predicate) {
+        return node -> {
+            if (F.size(node.getInputs()) <= idx) {
+                lastErrorMsg = "No input for node [idx=" + idx + ", node=" + node + ']';
+
+                return false;
+            }
+
+            return predicate.test((T)node.getInput(idx));
+        };
+    }
+
+    /** */
     abstract static class TestTable implements IgniteTable {
         /** */
         private final String name;
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/ExceptPlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/ExceptPlannerTest.java
index c9cb0cc..1b04e91 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/ExceptPlannerTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/ExceptPlannerTest.java
@@ -20,25 +20,15 @@ package org.apache.ignite.internal.processors.query.calcite.planner;
 import java.util.Arrays;
 import java.util.List;
 import java.util.UUID;
-import java.util.function.Predicate;
-import org.apache.calcite.plan.RelOptUtil;
-import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
-import org.apache.calcite.sql.SqlExplainLevel;
-import org.apache.ignite.internal.processors.query.calcite.metadata.ColocationGroup;
-import org.apache.ignite.internal.processors.query.calcite.prepare.PlanningContext;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
 import org.apache.ignite.internal.processors.query.calcite.rel.set.IgniteMapMinus;
 import org.apache.ignite.internal.processors.query.calcite.rel.set.IgniteReduceMinus;
 import org.apache.ignite.internal.processors.query.calcite.rel.set.IgniteSingleMinus;
 import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
-import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
 import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
 import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
 import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeSystem;
-import org.apache.ignite.internal.util.typedef.F;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -67,12 +57,12 @@ public class ExceptPlannerTest extends AbstractPlannerTest {
             .add("SALARY", f.createJavaType(Double.class))
             .build();
 
-        createTable("RANDOM_TBL1", type, IgniteDistributions.random(), null);
-        createTable("RANDOM_TBL2", type, IgniteDistributions.random(), null);
-        createTable("BROADCAST_TBL1", type, IgniteDistributions.broadcast(), null);
-        createTable("BROADCAST_TBL2", type, IgniteDistributions.broadcast(), null);
-        createTable("SINGLE_TBL1", type, IgniteDistributions.single(), null);
-        createTable("SINGLE_TBL2", type, IgniteDistributions.single(), null);
+        createTable(publicSchema, "RANDOM_TBL1", type, IgniteDistributions.random(), null);
+        createTable(publicSchema, "RANDOM_TBL2", type, IgniteDistributions.random(), null);
+        createTable(publicSchema, "BROADCAST_TBL1", type, IgniteDistributions.broadcast(), null);
+        createTable(publicSchema, "BROADCAST_TBL2", type, IgniteDistributions.broadcast(), null);
+        createTable(publicSchema, "SINGLE_TBL1", type, IgniteDistributions.single(), null);
+        createTable(publicSchema, "SINGLE_TBL2", type, IgniteDistributions.single(), null);
 
         List<List<UUID>> assignment = Arrays.asList(
             select(nodes, 0, 1),
@@ -82,11 +72,11 @@ public class ExceptPlannerTest extends AbstractPlannerTest {
             select(nodes, 1, 2)
         );
 
-        createTable("AFFINITY_TBL1", type, IgniteDistributions.affinity(0, "Test1", "hash"),
-            assignment);
+        createTable(publicSchema, "AFFINITY_TBL1", type,
+            IgniteDistributions.affinity(0, "Test1", "hash"), assignment);
 
-        createTable("AFFINITY_TBL2", type, IgniteDistributions.affinity(0, "Test2", "hash"),
-            assignment);
+        createTable(publicSchema, "AFFINITY_TBL2", type,
+            IgniteDistributions.affinity(0, "Test2", "hash"), assignment);
     }
 
     /**
@@ -99,7 +89,7 @@ public class ExceptPlannerTest extends AbstractPlannerTest {
             "EXCEPT " +
             "SELECT * FROM random_tbl2 ";
 
-        assertPlan(sql, isInstanceOf(IgniteReduceMinus.class).and(n -> !n.all)
+        assertPlan(sql, publicSchema, isInstanceOf(IgniteReduceMinus.class).and(n -> !n.all)
             .and(hasChildThat(isInstanceOf(IgniteMapMinus.class)
                 .and(input(0, isTableScan("random_tbl1")))
                 .and(input(1, isTableScan("random_tbl2")))
@@ -117,7 +107,7 @@ public class ExceptPlannerTest extends AbstractPlannerTest {
             "EXCEPT ALL " +
             "SELECT * FROM random_tbl2 ";
 
-        assertPlan(sql, isInstanceOf(IgniteReduceMinus.class).and(n -> n.all)
+        assertPlan(sql, publicSchema, isInstanceOf(IgniteReduceMinus.class).and(n -> n.all)
             .and(hasChildThat(isInstanceOf(IgniteMapMinus.class)
                 .and(input(0, isTableScan("random_tbl1")))
                 .and(input(1, isTableScan("random_tbl2")))
@@ -135,7 +125,7 @@ public class ExceptPlannerTest extends AbstractPlannerTest {
             "EXCEPT " +
             "SELECT * FROM broadcast_tbl2 ";
 
-        assertPlan(sql, isInstanceOf(IgniteSingleMinus.class)
+        assertPlan(sql, publicSchema, isInstanceOf(IgniteSingleMinus.class)
             .and(input(0, isTableScan("broadcast_tbl1")))
             .and(input(1, isTableScan("broadcast_tbl2")))
         );
@@ -151,7 +141,7 @@ public class ExceptPlannerTest extends AbstractPlannerTest {
             "EXCEPT " +
             "SELECT * FROM single_tbl2 ";
 
-        assertPlan(sql, isInstanceOf(IgniteSingleMinus.class)
+        assertPlan(sql, publicSchema, isInstanceOf(IgniteSingleMinus.class)
             .and(input(0, isTableScan("single_tbl1")))
             .and(input(1, isTableScan("single_tbl2"))));
     }
@@ -166,7 +156,7 @@ public class ExceptPlannerTest extends AbstractPlannerTest {
             "EXCEPT " +
             "SELECT * FROM random_tbl1 ";
 
-        assertPlan(sql, isInstanceOf(IgniteSingleMinus.class)
+        assertPlan(sql, publicSchema, isInstanceOf(IgniteSingleMinus.class)
             .and(input(0, isTableScan("single_tbl1")))
             .and(input(1, hasChildThat(isTableScan("random_tbl1")))));
     }
@@ -181,7 +171,7 @@ public class ExceptPlannerTest extends AbstractPlannerTest {
             "EXCEPT " +
             "SELECT * FROM affinity_tbl1 ";
 
-        assertPlan(sql, isInstanceOf(IgniteSingleMinus.class)
+        assertPlan(sql, publicSchema, isInstanceOf(IgniteSingleMinus.class)
             .and(input(0, isTableScan("single_tbl1")))
             .and(input(1, hasChildThat(isTableScan("affinity_tbl1")))));
     }
@@ -196,7 +186,7 @@ public class ExceptPlannerTest extends AbstractPlannerTest {
             "EXCEPT " +
             "SELECT * FROM broadcast_tbl1 ";
 
-        assertPlan(sql, isInstanceOf(IgniteSingleMinus.class)
+        assertPlan(sql, publicSchema, isInstanceOf(IgniteSingleMinus.class)
             .and(input(0, isTableScan("single_tbl1")))
             .and(input(1, isTableScan("broadcast_tbl1")))
         );
@@ -212,7 +202,7 @@ public class ExceptPlannerTest extends AbstractPlannerTest {
             "EXCEPT " +
             "SELECT * FROM affinity_tbl2 ";
 
-        assertPlan(sql, isInstanceOf(IgniteReduceMinus.class)
+        assertPlan(sql, publicSchema, isInstanceOf(IgniteReduceMinus.class)
             .and(hasChildThat(isInstanceOf(IgniteMapMinus.class)
                 .and(input(0, isTableScan("affinity_tbl1")))
                 .and(input(1, isTableScan("affinity_tbl2")))
@@ -230,7 +220,7 @@ public class ExceptPlannerTest extends AbstractPlannerTest {
             "EXCEPT " +
             "SELECT * FROM broadcast_tbl1 ";
 
-        assertPlan(sql, isInstanceOf(IgniteSingleMinus.class)
+        assertPlan(sql, publicSchema, isInstanceOf(IgniteSingleMinus.class)
             .and(input(0, hasChildThat(isTableScan("random_tbl1"))))
             .and(input(1, isTableScan("broadcast_tbl1")))
         );
@@ -248,7 +238,7 @@ public class ExceptPlannerTest extends AbstractPlannerTest {
             "   SELECT * FROM random_tbl2" +
             ")";
 
-        assertPlan(sql, isInstanceOf(IgniteSingleMinus.class)
+        assertPlan(sql, publicSchema, isInstanceOf(IgniteSingleMinus.class)
             .and(input(0, hasChildThat(isTableScan("random_tbl2"))))
             .and(input(1, isInstanceOf(IgniteReduceMinus.class)
                 .and(hasChildThat(isInstanceOf(IgniteMapMinus.class)
@@ -271,7 +261,7 @@ public class ExceptPlannerTest extends AbstractPlannerTest {
             "   SELECT * FROM random_tbl2" +
             ")";
 
-        assertPlan(sql, isInstanceOf(IgniteSingleMinus.class)
+        assertPlan(sql, publicSchema, isInstanceOf(IgniteSingleMinus.class)
             .and(input(0, isTableScan("broadcast_tbl1")))
             .and(input(1, isInstanceOf(IgniteReduceMinus.class)
                 .and(hasChildThat(isInstanceOf(IgniteMapMinus.class)
@@ -296,7 +286,7 @@ public class ExceptPlannerTest extends AbstractPlannerTest {
             "EXCEPT " +
             "SELECT * FROM affinity_tbl2 ";
 
-        assertPlan(sql, isInstanceOf(IgniteReduceMinus.class)
+        assertPlan(sql, publicSchema, isInstanceOf(IgniteReduceMinus.class)
             .and(hasChildThat(isInstanceOf(IgniteMapMinus.class)
                 .and(input(0, isTableScan("random_tbl1")))
                 .and(input(1, isTableScan("random_tbl2")))
@@ -320,7 +310,7 @@ public class ExceptPlannerTest extends AbstractPlannerTest {
             "EXCEPT ALL " +
             "SELECT * FROM affinity_tbl2 ";
 
-        assertPlan(sql, isInstanceOf(IgniteReduceMinus.class).and(n -> n.all)
+        assertPlan(sql, publicSchema, isInstanceOf(IgniteReduceMinus.class).and(n -> n.all)
             .and(hasChildThat(isInstanceOf(IgniteMapMinus.class)
                 .and(input(0, isTableScan("random_tbl1")))
                 .and(input(1, isTableScan("random_tbl2")))
@@ -342,7 +332,7 @@ public class ExceptPlannerTest extends AbstractPlannerTest {
             "EXCEPT " +
             "SELECT * FROM affinity_tbl1 ";
 
-        assertPlan(sql, isInstanceOf(IgniteReduceMinus.class).and(n -> !n.all)
+        assertPlan(sql, publicSchema, isInstanceOf(IgniteReduceMinus.class).and(n -> !n.all)
             .and(hasChildThat(isInstanceOf(IgniteMapMinus.class)
                 .and(input(0, isTableScan("random_tbl1")))
                 .and(input(1, isTableScan("random_tbl2")))
@@ -350,119 +340,4 @@ public class ExceptPlannerTest extends AbstractPlannerTest {
             ))
         );
     }
-
-    /** */
-    private <T extends RelNode> void assertPlan(String sql, Predicate<T> predicate) throws Exception {
-        IgniteRel plan = physicalPlan(sql, publicSchema);
-
-        if (!predicate.test((T)plan)) {
-            String invalidPlanMsg = "Invalid plan (" + lastError + "):\n" +
-                RelOptUtil.toString(plan, SqlExplainLevel.ALL_ATTRIBUTES);
-
-            fail(invalidPlanMsg);
-        }
-    }
-
-    /** */
-    private <T extends RelNode> Predicate<T> isInstanceOf(Class<T> cls) {
-        return node -> {
-            if (cls.isInstance(node))
-                return true;
-
-            lastError = "Unexpected node class [node=" + node + ", cls=" + cls.getSimpleName() + ']';
-
-            return false;
-        };
-    }
-
-    /** */
-    private <T extends RelNode> Predicate<IgniteTableScan> isTableScan(String tableName) {
-        return isInstanceOf(IgniteTableScan.class).and(
-            n -> {
-                String scanTableName = n.getTable().unwrap(TestTable.class).name();
-
-                if (tableName.equalsIgnoreCase(scanTableName))
-                    return true;
-
-                lastError = "Unexpected table name [exp=" + tableName + ", act=" + scanTableName + ']';
-
-                return false;
-            });
-    }
-
-    /** */
-    private <T extends RelNode> Predicate<RelNode> hasChildThat(Predicate<T> predicate) {
-        return new Predicate<RelNode>() {
-            public boolean checkRecursively(RelNode node) {
-                if (predicate.test((T)node))
-                    return true;
-
-                for (RelNode input : node.getInputs()) {
-                    if (checkRecursively(input))
-                        return true;
-                }
-
-                return false;
-            }
-
-            @Override public boolean test(RelNode node) {
-                for (RelNode input : node.getInputs()) {
-                    if (checkRecursively(input))
-                        return true;
-                }
-
-                lastError = "Not found child for defined condition [node=" + node + ']';
-
-                return false;
-            }
-        };
-    }
-
-    /** */
-    private <T extends RelNode> Predicate<RelNode> input(Predicate<T> predicate) {
-        return node -> {
-            if (F.isEmpty(node.getInputs())) {
-                lastError = "No inputs for node [node=" + node + ']';
-
-                return false;
-            }
-
-            return predicate.test((T)node.getInput(0));
-        };
-    }
-
-    /** */
-    private <T extends RelNode> Predicate<RelNode> input(int idx, Predicate<T> predicate) {
-        return node -> {
-            if (F.size(node.getInputs()) <= idx) {
-                lastError = "No input for node [idx=" + idx + ", node=" + node + ']';
-
-                return false;
-            }
-
-            return predicate.test((T)node.getInput(idx));
-        };
-    }
-
-    /** */
-    private void createTable(String name, RelDataType type, IgniteDistribution distr, List<List<UUID>> assignment) {
-        TestTable table = new TestTable(type) {
-            @Override public ColocationGroup colocationGroup(PlanningContext ctx) {
-                if (F.isEmpty(assignment))
-                    return super.colocationGroup(ctx);
-                else
-                    return ColocationGroup.forAssignments(assignment);
-            }
-
-            @Override public IgniteDistribution distribution() {
-                return distr;
-            }
-
-            @Override public String name() {
-                return name;
-            }
-        };
-
-        publicSchema.addTable(name, table);
-    }
 }
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/TableFunctionTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/TableFunctionTest.java
new file mode 100644
index 0000000..fdd026d
--- /dev/null
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/TableFunctionTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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 org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteCorrelatedNestedLoopJoin;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableFunctionScan;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
+import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
+import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
+import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeSystem;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test table functions.
+ */
+public class TableFunctionTest extends AbstractPlannerTest {
+    /** Public schema. */
+    private IgniteSchema publicSchema;
+
+
+    /** {@inheritDoc} */
+    @Before
+    @Override public void setup() {
+        super.setup();
+
+        publicSchema = new IgniteSchema("PUBLIC");
+
+        IgniteTypeFactory f = new IgniteTypeFactory(IgniteTypeSystem.INSTANCE);
+
+        RelDataType type = new RelDataTypeFactory.Builder(f)
+            .add("ID", f.createJavaType(Integer.class))
+            .add("NAME", f.createJavaType(String.class))
+            .add("SALARY", f.createJavaType(Double.class))
+            .build();
+
+        createTable(publicSchema, "RANDOM_TBL", type, IgniteDistributions.random(), null);
+        createTable(publicSchema, "BROADCAST_TBL", type, IgniteDistributions.broadcast(), null);
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testTableFunctionScan() throws Exception {
+        String sql = "SELECT * FROM TABLE(system_range(1, 1))";
+
+        assertPlan(sql, publicSchema, isInstanceOf(IgniteTableFunctionScan.class));
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testBroadcastTableAndTableFunctionJoin() throws Exception {
+        String sql = "SELECT * FROM broadcast_tbl t JOIN TABLE(system_range(1, 1)) r ON (t.id = r.x)";
+
+        assertPlan(sql, publicSchema, nodeOrAnyChild(isInstanceOf(IgniteExchange.class)).negate()
+            .and(nodeOrAnyChild(isInstanceOf(Join.class)
+                .and(input(0, nodeOrAnyChild(isTableScan("broadcast_tbl"))))
+                .and(input(1, nodeOrAnyChild(isInstanceOf(IgniteTableFunctionScan.class))))
+            )));
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testRandomTableAndTableFunctionJoin() throws Exception {
+        String sql = "SELECT * FROM random_tbl t JOIN TABLE(system_range(1, 1)) r ON (t.id = r.x)";
+
+        assertPlan(sql, publicSchema, nodeOrAnyChild(isInstanceOf(Join.class)
+            .and(input(0, nodeOrAnyChild(isInstanceOf(IgniteExchange.class)
+                .and(nodeOrAnyChild(isTableScan("random_tbl"))))))
+            .and(input(1, nodeOrAnyChild(isInstanceOf(IgniteTableFunctionScan.class))))
+        ));
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testCorrelatedTableFunctionJoin() throws Exception {
+        String sql = "SELECT t.id, (SELECT x FROM TABLE(system_range(t.id, t.id))) FROM random_tbl t";
+
+        assertPlan(sql, publicSchema, nodeOrAnyChild(isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)
+            .and(input(0, nodeOrAnyChild(isTableScan("random_tbl"))))
+            .and(input(1, nodeOrAnyChild(isInstanceOf(IgniteTableFunctionScan.class))))
+        ));
+    }
+}
diff --git a/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java b/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java
index c466f72..6c8ac6e 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java
@@ -27,6 +27,7 @@ import org.apache.ignite.internal.processors.query.calcite.planner.JoinColocatio
 import org.apache.ignite.internal.processors.query.calcite.planner.PlannerTest;
 import org.apache.ignite.internal.processors.query.calcite.planner.SortAggregatePlannerTest;
 import org.apache.ignite.internal.processors.query.calcite.planner.SortedIndexSpoolPlannerTest;
+import org.apache.ignite.internal.processors.query.calcite.planner.TableFunctionTest;
 import org.apache.ignite.internal.processors.query.calcite.planner.TableSpoolPlannerTest;
 import org.junit.runner.RunWith;
 import org.junit.runners.Suite;
@@ -46,7 +47,8 @@ import org.junit.runners.Suite;
     HashAggregatePlannerTest.class,
     SortAggregatePlannerTest.class,
     JoinColocationPlannerTest.class,
-    ExceptPlannerTest.class
+    ExceptPlannerTest.class,
+    TableFunctionTest.class,
 })
 public class PlannerTestSuite {
 }
diff --git a/modules/calcite/src/test/sql/aggregate/aggregates/test_approx_quantile.test_ignore b/modules/calcite/src/test/sql/aggregate/aggregates/test_approx_quantile.test_ignore
index f97c5cb..66cf6cd 100644
--- a/modules/calcite/src/test/sql/aggregate/aggregates/test_approx_quantile.test_ignore
+++ b/modules/calcite/src/test/sql/aggregate/aggregates/test_approx_quantile.test_ignore
@@ -1,7 +1,6 @@
 # name: test/sql/aggregate/aggregates/test_approx_quantile.test
 # description: Test approx quantile operator
 # group: [aggregates]
-# Ignored: https://issues.apache.org/jira/browse/IGNITE-14542
 # Ignored: https://issues.apache.org/jira/browse/IGNITE-14637
 
 statement ok
diff --git a/modules/calcite/src/test/sql/aggregate/aggregates/test_approximate_distinct_count.test_ignore b/modules/calcite/src/test/sql/aggregate/aggregates/test_approximate_distinct_count.test_ignore
index f5edbcb..4b14f51 100644
--- a/modules/calcite/src/test/sql/aggregate/aggregates/test_approximate_distinct_count.test_ignore
+++ b/modules/calcite/src/test/sql/aggregate/aggregates/test_approximate_distinct_count.test_ignore
@@ -1,7 +1,6 @@
 # name: test/sql/aggregate/aggregates/test_approximate_distinct_count.test
 # description: Test approx_count_distinct operator
 # group: [aggregates]
-# Ignored: https://issues.apache.org/jira/browse/IGNITE-14542
 # Ignored: https://issues.apache.org/jira/browse/IGNITE-14637
 
 
diff --git a/modules/calcite/src/test/sql/aggregate/aggregates/test_group_by_many_groups.test_slow_ignore b/modules/calcite/src/test/sql/aggregate/aggregates/test_group_by_many_groups.test_slow_ignore
index 648779d..92891f4 100644
--- a/modules/calcite/src/test/sql/aggregate/aggregates/test_group_by_many_groups.test_slow_ignore
+++ b/modules/calcite/src/test/sql/aggregate/aggregates/test_group_by_many_groups.test_slow_ignore
@@ -1,7 +1,8 @@
 # name: test/sql/aggregate/aggregates/test_group_by_many_groups.test_slow
 # description: Test GROUP BY with many groups
 # group: [aggregates]
-# Ignored: https://issues.apache.org/jira/browse/IGNITE-14542
+# Ignored: https://issues.apache.org/jira/browse/IGNITE-14680
+# Ignored: https://issues.apache.org/jira/browse/IGNITE-14555
 
 statement ok
 CREATE TABLE integers AS SELECT i, 1 AS j FROM range(0, 10000, 1) t1(i) UNION ALL SELECT i, 2 j FROM range(0, 10000, 1) t1(i);
diff --git a/modules/calcite/src/test/sql/aggregate/aggregates/test_perfect_ht.test b/modules/calcite/src/test/sql/aggregate/aggregates/test_perfect_ht.test
index fd8721b..4884c3b 100644
--- a/modules/calcite/src/test/sql/aggregate/aggregates/test_perfect_ht.test
+++ b/modules/calcite/src/test/sql/aggregate/aggregates/test_perfect_ht.test
@@ -22,3 +22,31 @@ SELECT year, SUM(val), COUNT(val), COUNT(*) FROM timeseries GROUP BY year ORDER
 2001	30	1	1
 NULL	1	1	1
 
+# many small columns each having only the values 0 and 1
+# total possible combinations is 2^10, but there are only 2 groups
+statement ok
+create table manycolumns(a INTEGER, b INTEGER, c INTEGER, d INTEGER, e INTEGER);
+
+statement ok
+insert into manycolumns select x a, x b, x c, x d, x e from table(system_range(0,1));
+
+query IIIII
+select a, b, c, d, e FROM manycolumns GROUP BY a, b, c, d, e ORDER BY 1
+----
+0	0	0	0	0
+1	1	1	1	1
+
+# test edge cases: multiple tinyints without statistics
+# create a table of tinyints [-127, 127] stored as varchar
+# by forcing a varchar to tinyint cast we lose statistics
+statement ok
+CREATE TABLE tinyints (t TINYINT);
+
+statement ok
+INSERT INTO tinyints SELECT x::TINYINT::VARCHAR AS t FROM table(system_range(-127, 127));
+
+query IIII
+SELECT COUNT(DISTINCT i), MIN(i), MAX(i), SUM(i) / COUNT(i) FROM (SELECT t::TINYINT i FROM tinyints GROUP BY t)
+----
+255	-127	127	0
+
diff --git a/modules/calcite/src/test/sql/aggregate/aggregates/test_perfect_ht.test_ignore b/modules/calcite/src/test/sql/aggregate/aggregates/test_perfect_ht.test_ignore
index a4b4a65..769c329 100644
--- a/modules/calcite/src/test/sql/aggregate/aggregates/test_perfect_ht.test_ignore
+++ b/modules/calcite/src/test/sql/aggregate/aggregates/test_perfect_ht.test_ignore
@@ -1,7 +1,8 @@
 # name: test/sql/aggregate/aggregates/test_perfect_ht.test
 # description: Test aggregates that can trigger a perfect HT
 # group: [aggregates]
-# Ignored: https://issues.apache.org/jira/browse/IGNITE-14542
+# Ignored: https://issues.apache.org/jira/browse/IGNITE-14555
+# Ignored: https://issues.apache.org/jira/browse/IGNITE-14680
 
 statement ok
 PRAGMA enable_verification
@@ -35,31 +36,31 @@ NULL	[1]	1
 # many small columns each having only the values 0 and 1
 # total possible combinations is 2^10, but there are only 2 groups
 statement ok
-create table manycolumns as select i a, i b, i c, i d, i e from range(0,2) tbl(i);
+create table manycolumns as select x a, x b, x c, x d, x e from table(system_range(0,1));
 
 query IIIII
-select a, b, c, d, e FROM manycolumns GROUP BY 1, 2, 3, 4, 5
+select a, b, c, d, e FROM manycolumns GROUP BY a, b, c, d, e ORDER BY 1
 ----
 0	0	0	0	0
 1	1	1	1	1
 
 # test edge cases: multiple tinyints without statistics
-# create a table of tinyints [-127, 128] stored as varchar
+# create a table of tinyints [-127, 127] stored as varchar
 # by forcing a varchar to tinyint cast we lose statistics
 statement ok
-CREATE TABLE tinyints AS SELECT i::TINYINT::VARCHAR AS t FROM range(-127, 128) tbl(i);
+CREATE TABLE tinyints AS SELECT x::TINYINT::VARCHAR AS t FROM table(system_range(-127, 127));
 
 query IIII
-SELECT COUNT(DISTINCT i), MIN(i), MAX(i), SUM(i) / COUNT(i) FROM (SELECT t::TINYINT t1 FROM tinyints GROUP BY t1) tbl(i)
+SELECT COUNT(DISTINCT i), MIN(i), MAX(i), SUM(i) / COUNT(i) FROM (SELECT t::TINYINT i FROM tinyints GROUP BY t)
 ----
 255	-127	127	0
 
 # now do the same with a single smallint column
 statement ok
-CREATE TABLE smallints AS SELECT i::SMALLINT::VARCHAR AS t FROM range(-32767, 32768) tbl(i);
+CREATE TABLE smallints AS SELECT x::SMALLINT::VARCHAR AS t FROM table(system_range(-32767, 32767));
 
 query IIII
-SELECT COUNT(DISTINCT i), MIN(i), MAX(i), SUM(i) / COUNT(i) FROM (SELECT t::SMALLINT t1 FROM smallints GROUP BY t1) tbl(i)
+SELECT COUNT(DISTINCT i), MIN(i), MAX(i), SUM(i) / COUNT(i) FROM (SELECT t::SMALLINT i FROM smallints GROUP BY t)
 ----
 65535	-32767	32767	0
 
diff --git a/modules/calcite/src/test/sql/aggregate/aggregates/test_string_agg_many_groups.test_slow_ignore b/modules/calcite/src/test/sql/aggregate/aggregates/test_string_agg_many_groups.test_slow_ignore
index e6932f4..daf8fa4 100644
--- a/modules/calcite/src/test/sql/aggregate/aggregates/test_string_agg_many_groups.test_slow_ignore
+++ b/modules/calcite/src/test/sql/aggregate/aggregates/test_string_agg_many_groups.test_slow_ignore
@@ -2,7 +2,6 @@
 # description: Test STRING_AGG operator with many groups
 # group: [aggregates]
 # Ignored: https://issues.apache.org/jira/browse/IGNITE-14636
-# Ignored: https://issues.apache.org/jira/browse/IGNITE-14542
 
 # generate a table
 
diff --git a/modules/calcite/src/test/sql/aggregate/aggregates/test_sum.test_ignore b/modules/calcite/src/test/sql/aggregate/aggregates/test_sum.test
similarity index 73%
copy from modules/calcite/src/test/sql/aggregate/aggregates/test_sum.test_ignore
copy to modules/calcite/src/test/sql/aggregate/aggregates/test_sum.test
index 40d7ed4..0593e6a 100644
--- a/modules/calcite/src/test/sql/aggregate/aggregates/test_sum.test_ignore
+++ b/modules/calcite/src/test/sql/aggregate/aggregates/test_sum.test
@@ -1,13 +1,12 @@
 # name: test/sql/aggregate/aggregates/test_sum.test
 # description: Test sum aggregate
 # group: [aggregates]
-# Ignored: https://issues.apache.org/jira/browse/IGNITE-14542
 
 statement ok
 CREATE TABLE integers(i INTEGER);
 
 statement ok
-INSERT INTO integers SELECT * FROM range(0, 1000, 1);
+INSERT INTO integers SELECT * FROM table(system_range(0, 999, 1));
 
 # positive numbers
 query I
@@ -17,7 +16,7 @@ SELECT SUM(i) FROM integers;
 
 # negative numbers
 statement ok
-INSERT INTO integers SELECT * FROM range(0, -1000, -1);
+INSERT INTO integers SELECT * FROM table(system_range(0, -999, -1));
 
 query I
 SELECT SUM(i) FROM integers;
@@ -26,7 +25,7 @@ SELECT SUM(i) FROM integers;
 
 # more negative numbers
 statement ok
-INSERT INTO integers SELECT * FROM range(0, -1000, -1);
+INSERT INTO integers SELECT * FROM table(system_range(0, -999, -1));
 
 query I
 SELECT SUM(i) FROM integers;
@@ -63,11 +62,11 @@ CREATE TABLE bigints(b BIGINT);
 
 # a bunch of huge values
 statement ok
-INSERT INTO bigints SELECT * FROM range(4611686018427387904, 4611686018427388904, 1);
+INSERT INTO bigints SELECT * FROM table(system_range(4611686018427387904, 4611686018427388903, 1));
 
 # sum them up
 query I
-SELECT SUM(b) FROM bigints
+SELECT SUM(b::DECIMAL) FROM bigints
 ----
 4611686018427388403500
 
diff --git a/modules/calcite/src/test/sql/aggregate/aggregates/test_sum.test_ignore b/modules/calcite/src/test/sql/aggregate/aggregates/test_sum.test_ignore
index 40d7ed4..c72e6f1 100644
--- a/modules/calcite/src/test/sql/aggregate/aggregates/test_sum.test_ignore
+++ b/modules/calcite/src/test/sql/aggregate/aggregates/test_sum.test_ignore
@@ -1,13 +1,13 @@
 # name: test/sql/aggregate/aggregates/test_sum.test
 # description: Test sum aggregate
 # group: [aggregates]
-# Ignored: https://issues.apache.org/jira/browse/IGNITE-14542
+# Ignored: https://issues.apache.org/jira/browse/IGNITE-14681
 
 statement ok
 CREATE TABLE integers(i INTEGER);
 
 statement ok
-INSERT INTO integers SELECT * FROM range(0, 1000, 1);
+INSERT INTO integers SELECT * FROM table(system_range(0, 999, 1));
 
 # positive numbers
 query I
@@ -17,7 +17,7 @@ SELECT SUM(i) FROM integers;
 
 # negative numbers
 statement ok
-INSERT INTO integers SELECT * FROM range(0, -1000, -1);
+INSERT INTO integers SELECT * FROM table(system_range(0, -999, -1));
 
 query I
 SELECT SUM(i) FROM integers;
@@ -26,7 +26,7 @@ SELECT SUM(i) FROM integers;
 
 # more negative numbers
 statement ok
-INSERT INTO integers SELECT * FROM range(0, -1000, -1);
+INSERT INTO integers SELECT * FROM table(system_range(0, -999, -1));
 
 query I
 SELECT SUM(i) FROM integers;
@@ -63,7 +63,7 @@ CREATE TABLE bigints(b BIGINT);
 
 # a bunch of huge values
 statement ok
-INSERT INTO bigints SELECT * FROM range(4611686018427387904, 4611686018427388904, 1);
+INSERT INTO bigints SELECT * FROM table(system_range(4611686018427387904, 4611686018427388903, 1));
 
 # sum them up
 query I