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

[ignite-3] branch ignite-19775 updated: Add index to TestBuilders.

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

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


The following commit(s) were added to refs/heads/ignite-19775 by this push:
     new 442980cd25 Add index to TestBuilders.
442980cd25 is described below

commit 442980cd250fcfe3b730e3aebfe195aae98b2ada
Author: amashenkov <an...@gmail.com>
AuthorDate: Wed Jun 28 01:05:00 2023 +0300

    Add index to TestBuilders.
---
 .../internal/sql/engine/schema/IgniteIndex.java    |  15 +
 .../sql/engine/framework/TestBuilders.java         | 327 ++++++++++++++++++---
 .../internal/sql/engine/framework/TestIndex.java   | 101 +++++++
 .../internal/sql/engine/framework/TestNode.java    |  11 +
 4 files changed, 416 insertions(+), 38 deletions(-)

diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteIndex.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteIndex.java
index 459baaec3e..47b736e442 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteIndex.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteIndex.java
@@ -30,6 +30,7 @@ import org.apache.ignite.internal.index.SortedIndex;
 import org.apache.ignite.internal.index.SortedIndexDescriptor;
 import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
 import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.TestOnly;
 
 /**
  * Schema object representing an Index.
@@ -88,6 +89,20 @@ public class IgniteIndex {
         this.type = index instanceof SortedIndex ? Type.SORTED : Type.HASH;
     }
 
+    /**
+     * Constructs the Index object.
+     */
+    @TestOnly
+    public IgniteIndex(Type type, List<String> columns, @Nullable List<Collation> collations) {
+        assert type == Type.HASH ^ collations == null;
+
+        this.columns = columns;
+        this.collations = collations;
+        this.type = type;
+
+        index = null;
+    }
+
     /** Returns a list of names of indexed columns. */
     public List<String> columns() {
         return columns;
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java
index 70ce3549c6..f9a2e4a880 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java
@@ -41,6 +41,8 @@ import org.apache.ignite.internal.sql.engine.metadata.FragmentDescription;
 import org.apache.ignite.internal.sql.engine.schema.ColumnDescriptor;
 import org.apache.ignite.internal.sql.engine.schema.ColumnDescriptorImpl;
 import org.apache.ignite.internal.sql.engine.schema.DefaultValueStrategy;
+import org.apache.ignite.internal.sql.engine.schema.IgniteIndex;
+import org.apache.ignite.internal.sql.engine.schema.IgniteIndex.Collation;
 import org.apache.ignite.internal.sql.engine.schema.IgniteSchema;
 import org.apache.ignite.internal.sql.engine.schema.TableDescriptorImpl;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
@@ -85,8 +87,8 @@ public class TestBuilders {
         /**
          * Sets desired names for the cluster nodes.
          *
-         * @param firstNodeName A name of the first node. There is no difference in what node should be first. This parameter was introduced
-         *     to force user to provide at least one node name.
+         * @param firstNodeName A name of the first node. There is no difference in what node should be first. This parameter was
+         *         introduced to force user to provide at least one node name.
          * @param otherNodeNames An array of rest of the names to create cluster from.
          * @return {@code this} for chaining.
          */
@@ -99,10 +101,14 @@ public class TestBuilders {
          */
         ClusterTableBuilder addTable();
 
+        ClusterSortedIndexBuilder addSortedIndex();
+
+        ClusterHashIndexBuilder addHashIndex();
+
         /**
          * When specified the given factory is used to create instances of
-         * {@link ClusterTableBuilder#defaultDataProvider(DataProvider) default data providers} for tables
-         * that have no {@link ClusterTableBuilder#defaultDataProvider(DataProvider) default data provider} set.
+         * {@link ClusterTableBuilder#defaultDataProvider(DataProvider) default data providers} for tables that have no
+         * {@link ClusterTableBuilder#defaultDataProvider(DataProvider) default data provider} set.
          *
          * <p>Note: when a table has default data provider this method has no effect.
          *
@@ -132,19 +138,80 @@ public class TestBuilders {
         public TestTable build();
     }
 
+    /**
+     * A builder to create a test object that representing sorted index.
+     *
+     * @see TestIndex
+     */
+    public interface SortedIndexBuilder extends SortedIndexBuilderBase<SortedIndexBuilder> {
+        /**
+         * Builds a index.
+         *
+         * @return Created index object.
+         */
+        IgniteIndex build();
+    }
+
+    /**
+     * A builder to create a test object that representing hash index.
+     *
+     * @see TestIndex
+     */
+    public interface HashIndexBuilder extends HashIndexBuilderBase<HashIndexBuilder> {
+        /**
+         * Builds a index.
+         *
+         * @return Created index object.
+         */
+        IgniteIndex build();
+    }
+
     /**
      * A builder to create a test table as nested object of the cluster.
      *
      * @see TestTable
      * @see TestCluster
      */
-    public interface ClusterTableBuilder extends TableBuilderBase<ClusterTableBuilder>, NestedBuilder<ClusterBuilder> {
+    public interface ClusterTableBuilder extends TableBuilderBase<ClusterTableBuilder>,
+            DataProviderAwareBuilder<ClusterTableBuilder>,
+            NestedBuilder<ClusterBuilder> {
+    }
+
+    /**
+     * A builder to create a test object, which represents sorted index, as nested object of the cluster.
+     *
+     * @see TestIndex
+     * @see TestCluster
+     */
+    public interface ClusterSortedIndexBuilder extends SortedIndexBuilderBase<ClusterSortedIndexBuilder>,
+            DataProviderAwareBuilder<ClusterSortedIndexBuilder>,
+            NestedBuilder<ClusterBuilder> {
+    }
+
+    /**
+     * A builder to create a test object, which represents hash index, as nested object of the cluster.
+     *
+     * @see TestIndex
+     * @see TestCluster
+     */
+    public interface ClusterHashIndexBuilder extends HashIndexBuilderBase<ClusterHashIndexBuilder>,
+            DataProviderAwareBuilder<ClusterHashIndexBuilder>,
+            NestedBuilder<ClusterBuilder> {
+    }
+
+    /**
+     * A builder interface to enrich a builder object with data-provider related fields.
+     */
+    public interface DataProviderAwareBuilder<ChildT> {
         /**
          * Adds a default data provider, which will be used for those nodes for which no specific provider is specified.
          *
-         * <p>Note: this method will force all nodes in the cluster to have a data provider for the given table.
+         * <p>Note: this method will force all nodes in the cluster to have a data provider for the given object.
          */
-        ClusterTableBuilder defaultDataProvider(DataProvider<?> dataProvider);
+        ChildT defaultDataProvider(DataProvider<?> dataProvider);
+
+        /** Adds a data provider for the given node to the object. */
+        ChildT addDataProvider(String targetNode, DataProvider<?> dataProvider);
     }
 
     /**
@@ -231,6 +298,7 @@ public class TestBuilders {
 
     private static class ClusterBuilderImpl implements ClusterBuilder {
         private final List<ClusterTableBuilderImpl> tableBuilders = new ArrayList<>();
+        private final List<AbstractIndexBuilderImpl> indexBuilders = new ArrayList<>();
         private DataProviderFactory dataProviderFactory;
         private List<String> nodeNames;
 
@@ -251,6 +319,19 @@ public class TestBuilders {
             return new ClusterTableBuilderImpl(this);
         }
 
+        /** {@inheritDoc} */
+        @Override
+        public ClusterSortedIndexBuilder addSortedIndex() {
+            return new ClusterSortedIndexBuilderImpl(this);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public ClusterHashIndexBuilder addHashIndex() {
+            return new ClusterHashIndexBuilderImpl(this);
+        }
+
+
         /** {@inheritDoc} */
         @Override
         public ClusterBuilder defaultDataProviderFactory(DataProviderFactory dataProviderFactory) {
@@ -264,7 +345,8 @@ public class TestBuilders {
             var clusterService = new ClusterServiceFactory(nodeNames);
 
             for (ClusterTableBuilderImpl tableBuilder : tableBuilders) {
-                validateTableBuilder(tableBuilder);
+                validateDataSourceBuilder(tableBuilder);
+                injectDefaultDataProvidersIfNeeded(tableBuilder);
                 injectDataProvidersIfNeeded(tableBuilder);
             }
 
@@ -272,7 +354,19 @@ public class TestBuilders {
                     .map(ClusterTableBuilderImpl::build)
                     .collect(Collectors.toMap(TestTable::name, Function.identity()));
 
-            var schemaManager = new PredefinedSchemaManager(new IgniteSchema("PUBLIC", tableMap, null, SCHEMA_VERSION));
+            for (AbstractIndexBuilderImpl<?> indexBuilder : indexBuilders) {
+                validateDataSourceBuilder(indexBuilder);
+                validateIndexBuilder(indexBuilder, tableMap);
+                injectDataProvidersIfNeeded(indexBuilder);
+            }
+
+            Map<Integer, IgniteIndex> indexMap = indexBuilders.stream()
+                    .map(AbstractIndexBuilderImpl::build)
+                    .collect(Collectors.toMap(TestIndex::id, Function.identity()));
+
+            indexMap.values().forEach(idx -> ((TestTable) tableMap.get(((TestIndex) idx).tableName())).addIndex(idx));
+
+            var schemaManager = new PredefinedSchemaManager(new IgniteSchema("PUBLIC", tableMap, indexMap, SCHEMA_VERSION));
 
             Map<String, TestNode> nodes = nodeNames.stream()
                     .map(name -> new TestNode(name, clusterService.forNode(name), schemaManager))
@@ -281,7 +375,7 @@ public class TestBuilders {
             return new TestCluster(nodes);
         }
 
-        private void validateTableBuilder(ClusterTableBuilderImpl tableBuilder) {
+        private void validateDataSourceBuilder(AbstractDataSourceBuilderImpl<?> tableBuilder) {
             Set<String> tableOwners = new HashSet<>(tableBuilder.dataProviders.keySet());
 
             tableOwners.removeAll(nodeNames);
@@ -292,21 +386,34 @@ public class TestBuilders {
             }
         }
 
-        private void injectDataProvidersIfNeeded(ClusterTableBuilderImpl tableBuilder) {
-            if (tableBuilder.defaultDataProvider == null) {
-                if (dataProviderFactory != null) {
-                    tableBuilder.defaultDataProvider = dataProviderFactory.createDataProvider(tableBuilder.name, tableBuilder.columns);
-                } else {
-                    return;
-                }
+        private void validateIndexBuilder(AbstractIndexBuilderImpl<?> indexBuilder, Map<String, Table> tableMap) {
+            String tableName = indexBuilder.tableName;
+
+            TestTable table = (TestTable) tableMap.get(tableName);
+
+            if (table == null) {
+                throw new AssertionError(format("The index refers to an invalid table "
+                        + "[indexName={}, tableName={}]", indexBuilder.name, tableName));
+            }
+        }
+
+        private void injectDefaultDataProvidersIfNeeded(ClusterTableBuilderImpl tableBuilder) {
+            if (tableBuilder.defaultDataProvider == null && dataProviderFactory != null) {
+                tableBuilder.defaultDataProvider = dataProviderFactory.createDataProvider(tableBuilder.name, tableBuilder.columns);
+            }
+        }
+
+        private void injectDataProvidersIfNeeded(AbstractDataSourceBuilderImpl<?> builder) {
+            if (builder.defaultDataProvider == null) {
+                return;
             }
 
             Set<String> nodesWithoutDataProvider = new HashSet<>(nodeNames);
 
-            nodesWithoutDataProvider.removeAll(tableBuilder.dataProviders.keySet());
+            nodesWithoutDataProvider.removeAll(builder.dataProviders.keySet());
 
             for (String name : nodesWithoutDataProvider) {
-                tableBuilder.addDataProvider(name, tableBuilder.defaultDataProvider);
+                builder.addDataProvider(name, builder.defaultDataProvider);
             }
         }
     }
@@ -330,7 +437,7 @@ public class TestBuilders {
             return new TestTable(
                     new TableDescriptorImpl(columns, distribution),
                     Objects.requireNonNull(name),
-                    dataProviders,
+                    Map.of(),
                     size
             );
         }
@@ -380,6 +487,66 @@ public class TestBuilders {
         }
     }
 
+    private static class ClusterSortedIndexBuilderImpl extends AbstractIndexBuilderImpl<ClusterSortedIndexBuilder>
+            implements ClusterSortedIndexBuilder {
+        private final ClusterBuilderImpl parent;
+
+        ClusterSortedIndexBuilderImpl(ClusterBuilderImpl parent) {
+            this.parent = parent;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        ClusterSortedIndexBuilder self() {
+            return this;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public ClusterBuilder end() {
+            parent.indexBuilders.add(this);
+
+            return parent;
+        }
+
+        @Override
+        TestIndex build() {
+            assert collations.size() == columns.size();
+
+            return TestIndex.createSorted(name, tableName, columns, collations, dataProviders);
+        }
+    }
+
+    private static class ClusterHashIndexBuilderImpl extends AbstractIndexBuilderImpl<ClusterHashIndexBuilder>
+            implements ClusterHashIndexBuilder {
+        private final ClusterBuilderImpl parent;
+
+        ClusterHashIndexBuilderImpl(ClusterBuilderImpl parent) {
+            this.parent = parent;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        ClusterHashIndexBuilder self() {
+            return this;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public ClusterBuilder end() {
+            parent.indexBuilders.add(this);
+
+            return parent;
+        }
+
+        @Override
+        TestIndex build() {
+            assert collations == null;
+
+            return TestIndex.createHash(name, tableName, columns, dataProviders);
+        }
+    }
+
     /**
      * A factory that creates {@link DataProvider data providers}.
      */
@@ -389,24 +556,20 @@ public class TestBuilders {
         /**
          * Creates a {@link DataProvider} for the given table.
          *
-         * @param tableName  a table name.
-         * @param columns  a list of columns.
-         *
-         * @return  an instance of {@link DataProvider}.
+         * @param tableName a table name.
+         * @param columns a list of columns.
+         * @return an instance of {@link DataProvider}.
          */
         DataProvider<Object[]> createDataProvider(String tableName, List<ColumnDescriptor> columns);
     }
 
-    private abstract static class AbstractTableBuilderImpl<ChildT> implements TableBuilderBase<ChildT> {
+    private abstract static class AbstractTableBuilderImpl<ChildT> extends AbstractDataSourceBuilderImpl<ChildT>
+            implements TableBuilderBase<ChildT> {
         protected final List<ColumnDescriptor> columns = new ArrayList<>();
-        protected final Map<String, DataProvider<?>> dataProviders = new HashMap<>();
 
-        protected String name;
         protected IgniteDistribution distribution;
         protected int size = 100_000;
 
-        protected abstract ChildT self();
-
         /** {@inheritDoc} */
         @Override
         public ChildT name(String name) {
@@ -449,16 +612,69 @@ public class TestBuilders {
 
         /** {@inheritDoc} */
         @Override
-        public ChildT addDataProvider(String targetNode, DataProvider<?> dataProvider) {
-            this.dataProviders.put(targetNode, dataProvider);
+        public ChildT size(int size) {
+            this.size = size;
 
             return self();
         }
+    }
+
+    private abstract static class AbstractIndexBuilderImpl<ChildT> extends AbstractDataSourceBuilderImpl<ChildT>
+            implements SortedIndexBuilderBase<ChildT>, HashIndexBuilderBase<ChildT> {
+        protected final List<String> columns = new ArrayList<>();
+        protected List<Collation> collations = new ArrayList<>();
+        protected String tableName;
 
         /** {@inheritDoc} */
         @Override
-        public ChildT size(int size) {
-            this.size = size;
+        public ChildT table(String tableName) {
+            this.tableName = tableName;
+
+            return self();
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public ChildT addColumn(String columnName) {
+            columns.add(columnName);
+
+            return self();
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public ChildT addColumn(String columnName, Collation collation) {
+            columns.add(columnName);
+            collations.add(collation);
+
+            return self();
+        }
+
+        abstract TestIndex build();
+    }
+
+    private abstract static class AbstractDataSourceBuilderImpl<ChildT> {
+
+        protected String name;
+        final Map<String, DataProvider<?>> dataProviders = new HashMap<>();
+        DataProvider<?> defaultDataProvider = null;
+
+        abstract ChildT self();
+
+        public ChildT name(String name) {
+            this.name = name;
+
+            return self();
+        }
+
+        public ChildT defaultDataProvider(DataProvider<?> dataProvider) {
+            this.defaultDataProvider = dataProvider;
+
+            return self();
+        }
+
+        public ChildT addDataProvider(String targetNode, DataProvider<?> dataProvider) {
+            this.dataProviders.put(targetNode, dataProvider);
 
             return self();
         }
@@ -486,13 +702,49 @@ public class TestBuilders {
         /** Adds a column with the given default value to the table. */
         ChildT addColumn(String name, NativeType type, @Nullable Object defaultValue);
 
-        /** Adds a data provider for the given node to the table. */
-        ChildT addDataProvider(String targetNode, DataProvider<?> dataProvider);
-
         /** Sets the size of the table. */
         ChildT size(int size);
     }
 
+    /**
+     * Base interface describing the common set of index-related fields.
+     *
+     * <p>The sole purpose of this interface is to keep in sync both variants of index's builders.
+     *
+     * @param <ChildT> An actual type of builder that should be exposed to the user.
+     * @see ClusterHashIndexBuilder
+     * @see ClusterSortedIndexBuilder
+     * @see HashIndexBuilder
+     * @see SortedIndexBuilder
+     */
+    private interface IndexBuilderBase<ChildT> {
+        /** Sets the name of the index. */
+        ChildT name(String name);
+
+        /** Sets the name of the table that index belongs to. */
+        ChildT table(String name);
+    }
+
+    /**
+     * Base interface describing the set of sorted-index related fields.
+     *
+     * @param <ChildT> An actual type of builder that should be exposed to the user.
+     */
+    private interface SortedIndexBuilderBase<ChildT> extends IndexBuilderBase<ChildT> {
+        /** Adds a column with specified collation to the index. */
+        ChildT addColumn(String columnName, Collation collation);
+    }
+
+    /**
+     * Base interface describing the set of hash-index related fields.
+     *
+     * @param <ChildT> An actual type of builder that should be exposed to the user.
+     */
+    private interface HashIndexBuilderBase<ChildT> extends IndexBuilderBase<ChildT> {
+        /** Adds a column to the index. */
+        ChildT addColumn(String columnName);
+    }
+
     /**
      * This interfaces provides a nested builder with ability to return on the previous layer.
      *
@@ -520,8 +772,7 @@ public class TestBuilders {
     @FunctionalInterface
     private interface NestedBuilder<ParentT> {
         /**
-         * Notifies the builder's chain of the nested builder that we need to return back to the
-         * previous layer.
+         * Notifies the builder's chain of the nested builder that we need to return back to the previous layer.
          *
          * @return An instance of the parent builder.
          */
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestIndex.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestIndex.java
new file mode 100644
index 0000000000..a64b58018f
--- /dev/null
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestIndex.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.sql.engine.framework;
+
+import static org.apache.ignite.lang.IgniteStringFormatter.format;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.ignite.internal.sql.engine.schema.IgniteIndex;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * A test index that implements all the necessary for the optimizer methods to be used to prepare a query, as well as provides access to the
+ * data to use this index in execution-related scenarios.
+ */
+public class TestIndex extends IgniteIndex {
+    private static final String DATA_PROVIDER_NOT_CONFIGURED_MESSAGE_TEMPLATE =
+            "DataProvider is not configured [index={}, node={}]";
+
+    /** Factory method for creating hash-index. */
+    static TestIndex createHash(String name, String tableName, List<String> indexedColumns, Map<String, DataProvider<?>> dataProviders) {
+        return new TestIndex(name, tableName, Type.HASH, indexedColumns, null, dataProviders);
+    }
+
+    /** Factory method for creating sorted-index. */
+    static TestIndex createSorted(String name, String tableName, List<String> columns, List<Collation> collations,
+            Map<String, DataProvider<?>> dataProviders) {
+        return new TestIndex(name, tableName, Type.SORTED, columns, collations, dataProviders);
+    }
+
+    private static final AtomicInteger ID = new AtomicInteger();
+
+    private final int id = ID.incrementAndGet();
+    private final String name;
+    private final String tableName;
+
+    private final Map<String, DataProvider<?>> dataProviders;
+
+    /** Constructor. */
+    private TestIndex(
+            String name,
+            String tableName,
+            Type type,
+            List<String> columns,
+            @Nullable List<Collation> collations,
+            Map<String, DataProvider<?>> dataProviders
+    ) {
+        super(type, columns, collations);
+
+        this.name = name;
+        this.tableName = tableName;
+        this.dataProviders = dataProviders;
+    }
+
+    /** Returns an id of the index. */
+    public int id() {
+        return id;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String name() {
+        return name;
+    }
+
+    public String tableName() {
+        return tableName;
+    }
+
+    /**
+     * Returns the data provider for the given node.
+     *
+     * @param nodeName Name of the node of interest.
+     * @param <RowT> A type of the rows the data provider should produce.
+     * @return A data provider for the node of interest.
+     * @throws AssertionError in case data provider is not configured for the given node.
+     */
+    <RowT> DataProvider<RowT> dataProvider(String nodeName) {
+        if (!dataProviders.containsKey(nodeName)) {
+            throw new AssertionError(format(DATA_PROVIDER_NOT_CONFIGURED_MESSAGE_TEMPLATE, name(), nodeName));
+        }
+
+        return (DataProvider<RowT>) dataProviders.get(nodeName);
+    }
+}
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestNode.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestNode.java
index 4360462bd9..8840ff3699 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestNode.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestNode.java
@@ -61,6 +61,7 @@ import org.apache.ignite.internal.sql.engine.prepare.PrepareService;
 import org.apache.ignite.internal.sql.engine.prepare.PrepareServiceImpl;
 import org.apache.ignite.internal.sql.engine.prepare.QueryPlan;
 import org.apache.ignite.internal.sql.engine.prepare.ddl.DdlSqlToCommandConverter;
+import org.apache.ignite.internal.sql.engine.rel.IgniteIndexScan;
 import org.apache.ignite.internal.sql.engine.rel.IgniteTableScan;
 import org.apache.ignite.internal.sql.engine.schema.SqlSchemaManager;
 import org.apache.ignite.internal.sql.engine.sql.IgniteSqlParser;
@@ -140,6 +141,16 @@ public class TestNode implements LifecycleAware {
 
                         return new ScanNode<>(ctx, dataProvider);
                     }
+
+                    @Override
+                    public Node<Object[]> visit(IgniteIndexScan rel) {
+                        TestTable tbl = rel.getTable().unwrap(TestTable.class);
+                        TestIndex idx = (TestIndex) tbl.getIndex(rel.indexName());
+
+                        DataProvider<Object[]> dataProvider = idx.dataProvider(ctx.localNode().name());
+
+                        return new ScanNode<>(ctx, dataProvider);
+                    }
                 }
         ));
     }