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/07/27 16:50:26 UTC

[ignite-3] 10/19: wip. switch to CalciteSqlSchemaManager. Drop dead code.

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

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

commit 1d4bb280927dbd011603fc62f41b4ad77c739082
Author: amashenkov <an...@gmail.com>
AuthorDate: Wed Jul 19 22:41:52 2023 +0300

    wip. switch to CalciteSqlSchemaManager.
    Drop dead code.
---
 .../ignite/internal/schema/SchemaManager.java      |  17 +-
 .../internal/sql/engine/SqlQueryProcessor.java     | 123 -----
 .../engine/exec/ExecutableTableRegistryImpl.java   |   1 -
 .../sql/engine/exec/LogicalRelImplementor.java     |  27 +-
 .../sql/engine/exec/rel/IndexScanNode.java         |  18 +-
 .../internal/sql/engine/rel/AbstractIndexScan.java |   8 +-
 .../internal/sql/engine/rel/IgniteIndexScan.java   |   6 +-
 .../engine/rel/logical/IgniteLogicalIndexScan.java |   5 +-
 .../sql/engine/rule/LogicalScanConverterRule.java  |   6 +-
 .../sql/engine/rule/logical/ExposeIndexRule.java   |  10 +-
 .../engine/rule/logical/LogicalOrToUnionRule.java  |   4 +-
 .../sql/engine/schema/CatalogSqlSchemaManager.java |  20 +-
 .../internal/sql/engine/schema/IgniteIndex.java    | 167 ++----
 .../sql/engine/schema/IgniteSchemaIndex.java       | 143 -----
 .../sql/engine/schema/IgniteSchemaTable.java       |  32 +-
 .../sql/engine/schema/IgniteTableImpl.java         | 307 -----------
 .../sql/engine/schema/SqlSchemaManagerImpl.java    | 588 ---------------------
 .../internal/sql/engine/trait/TraitUtils.java      | 105 ++--
 .../exec/ExecutionDependencyResolverSelfTest.java  |   7 +-
 .../exec/rel/IndexScanNodeExecutionTest.java       |  60 ++-
 .../engine/exec/schema/SqlSchemaManagerTest.java   | 328 ------------
 .../sql/engine/framework/TestBuilders.java         |  89 +++-
 .../internal/sql/engine/framework/TestIndex.java   |  34 +-
 .../planner/AbstractAggregatePlannerTest.java      |   3 +-
 .../sql/engine/planner/AbstractPlannerTest.java    |   3 +-
 .../sql/engine/planner/HashIndexPlannerTest.java   |   8 +-
 .../sql/engine/planner/ImplicitCastsTest.java      |   5 +-
 .../engine/planner/JoinColocationPlannerTest.java  |  16 +-
 .../internal/sql/engine/planner/PlannerTest.java   |   6 +-
 .../planner/ProjectFilterScanMergePlannerTest.java |   9 +-
 .../engine/schema/CatalogSqlSchemaManagerTest.java |   9 +-
 31 files changed, 346 insertions(+), 1818 deletions(-)

diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/SchemaManager.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/SchemaManager.java
index 85edc90e35..41f13823e2 100644
--- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/SchemaManager.java
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/SchemaManager.java
@@ -107,9 +107,9 @@ public class SchemaManager extends Producer<SchemaEvent, SchemaEventParameters>
     @Override
     public void start() {
         catalogManager.listen(CatalogEvent.TABLE_CREATE,
-                (params, ex) -> onSchemaChange((CreateTableEventParameters) params).thenApply(ignore -> false));
+                (params, ex) -> onTableCreated((CreateTableEventParameters) params).thenApply(ignore -> false));
         catalogManager.listen(CatalogEvent.TABLE_ALTER,
-                (params, ex) -> onSchemaChange(params).thenApply(ignore -> false));
+                (params, ex) -> onTableChanged(params).thenApply(ignore -> false));
     }
 
     /**
@@ -118,7 +118,7 @@ public class SchemaManager extends Producer<SchemaEvent, SchemaEventParameters>
      * @param evt Event parameters.
      * @return A future.
      */
-    private CompletableFuture<?> onSchemaChange(CreateTableEventParameters evt) {
+    private CompletableFuture<?> onTableCreated(CreateTableEventParameters evt) {
         CatalogTableDescriptor tableDescriptor = evt.tableDescriptor();
 
         int newSchemaVersion = INITIAL_SCHEMA_VERSION; // evt.catalogVersion();
@@ -135,7 +135,11 @@ public class SchemaManager extends Producer<SchemaEvent, SchemaEventParameters>
      * @param evt Event parameters.
      * @return A future.
      */
-    private CompletableFuture<?> onSchemaChange(CatalogEventParameters evt) {
+    private CompletableFuture<?> onTableChanged(CatalogEventParameters evt) {
+        if (!busyLock.enterBusy()) {
+            return failedFuture(new IgniteInternalException(NODE_STOPPING_ERR, new NodeStoppingException()));
+        }
+
         try {
             CatalogTableDescriptor tableDescriptor;
 
@@ -155,14 +159,17 @@ public class SchemaManager extends Producer<SchemaEvent, SchemaEventParameters>
                 return completedFuture(new UnsupportedOperationException("Unexpected event."));
             }
 
-            int newSchemaVersion = registriesVv.latest().get(tableDescriptor.id()).lastSchemaVersion() + 1;;
+            int newSchemaVersion = registriesVv.latest().get(tableDescriptor.id()).lastSchemaVersion() + 1;
 
             SchemaDescriptor newSchema = CatalogDescriptorUtils.convert(newSchemaVersion, tableDescriptor);
 
             return onSchemaChange(tableDescriptor.id(), newSchema, evt.causalityToken());
         } catch (Throwable th) {
+            //TODO: drop debug print.
             th.printStackTrace();
             return failedFuture(th);
+        } finally {
+            busyLock.leaveBusy();
         }
     }
 
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryProcessor.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryProcessor.java
index 6d5cd4c58a..4f64f709d3 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryProcessor.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryProcessor.java
@@ -44,7 +44,6 @@ import org.apache.ignite.internal.distributionzones.DistributionZoneManager;
 import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.index.IndexManager;
 import org.apache.ignite.internal.index.event.IndexEvent;
-import org.apache.ignite.internal.index.event.IndexEventParameters;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
 import org.apache.ignite.internal.manager.Event;
@@ -70,7 +69,6 @@ import org.apache.ignite.internal.sql.engine.property.PropertiesHelper;
 import org.apache.ignite.internal.sql.engine.property.PropertiesHolder;
 import org.apache.ignite.internal.sql.engine.schema.CatalogSqlSchemaManager;
 import org.apache.ignite.internal.sql.engine.schema.SqlSchemaManager;
-import org.apache.ignite.internal.sql.engine.schema.SqlSchemaManagerImpl;
 import org.apache.ignite.internal.sql.engine.session.Session;
 import org.apache.ignite.internal.sql.engine.session.SessionId;
 import org.apache.ignite.internal.sql.engine.session.SessionInfo;
@@ -86,7 +84,6 @@ import org.apache.ignite.internal.sql.engine.util.TypeUtils;
 import org.apache.ignite.internal.storage.DataStorageManager;
 import org.apache.ignite.internal.table.distributed.TableManager;
 import org.apache.ignite.internal.table.event.TableEvent;
-import org.apache.ignite.internal.table.event.TableEventParameters;
 import org.apache.ignite.internal.tx.InternalTransaction;
 import org.apache.ignite.internal.tx.TxManager;
 import org.apache.ignite.internal.util.IgniteSpinBusyLock;
@@ -96,7 +93,6 @@ import org.apache.ignite.lang.NodeStoppingException;
 import org.apache.ignite.lang.SchemaNotFoundException;
 import org.apache.ignite.network.ClusterService;
 import org.apache.ignite.sql.SqlException;
-import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 /**
@@ -355,18 +351,6 @@ public class SqlQueryProcessor implements QueryProcessor {
         return service;
     }
 
-    private void registerTableListener(TableEvent evt, AbstractTableEventListener lsnr) {
-        evtLsnrs.add(Pair.of(evt, lsnr));
-
-        tableManager.listen(evt, lsnr);
-    }
-
-    private void registerIndexListener(IndexEvent evt, AbstractIndexEventListener lsnr) {
-        evtLsnrs.add(Pair.of(evt, lsnr));
-
-        indexManager.listen(evt, lsnr);
-    }
-
     private CompletableFuture<AsyncSqlCursor<List<Object>>> querySingle0(
             SessionId sessionId,
             QueryContext context,
@@ -485,113 +469,6 @@ public class SqlQueryProcessor implements QueryProcessor {
         return stage;
     }
 
-    private abstract static class AbstractTableEventListener implements EventListener<TableEventParameters> {
-        protected final SqlSchemaManagerImpl schemaHolder;
-
-        private AbstractTableEventListener(SqlSchemaManagerImpl schemaHolder) {
-            this.schemaHolder = schemaHolder;
-        }
-    }
-
-    private abstract static class AbstractIndexEventListener implements EventListener<IndexEventParameters> {
-        protected final SqlSchemaManagerImpl schemaHolder;
-
-        private AbstractIndexEventListener(SqlSchemaManagerImpl schemaHolder) {
-            this.schemaHolder = schemaHolder;
-        }
-    }
-
-    private static class TableCreatedListener extends AbstractTableEventListener {
-        private TableCreatedListener(SqlSchemaManagerImpl schemaHolder) {
-            super(schemaHolder);
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public CompletableFuture<Boolean> notify(@NotNull TableEventParameters parameters, @Nullable Throwable exception) {
-            return schemaHolder.onTableCreated(
-                    // TODO: https://issues.apache.org/jira/browse/IGNITE-17694 Hardcoded schemas
-                    DEFAULT_SCHEMA_NAME,
-                    parameters.tableId(),
-                    parameters.causalityToken()
-            )
-                    .thenApply(v -> false);
-        }
-    }
-
-    private static class TableUpdatedListener extends AbstractTableEventListener {
-        private TableUpdatedListener(SqlSchemaManagerImpl schemaHolder) {
-            super(schemaHolder);
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public CompletableFuture<Boolean> notify(@NotNull TableEventParameters parameters, @Nullable Throwable exception) {
-            return schemaHolder.onTableUpdated(
-                    // TODO: https://issues.apache.org/jira/browse/IGNITE-17694 Hardcoded schemas
-                    DEFAULT_SCHEMA_NAME,
-                    parameters.tableId(),
-                    parameters.causalityToken()
-            )
-                    .thenApply(v -> false);
-        }
-    }
-
-    private static class TableDroppedListener extends AbstractTableEventListener {
-        private TableDroppedListener(SqlSchemaManagerImpl schemaHolder) {
-            super(schemaHolder);
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public CompletableFuture<Boolean> notify(@NotNull TableEventParameters parameters, @Nullable Throwable exception) {
-            return schemaHolder.onTableDropped(
-                    // TODO: https://issues.apache.org/jira/browse/IGNITE-17694 Hardcoded schemas
-                    DEFAULT_SCHEMA_NAME,
-                    parameters.tableId(),
-                    parameters.causalityToken()
-            )
-                    .thenApply(v -> false);
-        }
-    }
-
-    private static class IndexDroppedListener extends AbstractIndexEventListener {
-        private IndexDroppedListener(SqlSchemaManagerImpl schemaHolder) {
-            super(schemaHolder);
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public CompletableFuture<Boolean> notify(@NotNull IndexEventParameters parameters, @Nullable Throwable exception) {
-            return schemaHolder.onIndexDropped(
-                    // TODO: https://issues.apache.org/jira/browse/IGNITE-17694 Hardcoded schemas
-                    DEFAULT_SCHEMA_NAME,
-                    parameters.tableId(),
-                    parameters.indexId(),
-                    parameters.causalityToken()
-            )
-                    .thenApply(v -> false);
-        }
-    }
-
-    private static class IndexCreatedListener extends AbstractIndexEventListener {
-        private IndexCreatedListener(SqlSchemaManagerImpl schemaHolder) {
-            super(schemaHolder);
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public CompletableFuture<Boolean> notify(@NotNull IndexEventParameters parameters, @Nullable Throwable exception) {
-            return schemaHolder.onIndexCreated(
-                    parameters.tableId(),
-                    parameters.indexId(),
-                    parameters.indexDescriptor(),
-                    parameters.causalityToken()
-            )
-                    .thenApply(v -> false);
-        }
-    }
-
     /** Returns {@code true} if this is data modification operation. */
     private static boolean dataModificationOp(ParsedResult parsedResult) {
         return parsedResult.queryType() == SqlQueryType.DML;
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutableTableRegistryImpl.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutableTableRegistryImpl.java
index 096a0989d9..92b254855d 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutableTableRegistryImpl.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutableTableRegistryImpl.java
@@ -18,7 +18,6 @@
 package org.apache.ignite.internal.sql.engine.exec;
 
 import com.github.benmanes.caffeine.cache.Caffeine;
-import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentMap;
 import org.apache.ignite.internal.hlc.HybridClock;
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/LogicalRelImplementor.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/LogicalRelImplementor.java
index 464a0c0d5a..d1bfb1187f 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/LogicalRelImplementor.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/LogicalRelImplementor.java
@@ -32,12 +32,14 @@ import java.util.function.Predicate;
 import java.util.function.Supplier;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Intersect;
 import org.apache.calcite.rel.core.JoinRelType;
 import org.apache.calcite.rel.core.Minus;
 import org.apache.calcite.rel.core.Spool;
 import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rex.RexInputRef;
 import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.rex.RexNode;
@@ -343,11 +345,15 @@ public class LogicalRelImplementor<RowT> implements IgniteRelVisitor<Node<RowT>>
         if (searchBounds != null) {
             Comparator<RowT> searchRowComparator = null;
 
-            if (idx.collations() != null) {
-                searchRowComparator = expressionFactory.comparator(TraitUtils.createCollation(idx.collations()));
+            // TODO: cache comparator?
+            if (idx.type() != Type.HASH) {
+                searchRowComparator = expressionFactory.comparator(idx.collation());
             }
 
-            ranges = expressionFactory.ranges(searchBounds, idx.getRowType(typeFactory, tbl.descriptor()), searchRowComparator);
+            // TODO: cache rowtype?
+            RelDataType idxRowType = getIndexRowType(typeFactory, tbl, idx);
+
+            ranges = expressionFactory.ranges(searchBounds, idxRowType, searchRowComparator);
         }
 
         RelCollation outputCollation = rel.collation();
@@ -361,13 +367,14 @@ public class LogicalRelImplementor<RowT> implements IgniteRelVisitor<Node<RowT>>
         }
 
         ColocationGroup group = ctx.group(rel.sourceId());
-        Comparator<RowT> comp = idx.type() == Type.SORTED ? ctx.expressionFactory().comparator(outputCollation) : null;
+        Comparator<RowT> comp = idx.type() == IgniteIndex.Type.SORTED ? ctx.expressionFactory().comparator(outputCollation) : null;
 
         if (!group.nodeNames().contains(ctx.localNode().name())) {
             return new ScanNode<>(ctx, Collections.emptyList());
         }
 
         return new IndexScanNode<>(
+                idx.id(),
                 ctx,
                 ctx.rowHandler().factory(ctx.getTypeFactory(), rowType),
                 idx,
@@ -382,6 +389,18 @@ public class LogicalRelImplementor<RowT> implements IgniteRelVisitor<Node<RowT>>
         );
     }
 
+    private static RelDataType getIndexRowType(IgniteTypeFactory typeFactory, IgniteTable tbl, IgniteIndex idx) {
+        RelDataTypeFactory.Builder b = new RelDataTypeFactory.Builder(typeFactory);
+
+        RelDataType rt = tbl.getRowType(typeFactory);
+        for (RelFieldCollation fc : idx.collation().getFieldCollations()) {
+            b.add(rt.getFieldList().get(fc.getFieldIndex()));
+        }
+
+        RelDataType idxRowType = b.build();
+        return idxRowType;
+    }
+
     /** {@inheritDoc} */
     @Override
     public Node<RowT> visit(IgniteTableScan rel) {
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/IndexScanNode.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/IndexScanNode.java
index 55ad834cdf..ecc3bb04bc 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/IndexScanNode.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/IndexScanNode.java
@@ -25,12 +25,15 @@ import java.util.List;
 import java.util.concurrent.Flow.Publisher;
 import java.util.function.Function;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.ignite.internal.sql.engine.exec.ExecutionContext;
 import org.apache.ignite.internal.sql.engine.exec.RowHandler;
 import org.apache.ignite.internal.sql.engine.exec.ScannableTable;
 import org.apache.ignite.internal.sql.engine.exec.exp.RangeCondition;
 import org.apache.ignite.internal.sql.engine.exec.exp.RangeIterable;
 import org.apache.ignite.internal.sql.engine.metadata.PartitionWithTerm;
+import org.apache.ignite.internal.sql.engine.schema.ColumnDescriptor;
 import org.apache.ignite.internal.sql.engine.schema.IgniteIndex;
 import org.apache.ignite.internal.sql.engine.schema.TableDescriptor;
 import org.apache.ignite.internal.sql.engine.util.Commons;
@@ -42,6 +45,8 @@ import org.jetbrains.annotations.Nullable;
  * Execution node for index scan. Provide result of index scan by given index, partitions and range conditions.
  */
 public class IndexScanNode<RowT> extends StorageScanNode<RowT> {
+    private final int indexId;
+
     /** Schema index. */
     private final IgniteIndex schemaIndex;
 
@@ -59,6 +64,8 @@ public class IndexScanNode<RowT> extends StorageScanNode<RowT> {
 
     private final @Nullable Comparator<RowT> comp;
 
+    private final TableDescriptor tableDescriptor;
+
     /**
      * Constructor.
      *
@@ -73,6 +80,7 @@ public class IndexScanNode<RowT> extends StorageScanNode<RowT> {
      * @param requiredColumns Optional set of column of interest.
      */
     public IndexScanNode(
+            int indexId,
             ExecutionContext<RowT> ctx,
             RowHandler.RowFactory<RowT> rowFactory,
             IgniteIndex schemaIndex,
@@ -89,6 +97,7 @@ public class IndexScanNode<RowT> extends StorageScanNode<RowT> {
 
         assert partsWithTerms != null && !partsWithTerms.isEmpty();
 
+        this.indexId = indexId;
         this.schemaIndex = schemaIndex;
         this.table = table;
         this.partsWithTerms = partsWithTerms;
@@ -96,6 +105,7 @@ public class IndexScanNode<RowT> extends StorageScanNode<RowT> {
         this.rangeConditions = rangeConditions;
         this.comp = comp;
         this.factory = rowFactory;
+        this.tableDescriptor = tableDescriptor;
     }
 
     /** {@inheritDoc} */
@@ -123,8 +133,12 @@ public class IndexScanNode<RowT> extends StorageScanNode<RowT> {
     }
 
     private Publisher<RowT> partitionPublisher(PartitionWithTerm partWithTerm, @Nullable RangeCondition<RowT> cond) {
-        int indexId = schemaIndex.id();
-        List<String> columns = schemaIndex.columns();
+        //TODO: optimize this
+        List<String> columns = schemaIndex.collation().getFieldCollations().stream()
+                .map(RelFieldCollation::getFieldIndex)
+                .map(tableDescriptor::columnDescriptor)
+                .map(ColumnDescriptor::name)
+                .collect(Collectors.toList());
         ExecutionContext<RowT> ctx = context();
 
         switch (schemaIndex.type()) {
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/AbstractIndexScan.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/AbstractIndexScan.java
index d7e416f6ce..22fb6a21d2 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/AbstractIndexScan.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/AbstractIndexScan.java
@@ -53,7 +53,7 @@ public abstract class AbstractIndexScan extends ProjectableFilterableTableScan {
 
     protected final @Nullable List<SearchBounds> searchBounds;
 
-    protected final IgniteIndex.Type type;
+    protected final Type type;
 
     /**
      * Constructor used for deserialization.
@@ -63,7 +63,7 @@ public abstract class AbstractIndexScan extends ProjectableFilterableTableScan {
     AbstractIndexScan(RelInput input) {
         super(input);
         idxName = input.getString("index");
-        type = input.getEnum("type", IgniteIndex.Type.class);
+        type = input.getEnum("type", Type.class);
         searchBounds = ((RelInputEx) input).getSearchBounds("searchBounds");
     }
 
@@ -77,7 +77,7 @@ public abstract class AbstractIndexScan extends ProjectableFilterableTableScan {
             List<RelHint> hints,
             RelOptTable table,
             String idxName,
-            IgniteIndex.Type type,
+            Type type,
             @Nullable List<RexNode> proj,
             @Nullable RexNode cond,
             @Nullable List<SearchBounds> searchBounds,
@@ -126,7 +126,7 @@ public abstract class AbstractIndexScan extends ProjectableFilterableTableScan {
 
         double cost = 0;
 
-        if (type == Type.HASH) {
+        if (type == IgniteIndex.Type.HASH) {
             boolean notExact = (searchBounds() == null)
                     || searchBounds().stream().anyMatch(bound -> bound.type() == SearchBounds.Type.RANGE);
 
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteIndexScan.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteIndexScan.java
index 52d2ff9d3a..ad29121527 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteIndexScan.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteIndexScan.java
@@ -29,7 +29,7 @@ import org.apache.calcite.rel.RelWriter;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.ignite.internal.sql.engine.prepare.bounds.SearchBounds;
-import org.apache.ignite.internal.sql.engine.schema.IgniteIndex;
+import org.apache.ignite.internal.sql.engine.schema.IgniteIndex.Type;
 import org.jetbrains.annotations.Nullable;
 
 /**
@@ -78,7 +78,7 @@ public class IgniteIndexScan extends AbstractIndexScan implements SourceAwareIgn
             RelTraitSet traits,
             RelOptTable tbl,
             String idxName,
-            IgniteIndex.Type type,
+            Type type,
             RelCollation collation,
             @Nullable List<RexNode> proj,
             @Nullable RexNode cond,
@@ -109,7 +109,7 @@ public class IgniteIndexScan extends AbstractIndexScan implements SourceAwareIgn
             RelTraitSet traits,
             RelOptTable tbl,
             String idxName,
-            IgniteIndex.Type type,
+            Type type,
             RelCollation collation,
             @Nullable List<RexNode> proj,
             @Nullable RexNode cond,
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/logical/IgniteLogicalIndexScan.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/logical/IgniteLogicalIndexScan.java
index d76a263c68..631ee4d053 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/logical/IgniteLogicalIndexScan.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/logical/IgniteLogicalIndexScan.java
@@ -30,7 +30,6 @@ import org.apache.ignite.internal.sql.engine.rel.AbstractIndexScan;
 import org.apache.ignite.internal.sql.engine.schema.IgniteIndex;
 import org.apache.ignite.internal.sql.engine.schema.IgniteIndex.Type;
 import org.apache.ignite.internal.sql.engine.schema.IgniteTable;
-import org.apache.ignite.internal.sql.engine.trait.TraitUtils;
 import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
 import org.apache.ignite.internal.sql.engine.util.Commons;
 import org.apache.ignite.internal.sql.engine.util.RexUtils;
@@ -54,7 +53,7 @@ public class IgniteLogicalIndexScan extends AbstractIndexScan {
         IgniteTable tbl = table.unwrap(IgniteTable.class);
         IgniteTypeFactory typeFactory = Commons.typeFactory(cluster);
         IgniteIndex index = tbl.getIndex(idxName);
-        RelCollation collation = TraitUtils.createCollation(index.columns(), index.collations(), tbl.descriptor());
+        RelCollation collation = index.collation();
 
         List<SearchBounds> searchBounds;
         if (index.type() == Type.HASH) {
@@ -111,7 +110,7 @@ public class IgniteLogicalIndexScan extends AbstractIndexScan {
             RelTraitSet traits,
             RelOptTable tbl,
             String idxName,
-            IgniteIndex.Type type,
+            Type type,
             @Nullable List<RexNode> proj,
             @Nullable RexNode cond,
             @Nullable List<SearchBounds> searchBounds,
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/LogicalScanConverterRule.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/LogicalScanConverterRule.java
index 836cfe19de..359b2b15fe 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/LogicalScanConverterRule.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/LogicalScanConverterRule.java
@@ -40,9 +40,7 @@ import org.apache.ignite.internal.sql.engine.rel.ProjectableFilterableTableScan;
 import org.apache.ignite.internal.sql.engine.rel.logical.IgniteLogicalIndexScan;
 import org.apache.ignite.internal.sql.engine.rel.logical.IgniteLogicalTableScan;
 import org.apache.ignite.internal.sql.engine.schema.IgniteIndex;
-import org.apache.ignite.internal.sql.engine.schema.IgniteIndex.Type;
 import org.apache.ignite.internal.sql.engine.schema.IgniteTable;
-import org.apache.ignite.internal.sql.engine.trait.TraitUtils;
 
 /**
  * LogicalScanConverterRule.
@@ -65,8 +63,8 @@ public abstract class LogicalScanConverterRule<T extends ProjectableFilterableTa
                     IgniteIndex index = table.getIndex(rel.indexName());
 
                     RelDistribution distribution = table.distribution();
-                    RelCollation collation = TraitUtils.createCollation(index.columns(), index.collations(), table.descriptor());
-                    RelCollation outputCollation = index.type() == Type.HASH ? RelCollations.EMPTY : collation;
+                    RelCollation collation = index.collation();
+                    RelCollation outputCollation = index.type() == IgniteIndex.Type.HASH ? RelCollations.EMPTY : collation;
 
                     if (rel.projects() != null || rel.requiredColumns() != null) {
                         Mappings.TargetMapping mapping = createMapping(
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/ExposeIndexRule.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/ExposeIndexRule.java
index d9078b4d4c..f31c9e6b07 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/ExposeIndexRule.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/ExposeIndexRule.java
@@ -33,8 +33,6 @@ import org.apache.ignite.internal.sql.engine.prepare.bounds.SearchBounds;
 import org.apache.ignite.internal.sql.engine.rel.logical.IgniteLogicalIndexScan;
 import org.apache.ignite.internal.sql.engine.rel.logical.IgniteLogicalTableScan;
 import org.apache.ignite.internal.sql.engine.schema.IgniteIndex;
-import org.apache.ignite.internal.sql.engine.schema.IgniteIndex.Type;
-import org.apache.ignite.internal.sql.engine.schema.IgniteSchemaTable;
 import org.apache.ignite.internal.sql.engine.schema.IgniteTable;
 import org.immutables.value.Value;
 
@@ -52,7 +50,7 @@ public class ExposeIndexRule extends RelRule<ExposeIndexRule.Config> {
 
     private static boolean preMatch(IgniteLogicalTableScan scan) {
         // has indexes to expose
-        return !scan.getTable().unwrap(IgniteSchemaTable.class).getIndexes().isEmpty();
+        return !scan.getTable().unwrap(IgniteTable.class).indexes().isEmpty();
     }
 
     /** {@inheritDoc} */
@@ -62,12 +60,12 @@ public class ExposeIndexRule extends RelRule<ExposeIndexRule.Config> {
         RelOptCluster cluster = scan.getCluster();
 
         RelOptTable optTable = scan.getTable();
-        IgniteSchemaTable igniteTable = optTable.unwrap(IgniteSchemaTable.class);
+        IgniteTable igniteTable = optTable.unwrap(IgniteTable.class);
         List<RexNode> proj = scan.projects();
         RexNode condition = scan.condition();
         ImmutableBitSet requiredCols = scan.requiredColumns();
 
-        List<IgniteLogicalIndexScan> indexes = igniteTable.getIndexes().values().stream()
+        List<IgniteLogicalIndexScan> indexes = igniteTable.indexes().values().stream()
                 .map(idx -> idx.toRel(cluster, optTable, proj, condition, requiredCols))
                 .filter(idx -> filter(igniteTable, idx.indexName(), idx.searchBounds()))
                 .collect(Collectors.toList());
@@ -88,7 +86,7 @@ public class ExposeIndexRule extends RelRule<ExposeIndexRule.Config> {
     private static boolean filter(IgniteTable table, String idxName, List<SearchBounds> searchBounds) {
         IgniteIndex index = table.getIndex(idxName);
 
-        return index.type() == Type.SORTED || (searchBounds != null
+        return index.type() == IgniteIndex.Type.SORTED || (searchBounds != null
                 && searchBounds.stream().noneMatch(bound -> bound.type() == SearchBounds.Type.RANGE));
     }
 
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/LogicalOrToUnionRule.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/LogicalOrToUnionRule.java
index eb2bb7d442..0612c7612e 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/LogicalOrToUnionRule.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/logical/LogicalOrToUnionRule.java
@@ -37,7 +37,7 @@ import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.tools.RelBuilder;
 import org.apache.calcite.util.mapping.Mappings;
 import org.apache.ignite.internal.sql.engine.rel.logical.IgniteLogicalTableScan;
-import org.apache.ignite.internal.sql.engine.schema.IgniteSchemaIndex;
+import org.apache.ignite.internal.sql.engine.schema.IgniteIndex;
 import org.apache.ignite.internal.sql.engine.schema.IgniteSchemaTable;
 import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
 import org.apache.ignite.internal.sql.engine.util.Commons;
@@ -137,7 +137,7 @@ public class LogicalOrToUnionRule extends RelRule<LogicalOrToUnionRule.Config> {
 
         BitSet idxsFirstFields = new BitSet(fieldCnt);
 
-        for (IgniteSchemaIndex idx : tbl.getIndexes().values()) {
+        for (IgniteIndex idx : tbl.indexes().values()) {
             List<RelFieldCollation> fieldCollations = idx.collation().getFieldCollations();
 
             if (!CollectionUtils.nullOrEmpty(fieldCollations)) {
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/CatalogSqlSchemaManager.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/CatalogSqlSchemaManager.java
index 4f75c4e454..8dbd7e8fad 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/CatalogSqlSchemaManager.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/CatalogSqlSchemaManager.java
@@ -50,6 +50,7 @@ import org.apache.ignite.internal.schema.DefaultValueGenerator;
 import org.apache.ignite.internal.sql.engine.schema.IgniteIndex.Type;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
+import org.apache.ignite.internal.sql.engine.trait.TraitUtils;
 import org.jetbrains.annotations.Nullable;
 
 /**
@@ -72,8 +73,7 @@ public class CatalogSqlSchemaManager implements SqlSchemaManager {
     @Override
     public SchemaPlus schema(@Nullable String schema) {
         // Should be removed -schema(name, version) must be used instead
-        // throw new UnsupportedOperationException();
-        return activeSchema(schema, catalogManager.activeCatalogVersion(Long.MAX_VALUE));
+        throw new UnsupportedOperationException();
     }
 
     /** {@inheritDoc} */
@@ -129,7 +129,7 @@ public class CatalogSqlSchemaManager implements SqlSchemaManager {
             tableDescriptorMap.put(tableDescriptor.id(), descriptorImpl);
         }
 
-        Map<Integer, Map<String, IgniteSchemaIndex>> schemaTableIndexes = new HashMap<>(descriptor.indexes().length);
+        Map<Integer, Map<String, IgniteIndex>> schemaTableIndexes = new HashMap<>(descriptor.indexes().length);
 
         // Assemble indexes as they are required by tables.
         for (CatalogIndexDescriptor indexDescriptor : descriptor.indexes()) {
@@ -138,19 +138,20 @@ public class CatalogSqlSchemaManager implements SqlSchemaManager {
             assert tableDescriptorImpl != null : "Table is not found in schema: " + tableId;
 
             String indexName = indexDescriptor.name();
-            Map<String, IgniteSchemaIndex> tableIndexes = schemaTableIndexes.computeIfAbsent(tableId, id -> new LinkedHashMap<>());
+            Map<String, IgniteIndex> tableIndexes = schemaTableIndexes.computeIfAbsent(tableId, id -> new LinkedHashMap<>());
 
             Type type;
             if (indexDescriptor instanceof CatalogSortedIndexDescriptor) {
-                type = Type.SORTED;
+                type = IgniteIndex.Type.SORTED;
             } else if (indexDescriptor instanceof CatalogHashIndexDescriptor) {
-                type = Type.HASH;
+                type = IgniteIndex.Type.HASH;
             } else {
                 throw new IllegalArgumentException("Unexpected index type: " + indexDescriptor);
             }
 
-            RelCollation indexCollation = IgniteSchemaIndex.createIndexCollation(indexDescriptor, tableDescriptorImpl);
-            IgniteSchemaIndex schemaIndex = new IgniteSchemaIndex(indexName, type, tableDescriptorImpl.distribution(), indexCollation);
+            RelCollation indexCollation = TraitUtils.createIndexCollation(indexDescriptor, tableDescriptorImpl);
+            IgniteDistribution distribution = tableDescriptorImpl.distribution();
+            IgniteIndex schemaIndex = new IgniteIndex(indexDescriptor.id(), indexName, type, distribution, indexCollation);
             tableIndexes.put(indexName, schemaIndex);
 
             schemaTableIndexes.put(tableId, tableIndexes);
@@ -164,7 +165,7 @@ public class CatalogSqlSchemaManager implements SqlSchemaManager {
             assert descriptorImpl != null;
 
             IgniteStatistic statistic = new IgniteStatistic(() -> 0.0d, descriptorImpl.distribution());
-            Map<String, IgniteSchemaIndex> tableIndexMap = schemaTableIndexes.getOrDefault(tableId, Collections.emptyMap());
+            Map<String, IgniteIndex> tableIndexMap = schemaTableIndexes.getOrDefault(tableId, Collections.emptyMap());
 
             IgniteSchemaTable schemaTable = new IgniteSchemaTable(tableName, tableId, version, descriptorImpl, statistic, tableIndexMap);
 
@@ -218,7 +219,6 @@ public class CatalogSqlSchemaManager implements SqlSchemaManager {
                 defaultValueSupplier = null;
             }
 
-
             CatalogColumnDescriptor columnDescriptor = new CatalogColumnDescriptor(
                     col.name(),
                     key,
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 244ccc3784..34557790d3 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
@@ -17,23 +17,20 @@
 
 package org.apache.ignite.internal.sql.engine.schema;
 
-import static org.apache.ignite.internal.sql.engine.util.TypeUtils.native2relationalType;
-
-import java.util.ArrayList;
 import java.util.List;
-import java.util.Objects;
-import org.apache.calcite.rel.type.RelDataType;
-import org.apache.calcite.rel.type.RelDataTypeFactory;
-import org.apache.ignite.internal.index.ColumnCollation;
-import org.apache.ignite.internal.index.Index;
-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;
+import org.apache.calcite.plan.Convention;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelOptTable;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.ignite.internal.sql.engine.rel.logical.IgniteLogicalIndexScan;
+import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
 
 /**
- * Schema object representing an Index.
+ * Auxiliary data structure to represent a table index.
  */
 public class IgniteIndex {
     /**
@@ -61,130 +58,66 @@ public class IgniteIndex {
         }
     }
 
-    /**
-     * Type of the index.
-     */
-    public enum Type {
-        HASH, SORTED
-    }
+    private final int indexId;
 
-    private final List<String> columns;
+    private final String name;
 
-    private final @Nullable List<Collation> collations;
+    private final IgniteDistribution tableDistribution;
 
-    private final Index<?> index;
+    private final RelCollation collation;
 
     private final Type type;
 
-    /**
-     * Constructs the Index object.
-     *
-     * @param index A data access object to wrap.
-     */
-    public IgniteIndex(Index<?> index) {
-        this.index = Objects.requireNonNull(index, "index");
-
-        this.columns = index.descriptor().columns();
-        this.collations = deriveCollations(index);
-        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.SORTED ^ collations == null;
-
-        this.columns = columns;
-        this.collations = collations;
+    /** Constructor. */
+    public IgniteIndex(int indexId, String name, Type type, IgniteDistribution tableDistribution, RelCollation collation) {
+        this.indexId = indexId;
+        this.name = name;
         this.type = type;
-
-        index = null;
-    }
-
-    /** Returns a list of names of indexed columns. */
-    public List<String> columns() {
-        return columns;
-    }
-
-    /**
-     * Returns a list of collations.
-     *
-     * <p>The size of the collations list is guaranteed to match the size of indexed columns. The i-th
-     * collation is related to an i-th column.
-     *
-     * @return The list of collations or {@code null} if not applicable.
-     */
-    public @Nullable List<Collation> collations() {
-        return collations;
+        this.tableDistribution = tableDistribution;
+        this.collation = collation;
     }
 
-    /** Returns the name of a current index. */
+    /** Returns the name of this index. */
     public String name() {
-        return index.name();
+        return name;
     }
 
-    /** Returns an object providing access to a data. */
-    public Index<?> index() {
-        return index;
+    /** Returns the type of this index. */
+    public Type type() {
+        return type;
     }
 
-    /** Returns id of this index. */
+    /** Returns index id. */
     public int id() {
-        return index.id();
-    }
-
-    public int tableId() {
-        return index.tableId();
+        return indexId;
     }
 
-    public Type type() {
-        return type;
+    /** Returns the collation of this index. */
+    public RelCollation collation() {
+        return collation;
     }
 
-    //TODO: cache rowType as it can't be changed.
-
-    /** Returns index row type.
-     *
-     * <p>This is a struct type whose fields describe the names and types of indexed columns.</p>
-     *
-     * <p>The implementer must use the type factory provided. This ensures that
-     * the type is converted into a canonical form; other equal types in the same
-     * query will use the same object.</p>
-     *
-     * @param typeFactory Type factory with which to create the type
-     * @param tableDescriptor Table descriptor.
-     * @return Row type.
+    /**
+     * Translates this index into relational operator.
      */
-    public RelDataType getRowType(IgniteTypeFactory typeFactory, TableDescriptor tableDescriptor) {
-        RelDataTypeFactory.Builder b = new RelDataTypeFactory.Builder(typeFactory);
-
-        for (String colName : columns) {
-            ColumnDescriptor colDesc = tableDescriptor.columnDescriptor(colName);
-            b.add(colName, native2relationalType(typeFactory, colDesc.physicalType(), colDesc.nullable()));
-        }
-
-        return b.build();
+    public IgniteLogicalIndexScan toRel(
+            RelOptCluster cluster,
+            RelOptTable relOptTable,
+            List<RexNode> proj,
+            RexNode condition,
+            ImmutableBitSet requiredCols
+    ) {
+        RelTraitSet traitSet = cluster.traitSetOf(Convention.Impl.NONE)
+                .replace(tableDistribution)
+                .replace(type() == Type.HASH ? RelCollations.EMPTY : collation);
+
+        return IgniteLogicalIndexScan.create(cluster, traitSet, relOptTable, name, proj, condition, requiredCols);
     }
 
-    private static @Nullable List<Collation> deriveCollations(Index<?> index) {
-        if (index.descriptor() instanceof SortedIndexDescriptor) {
-            SortedIndexDescriptor descriptor = (SortedIndexDescriptor) index.descriptor();
-
-            List<Collation> orders = new ArrayList<>(descriptor.columns().size());
-
-            for (var column : descriptor.columns()) {
-                ColumnCollation collation = descriptor.collation(column);
-
-                assert collation != null;
-
-                orders.add(Collation.of(collation.asc(), collation.nullsFirst()));
-            }
-
-            return List.copyOf(orders);
-        }
-
-        return null;
+    /**
+     * Type of the index.
+     */
+    public enum Type {
+        HASH, SORTED
     }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteSchemaIndex.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteSchemaIndex.java
deleted file mode 100644
index 5a5171bca4..0000000000
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteSchemaIndex.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * 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.schema;
-
-import java.util.ArrayList;
-import java.util.List;
-import org.apache.calcite.plan.Convention;
-import org.apache.calcite.plan.RelOptCluster;
-import org.apache.calcite.plan.RelOptTable;
-import org.apache.calcite.plan.RelTraitSet;
-import org.apache.calcite.rel.RelCollation;
-import org.apache.calcite.rel.RelCollations;
-import org.apache.calcite.rel.RelFieldCollation;
-import org.apache.calcite.rel.RelFieldCollation.Direction;
-import org.apache.calcite.rel.RelFieldCollation.NullDirection;
-import org.apache.calcite.rex.RexNode;
-import org.apache.calcite.util.ImmutableBitSet;
-import org.apache.ignite.internal.catalog.descriptors.CatalogHashIndexDescriptor;
-import org.apache.ignite.internal.catalog.descriptors.CatalogIndexColumnDescriptor;
-import org.apache.ignite.internal.catalog.descriptors.CatalogIndexDescriptor;
-import org.apache.ignite.internal.catalog.descriptors.CatalogSortedIndexDescriptor;
-import org.apache.ignite.internal.sql.engine.rel.logical.IgniteLogicalIndexScan;
-import org.apache.ignite.internal.sql.engine.schema.IgniteIndex.Type;
-import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
-
-/**
- * Auxiliary data structure to represent a table index.
- */
-public class IgniteSchemaIndex {
-
-    private final String name;
-
-    private final IgniteDistribution tableDistribution;
-
-    private final RelCollation collation;
-
-    private final Type type;
-
-    /** Constructor. */
-    public IgniteSchemaIndex(String name, Type type, IgniteDistribution tableDistribution, RelCollation collation) {
-        this.name = name;
-        this.type = type;
-        this.tableDistribution = tableDistribution;
-        this.collation = collation;
-    }
-
-    /** Returns the name of this index. */
-    public String name() {
-        return name;
-    }
-
-    /** Returns the type of this index. */
-    public Type type() {
-        return type;
-    }
-
-    /** Returns the collation of this index. */
-    public RelCollation collation() {
-        return collation;
-    }
-
-    /**
-     * Translates this index into relational operator.
-     */
-    public IgniteLogicalIndexScan toRel(
-            RelOptCluster cluster,
-            RelOptTable relOptTable,
-            List<RexNode> proj,
-            RexNode condition,
-            ImmutableBitSet requiredCols
-    ) {
-        RelTraitSet traitSet = cluster.traitSetOf(Convention.Impl.NONE)
-                .replace(tableDistribution)
-                .replace(type() == Type.HASH ? RelCollations.EMPTY : collation);
-
-        return IgniteLogicalIndexScan.create(cluster, traitSet, relOptTable, name, proj, condition, requiredCols);
-    }
-
-    static RelCollation createIndexCollation(CatalogIndexDescriptor descriptor, TableDescriptor tableDescriptor) {
-        if (descriptor instanceof CatalogSortedIndexDescriptor) {
-            CatalogSortedIndexDescriptor sortedIndexDescriptor = (CatalogSortedIndexDescriptor) descriptor;
-            List<CatalogIndexColumnDescriptor> columns = sortedIndexDescriptor.columns();
-            List<RelFieldCollation> fieldCollations = new ArrayList<>(columns.size());
-
-            for (int i = 0; i < columns.size(); i++) {
-                CatalogIndexColumnDescriptor column = columns.get(i);
-                ColumnDescriptor columnDesc = tableDescriptor.columnDescriptor(column.name());
-                int fieldIndex = columnDesc.logicalIndex();
-
-                RelFieldCollation fieldCollation;
-                switch (column.collation()) {
-                    case ASC_NULLS_FIRST:
-                        fieldCollation = new RelFieldCollation(fieldIndex, Direction.ASCENDING, NullDirection.FIRST);
-                        break;
-                    case ASC_NULLS_LAST:
-                        fieldCollation = new RelFieldCollation(fieldIndex, Direction.ASCENDING, NullDirection.LAST);
-                        break;
-                    case DESC_NULLS_FIRST:
-                        fieldCollation = new RelFieldCollation(fieldIndex, Direction.DESCENDING, NullDirection.FIRST);
-                        break;
-                    case DESC_NULLS_LAST:
-                        fieldCollation = new RelFieldCollation(fieldIndex, Direction.DESCENDING, NullDirection.LAST);
-                        break;
-                    default:
-                        throw new IllegalArgumentException("Unexpected collation: " + column.collation());
-                }
-
-                fieldCollations.add(fieldCollation);
-            }
-
-            return RelCollations.of(fieldCollations);
-        } else if (descriptor instanceof CatalogHashIndexDescriptor) {
-            CatalogHashIndexDescriptor hashIndexDescriptor = (CatalogHashIndexDescriptor) descriptor;
-            List<String> columns = hashIndexDescriptor.columns();
-            List<RelFieldCollation> fieldCollations = new ArrayList<>(columns.size());
-
-            for (String columnName : columns) {
-                ColumnDescriptor columnDesc = tableDescriptor.columnDescriptor(columnName);
-
-                fieldCollations.add(new RelFieldCollation(columnDesc.logicalIndex(), Direction.CLUSTERED, NullDirection.UNSPECIFIED));
-            }
-
-            return RelCollations.of(fieldCollations);
-        } else {
-            throw new IllegalArgumentException("Unexpected index type: " + descriptor);
-        }
-    }
-}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteSchemaTable.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteSchemaTable.java
index e6d34557e7..367953d654 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteSchemaTable.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteSchemaTable.java
@@ -17,8 +17,11 @@
 
 package org.apache.ignite.internal.sql.engine.schema;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
 import org.apache.calcite.config.CalciteConnectionConfig;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptTable;
@@ -35,11 +38,13 @@ import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.ignite.internal.sql.engine.metadata.ColocationGroup;
+import org.apache.ignite.internal.sql.engine.metadata.NodeWithTerm;
 import org.apache.ignite.internal.sql.engine.prepare.MappingQueryContext;
 import org.apache.ignite.internal.sql.engine.rel.logical.IgniteLogicalIndexScan;
 import org.apache.ignite.internal.sql.engine.rel.logical.IgniteLogicalTableScan;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
 import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
+import org.apache.ignite.internal.table.InternalTable;
 import org.jetbrains.annotations.Nullable;
 
 /**
@@ -57,11 +62,11 @@ public final class IgniteSchemaTable extends AbstractTable implements IgniteTabl
 
     private final IgniteStatistic statistic;
 
-    private final Map<String, IgniteSchemaIndex> indexMap;
+    private final Map<String, IgniteIndex> indexMap;
 
     /** Constructor. */
     public IgniteSchemaTable(String name, int tableId,  int version, TableDescriptor desc,
-            IgniteStatistic statistic, Map<String, IgniteSchemaIndex> indexMap) {
+            IgniteStatistic statistic, Map<String, IgniteIndex> indexMap) {
 
         this.id = tableId;
         this.name = name;
@@ -71,6 +76,18 @@ public final class IgniteSchemaTable extends AbstractTable implements IgniteTabl
         this.indexMap = indexMap;
     }
 
+    // TODO: should be moved to a separate component after https://issues.apache.org/jira/browse/IGNITE-18453
+    static Supplier<ColocationGroup> partitionedGroup(InternalTable table) {
+        return () -> {
+            List<List<NodeWithTerm>> assignments = table.primaryReplicas().stream()
+                    .map(primaryReplica -> new NodeWithTerm(primaryReplica.node().name(), primaryReplica.term()))
+                    .map(Collections::singletonList)
+                    .collect(Collectors.toList());
+
+            return ColocationGroup.forAssignments(assignments);
+        };
+    }
+
     /** {@inheritDoc} */
     @Override
     public int id() {
@@ -193,13 +210,13 @@ public final class IgniteSchemaTable extends AbstractTable implements IgniteTabl
     /** {@inheritDoc} */
     @Override
     public ColocationGroup colocationGroup(MappingQueryContext ctx) {
-        return IgniteTableImpl.partitionedGroup(ctx.tableManager().getTable(id).internalTable()).get();
+        return partitionedGroup(ctx.tableManager().getTable(id).internalTable()).get();
     }
 
     /** {@inheritDoc} */
     @Override
     public Map<String, IgniteIndex> indexes() {
-        throw new UnsupportedOperationException("getIndexes() should be used instead");
+        return indexMap;
     }
 
     /** {@inheritDoc} */
@@ -211,7 +228,7 @@ public final class IgniteSchemaTable extends AbstractTable implements IgniteTabl
     /** {@inheritDoc} */
     @Override
     public IgniteIndex getIndex(String idxName) {
-        throw new UnsupportedOperationException("getIndexes(name) should be used instead");
+        return indexMap.get(idxName);
     }
 
     /** {@inheritDoc} */
@@ -219,9 +236,4 @@ public final class IgniteSchemaTable extends AbstractTable implements IgniteTabl
     public void removeIndex(String idxName) {
         throw new UnsupportedOperationException("IndexMap is not modifiable");
     }
-
-    /** Returns a map of indexes defined for this table. */
-    public Map<String, IgniteSchemaIndex> getIndexes() {
-        return indexMap;
-    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteTableImpl.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteTableImpl.java
deleted file mode 100644
index 844c0c7d2e..0000000000
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteTableImpl.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * 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.schema;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.function.DoubleSupplier;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-import org.apache.calcite.plan.Convention;
-import org.apache.calcite.plan.RelOptCluster;
-import org.apache.calcite.plan.RelOptTable;
-import org.apache.calcite.plan.RelTraitSet;
-import org.apache.calcite.rel.RelCollation;
-import org.apache.calcite.rel.RelCollations;
-import org.apache.calcite.rel.hint.RelHint;
-import org.apache.calcite.rel.type.RelDataType;
-import org.apache.calcite.rel.type.RelDataTypeFactory;
-import org.apache.calcite.rex.RexNode;
-import org.apache.calcite.schema.Statistic;
-import org.apache.calcite.schema.impl.AbstractTable;
-import org.apache.calcite.util.ImmutableBitSet;
-import org.apache.ignite.internal.distributionzones.DistributionZoneManager;
-import org.apache.ignite.internal.sql.engine.metadata.ColocationGroup;
-import org.apache.ignite.internal.sql.engine.metadata.NodeWithTerm;
-import org.apache.ignite.internal.sql.engine.prepare.MappingQueryContext;
-import org.apache.ignite.internal.sql.engine.rel.logical.IgniteLogicalIndexScan;
-import org.apache.ignite.internal.sql.engine.rel.logical.IgniteLogicalTableScan;
-import org.apache.ignite.internal.sql.engine.schema.IgniteIndex.Type;
-import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
-import org.apache.ignite.internal.sql.engine.trait.TraitUtils;
-import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
-import org.apache.ignite.internal.storage.MvPartitionStorage;
-import org.apache.ignite.internal.storage.StorageRebalanceException;
-import org.apache.ignite.internal.table.InternalTable;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Ignite table implementation.
- */
-public class IgniteTableImpl extends AbstractTable implements IgniteTable {
-
-    private final TableDescriptor desc;
-
-    private final int ver;
-
-    private final int id;
-
-    private final String name;
-
-    private final Supplier<ColocationGroup> colocationGroup;
-
-    private final Statistic statistic;
-
-    private final Map<String, IgniteIndex> indexes = new HashMap<>();
-
-    /**
-     * Constructor.
-     *
-     * @param desc Table descriptor.
-     * @param tableId Table id.
-     * @param name Table name.
-     */
-    IgniteTableImpl(TableDescriptor desc, int tableId, String name, int version,
-            Supplier<ColocationGroup> colocationGroup, DoubleSupplier rowCount) {
-        this.ver = version;
-        this.desc = desc;
-        this.id = tableId;
-        this.name = name;
-        this.colocationGroup = colocationGroup;
-        this.statistic = new IgniteStatistic(rowCount, desc.distribution());
-    }
-
-    private IgniteTableImpl(IgniteTableImpl t) {
-        this.desc = t.desc;
-        this.ver = t.ver;
-        this.id = t.id;
-        this.name = t.name;
-        this.statistic = t.statistic;
-        this.colocationGroup = t.colocationGroup;
-        this.indexes.putAll(t.indexes);
-    }
-
-    public static IgniteTableImpl copyOf(IgniteTableImpl v) {
-        return new IgniteTableImpl(v);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public int id() {
-        return id;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public int version() {
-        return ver;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public String name() {
-        return name;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public RelDataType getRowType(RelDataTypeFactory typeFactory, ImmutableBitSet requiredColumns) {
-        return desc.rowType((IgniteTypeFactory) typeFactory, requiredColumns);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public Statistic getStatistic() {
-        return statistic;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public TableDescriptor descriptor() {
-        return desc;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public IgniteLogicalTableScan toRel(
-            RelOptCluster cluster,
-            RelOptTable relOptTbl,
-            List<RelHint> hints,
-            @Nullable List<RexNode> proj,
-            @Nullable RexNode cond,
-            @Nullable ImmutableBitSet requiredColumns
-    ) {
-        RelTraitSet traitSet = cluster.traitSetOf(distribution());
-
-        return IgniteLogicalTableScan.create(cluster, traitSet, hints, relOptTbl, proj, cond, requiredColumns);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public IgniteLogicalIndexScan toRel(
-            RelOptCluster cluster,
-            RelOptTable relOptTable,
-            String idxName,
-            List<RexNode> proj,
-            RexNode condition,
-            ImmutableBitSet requiredCols
-    ) {
-        IgniteIndex index = getIndex(idxName);
-
-        RelCollation collation = TraitUtils.createCollation(index.columns(), index.collations(), descriptor());
-
-        RelTraitSet traitSet = cluster.traitSetOf(Convention.Impl.NONE)
-                .replace(distribution())
-                .replace(index.type() == Type.HASH ? RelCollations.EMPTY : collation);
-
-        return IgniteLogicalIndexScan.create(cluster, traitSet, relOptTable, idxName, proj, condition, requiredCols);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public IgniteDistribution distribution() {
-        return desc.distribution();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public ColocationGroup colocationGroup(MappingQueryContext ctx) {
-        return partitionedGroup();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public Map<String, IgniteIndex> indexes() {
-        return Collections.unmodifiableMap(indexes);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void addIndex(IgniteIndex idxTbl) {
-        indexes.put(idxTbl.name(), idxTbl);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public IgniteIndex getIndex(String idxName) {
-        return indexes.get(idxName);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void removeIndex(String idxName) {
-        indexes.remove(idxName);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public <C> C unwrap(Class<C> cls) {
-        if (cls.isInstance(desc)) {
-            return cls.cast(desc);
-        }
-
-        return super.unwrap(cls);
-    }
-
-    private ColocationGroup partitionedGroup() {
-        return colocationGroup.get();
-    }
-
-    // TODO: should be moved to a separate component after https://issues.apache.org/jira/browse/IGNITE-18453
-    static Supplier<ColocationGroup> partitionedGroup(InternalTable table) {
-        return () -> {
-            List<List<NodeWithTerm>> assignments = table.primaryReplicas().stream()
-                    .map(primaryReplica -> new NodeWithTerm(primaryReplica.node().name(), primaryReplica.term()))
-                    .map(Collections::singletonList)
-                    .collect(Collectors.toList());
-
-            return ColocationGroup.forAssignments(assignments);
-        };
-    }
-
-    static DoubleSupplier rowCountStatistic(InternalTable table) {
-        return new RowCountStatistic(table);
-    }
-
-    private static final class RowCountStatistic implements DoubleSupplier {
-        private static final int UPDATE_THRESHOLD = DistributionZoneManager.DEFAULT_PARTITION_COUNT;
-
-        private final AtomicLong lastUpd = new AtomicLong();
-
-        private volatile long localRowCnt = 0L;
-
-        private final InternalTable table;
-
-        private RowCountStatistic(InternalTable table) {
-            this.table = table;
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        // TODO: need to be refactored https://issues.apache.org/jira/browse/IGNITE-19558
-        public double getAsDouble() {
-            int parts = table.storage().getTableDescriptor().getPartitions();
-
-            long partitionsRevisionCounter = 0L;
-
-            for (int p = 0; p < parts; ++p) {
-                @Nullable MvPartitionStorage part = table.storage().getMvPartition(p);
-
-                if (part == null) {
-                    continue;
-                }
-
-                long upd = part.lastAppliedIndex();
-
-                partitionsRevisionCounter += upd;
-            }
-
-            long prev = lastUpd.get();
-
-            if (partitionsRevisionCounter - prev > UPDATE_THRESHOLD) {
-                synchronized (this) {
-                    if (lastUpd.compareAndSet(prev, partitionsRevisionCounter)) {
-                        long size = 0L;
-
-                        for (int p = 0; p < parts; ++p) {
-                            @Nullable MvPartitionStorage part = table.storage().getMvPartition(p);
-
-                            if (part == null) {
-                                continue;
-                            }
-
-                            try {
-                                size += part.rowsCount();
-                            } catch (StorageRebalanceException ignore) {
-                                // No-op.
-                            }
-                        }
-
-                        localRowCnt = size;
-                    }
-                }
-            }
-
-            // Forbid zero result, to prevent zero cost for table and index scans.
-            return Math.max(10_000.0, (double) localRowCnt);
-        }
-    }
-}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/SqlSchemaManagerImpl.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/SqlSchemaManagerImpl.java
deleted file mode 100644
index 4a333cca22..0000000000
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/SqlSchemaManagerImpl.java
+++ /dev/null
@@ -1,588 +0,0 @@
-/*
- * 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.schema;
-
-import static java.util.concurrent.CompletableFuture.completedFuture;
-import static java.util.concurrent.CompletableFuture.failedFuture;
-import static org.apache.ignite.internal.sql.engine.SqlQueryProcessor.DEFAULT_SCHEMA_NAME;
-import static org.apache.ignite.internal.util.IgniteUtils.inBusyLock;
-import static org.apache.ignite.lang.ErrorGroups.Common.INTERNAL_ERR;
-import static org.apache.ignite.lang.ErrorGroups.Common.NODE_STOPPING_ERR;
-import static org.apache.ignite.lang.IgniteStringFormatter.format;
-
-import it.unimi.dsi.fastutil.ints.IntArrayList;
-import it.unimi.dsi.fastutil.ints.IntList;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArraySet;
-import java.util.function.Consumer;
-import java.util.function.DoubleSupplier;
-import java.util.function.LongFunction;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-import org.apache.calcite.schema.SchemaPlus;
-import org.apache.calcite.tools.Frameworks;
-import org.apache.ignite.internal.causality.CompletableVersionedValue;
-import org.apache.ignite.internal.causality.IncrementalVersionedValue;
-import org.apache.ignite.internal.causality.OutdatedTokenException;
-import org.apache.ignite.internal.index.HashIndex;
-import org.apache.ignite.internal.index.Index;
-import org.apache.ignite.internal.index.IndexDescriptor;
-import org.apache.ignite.internal.index.SortedIndexDescriptor;
-import org.apache.ignite.internal.index.SortedIndexImpl;
-import org.apache.ignite.internal.schema.Column;
-import org.apache.ignite.internal.schema.DefaultValueProvider;
-import org.apache.ignite.internal.schema.DefaultValueProvider.Type;
-import org.apache.ignite.internal.schema.SchemaDescriptor;
-import org.apache.ignite.internal.schema.SchemaManager;
-import org.apache.ignite.internal.schema.SchemaRegistry;
-import org.apache.ignite.internal.sql.engine.metadata.ColocationGroup;
-import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
-import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
-import org.apache.ignite.internal.table.InternalTable;
-import org.apache.ignite.internal.table.TableImpl;
-import org.apache.ignite.internal.table.distributed.TableManager;
-import org.apache.ignite.internal.util.IgniteSpinBusyLock;
-import org.apache.ignite.lang.IgniteInternalException;
-import org.apache.ignite.lang.NodeStoppingException;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Holds actual schema and mutates it on schema change, requested by Ignite.
- */
-public class SqlSchemaManagerImpl implements SqlSchemaManager {
-    private final IncrementalVersionedValue<Map<String, IgniteSchema>> schemasVv;
-
-    private final IncrementalVersionedValue<Map<Integer, IgniteTable>> tablesVv;
-
-    private final Map<Integer, CompletableFuture<?>> pkIdxReady = new ConcurrentHashMap<>();
-
-    private final IncrementalVersionedValue<Map<Integer, IgniteIndex>> indicesVv;
-
-    private final TableManager tableManager;
-    private final SchemaManager schemaManager;
-
-    private final CompletableVersionedValue<SchemaPlus> calciteSchemaVv;
-
-    private final Set<SchemaUpdateListener> listeners = new CopyOnWriteArraySet<>();
-
-    /** Busy lock for stop synchronisation. */
-    private final IgniteSpinBusyLock busyLock;
-
-    /**
-     * Constructor.
-     * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
-     */
-    public SqlSchemaManagerImpl(
-            TableManager tableManager,
-            SchemaManager schemaManager,
-            Consumer<LongFunction<CompletableFuture<?>>> registry,
-            IgniteSpinBusyLock busyLock
-    ) {
-        this.tableManager = tableManager;
-        this.schemaManager = schemaManager;
-
-        schemasVv = new IncrementalVersionedValue<>(registry, HashMap::new);
-        tablesVv = new IncrementalVersionedValue<>(registry, HashMap::new);
-        indicesVv = new IncrementalVersionedValue<>(registry, HashMap::new);
-        this.busyLock = busyLock;
-
-        calciteSchemaVv = new CompletableVersionedValue<>(() -> {
-            SchemaPlus newCalciteSchema = Frameworks.createRootSchema(false);
-            newCalciteSchema.add(DEFAULT_SCHEMA_NAME, new IgniteSchema(DEFAULT_SCHEMA_NAME));
-            return newCalciteSchema;
-        });
-
-        schemasVv.whenComplete((token, stringIgniteSchemaMap, throwable) -> {
-            if (!busyLock.enterBusy()) {
-                calciteSchemaVv.completeExceptionally(token, new IgniteInternalException(NODE_STOPPING_ERR, new NodeStoppingException()));
-
-                return;
-            }
-            try {
-                if (throwable != null) {
-                    calciteSchemaVv.completeExceptionally(
-                            token,
-                            new IgniteInternalException(
-                                    INTERNAL_ERR, "Couldn't evaluate sql schemas for causality token: " + token, throwable)
-                    );
-
-                    return;
-                }
-
-                SchemaPlus newCalciteSchema = rebuild(stringIgniteSchemaMap);
-
-                listeners.forEach(SchemaUpdateListener::onSchemaUpdated);
-
-                calciteSchemaVv.complete(token, newCalciteSchema);
-            } finally {
-                busyLock.leaveBusy();
-            }
-        });
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public SchemaPlus schema(@Nullable String schema) {
-        // stub for waiting pk indexes, more clear place is IgniteSchema
-        CompletableFuture.allOf(pkIdxReady.values().toArray(CompletableFuture[]::new)).join();
-
-        SchemaPlus schemaPlus = calciteSchemaVv.latest();
-
-        return schema != null ? schemaPlus.getSubSchema(schema) : schemaPlus.getSubSchema(DEFAULT_SCHEMA_NAME);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public SchemaPlus schema(String name, int version) {
-        throw new UnsupportedOperationException();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public CompletableFuture<?> actualSchemaAsync(long ver) {
-        if (!busyLock.enterBusy()) {
-            throw new IgniteInternalException(NODE_STOPPING_ERR, new NodeStoppingException());
-        }
-        try {
-            if (ver == IgniteSchema.INITIAL_VERSION) {
-                return completedFuture(calciteSchemaVv.latest());
-            }
-
-            CompletableFuture<SchemaPlus> lastSchemaFut;
-
-            try {
-                lastSchemaFut = calciteSchemaVv.get(ver);
-            } catch (OutdatedTokenException e) {
-                return completedFuture(null);
-            }
-
-            return lastSchemaFut;
-        } finally {
-            busyLock.leaveBusy();
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public SchemaPlus activeSchema(@Nullable String name, long timestamp) {
-        throw new UnsupportedOperationException();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public IgniteTable tableById(int id) {
-        if (!busyLock.enterBusy()) {
-            throw new IgniteInternalException(NODE_STOPPING_ERR, new NodeStoppingException());
-        }
-        try {
-            IgniteTable table = tablesVv.latest().get(id);
-
-            if (table == null) {
-                throw new IgniteInternalException(INTERNAL_ERR,
-                        format("Table not found [tableId={}]", id));
-            }
-
-            return table;
-        } finally {
-            busyLock.leaveBusy();
-        }
-    }
-
-    public void registerListener(SchemaUpdateListener listener) {
-        listeners.add(listener);
-    }
-
-    /**
-     * OnSqlTypeCreated.
-     * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
-     */
-    public CompletableFuture<?> onTableCreated(String schemaName, int tableId, long causalityToken) {
-        if (!busyLock.enterBusy()) {
-            return failedFuture(new IgniteInternalException(NODE_STOPPING_ERR, new NodeStoppingException()));
-        }
-
-        try {
-            pkIdxReady.computeIfAbsent(tableId, k -> new CompletableFuture<>());
-
-            CompletableFuture<Map<Integer, IgniteTable>> updatedTables = tablesVv.update(causalityToken, (tables, e) ->
-                    inBusyLock(busyLock, () -> {
-                        if (e != null) {
-                            return failedFuture(e);
-                        }
-
-                        return tableManager.tableAsync(causalityToken, tableId)
-                                .thenCompose(table -> convert(causalityToken, table))
-                                .thenApply(igniteTable -> {
-                                    Map<Integer, IgniteTable> resTbls = new HashMap<>(tables);
-
-                                    IgniteTable oldTable = resTbls.put(igniteTable.id(), igniteTable);
-
-                                    // looks like this is UPDATE operation
-                                    if (oldTable != null) {
-                                        for (var index : oldTable.indexes().values()) {
-                                            igniteTable.addIndex(index);
-                                        }
-                                    }
-
-                                    return resTbls;
-                                });
-                    })
-            );
-
-            schemasVv.update(causalityToken, (schemas, e) -> inBusyLock(busyLock, () -> {
-                if (e != null) {
-                    return failedFuture(e);
-                }
-
-                return updatedTables.thenApply(tables -> {
-                    IgniteTable igniteTable = tables.get(tableId);
-
-                    Map<String, IgniteSchema> res = new HashMap<>(schemas);
-
-                    IgniteSchema schema = res.compute(schemaName,
-                            (k, v) -> v == null ? new IgniteSchema(schemaName, causalityToken) : IgniteSchema.copy(v, causalityToken));
-
-                    schema.addTable(igniteTable);
-
-                    return res;
-                });
-            }));
-
-            // calciteSchemaVv depends on all other Versioned Values and is completed last.
-            return calciteSchemaVv.get(causalityToken);
-        } finally {
-            busyLock.leaveBusy();
-        }
-    }
-
-    /**
-     * OnSqlTypeUpdated.
-     * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
-     */
-    public CompletableFuture<?> onTableUpdated(String schemaName, int tableId, long causalityToken) {
-        return onTableCreated(schemaName, tableId, causalityToken);
-    }
-
-    /**
-     * OnSqlTypeDropped.
-     * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
-     */
-    public CompletableFuture<?> onTableDropped(String schemaName, int tableId, long causalityToken) {
-        if (!busyLock.enterBusy()) {
-            return failedFuture(new IgniteInternalException(NODE_STOPPING_ERR, new NodeStoppingException()));
-        }
-
-        try {
-            CompletableFuture<IgniteTable> removedTableFuture = new CompletableFuture<>();
-
-            tablesVv.update(causalityToken, (tables, e) -> inBusyLock(busyLock, () -> {
-                if (e != null) {
-                    removedTableFuture.completeExceptionally(e);
-
-                    return failedFuture(e);
-                }
-
-                Map<Integer, IgniteTable> resTbls = new HashMap<>(tables);
-
-                IgniteTable removedTable = resTbls.remove(tableId);
-
-                removedTableFuture.complete(removedTable);
-
-                return completedFuture(resTbls);
-            }));
-
-            schemasVv.update(causalityToken, (schemas, e) -> inBusyLock(busyLock, () -> {
-                if (e != null) {
-                    return failedFuture(e);
-                }
-
-                return removedTableFuture.thenApply(table -> {
-                    if (table == null) {
-                        return schemas;
-                    }
-
-                    Map<String, IgniteSchema> res = new HashMap<>(schemas);
-
-                    IgniteSchema schema = res.compute(schemaName,
-                            (k, v) -> v == null ? new IgniteSchema(schemaName, causalityToken) : IgniteSchema.copy(v, causalityToken));
-
-                    schema.removeTable(table.name());
-
-                    pkIdxReady.remove(table.id());
-
-                    return res;
-                });
-            }));
-
-            // calciteSchemaVv depends on all other Versioned Values and is completed last.
-            return calciteSchemaVv.get(causalityToken);
-        } finally {
-            busyLock.leaveBusy();
-        }
-    }
-
-    /**
-     * Rebuilds Calcite schemas.
-     *
-     * @param schemas Ignite schemas.
-     */
-    private SchemaPlus rebuild(Map<String, IgniteSchema> schemas) {
-        SchemaPlus newCalciteSchema = Frameworks.createRootSchema(false);
-
-        newCalciteSchema.add(DEFAULT_SCHEMA_NAME, new IgniteSchema(DEFAULT_SCHEMA_NAME));
-
-        schemas.forEach(newCalciteSchema::add);
-
-        return newCalciteSchema;
-    }
-
-    private CompletableFuture<IgniteTableImpl> convert(long causalityToken, TableImpl table) {
-        return schemaManager.schemaRegistry(causalityToken, table.tableId())
-                .thenApply(schemaRegistry -> inBusyLock(busyLock, () -> convert(table, schemaRegistry, causalityToken)));
-    }
-
-    private IgniteTableImpl convert(TableImpl table, SchemaRegistry schemaRegistry, long schemaVersion) {
-        SchemaDescriptor descriptor = schemaRegistry.schema();
-
-        List<ColumnDescriptor> colDescriptors = descriptor.columnNames().stream()
-                .map(descriptor::column)
-                .sorted(Comparator.comparingInt(Column::columnOrder))
-                .map(col -> new ColumnDescriptorImpl(
-                        col.name(),
-                        descriptor.isKeyColumn(col.schemaIndex()),
-                        col.nullable(),
-                        col.columnOrder(),
-                        col.schemaIndex(),
-                        col.type(),
-                        convertDefaultValueProvider(col.defaultValueProvider()),
-                        col::defaultValue
-                ))
-                .collect(Collectors.toList());
-
-        IntList colocationColumns = new IntArrayList();
-
-        for (Column column : descriptor.colocationColumns()) {
-            colocationColumns.add(column.columnOrder());
-        }
-
-        // TODO Use the actual zone ID after implementing https://issues.apache.org/jira/browse/IGNITE-18426.
-        IgniteDistribution distribution = IgniteDistributions.affinity(colocationColumns, table.tableId(), table.tableId());
-
-        InternalTable internalTable = table.internalTable();
-        Supplier<ColocationGroup> colocationGroup = IgniteTableImpl.partitionedGroup(internalTable);
-        DoubleSupplier rowCount = IgniteTableImpl.rowCountStatistic(internalTable);
-
-        return new IgniteTableImpl(
-                new TableDescriptorImpl(colDescriptors, distribution),
-                internalTable.tableId(),
-                internalTable.name(),
-                schemaRegistry.lastSchemaVersion(),
-                colocationGroup,
-                rowCount
-        );
-    }
-
-    private DefaultValueStrategy convertDefaultValueProvider(DefaultValueProvider defaultValueProvider) {
-        return defaultValueProvider.type() == Type.CONSTANT
-                ? DefaultValueStrategy.DEFAULT_CONSTANT
-                : DefaultValueStrategy.DEFAULT_COMPUTED;
-    }
-
-    /**
-     * Index created callback method register index in Calcite schema.
-     *
-     * @param tableId Table ID.
-     * @param indexId Index ID.
-     * @param causalityToken Causality token.
-     * @return Schema registration future.
-     */
-    public CompletableFuture<?> onIndexCreated(int tableId, int indexId, IndexDescriptor indexDescriptor, long causalityToken) {
-        if (!busyLock.enterBusy()) {
-            return failedFuture(new IgniteInternalException(NODE_STOPPING_ERR, new NodeStoppingException()));
-        }
-
-        try {
-            CompletableFuture<Map<Integer, IgniteIndex>> updatedIndices = indicesVv.update(causalityToken, (indices, e) ->
-                    inBusyLock(busyLock, () -> {
-                        if (e != null) {
-                            return failedFuture(e);
-                        }
-
-                        return tableManager.tableAsync(causalityToken, tableId).thenApply(table -> {
-                            var igniteIndex = new IgniteIndex(newIndex(table, indexId, indexDescriptor));
-
-                            Map<Integer, IgniteIndex> resIdxs = new HashMap<>(indices);
-
-                            resIdxs.put(indexId, igniteIndex);
-
-                            return resIdxs;
-                        });
-                    }));
-
-            CompletableFuture<Map<Integer, IgniteTable>> updatedTables = tablesVv.update(causalityToken, (tables, e) ->
-                    inBusyLock(busyLock, () -> {
-                        if (e != null) {
-                            return failedFuture(e);
-                        }
-
-                        return updatedIndices.thenApply(indices -> {
-                            IgniteIndex igniteIndex = indices.get(indexId);
-
-                            Map<Integer, IgniteTable> resTbls = new HashMap<>(tables);
-
-                            IgniteTable igniteTable = resTbls.computeIfPresent(tableId,
-                                    (k, v) -> IgniteTableImpl.copyOf((IgniteTableImpl) v));
-
-                            assert igniteTable != null : "Table " + tableId + " was not found";
-
-                            igniteTable.addIndex(igniteIndex);
-
-                            return resTbls;
-                        });
-                    }));
-
-            schemasVv.update(causalityToken, (schemas, e) -> inBusyLock(busyLock, () -> {
-                if (e != null) {
-                    return failedFuture(e);
-                }
-
-                return updatedTables.thenCombine(updatedIndices, (tables, indices) -> inBusyLock(busyLock, () -> {
-                    Map<String, IgniteSchema> res = new HashMap<>(schemas);
-
-                    IgniteSchema schema = res.compute(DEFAULT_SCHEMA_NAME,
-                            (k, v) -> v == null ? new IgniteSchema(k, causalityToken) : IgniteSchema.copy(v, causalityToken));
-
-                    schema.addTable(tables.get(tableId));
-
-                    schema.addIndex(indexId, indices.get(indexId));
-
-                    return res;
-                }));
-            }));
-
-            // this stub is necessary for observing pk index creation.
-            schemasVv.whenComplete((token, stringIgniteSchemaMap, throwable) -> {
-                CompletableFuture<?> pkFut = pkIdxReady.get(tableId);
-                // this listener is called repeatedly on node stop.
-                if (pkFut != null) {
-                    pkFut.complete(null);
-                }
-            });
-
-            return calciteSchemaVv.get(causalityToken);
-        } finally {
-            busyLock.leaveBusy();
-        }
-    }
-
-    private static Index<?> newIndex(TableImpl table, int indexId, IndexDescriptor descriptor) {
-        if (descriptor instanceof SortedIndexDescriptor) {
-            return new SortedIndexImpl(indexId, table.internalTable(), (SortedIndexDescriptor) descriptor);
-        } else {
-            return new HashIndex(indexId, table.internalTable(), descriptor);
-        }
-    }
-
-    /**
-     * Index dropped callback method deregisters index from Calcite schema.
-     *
-     * @param schemaName Schema name.
-     * @param indexId Index id.
-     * @param causalityToken Causality token.
-     * @return Schema registration future.
-     */
-    public CompletableFuture<?> onIndexDropped(String schemaName, int tableId, int indexId, long causalityToken) {
-        if (!busyLock.enterBusy()) {
-            return failedFuture(new IgniteInternalException(NODE_STOPPING_ERR, new NodeStoppingException()));
-        }
-
-        try {
-            CompletableFuture<IgniteIndex> removedIndexFuture = new CompletableFuture<>();
-
-            indicesVv.update(causalityToken, (indices, e) -> inBusyLock(busyLock, () -> {
-                if (e != null) {
-                    removedIndexFuture.completeExceptionally(e);
-
-                    return failedFuture(e);
-                }
-
-                Map<Integer, IgniteIndex> resIdxs = new HashMap<>(indices);
-
-                IgniteIndex rmvIdx = resIdxs.remove(indexId);
-
-                removedIndexFuture.complete(rmvIdx);
-
-                return completedFuture(resIdxs);
-            }));
-
-            CompletableFuture<Map<Integer, IgniteTable>> updatedTables = tablesVv.update(causalityToken, (tables, e) ->
-                    inBusyLock(busyLock, () -> {
-                        if (e != null) {
-                            return failedFuture(e);
-                        }
-
-                        Map<Integer, IgniteTable> resTbls = new HashMap<>(tables);
-
-                        IgniteTable table = resTbls.computeIfPresent(tableId, (k, v) -> IgniteTableImpl.copyOf((IgniteTableImpl) v));
-
-                        if (table == null) {
-                            return completedFuture(resTbls);
-                        } else {
-                            return removedIndexFuture.thenApply(rmvIndex -> {
-                                table.removeIndex(rmvIndex.name());
-
-                                return resTbls;
-                            });
-                        }
-                    }));
-
-            schemasVv.update(causalityToken, (schemas, e) -> inBusyLock(busyLock, () -> {
-                if (e != null) {
-                    return failedFuture(e);
-                }
-
-                Map<String, IgniteSchema> res = new HashMap<>(schemas);
-
-                IgniteSchema schema = res.compute(schemaName,
-                        (k, v) -> v == null ? new IgniteSchema(schemaName, causalityToken) : IgniteSchema.copy(v, causalityToken));
-
-                schema.removeIndex(indexId);
-
-                return updatedTables.thenApply(tables -> {
-                    IgniteTable table = tables.get(tableId);
-
-                    if (table != null) {
-                        schema.addTable(tables.get(tableId));
-                    }
-
-                    return res;
-                });
-            }));
-
-            return calciteSchemaVv.get(causalityToken);
-        } finally {
-            busyLock.leaveBusy();
-        }
-    }
-}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/TraitUtils.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/TraitUtils.java
index 858a6380b5..8357b5a1f6 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/TraitUtils.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/TraitUtils.java
@@ -24,7 +24,6 @@ import static org.apache.calcite.rel.RelDistribution.Type.BROADCAST_DISTRIBUTED;
 import static org.apache.calcite.rel.RelDistribution.Type.HASH_DISTRIBUTED;
 import static org.apache.ignite.internal.util.CollectionUtils.first;
 import static org.apache.ignite.internal.util.CollectionUtils.nullOrEmpty;
-import static org.apache.ignite.lang.IgniteStringFormatter.format;
 
 import com.google.common.collect.ImmutableList;
 import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
@@ -58,6 +57,10 @@ import org.apache.calcite.rex.RexSlot;
 import org.apache.calcite.util.ControlFlowException;
 import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.mapping.Mappings;
+import org.apache.ignite.internal.catalog.descriptors.CatalogHashIndexDescriptor;
+import org.apache.ignite.internal.catalog.descriptors.CatalogIndexColumnDescriptor;
+import org.apache.ignite.internal.catalog.descriptors.CatalogIndexDescriptor;
+import org.apache.ignite.internal.catalog.descriptors.CatalogSortedIndexDescriptor;
 import org.apache.ignite.internal.index.ColumnCollation;
 import org.apache.ignite.internal.sql.engine.rel.IgniteConvention;
 import org.apache.ignite.internal.sql.engine.rel.IgniteExchange;
@@ -65,11 +68,7 @@ import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
 import org.apache.ignite.internal.sql.engine.rel.IgniteSort;
 import org.apache.ignite.internal.sql.engine.rel.IgniteTrimExchange;
 import org.apache.ignite.internal.sql.engine.schema.ColumnDescriptor;
-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.TableDescriptor;
-import org.apache.ignite.lang.ErrorGroups.Common;
-import org.apache.ignite.lang.IgniteInternalException;
 import org.jetbrains.annotations.Nullable;
 
 /**
@@ -411,56 +410,60 @@ public class TraitUtils {
     }
 
     /**
-     * Creates {@link RelCollation} object from a given collations.
+     * Creates {@link RelCollation} object for given index.
      *
-     * @param collations List of collations.
+     * @param descriptor Index descriptor.
+     * @param tableDescriptor Table descriptor to derive column indexes from.
      * @return a {@link RelCollation} object.
      */
-    public static RelCollation createCollation(List<Collation> collations) {
-        List<RelFieldCollation> fieldCollations = new ArrayList<>(collations.size());
+    public static RelCollation createIndexCollation(CatalogIndexDescriptor descriptor, TableDescriptor tableDescriptor) {
+        if (descriptor instanceof CatalogSortedIndexDescriptor) {
+            CatalogSortedIndexDescriptor sortedIndexDescriptor = (CatalogSortedIndexDescriptor) descriptor;
+            List<CatalogIndexColumnDescriptor> columns = sortedIndexDescriptor.columns();
+            List<RelFieldCollation> fieldCollations = new ArrayList<>(columns.size());
+
+            for (int i = 0; i < columns.size(); i++) {
+                CatalogIndexColumnDescriptor column = columns.get(i);
+                ColumnDescriptor columnDesc = tableDescriptor.columnDescriptor(column.name());
+                int fieldIndex = columnDesc.logicalIndex();
+
+                RelFieldCollation fieldCollation;
+                switch (column.collation()) {
+                    case ASC_NULLS_FIRST:
+                        fieldCollation = new RelFieldCollation(fieldIndex, Direction.ASCENDING, NullDirection.FIRST);
+                        break;
+                    case ASC_NULLS_LAST:
+                        fieldCollation = new RelFieldCollation(fieldIndex, Direction.ASCENDING, NullDirection.LAST);
+                        break;
+                    case DESC_NULLS_FIRST:
+                        fieldCollation = new RelFieldCollation(fieldIndex, Direction.DESCENDING, NullDirection.FIRST);
+                        break;
+                    case DESC_NULLS_LAST:
+                        fieldCollation = new RelFieldCollation(fieldIndex, Direction.DESCENDING, NullDirection.LAST);
+                        break;
+                    default:
+                        throw new IllegalArgumentException("Unexpected collation: " + column.collation());
+                }
 
-        for (int i = 0; i < collations.size(); i++) {
-            fieldCollations.add(createFieldCollation(i,  collations.get(i)));
-        }
+                fieldCollations.add(fieldCollation);
+            }
 
-        return RelCollations.of(fieldCollations);
-    }
+            return RelCollations.of(fieldCollations);
+        } else if (descriptor instanceof CatalogHashIndexDescriptor) {
+            CatalogHashIndexDescriptor hashIndexDescriptor = (CatalogHashIndexDescriptor) descriptor;
+            List<String> columns = hashIndexDescriptor.columns();
+            List<RelFieldCollation> fieldCollations = new ArrayList<>(columns.size());
 
-    /**
-     * Creates {@link RelCollation} object from a given collations.
-     *
-     * @param indexedColumns List of columns names.
-     * @param collations List of collations.
-     * @param descriptor Table descriptor to derive column indexes from.
-     * @return a {@link RelCollation} object.
-     */
-    public static RelCollation createCollation(
-            List<String> indexedColumns,
-            @Nullable List<IgniteIndex.Collation> collations,
-            TableDescriptor descriptor
-    ) {
-        List<RelFieldCollation> fieldCollations = new ArrayList<>(indexedColumns.size());
-
-        if (collations == null) { // Build collation for Hash index.
-            for (int i = 0; i < indexedColumns.size(); i++) {
-                String columnName = indexedColumns.get(i);
-                ColumnDescriptor columnDesc = descriptor.columnDescriptor(columnName);
+            for (String columnName : columns) {
+                ColumnDescriptor columnDesc = tableDescriptor.columnDescriptor(columnName);
 
                 fieldCollations.add(new RelFieldCollation(columnDesc.logicalIndex(), Direction.CLUSTERED, NullDirection.UNSPECIFIED));
             }
 
             return RelCollations.of(fieldCollations);
+        } else {
+            throw new IllegalArgumentException("Unexpected index type: " + descriptor);
         }
-
-        for (int i = 0; i < indexedColumns.size(); i++) {
-            String columnName = indexedColumns.get(i);
-            IgniteIndex.Collation collation = collations.get(i);
-            ColumnDescriptor columnDesc = descriptor.columnDescriptor(columnName);
-
-            fieldCollations.add(createFieldCollation(columnDesc.logicalIndex(), collation));
-        }
-
-        return RelCollations.of(fieldCollations);
     }
 
     /**
@@ -485,22 +488,6 @@ public class TraitUtils {
         return new RelFieldCollation(fieldIdx, direction, nullDirection);
     }
 
-    /** Creates field collation. */
-    public static RelFieldCollation createFieldCollation(int fieldIdx, IgniteIndex.Collation collation) {
-        switch (collation) {
-            case ASC_NULLS_LAST:
-                return new RelFieldCollation(fieldIdx, RelFieldCollation.Direction.ASCENDING, RelFieldCollation.NullDirection.LAST);
-            case ASC_NULLS_FIRST:
-                return new RelFieldCollation(fieldIdx, RelFieldCollation.Direction.ASCENDING, RelFieldCollation.NullDirection.FIRST);
-            case DESC_NULLS_LAST:
-                return new RelFieldCollation(fieldIdx, RelFieldCollation.Direction.DESCENDING, RelFieldCollation.NullDirection.LAST);
-            case DESC_NULLS_FIRST:
-                return new RelFieldCollation(fieldIdx, RelFieldCollation.Direction.DESCENDING, RelFieldCollation.NullDirection.FIRST);
-            default:
-                throw new IgniteInternalException(Common.INTERNAL_ERR, format("Unknown collation [collation={}]", collation));
-        }
-    }
-
     public static boolean distributionEnabled(RelNode node) {
         return distribution(node) != null;
     }
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionDependencyResolverSelfTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionDependencyResolverSelfTest.java
index 041738819a..6a73dff178 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionDependencyResolverSelfTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionDependencyResolverSelfTest.java
@@ -29,17 +29,18 @@ import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionException;
+import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.ignite.internal.sql.engine.planner.AbstractPlannerTest;
 import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
 import org.apache.ignite.internal.sql.engine.schema.IgniteIndex;
+import org.apache.ignite.internal.sql.engine.schema.IgniteIndex.Type;
 import org.apache.ignite.internal.sql.engine.schema.IgniteSchema;
 import org.apache.ignite.internal.sql.engine.schema.IgniteTable;
 import org.apache.ignite.internal.sql.engine.schema.TableDescriptor;
@@ -114,7 +115,9 @@ public class ExecutionDependencyResolverSelfTest extends AbstractPlannerTest {
 
         int t1Id = tester.addTable("TEST1", tableType);
         tester.setDependencies(t1Id, table1, update1);
-        tester.addIndex("TEST1", new IgniteIndex(TestHashIndex.create(List.of("ID"), "ID_IDX")));
+        tester.addIndex("TEST1", new IgniteIndex(1, "ID_IDX", Type.HASH,
+                tester.tableDescriptor("TEST1").distribution(),
+                RelCollations.of(0)));
 
         CompletableFuture<ResolvedDependencies> f = tester.resolveDependencies("SELECT * FROM test1 WHERE id=1");
         tester.checkDependencies(f.join(), t1Id);
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/IndexScanNodeExecutionTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/IndexScanNodeExecutionTest.java
index 1b6fb59cde..02e84724a1 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/IndexScanNodeExecutionTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/IndexScanNodeExecutionTest.java
@@ -36,12 +36,11 @@ import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rel.type.RelDataTypeFactory.Builder;
 import org.apache.calcite.sql.type.SqlTypeName;
-import org.apache.ignite.internal.index.ColumnCollation;
-import org.apache.ignite.internal.index.HashIndex;
-import org.apache.ignite.internal.index.Index;
-import org.apache.ignite.internal.index.IndexDescriptor;
-import org.apache.ignite.internal.index.SortedIndexDescriptor;
-import org.apache.ignite.internal.index.SortedIndexImpl;
+import org.apache.ignite.internal.catalog.descriptors.CatalogColumnCollation;
+import org.apache.ignite.internal.catalog.descriptors.CatalogHashIndexDescriptor;
+import org.apache.ignite.internal.catalog.descriptors.CatalogIndexColumnDescriptor;
+import org.apache.ignite.internal.catalog.descriptors.CatalogIndexDescriptor;
+import org.apache.ignite.internal.catalog.descriptors.CatalogSortedIndexDescriptor;
 import org.apache.ignite.internal.sql.engine.exec.ExecutionContext;
 import org.apache.ignite.internal.sql.engine.exec.RowHandler.RowFactory;
 import org.apache.ignite.internal.sql.engine.exec.ScannableTable;
@@ -51,11 +50,10 @@ import org.apache.ignite.internal.sql.engine.planner.AbstractPlannerTest.TestTab
 import org.apache.ignite.internal.sql.engine.schema.IgniteIndex;
 import org.apache.ignite.internal.sql.engine.schema.TableDescriptor;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
+import org.apache.ignite.internal.sql.engine.trait.TraitUtils;
 import org.apache.ignite.internal.sql.engine.util.Commons;
-import org.apache.ignite.internal.table.InternalTable;
 import org.jetbrains.annotations.Nullable;
 import org.junit.jupiter.api.Test;
-import org.mockito.Mockito;
 
 /**
  * Test {@link IndexScanNode} execution.
@@ -67,7 +65,7 @@ public class IndexScanNodeExecutionTest extends AbstractExecutionTest {
      */
     @Test
     public void testSortedIndex() {
-        Index<?> index = newSortedIndex();
+        CatalogSortedIndexDescriptor index = newSortedIndex();
 
         ExecutionContext<Object[]> ctx = executionContext();
 
@@ -90,7 +88,7 @@ public class IndexScanNodeExecutionTest extends AbstractExecutionTest {
      */
     @Test
     public void testHashIndex() {
-        Index<?> index = newHashIndex();
+        CatalogHashIndexDescriptor index = newHashIndex();
 
         ExecutionContext<Object[]> ctx = executionContext();
 
@@ -114,11 +112,12 @@ public class IndexScanNodeExecutionTest extends AbstractExecutionTest {
             this.ctx = ctx;
         }
 
-        IndexScanNode<Object[]> createSortedIndex(Index<?> index, TestScannableTable<?> scannableTable, Comparator<Object[]> cmp) {
+        IndexScanNode<Object[]> createSortedIndex(CatalogIndexDescriptor index, TestScannableTable<?> scannableTable,
+                Comparator<Object[]> cmp) {
             return createIndexNode(ctx, index, scannableTable, cmp);
         }
 
-        IndexScanNode<Object[]> createHashIndex(Index<?> index, TestScannableTable<?> scannableTable) {
+        IndexScanNode<Object[]> createHashIndex(CatalogIndexDescriptor index, TestScannableTable<?> scannableTable) {
             return createIndexNode(ctx, index, scannableTable, null);
         }
 
@@ -150,39 +149,48 @@ public class IndexScanNodeExecutionTest extends AbstractExecutionTest {
         }
     }
 
-    private Index<?> newHashIndex() {
-        IndexDescriptor descriptor = new IndexDescriptor("IDX", List.of("C1"));
+    private CatalogHashIndexDescriptor newHashIndex() {
+        return new CatalogHashIndexDescriptor(1, "IDX", 0, false, List.of("C1"));
 
-        return new HashIndex(1, Mockito.mock(InternalTable.class), descriptor);
     }
 
-    private Index<?> newSortedIndex() {
-        List<String> columnNames = List.of("C1");
-        List<ColumnCollation> columnCollations = List.of(ColumnCollation.ASC_NULLS_LAST);
-        SortedIndexDescriptor descriptor = new SortedIndexDescriptor("IDX", columnNames, columnCollations);
-
-        return new SortedIndexImpl(1, Mockito.mock(InternalTable.class), descriptor);
+    private CatalogSortedIndexDescriptor newSortedIndex() {
+        return new CatalogSortedIndexDescriptor(1, "IDX", 0, false,
+                List.of(new CatalogIndexColumnDescriptor("C1", CatalogColumnCollation.ASC_NULLS_LAST)));
     }
 
-    private IndexScanNode<Object[]> createIndexNode(ExecutionContext<Object[]> ctx, Index<?> index,
+    private IndexScanNode<Object[]> createIndexNode(ExecutionContext<Object[]> ctx, CatalogIndexDescriptor indexDescriptor,
             TestScannableTable<?> scannableTable, @Nullable Comparator<Object[]> comparator) {
 
         RelDataTypeFactory.Builder rowTypeBuilder = new Builder(Commons.typeFactory());
 
-        for (String column : index.descriptor().columns()) {
-            rowTypeBuilder = rowTypeBuilder.add(column, SqlTypeName.INTEGER);
+        if (indexDescriptor instanceof CatalogHashIndexDescriptor) {
+            for (String column : ((CatalogHashIndexDescriptor) indexDescriptor).columns()) {
+                rowTypeBuilder = rowTypeBuilder.add(column, SqlTypeName.INTEGER);
+            }
+        } else {
+            for (CatalogIndexColumnDescriptor column : ((CatalogSortedIndexDescriptor) indexDescriptor).columns()) {
+                rowTypeBuilder = rowTypeBuilder.add(column.name(), SqlTypeName.INTEGER);
+            }
         }
 
         RelDataType rowType = rowTypeBuilder.build();
 
         TableDescriptor tableDescriptor = new TestTableDescriptor(IgniteDistributions::single, rowType);
 
-        IgniteIndex schemaIndex = new IgniteIndex(index);
+        IgniteIndex schemaIndex = new IgniteIndex(
+                indexDescriptor.id(),
+                indexDescriptor.name(),
+                indexDescriptor instanceof CatalogHashIndexDescriptor ? IgniteIndex.Type.HASH : IgniteIndex.Type.SORTED,
+                tableDescriptor.distribution(),
+                TraitUtils.createIndexCollation(indexDescriptor, tableDescriptor)
+        );
+
         RowFactory<Object[]> rowFactory = ctx.rowHandler().factory(ctx.getTypeFactory(), rowType);
         SingleRangeIterable<Object[]> conditions = new SingleRangeIterable<>(new Object[]{}, null, false, false);
         List<PartitionWithTerm> partitions = scannableTable.getPartitions();
 
-        return new IndexScanNode<>(ctx, rowFactory, schemaIndex, scannableTable, tableDescriptor, partitions,
+        return new IndexScanNode<>(indexDescriptor.id(), ctx, rowFactory, schemaIndex, scannableTable, tableDescriptor, partitions,
                 comparator, conditions, null, null, null);
     }
 
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/schema/SqlSchemaManagerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/schema/SqlSchemaManagerTest.java
deleted file mode 100644
index 6061fbf5d0..0000000000
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/schema/SqlSchemaManagerTest.java
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.sql.engine.exec.schema;
-
-import static java.util.concurrent.CompletableFuture.allOf;
-import static java.util.concurrent.CompletableFuture.completedFuture;
-import static org.apache.ignite.internal.testframework.IgniteTestUtils.await;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertInstanceOf;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNotSame;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertSame;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.function.Consumer;
-import java.util.function.LongFunction;
-import org.apache.calcite.schema.SchemaPlus;
-import org.apache.calcite.schema.Table;
-import org.apache.ignite.internal.index.Index;
-import org.apache.ignite.internal.index.IndexDescriptor;
-import org.apache.ignite.internal.schema.Column;
-import org.apache.ignite.internal.schema.NativeTypes;
-import org.apache.ignite.internal.schema.SchemaDescriptor;
-import org.apache.ignite.internal.schema.SchemaManager;
-import org.apache.ignite.internal.schema.registry.SchemaRegistryImpl;
-import org.apache.ignite.internal.sql.engine.planner.AbstractPlannerTest.TestHashIndex;
-import org.apache.ignite.internal.sql.engine.schema.IgniteIndex;
-import org.apache.ignite.internal.sql.engine.schema.IgniteSchema;
-import org.apache.ignite.internal.sql.engine.schema.IgniteTable;
-import org.apache.ignite.internal.sql.engine.schema.IgniteTableImpl;
-import org.apache.ignite.internal.sql.engine.schema.SqlSchemaManagerImpl;
-import org.apache.ignite.internal.table.InternalTable;
-import org.apache.ignite.internal.table.TableImpl;
-import org.apache.ignite.internal.table.distributed.TableManager;
-import org.apache.ignite.internal.util.IgniteSpinBusyLock;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.junit.jupiter.MockitoExtension;
-
-/**
- * Tests to verify {@link SqlSchemaManagerImpl}.
- */
-@ExtendWith(MockitoExtension.class)
-public class SqlSchemaManagerTest {
-    private final int tableId = 1;
-
-    private final int indexId = 2;
-
-    private final SchemaDescriptor schemaDescriptor = new SchemaDescriptor(
-            1,
-            new Column[]{new Column(0, "ID", NativeTypes.INT64, false)},
-            new Column[]{new Column(1, "VAL", NativeTypes.INT64, false)}
-    );
-
-    @Mock
-    private TableManager tableManager;
-
-    @Mock
-    private SchemaManager schemaManager;
-
-    @Mock
-    private TableImpl table;
-
-    @Mock
-    private Index<IndexDescriptor> index;
-
-    @Mock
-    private SchemaRegistryImpl schemaRegistry;
-
-    private SqlSchemaManagerImpl sqlSchemaManager;
-
-    private TestRevisionRegister testRevisionRegister;
-
-    /** Busy lock for stop synchronisation. */
-    private final IgniteSpinBusyLock busyLock = new IgniteSpinBusyLock();
-
-    @BeforeEach
-    public void setup() {
-        Mockito.reset(tableManager);
-
-        when(tableManager.tableAsync(anyLong(), eq(tableId))).thenReturn(completedFuture(table));
-
-        testRevisionRegister = new TestRevisionRegister();
-
-        sqlSchemaManager = new SqlSchemaManagerImpl(
-                tableManager,
-                schemaManager,
-                testRevisionRegister,
-                busyLock
-        );
-
-        testRevisionRegister.moveForward();
-    }
-
-    @Test
-    public void testOnTableDroppedHandler() {
-        InternalTable mock = mock(InternalTable.class);
-        when(mock.tableId()).thenReturn(tableId);
-        when(mock.name()).thenReturn("T");
-
-        when(table.internalTable()).thenReturn(mock);
-        when(table.tableId()).thenReturn(tableId);
-        when(schemaRegistry.schema()).thenReturn(schemaDescriptor);
-        when(schemaRegistry.lastSchemaVersion()).thenReturn(schemaDescriptor.version());
-
-        when(schemaManager.schemaRegistry(anyLong(), anyInt())).thenReturn(completedFuture(schemaRegistry));
-
-        sqlSchemaManager.onTableCreated("PUBLIC", tableId, testRevisionRegister.actualToken() + 1);
-        testRevisionRegister.moveForward();
-
-        TestHashIndex testHashIndex = TestHashIndex.create(List.of("ID"), "pk_idx", tableId);
-
-        sqlSchemaManager.onIndexCreated(
-                testHashIndex.tableId(),
-                testHashIndex.id(),
-                testHashIndex.descriptor(),
-                testRevisionRegister.actualToken() + 1
-        );
-
-        testRevisionRegister.moveForward();
-
-        Table schemaTable = sqlSchemaManager.schema("PUBLIC").getTable("T");
-
-        assertNotNull(schemaTable);
-        IgniteTableImpl igniteTable = assertInstanceOf(IgniteTableImpl.class, schemaTable);
-        assertEquals(tableId, igniteTable.id());
-
-        sqlSchemaManager.onTableDropped("PUBLIC", tableId, testRevisionRegister.actualToken() + 1);
-        testRevisionRegister.moveForward();
-
-        assertNull(sqlSchemaManager.schema("PUBLIC").getTable("T"));
-    }
-
-    @Test
-    public void testIndexEventHandler() {
-        InternalTable mock = mock(InternalTable.class);
-        when(mock.tableId()).thenReturn(tableId);
-        when(mock.name()).thenReturn("T");
-
-        when(table.internalTable()).thenReturn(mock);
-        when(table.tableId()).thenReturn(tableId);
-        when(schemaRegistry.schema()).thenReturn(schemaDescriptor);
-        when(schemaRegistry.lastSchemaVersion()).thenReturn(schemaDescriptor.version());
-        when(schemaManager.schemaRegistry(anyLong(), anyInt())).thenReturn(completedFuture(schemaRegistry));
-
-        sqlSchemaManager.onTableCreated("PUBLIC", tableId, testRevisionRegister.actualToken() + 1);
-        testRevisionRegister.moveForward();
-
-        TestHashIndex testHashIndex = TestHashIndex.create(List.of("ID"), "pk_idx", tableId);
-
-        sqlSchemaManager.onIndexCreated(
-                testHashIndex.tableId(),
-                testHashIndex.id(),
-                testHashIndex.descriptor(),
-                testRevisionRegister.actualToken() + 1
-        );
-
-        testRevisionRegister.moveForward();
-
-        assertEquals(1, ((IgniteTableImpl) sqlSchemaManager.schema("PUBLIC").getTable("T")).indexes().size());
-
-        IndexDescriptor descMock = mock(IndexDescriptor.class);
-        when(descMock.columns()).thenReturn(List.of());
-        when(descMock.name()).thenReturn("PUBLIC.I");
-
-        sqlSchemaManager.onIndexCreated(tableId, indexId, descMock, testRevisionRegister.actualToken() + 1);
-
-        testRevisionRegister.moveForward();
-
-        IgniteSchema schema = sqlSchemaManager.schema("PUBLIC").unwrap(IgniteSchema.class);
-        Table schemaTable = schema.getTable("T");
-        IgniteIndex igniteIndex = schema.index(indexId);
-
-        assertNotNull(igniteIndex);
-
-        IgniteTableImpl igniteTable = assertInstanceOf(IgniteTableImpl.class, schemaTable);
-
-        assertEquals(igniteTable.id(), igniteIndex.tableId());
-        assertSame(igniteIndex, igniteTable.indexes().get("PUBLIC.I"));
-
-        sqlSchemaManager.onIndexDropped("PUBLIC", igniteTable.id(), indexId, testRevisionRegister.actualToken() + 1);
-        testRevisionRegister.moveForward();
-
-        assertNull(sqlSchemaManager.schema("PUBLIC").unwrap(IgniteSchema.class).index(indexId));
-
-        verifyNoMoreInteractions(tableManager);
-    }
-
-
-    @Test
-    public void testIndexEventsProcessed() {
-        InternalTable mock = mock(InternalTable.class);
-        when(mock.tableId()).thenReturn(tableId);
-        when(mock.name()).thenReturn("T");
-
-        when(table.internalTable()).thenReturn(mock);
-        when(table.tableId()).thenReturn(tableId);
-        when(schemaRegistry.schema()).thenReturn(schemaDescriptor);
-        when(schemaRegistry.lastSchemaVersion()).thenReturn(schemaDescriptor.version());
-        when(schemaManager.schemaRegistry(anyLong(), anyInt())).thenReturn(completedFuture(schemaRegistry));
-
-        sqlSchemaManager.onTableCreated("PUBLIC", table.tableId(), testRevisionRegister.actualToken() + 1);
-        testRevisionRegister.moveForward();
-
-        TestHashIndex testHashIndex = TestHashIndex.create(List.of("ID"), "pk_idx", tableId);
-
-        sqlSchemaManager.onIndexCreated(
-                testHashIndex.tableId(),
-                testHashIndex.id(),
-                testHashIndex.descriptor(),
-                testRevisionRegister.actualToken() + 1
-        );
-
-        testRevisionRegister.moveForward();
-
-        String idxName = "I";
-
-        IndexDescriptor descMock = mock(IndexDescriptor.class);
-        when(descMock.columns()).thenReturn(List.of());
-        when(descMock.name()).thenReturn(idxName);
-
-        {
-            SchemaPlus schema1 = sqlSchemaManager.schema("PUBLIC");
-
-            sqlSchemaManager.onIndexCreated(tableId, indexId, descMock, testRevisionRegister.actualToken() + 1);
-            testRevisionRegister.moveForward();
-
-            SchemaPlus schema2 = sqlSchemaManager.schema("PUBLIC");
-
-            // Validate schema snapshot.
-            assertNotSame(schema1, schema2);
-            assertNotSame(schema1.getTable("T"), schema2.getTable("T"));
-
-            assertNull(schema1.unwrap(IgniteSchema.class).index(indexId));
-            assertNotNull(schema2.unwrap(IgniteSchema.class).index(indexId));
-
-            assertNull(((IgniteTable) schema1.getTable("T")).getIndex(idxName));
-            assertNotNull(((IgniteTable) schema2.getTable("T")).getIndex(idxName));
-        }
-        {
-            sqlSchemaManager.onIndexDropped("PUBLIC", table.tableId(), indexId, testRevisionRegister.actualToken() + 1);
-            SchemaPlus schema1 = sqlSchemaManager.schema("PUBLIC");
-            testRevisionRegister.moveForward();
-
-            SchemaPlus schema2 = sqlSchemaManager.schema("PUBLIC");
-
-            // Validate schema snapshot.
-            assertNotSame(schema1, schema2);
-            assertNotSame(schema1.getTable("T"), schema2.getTable("T"));
-
-            assertNotNull(schema1.unwrap(IgniteSchema.class).index(indexId));
-            assertNull(schema2.unwrap(IgniteSchema.class).index(indexId));
-
-            assertNull(((IgniteTable) schema2.getTable("T")).getIndex(idxName));
-            assertNotNull(((IgniteTable) schema1.getTable("T")).getIndex(idxName));
-        }
-
-        verifyNoMoreInteractions(tableManager);
-    }
-
-    /**
-     * Test revision register.
-     */
-    private static class TestRevisionRegister implements Consumer<LongFunction<CompletableFuture<?>>> {
-        AtomicLong token = new AtomicLong(-1);
-
-        /** Revision consumer. */
-        private LongFunction<CompletableFuture<?>> moveRevision;
-
-        /**
-         * Moves forward token.
-         */
-        void moveForward() {
-            await(moveRevision.apply(token.incrementAndGet()));
-        }
-
-        /**
-         * Gets an actual token.
-         *
-         * @return Actual token.
-         */
-        long actualToken() {
-            return token.get();
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public void accept(LongFunction<CompletableFuture<?>> function) {
-            if (moveRevision == null) {
-                moveRevision = function;
-            } else {
-                LongFunction<CompletableFuture<?>> old = moveRevision;
-
-                moveRevision = rev -> allOf(
-                        old.apply(rev),
-                        function.apply(rev)
-                );
-            }
-        }
-    }
-}
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 1673721150..f705763bb0 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
@@ -31,7 +31,13 @@ import java.util.Set;
 import java.util.UUID;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.RelFieldCollation;
+import org.apache.calcite.rel.RelFieldCollation.Direction;
+import org.apache.calcite.rel.RelFieldCollation.NullDirection;
 import org.apache.calcite.schema.Table;
+import org.apache.ignite.internal.catalog.descriptors.CatalogColumnCollation;
 import org.apache.ignite.internal.schema.NativeType;
 import org.apache.ignite.internal.sql.engine.exec.ArrayRowHandler;
 import org.apache.ignite.internal.sql.engine.exec.ExecutionContext;
@@ -42,9 +48,9 @@ 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.IgniteIndex.Type;
 import org.apache.ignite.internal.sql.engine.schema.IgniteSchema;
+import org.apache.ignite.internal.sql.engine.schema.TableDescriptor;
 import org.apache.ignite.internal.sql.engine.schema.TableDescriptorImpl;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
 import org.apache.ignite.internal.sql.engine.util.BaseQueryContext;
@@ -431,7 +437,7 @@ public class TestBuilders {
                     size
             );
 
-            indexBuilders.stream().map(AbstractIndexBuilderImpl::build).forEach(testTable::addIndex);
+            indexBuilders.stream().map(idxBuilder -> idxBuilder.build(testTable)).forEach(testTable::addIndex);
 
             return testTable;
         }
@@ -479,7 +485,7 @@ public class TestBuilders {
         private TestTable build() {
             TestTable testTable = new TestTable(new TableDescriptorImpl(columns, distribution), name, dataProviders, size);
 
-            indexBuilders.forEach(idx -> testTable.addIndex(idx.build()));
+            indexBuilders.forEach(idx -> testTable.addIndex(idx.build(testTable)));
 
             return testTable;
         }
@@ -509,7 +515,7 @@ public class TestBuilders {
 
         /** {@inheritDoc} */
         @Override
-        public TestIndex build() {
+        public TestIndex build(TestTable table) {
             if (name == null) {
                 throw new IllegalArgumentException("Name is not specified");
             }
@@ -522,7 +528,9 @@ public class TestBuilders {
                 throw new IllegalArgumentException("Collation must be specified for each of columns.");
             }
 
-            return new TestIndex(name, Type.SORTED, columns, collations, dataProviders);
+            RelCollation relCollation = sortedIndexCollation(columns, collations, table.descriptor());
+
+            return new TestIndex(name, Type.SORTED, table.distribution(), relCollation, dataProviders);
         }
     }
 
@@ -549,7 +557,7 @@ public class TestBuilders {
 
         /** {@inheritDoc} */
         @Override
-        public TestIndex build() {
+        public TestIndex build(TestTable table) {
             if (name == null) {
                 throw new IllegalArgumentException("Name is not specified");
             }
@@ -560,7 +568,10 @@ public class TestBuilders {
 
             assert collations == null : "Collation is not supported.";
 
-            return new TestIndex(name, Type.HASH, columns, null, dataProviders);
+
+            RelCollation relCollation = hashIndexCollation(columns, table.descriptor());
+
+            return new TestIndex(name, Type.SORTED, table.distribution(), relCollation, dataProviders);
         }
     }
 
@@ -587,10 +598,12 @@ public class TestBuilders {
         }
 
         @Override
-        TestIndex build() {
+        TestIndex build(TestTable table) {
             assert collations.size() == columns.size();
 
-            return TestIndex.createSorted(name, columns, collations, dataProviders);
+            RelCollation relCollation = sortedIndexCollation(columns, collations, table.descriptor());
+
+            return new TestIndex(name, Type.SORTED, table.distribution(), relCollation, dataProviders);
         }
     }
 
@@ -617,10 +630,12 @@ public class TestBuilders {
         }
 
         @Override
-        TestIndex build() {
+        TestIndex build(TestTable table) {
             assert collations == null;
 
-            return TestIndex.createHash(name, columns, dataProviders);
+            RelCollation relCollation = hashIndexCollation(columns, table.descriptor());
+
+            return new TestIndex(name, Type.SORTED, table.distribution(), relCollation, dataProviders);
         }
     }
 
@@ -700,7 +715,7 @@ public class TestBuilders {
     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;
+        protected List<CatalogColumnCollation> collations;
 
         /** {@inheritDoc} */
         @Override
@@ -712,7 +727,7 @@ public class TestBuilders {
 
         /** {@inheritDoc} */
         @Override
-        public ChildT addColumn(String columnName, Collation collation) {
+        public ChildT addColumn(String columnName, CatalogColumnCollation collation) {
             if (collations == null) {
                 collations = new ArrayList<>();
             }
@@ -723,7 +738,7 @@ public class TestBuilders {
             return self();
         }
 
-        abstract TestIndex build();
+        abstract TestIndex build(TestTable table);
     }
 
     private abstract static class AbstractDataSourceBuilderImpl<ChildT> {
@@ -802,7 +817,7 @@ public class TestBuilders {
      */
     private interface SortedIndexBuilderBase<ChildT> extends IndexBuilderBase<ChildT> {
         /** Adds a column with specified collation to the index. */
-        ChildT addColumn(String columnName, Collation collation);
+        ChildT addColumn(String columnName, CatalogColumnCollation collation);
     }
 
     /**
@@ -848,4 +863,48 @@ public class TestBuilders {
          */
         ParentT end();
     }
+
+    private static RelCollation sortedIndexCollation(List<String> columns, List<CatalogColumnCollation> collations, TableDescriptor table) {
+        assert collations.size() == columns.size();
+
+        List<RelFieldCollation> fieldCollations = new ArrayList<>(columns.size());
+        for (int i = 0; i < columns.size(); i++) {
+            ColumnDescriptor columnDesc = table.columnDescriptor(columns.get(i));
+            int fieldIndex = columnDesc.logicalIndex();
+
+            RelFieldCollation fieldCollation;
+            switch (collations.get(i)) {
+                case ASC_NULLS_FIRST:
+                    fieldCollation = new RelFieldCollation(fieldIndex, Direction.ASCENDING, NullDirection.FIRST);
+                    break;
+                case ASC_NULLS_LAST:
+                    fieldCollation = new RelFieldCollation(fieldIndex, Direction.ASCENDING, NullDirection.LAST);
+                    break;
+                case DESC_NULLS_FIRST:
+                    fieldCollation = new RelFieldCollation(fieldIndex, Direction.DESCENDING, NullDirection.FIRST);
+                    break;
+                case DESC_NULLS_LAST:
+                    fieldCollation = new RelFieldCollation(fieldIndex, Direction.DESCENDING, NullDirection.LAST);
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unexpected collation: " + collations.get(i));
+            }
+
+            fieldCollations.add(fieldCollation);
+        }
+
+        return RelCollations.of(fieldCollations);
+    }
+
+    private static RelCollation hashIndexCollation(List<String> columns, TableDescriptor tableDescriptor) {
+        List<RelFieldCollation> fieldCollations = new ArrayList<>(columns.size());
+
+        for (String columnName : columns) {
+            ColumnDescriptor columnDesc = tableDescriptor.columnDescriptor(columnName);
+
+            fieldCollations.add(new RelFieldCollation(columnDesc.logicalIndex(), Direction.CLUSTERED, NullDirection.UNSPECIFIED));
+        }
+
+        return RelCollations.of(fieldCollations);
+    }
 }
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
index f9da45677b..aaae987d4a 100644
--- 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
@@ -19,11 +19,11 @@ 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.calcite.rel.RelCollation;
 import org.apache.ignite.internal.sql.engine.schema.IgniteIndex;
-import org.jetbrains.annotations.Nullable;
+import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
 
 /**
  * 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
@@ -34,48 +34,34 @@ public class TestIndex extends IgniteIndex {
             "DataProvider is not configured [index={}, node={}]";
 
     /** Factory method for creating hash-index. */
-    static TestIndex createHash(String name, List<String> indexedColumns, Map<String, DataProvider<?>> dataProviders) {
-        return new TestIndex(name, Type.HASH, indexedColumns, null, dataProviders);
+    static TestIndex createHash(String name, IgniteDistribution distribution, RelCollation collations,
+            Map<String, DataProvider<?>> dataProviders) {
+        return new TestIndex(name, Type.HASH, distribution, collations, dataProviders);
     }
 
     /** Factory method for creating sorted-index. */
-    static TestIndex createSorted(String name, List<String> columns, List<Collation> collations,
+    static TestIndex createSorted(String name, IgniteDistribution distribution, RelCollation collations,
             Map<String, DataProvider<?>> dataProviders) {
-        return new TestIndex(name, Type.SORTED, columns, collations, dataProviders);
+        return new TestIndex(name, Type.SORTED, distribution, collations, dataProviders);
     }
 
     private static final AtomicInteger ID = new AtomicInteger();
 
-    private final int id = ID.incrementAndGet();
-    private final String name;
-
     private final Map<String, DataProvider<?>> dataProviders;
 
     /** Constructor. */
     TestIndex(
             String name,
             Type type,
-            List<String> columns,
-            @Nullable List<Collation> collations,
+            IgniteDistribution distribution,
+            RelCollation collations,
             Map<String, DataProvider<?>> dataProviders
     ) {
-        super(type, columns, collations);
+        super(ID.incrementAndGet(), name, type, distribution, collations);
 
-        this.name = name;
         this.dataProviders = dataProviders;
     }
 
-    /** Returns an id of the index. */
-    public int id() {
-        return id;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public String name() {
-        return name;
-    }
-
     /**
      * Returns the data provider for the given node.
      *
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractAggregatePlannerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractAggregatePlannerTest.java
index fbff6b9e03..f231d66dbc 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractAggregatePlannerTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractAggregatePlannerTest.java
@@ -39,6 +39,7 @@ import org.apache.ignite.internal.sql.engine.framework.TestBuilders;
 import org.apache.ignite.internal.sql.engine.rel.IgniteAggregate;
 import org.apache.ignite.internal.sql.engine.rel.agg.IgniteReduceAggregateBase;
 import org.apache.ignite.internal.sql.engine.schema.IgniteIndex;
+import org.apache.ignite.internal.sql.engine.schema.IgniteIndex.Type;
 import org.apache.ignite.internal.sql.engine.schema.IgniteSchema;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
 import org.apache.ignite.internal.sql.engine.trait.TraitUtils;
@@ -556,7 +557,7 @@ public abstract class AbstractAggregatePlannerTest extends AbstractPlannerTest {
 
     private static IgniteIndex createIndex(org.apache.ignite.internal.sql.engine.framework.TestTable tbl, String name,
             RelCollation collation) {
-        return new IgniteIndex(TestSortedIndex.create(collation, name, tbl));
+        return new IgniteIndex(1, name, Type.SORTED, tbl.distribution(), collation);
     }
 
     <T extends RelNode> Predicate<T> hasAggregate() {
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractPlannerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractPlannerTest.java
index d4ea5e47b1..6e7f88ad18 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractPlannerTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractPlannerTest.java
@@ -114,6 +114,7 @@ import org.apache.ignite.internal.sql.engine.rel.logical.IgniteLogicalTableScan;
 import org.apache.ignite.internal.sql.engine.schema.ColumnDescriptor;
 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.Type;
 import org.apache.ignite.internal.sql.engine.schema.IgniteSchema;
 import org.apache.ignite.internal.sql.engine.schema.IgniteTable;
 import org.apache.ignite.internal.sql.engine.schema.TableDescriptor;
@@ -1002,7 +1003,7 @@ public abstract class AbstractPlannerTest extends IgniteAbstractTest {
          * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
          */
         public TestTable addIndex(RelCollation collation, String name) {
-            indexes.put(name, new IgniteIndex(TestSortedIndex.create(collation, name, this)));
+            indexes.put(name, new IgniteIndex(1, name, Type.SORTED, distribution(), collation));
 
             return this;
         }
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/HashIndexPlannerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/HashIndexPlannerTest.java
index c84fa99d77..2c6081575f 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/HashIndexPlannerTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/HashIndexPlannerTest.java
@@ -22,14 +22,12 @@ import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.nullValue;
 
-import java.util.List;
 import org.apache.calcite.plan.RelOptPlanner.CannotPlanException;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.rel.RelNode;
 import org.apache.ignite.internal.sql.engine.rel.IgniteIndexScan;
 import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
 import org.apache.ignite.internal.sql.engine.rel.IgniteTableScan;
-import org.apache.ignite.internal.sql.engine.schema.IgniteIndex;
 import org.apache.ignite.internal.sql.engine.schema.IgniteSchema;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
 import org.apache.ignite.internal.testframework.IgniteTestUtils;
@@ -113,7 +111,7 @@ public class HashIndexPlannerTest extends AbstractPlannerTest {
                 "VAL", Integer.class
         );
 
-        tbl.addIndex(new IgniteIndex(TestHashIndex.create(List.of("VAL"), indexName)));
+        tbl.addIndex(indexName, 1);
 
         return createSchema(tbl);
     }
@@ -138,7 +136,7 @@ public class HashIndexPlannerTest extends AbstractPlannerTest {
                 "VAL1", Integer.class
         );
 
-        rightTable.addIndex(new IgniteIndex(TestHashIndex.create(List.of("VAL0", "VAL1"), indexName)));
+        rightTable.addIndex(indexName, 1, 2);
 
         IgniteSchema schema = createSchema(leftTable, rightTable);
 
@@ -174,7 +172,7 @@ public class HashIndexPlannerTest extends AbstractPlannerTest {
                 "VAL1", Integer.class
         );
 
-        rightTable.addIndex(new IgniteIndex(TestHashIndex.create(List.of("VAL0", "VAL1"), indexName)));
+        rightTable.addIndex(indexName, 1, 2);
 
         IgniteSchema schema = createSchema(leftTable, rightTable);
 
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ImplicitCastsTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ImplicitCastsTest.java
index 8f4f1e213f..50de9383be 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ImplicitCastsTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ImplicitCastsTest.java
@@ -44,6 +44,7 @@ import org.apache.ignite.internal.sql.engine.rel.IgniteMergeJoin;
 import org.apache.ignite.internal.sql.engine.rel.IgniteNestedLoopJoin;
 import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
 import org.apache.ignite.internal.sql.engine.rel.IgniteTableScan;
+import org.apache.ignite.internal.sql.engine.schema.IgniteIndex;
 import org.apache.ignite.internal.sql.engine.schema.IgniteIndex.Type;
 import org.apache.ignite.internal.sql.engine.schema.IgniteSchema;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
@@ -271,8 +272,8 @@ public class ImplicitCastsTest extends AbstractPlannerTest {
             }
         }
 
-        Stream<Arguments> s1 = filterTypes().filter(filter).map(new AddIndexType(Type.SORTED));
-        Stream<Arguments> s2 = filterTypes().filter(filter).map(new AddIndexType(Type.HASH));
+        Stream<Arguments> s1 = filterTypes().filter(filter).map(new AddIndexType(IgniteIndex.Type.SORTED));
+        Stream<Arguments> s2 = filterTypes().filter(filter).map(new AddIndexType(IgniteIndex.Type.HASH));
 
         return Stream.concat(s1, s2);
     }
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/JoinColocationPlannerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/JoinColocationPlannerTest.java
index b8decb7a1f..d49b5be243 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/JoinColocationPlannerTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/JoinColocationPlannerTest.java
@@ -26,7 +26,6 @@ import static org.hamcrest.Matchers.hasSize;
 
 import java.util.List;
 import org.apache.calcite.plan.RelOptUtil;
-import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.util.ImmutableIntList;
 import org.apache.ignite.internal.sql.engine.rel.IgniteExchange;
@@ -35,7 +34,6 @@ import org.apache.ignite.internal.sql.engine.rel.IgniteMergeJoin;
 import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
 import org.apache.ignite.internal.sql.engine.rel.IgniteSort;
 import org.apache.ignite.internal.sql.engine.rel.IgniteTableScan;
-import org.apache.ignite.internal.sql.engine.schema.IgniteIndex;
 import org.apache.ignite.internal.sql.engine.schema.IgniteSchema;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
 import org.junit.jupiter.api.Test;
@@ -56,7 +54,7 @@ public class JoinColocationPlannerTest extends AbstractPlannerTest {
                 "VAL", String.class
         );
 
-        tbl.addIndex(new IgniteIndex(TestSortedIndex.create(RelCollations.of(0), "PK", tbl)));
+        tbl.addIndex("PK", 0);
 
         IgniteSchema schema = createSchema(tbl);
 
@@ -89,7 +87,7 @@ public class JoinColocationPlannerTest extends AbstractPlannerTest {
                 "VAL", String.class
         );
 
-        tbl.addIndex(new IgniteIndex(TestSortedIndex.create(RelCollations.of(ImmutableIntList.of(0, 1)), "PK", tbl)));
+        tbl.addIndex("PK", 0, 1);
 
         IgniteSchema schema = createSchema(tbl);
 
@@ -125,7 +123,7 @@ public class JoinColocationPlannerTest extends AbstractPlannerTest {
                 "VAL", String.class
         );
 
-        complexTbl.addIndex(new IgniteIndex(TestSortedIndex.create(RelCollations.of(ImmutableIntList.of(0, 1)), "PK", complexTbl)));
+        complexTbl.addIndex("PK", 0, 1);
 
         TestTable simpleTbl = createTable(
                 "SIMPLE_TBL",
@@ -136,7 +134,7 @@ public class JoinColocationPlannerTest extends AbstractPlannerTest {
                 "VAL", String.class
         );
 
-        simpleTbl.addIndex(new IgniteIndex(TestSortedIndex.create(RelCollations.of(0), "PK", simpleTbl)));
+        simpleTbl.addIndex("PK", 0);
 
         IgniteSchema schema = createSchema(complexTbl, simpleTbl);
 
@@ -179,8 +177,7 @@ public class JoinColocationPlannerTest extends AbstractPlannerTest {
                 "VAL", String.class
         );
 
-        complexTblDirect.addIndex(new IgniteIndex(
-                TestSortedIndex.create(RelCollations.of(ImmutableIntList.of(0, 1)), "PK", complexTblDirect)));
+        complexTblDirect.addIndex("PK", 0, 1);
 
         TestTable complexTblIndirect = createTable(
                 "COMPLEX_TBL_INDIRECT",
@@ -191,8 +188,7 @@ public class JoinColocationPlannerTest extends AbstractPlannerTest {
                 "VAL", String.class
         );
 
-        complexTblIndirect.addIndex(new IgniteIndex(
-                TestSortedIndex.create(RelCollations.of(ImmutableIntList.of(0, 1)), "PK", complexTblIndirect)));
+        complexTblIndirect.addIndex("PK", 0, 1);
 
         IgniteSchema schema = createSchema(complexTblDirect, complexTblIndirect);
 
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/PlannerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/PlannerTest.java
index 3499c20e0b..25e967974b 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/PlannerTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/PlannerTest.java
@@ -31,7 +31,6 @@ import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Predicate;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.plan.RelTraitSet;
-import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelRoot;
 import org.apache.calcite.rel.RelVisitor;
@@ -40,7 +39,6 @@ import org.apache.calcite.rel.hint.HintStrategyTable;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.schema.SchemaPlus;
 import org.apache.calcite.sql.SqlNode;
-import org.apache.calcite.util.ImmutableIntList;
 import org.apache.calcite.util.Util;
 import org.apache.ignite.internal.sql.engine.metadata.ColocationGroup;
 import org.apache.ignite.internal.sql.engine.metadata.MappingService;
@@ -767,7 +765,7 @@ public class PlannerTest extends AbstractPlannerTest {
             }
         };
 
-        emp.addIndex(new IgniteIndex(TestSortedIndex.create(RelCollations.of(ImmutableIntList.of(1, 2)), "emp_idx", emp)));
+        emp.addIndex("emp_idx", 1, 2);
 
         TestTable dept = new TestTable("DEPT",
                 new RelDataTypeFactory.Builder(f)
@@ -781,7 +779,7 @@ public class PlannerTest extends AbstractPlannerTest {
             }
         };
 
-        dept.addIndex(new IgniteIndex(TestSortedIndex.create(RelCollations.of(ImmutableIntList.of(1, 0)), "dep_idx", dept)));
+        dept.addIndex("dep_idx", 1, 0);
 
         IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
 
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java
index 066bc211c0..8674e300b1 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ProjectFilterScanMergePlannerTest.java
@@ -31,6 +31,7 @@ import org.apache.ignite.internal.sql.engine.rel.IgniteAggregate;
 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.IgniteIndex;
+import org.apache.ignite.internal.sql.engine.schema.IgniteIndex.Type;
 import org.apache.ignite.internal.sql.engine.schema.IgniteSchema;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
 import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
@@ -91,7 +92,7 @@ public class ProjectFilterScanMergePlannerTest extends AbstractPlannerTest {
     public void testProjectFilterMergeSortedIndex() throws Exception {
         // Test project and filter merge into index scan.
         TestTable tbl = ((TestTable) publicSchema.getTable("TBL"));
-        tbl.addIndex(new IgniteIndex(TestSortedIndex.create(RelCollations.of(2), "IDX_C", tbl)));
+        tbl.addIndex("IDX_C", 2);
 
         // Without index condition shift.
         assertPlan("SELECT a, b FROM tbl WHERE c = 0", publicSchema, isInstanceOf(IgniteIndexScan.class)
@@ -118,7 +119,7 @@ public class ProjectFilterScanMergePlannerTest extends AbstractPlannerTest {
     public void testProjectFilterMergeHashIndex() throws Exception {
         // Test project and filter merge into index scan.
         TestTable tbl = ((TestTable) publicSchema.getTable("TBL"));
-        tbl.addIndex(new IgniteIndex(TestHashIndex.create(List.of("c"), "IDX_C")));
+        tbl.addIndex(new IgniteIndex(1, "IDX_C", Type.HASH, tbl.distribution(), RelCollations.of(2)));
 
         // Without index condition shift.
         assertPlan("SELECT a, b FROM tbl WHERE c = 0", publicSchema, isInstanceOf(IgniteIndexScan.class)
@@ -145,7 +146,7 @@ public class ProjectFilterScanMergePlannerTest extends AbstractPlannerTest {
     public void testIdentityFilterMergeIndex() throws Exception {
         // Test project and filter merge into index scan.
         TestTable tbl = ((TestTable) publicSchema.getTable("TBL"));
-        tbl.addIndex(new IgniteIndex(TestSortedIndex.create(RelCollations.of(2), "IDX_C", tbl)));
+        tbl.addIndex("IDX_C", 2);
 
         // Without index condition shift.
         assertPlan("SELECT a, b, c FROM tbl WHERE c = 0", publicSchema, isInstanceOf(IgniteIndexScan.class)
@@ -170,7 +171,7 @@ public class ProjectFilterScanMergePlannerTest extends AbstractPlannerTest {
     public void testIdentityFilterMergeHashIndex() throws Exception {
         // Test project and filter merge into index scan.
         TestTable tbl = ((TestTable) publicSchema.getTable("TBL"));
-        tbl.addIndex(new IgniteIndex(TestHashIndex.create(List.of("c"), "IDX_C")));
+        tbl.addIndex(new IgniteIndex(1, "IDX_C", Type.HASH, tbl.distribution(), RelCollations.of(2)));
 
         // Without index condition shift.
         assertPlan("SELECT a, b, c FROM tbl WHERE c = 0", publicSchema, isInstanceOf(IgniteIndexScan.class)
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/schema/CatalogSqlSchemaManagerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/schema/CatalogSqlSchemaManagerTest.java
index aecb8d8eaa..894a6a60ba 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/schema/CatalogSqlSchemaManagerTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/schema/CatalogSqlSchemaManagerTest.java
@@ -57,7 +57,6 @@ import org.apache.ignite.internal.catalog.descriptors.CatalogTableColumnDescript
 import org.apache.ignite.internal.catalog.descriptors.CatalogTableDescriptor;
 import org.apache.ignite.internal.schema.DefaultValueGenerator;
 import org.apache.ignite.internal.schema.NativeType;
-import org.apache.ignite.internal.sql.engine.schema.IgniteIndex.Type;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
 import org.apache.ignite.internal.sql.engine.util.TypeUtils;
@@ -375,10 +374,10 @@ public class CatalogSqlSchemaManagerTest {
         IgniteSchemaTable table = (IgniteSchemaTable) schema.getTable(testTable.name);
         assertNotNull(table);
 
-        IgniteSchemaIndex testIdx = table.getIndexes().get(testIndex.name);
+        IgniteIndex testIdx = table.getIndex(testIndex.name);
 
         assertEquals(testIndex.name, testIdx.name());
-        assertEquals(Type.HASH, testIdx.type());
+        assertEquals(IgniteIndex.Type.HASH, testIdx.type());
         assertEquals(RelCollations.of(
                 new RelFieldCollation(0, Direction.CLUSTERED, NullDirection.UNSPECIFIED),
                 new RelFieldCollation(1, Direction.CLUSTERED, NullDirection.UNSPECIFIED)
@@ -419,10 +418,10 @@ public class CatalogSqlSchemaManagerTest {
         IgniteSchemaTable table = (IgniteSchemaTable) schema.getTable(testTable.name);
         assertNotNull(table);
 
-        IgniteSchemaIndex testIdx = table.getIndexes().get(testIndex.name);
+        IgniteIndex testIdx = table.getIndex(testIndex.name);
 
         assertEquals(testIndex.name, testIdx.name());
-        assertEquals(Type.SORTED, testIdx.type());
+        assertEquals(IgniteIndex.Type.SORTED, testIdx.type());
         assertEquals(RelCollations.of(
                 new RelFieldCollation(0, Direction.ASCENDING, NullDirection.LAST),
                 new RelFieldCollation(1, Direction.ASCENDING, NullDirection.FIRST),