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 2025/01/09 12:03:24 UTC

(ignite-3) 01/01: Fix byte[] comparison for byte strings.

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

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

commit 64a5127c21379ab0a0d96392a5b104ab20b64b5b
Author: amashenkov <an...@gmail.com>
AuthorDate: Thu Jan 9 14:59:17 2025 +0300

    Fix byte[] comparison for byte strings.
---
 .../internal/schema}/BinaryTupleComparator.java    | 110 ++++++++++++++-------
 .../schema}/BinaryTupleComparatorTest.java         |  53 +++++-----
 .../ignite/internal/sql/engine/util/VarBinary.java |   2 +-
 .../ignite/internal/storage/util/StorageUtils.java |  18 ++++
 .../storage/index/impl/TestSortedIndexStorage.java |   4 +-
 .../index/sorted/PageMemorySortedIndexStorage.java |   4 +-
 .../pagememory/index/sorted/SortedIndexTree.java   |   5 +-
 .../index/sorted/io/SortedIndexTreeIo.java         |   4 +-
 .../index/RocksDbBinaryTupleComparator.java        |   7 +-
 .../replicator/PartitionReplicaListener.java       |   4 +-
 10 files changed, 139 insertions(+), 72 deletions(-)

diff --git a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/BinaryTupleComparator.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryTupleComparator.java
similarity index 59%
rename from modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/BinaryTupleComparator.java
rename to modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryTupleComparator.java
index e79b86cc3b..37283abdb9 100644
--- a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/BinaryTupleComparator.java
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryTupleComparator.java
@@ -15,21 +15,22 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.storage.index;
+package org.apache.ignite.internal.schema;
 
 import static org.apache.ignite.internal.binarytuple.BinaryTupleCommon.EQUALITY_FLAG;
 import static org.apache.ignite.internal.binarytuple.BinaryTupleCommon.PREFIX_FLAG;
 import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
 
+import java.math.BigDecimal;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.Arrays;
 import java.util.Comparator;
-import java.util.List;
+import java.util.function.IntUnaryOperator;
 import org.apache.ignite.internal.binarytuple.BinaryTupleReader;
-import org.apache.ignite.internal.schema.BinaryTuple;
-import org.apache.ignite.internal.schema.BinaryTuplePrefix;
-import org.apache.ignite.internal.storage.index.StorageSortedIndexDescriptor.StorageSortedIndexColumnDescriptor;
+import org.apache.ignite.internal.catalog.descriptors.CatalogColumnCollation;
+import org.apache.ignite.internal.type.DecimalNativeType;
+import org.apache.ignite.internal.type.NativeType;
 import org.apache.ignite.internal.type.NativeTypeSpec;
 
 /**
@@ -39,14 +40,45 @@ import org.apache.ignite.internal.type.NativeTypeSpec;
  * the following logic is applied: if all N columns of the prefix match the first N columns of the tuple, they are considered equal.
  * Otherwise comparison result is determined by the first non-matching column.
  */
+@SuppressWarnings("ComparatorNotSerializable")
 public class BinaryTupleComparator implements Comparator<ByteBuffer> {
-    private final List<StorageSortedIndexColumnDescriptor> columns;
+    private final NativeType[] columnTypes;
+    private final IntUnaryOperator columnMapping;
+    private final CatalogColumnCollation[] columnCollations;
+    private final boolean reversed;
 
     /**
-     * Creates a comparator for a Sorted Index identified by the given columns descriptors.
+     * Creates BinaryTuple comparator.
+     *
+     * @param columnCollations Columns collations.
+     * @param columnMapping Mapping function defines column comparing order. Returns column index as defined in BinaryTuple schema.
+     * @param columnTypes Column types in order, which is defined in BinaryTuple schema.
      */
-    public BinaryTupleComparator(List<StorageSortedIndexColumnDescriptor> columns) {
-        this.columns = columns;
+    public BinaryTupleComparator(
+            CatalogColumnCollation[] columnCollations,
+            IntUnaryOperator columnMapping,
+            NativeType[] columnTypes
+    ) {
+        this(columnCollations, columnMapping, columnTypes, false);
+    }
+
+    private BinaryTupleComparator(
+            CatalogColumnCollation[] columnCollations,
+            IntUnaryOperator columnMapping,
+            NativeType[] columnTypes,
+            boolean reversed
+    ) {
+        this.columnMapping = columnMapping;
+        this.columnCollations = columnCollations;
+        this.columnTypes = columnTypes;
+        this.reversed = reversed;
+    }
+
+    /**
+     * Returns a comparator that imposes the reverse ordering of this comparator.
+     */
+    public BinaryTupleComparator reverse() {
+        return new BinaryTupleComparator(columnCollations, columnMapping, columnTypes, !reversed);
     }
 
     @Override
@@ -57,22 +89,36 @@ public class BinaryTupleComparator implements Comparator<ByteBuffer> {
         boolean isBuffer1Prefix = isFlagSet(buffer1, PREFIX_FLAG);
         boolean isBuffer2Prefix = isFlagSet(buffer2, PREFIX_FLAG);
 
-        int numElements = columns.size();
+        int numElements = columnTypes.length;
 
         BinaryTupleReader tuple1 = isBuffer1Prefix ? new BinaryTuplePrefix(numElements, buffer1) : new BinaryTuple(numElements, buffer1);
         BinaryTupleReader tuple2 = isBuffer2Prefix ? new BinaryTuplePrefix(numElements, buffer2) : new BinaryTuple(numElements, buffer2);
 
         int columnsToCompare = Math.min(tuple1.elementCount(), tuple2.elementCount());
 
-        assert columnsToCompare <= columns.size();
+        assert columnsToCompare <= numElements;
 
         for (int i = 0; i < columnsToCompare; i++) {
-            StorageSortedIndexColumnDescriptor columnDescriptor = columns.get(i);
+            CatalogColumnCollation collation = columnCollations[i];
+
+            int colIdx = columnMapping.applyAsInt(i);
+
+            boolean tuple1HasNull = tuple1.hasNullValue(colIdx);
+            boolean tuple2HasNull = tuple2.hasNullValue(colIdx);
 
-            int compare = compareField(tuple1, tuple2, i);
+            if (tuple1HasNull && tuple2HasNull) {
+                continue;
+            } else if (tuple1HasNull || tuple2HasNull) {
+                int nullComparison = (collation.nullsFirst() ^ reversed) ? -1 : 1;
 
-            if (compare != 0) {
-                return columnDescriptor.asc() ? compare : -compare;
+                return tuple1HasNull ? nullComparison : -nullComparison;
+            }
+
+            NativeType nativeType = columnTypes[colIdx];
+            int res = compareField(tuple1, tuple2, nativeType, colIdx);
+
+            if (res != 0) {
+                return (collation.asc() ^ reversed) ? res : -res;
             }
         }
 
@@ -91,22 +137,11 @@ public class BinaryTupleComparator implements Comparator<ByteBuffer> {
     /**
      * Compares individual fields of two tuples.
      */
-    private int compareField(BinaryTupleReader tuple1, BinaryTupleReader tuple2, int index) {
-        boolean tuple1HasNull = tuple1.hasNullValue(index);
-        boolean tuple2HasNull = tuple2.hasNullValue(index);
-
-        // TODO IGNITE-15141: Make null-order configurable.
-        if (tuple1HasNull && tuple2HasNull) {
-            return 0;
-        } else if (tuple1HasNull) {
-            return 1;
-        } else if (tuple2HasNull) {
-            return -1;
-        }
+    @SuppressWarnings("DataFlowIssue")
+    private static int compareField(BinaryTupleReader tuple1, BinaryTupleReader tuple2,
+            NativeType nativeType, int index) {
 
-        StorageSortedIndexColumnDescriptor columnDescriptor = columns.get(index);
-
-        NativeTypeSpec typeSpec = columnDescriptor.type().spec();
+        NativeTypeSpec typeSpec = nativeType.spec();
 
         switch (typeSpec) {
             case INT8:
@@ -129,7 +164,7 @@ public class BinaryTupleComparator implements Comparator<ByteBuffer> {
                 return Double.compare(tuple1.doubleValue(index), tuple2.doubleValue(index));
 
             case BYTES:
-                return Arrays.compare(tuple1.bytesValue(index), tuple2.bytesValue(index));
+                return Arrays.compareUnsigned(tuple1.bytesValue(index), tuple2.bytesValue(index));
 
             case UUID:
                 return tuple1.uuidValue(index).compareTo(tuple2.uuidValue(index));
@@ -138,7 +173,12 @@ public class BinaryTupleComparator implements Comparator<ByteBuffer> {
                 return tuple1.stringValue(index).compareTo(tuple2.stringValue(index));
 
             case DECIMAL:
-                return tuple1.decimalValue(index, Integer.MIN_VALUE).compareTo(tuple2.decimalValue(index, Integer.MIN_VALUE));
+                BigDecimal numeric1 = tuple1.decimalValue(index,
+                        ((DecimalNativeType) nativeType).scale());
+                BigDecimal numeric2 = tuple2.decimalValue(index,
+                        ((DecimalNativeType) nativeType).scale());
+
+                return numeric1.compareTo(numeric2);
 
             case TIMESTAMP:
                 return tuple1.timestampValue(index).compareTo(tuple2.timestampValue(index));
@@ -155,7 +195,7 @@ public class BinaryTupleComparator implements Comparator<ByteBuffer> {
             default:
                 throw new IllegalArgumentException(format(
                         "Unsupported column type in binary tuple comparator. [index={}, type={}]",
-                        index, columnDescriptor.type()
+                        index, typeSpec
                 ));
         }
     }
@@ -164,7 +204,7 @@ public class BinaryTupleComparator implements Comparator<ByteBuffer> {
         return (tuple.get(0) & flag) != 0;
     }
 
-    private static int equalityFlag(ByteBuffer tuple) {
-        return isFlagSet(tuple, EQUALITY_FLAG) ? 1 : -1;
+    private int equalityFlag(ByteBuffer tuple) {
+        return isFlagSet(tuple, EQUALITY_FLAG) ^ reversed ? 1 : -1;
     }
 }
diff --git a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/BinaryTupleComparatorTest.java b/modules/schema/src/test/java/org/apache/ignite/internal/schema/BinaryTupleComparatorTest.java
similarity index 89%
rename from modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/BinaryTupleComparatorTest.java
rename to modules/schema/src/test/java/org/apache/ignite/internal/schema/BinaryTupleComparatorTest.java
index 4f6cb18d18..22b8af7c9c 100644
--- a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/BinaryTupleComparatorTest.java
+++ b/modules/schema/src/test/java/org/apache/ignite/internal/schema/BinaryTupleComparatorTest.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.storage.index;
+package org.apache.ignite.internal.schema;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
@@ -33,8 +33,8 @@ import java.util.UUID;
 import org.apache.ignite.internal.binarytuple.BinaryTupleBuilder;
 import org.apache.ignite.internal.binarytuple.BinaryTupleCommon;
 import org.apache.ignite.internal.binarytuple.BinaryTuplePrefixBuilder;
+import org.apache.ignite.internal.catalog.descriptors.CatalogColumnCollation;
 import org.apache.ignite.internal.lang.IgniteBiTuple;
-import org.apache.ignite.internal.storage.index.StorageSortedIndexDescriptor.StorageSortedIndexColumnDescriptor;
 import org.apache.ignite.internal.type.NativeType;
 import org.apache.ignite.internal.type.NativeTypes;
 import org.junit.jupiter.api.Test;
@@ -49,9 +49,11 @@ public class BinaryTupleComparatorTest {
     @ParameterizedTest
     @MethodSource("allTypes")
     public void testCompareSingleColumnTuples(NativeType type) {
-        var comparator = new BinaryTupleComparator(List.of(
-                new StorageSortedIndexColumnDescriptor("column", type, false, true)
-        ));
+        var comparator = new BinaryTupleComparator(
+                new CatalogColumnCollation[]{CatalogColumnCollation.ASC_NULLS_LAST},
+                i -> i,
+                new NativeType[]{type}
+        );
 
         IgniteBiTuple<ByteBuffer, ByteBuffer> tuples = createTestValues(type);
 
@@ -76,6 +78,7 @@ public class BinaryTupleComparatorTest {
                 NativeTypes.STRING,
                 NativeTypes.timestamp(6),
                 NativeTypes.DATE,
+                NativeTypes.time(6),
                 NativeTypes.datetime(6)
         );
     }
@@ -171,11 +174,11 @@ public class BinaryTupleComparatorTest {
 
             case BYTES: {
                 tuple1 = new BinaryTupleBuilder(1)
-                        .appendBytes(new byte[] {1, 2, 3})
+                        .appendBytes(new byte[]{1, 2, 3})
                         .build();
 
                 tuple2 = new BinaryTupleBuilder(1)
-                        .appendBytes(new byte[] {1, 2, 6})
+                        .appendBytes(new byte[]{1, 2, 6})
                         .build();
 
                 break;
@@ -283,10 +286,11 @@ public class BinaryTupleComparatorTest {
 
     @Test
     public void testCompareMultipleColumnTuples() {
-        var comparator = new BinaryTupleComparator(List.of(
-                new StorageSortedIndexColumnDescriptor("column", NativeTypes.INT32, false, true),
-                new StorageSortedIndexColumnDescriptor("column", NativeTypes.STRING, false,  false)
-        ));
+        var comparator = new BinaryTupleComparator(
+                new CatalogColumnCollation[]{CatalogColumnCollation.ASC_NULLS_LAST, CatalogColumnCollation.DESC_NULLS_FIRST},
+                i -> i,
+                new NativeType[]{NativeTypes.INT32, NativeTypes.STRING}
+        );
 
         ByteBuffer tuple1 = new BinaryTupleBuilder(2)
                 .appendInt(0)
@@ -313,10 +317,11 @@ public class BinaryTupleComparatorTest {
 
     @Test
     public void testCompareMultipleColumnTuplesWithNulls() {
-        var comparator = new BinaryTupleComparator(List.of(
-                new StorageSortedIndexColumnDescriptor("column", NativeTypes.INT32, true, true),
-                new StorageSortedIndexColumnDescriptor("column", NativeTypes.STRING, true,  false)
-        ));
+        var comparator = new BinaryTupleComparator(
+                new CatalogColumnCollation[]{CatalogColumnCollation.ASC_NULLS_LAST, CatalogColumnCollation.DESC_NULLS_FIRST},
+                i -> i,
+                new NativeType[]{NativeTypes.INT32, NativeTypes.STRING}
+        );
 
         ByteBuffer tuple1 = new BinaryTupleBuilder(2)
                 .appendInt(null)
@@ -352,10 +357,11 @@ public class BinaryTupleComparatorTest {
 
     @Test
     public void testCompareWithPrefix() {
-        var comparator = new BinaryTupleComparator(List.of(
-                new StorageSortedIndexColumnDescriptor("column", NativeTypes.INT32, false, true),
-                new StorageSortedIndexColumnDescriptor("column", NativeTypes.STRING, false,  false)
-        ));
+        var comparator = new BinaryTupleComparator(
+                new CatalogColumnCollation[]{CatalogColumnCollation.ASC_NULLS_LAST, CatalogColumnCollation.DESC_NULLS_FIRST},
+                i -> i,
+                new NativeType[]{NativeTypes.INT32, NativeTypes.STRING}
+        );
 
         ByteBuffer tuple1 = new BinaryTupleBuilder(2)
                 .appendInt(1)
@@ -391,10 +397,11 @@ public class BinaryTupleComparatorTest {
 
     @Test
     public void testCompareWithPrefixWithNulls() {
-        var comparator = new BinaryTupleComparator(List.of(
-                new StorageSortedIndexColumnDescriptor("column", NativeTypes.INT32, true, true),
-                new StorageSortedIndexColumnDescriptor("column", NativeTypes.STRING, false,  false)
-        ));
+        var comparator = new BinaryTupleComparator(
+                new CatalogColumnCollation[]{CatalogColumnCollation.ASC_NULLS_LAST, CatalogColumnCollation.DESC_NULLS_FIRST},
+                i -> i,
+                new NativeType[]{NativeTypes.INT32, NativeTypes.STRING}
+        );
 
         ByteBuffer tuple1 = new BinaryTupleBuilder(2)
                 .appendInt(null)
diff --git a/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/VarBinary.java b/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/VarBinary.java
index a50ef5eeba..da3cb45c87 100644
--- a/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/VarBinary.java
+++ b/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/VarBinary.java
@@ -65,7 +65,7 @@ public final class VarBinary implements NativeTypeWrapper<VarBinary> {
     /** {@inheritDoc} */
     @Override
     public int compareTo(VarBinary o) {
-        return Arrays.compare(bytes, o.bytes);
+        return Arrays.compareUnsigned(bytes, o.bytes);
     }
 
     /** {@inheritDoc} */
diff --git a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/util/StorageUtils.java b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/util/StorageUtils.java
index 456b01baea..9e4e9d8afb 100644
--- a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/util/StorageUtils.java
+++ b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/util/StorageUtils.java
@@ -17,16 +17,21 @@
 
 package org.apache.ignite.internal.storage.util;
 
+import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Supplier;
+import org.apache.ignite.internal.catalog.descriptors.CatalogColumnCollation;
 import org.apache.ignite.internal.lang.IgniteInternalCheckedException;
 import org.apache.ignite.internal.lang.IgniteStringFormatter;
+import org.apache.ignite.internal.schema.BinaryTupleComparator;
 import org.apache.ignite.internal.storage.RowId;
 import org.apache.ignite.internal.storage.StorageClosedException;
 import org.apache.ignite.internal.storage.StorageDestroyedException;
 import org.apache.ignite.internal.storage.StorageException;
 import org.apache.ignite.internal.storage.StorageRebalanceException;
 import org.apache.ignite.internal.storage.index.IndexNotBuiltException;
+import org.apache.ignite.internal.storage.index.StorageSortedIndexDescriptor.StorageSortedIndexColumnDescriptor;
+import org.apache.ignite.internal.type.NativeType;
 import org.jetbrains.annotations.Nullable;
 
 /**
@@ -253,4 +258,17 @@ public class StorageUtils {
             throw new IndexNotBuiltException("Index not built yet: [{}]", storageInfoSupplier.get());
         }
     }
+
+    /**
+     * Creates a comparator for a Sorted Index identified by the given columns descriptors.
+     */
+    public static BinaryTupleComparator binaryTupleComparator(List<StorageSortedIndexColumnDescriptor> columns) {
+        CatalogColumnCollation[] collations = columns.stream()
+                .map(c -> CatalogColumnCollation.get(c.asc(), !c.asc()))
+                .toArray(CatalogColumnCollation[]::new);
+
+        NativeType[] columnTypes = columns.stream().map(StorageSortedIndexColumnDescriptor::type).toArray(NativeType[]::new);
+
+        return new BinaryTupleComparator(collations, i -> i, columnTypes);
+    }
 }
diff --git a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/impl/TestSortedIndexStorage.java b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/impl/TestSortedIndexStorage.java
index e0dc7376d3..7e1714c161 100644
--- a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/impl/TestSortedIndexStorage.java
+++ b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/impl/TestSortedIndexStorage.java
@@ -33,12 +33,12 @@ import org.apache.ignite.internal.binarytuple.BinaryTupleCommon;
 import org.apache.ignite.internal.schema.BinaryTuple;
 import org.apache.ignite.internal.schema.BinaryTuplePrefix;
 import org.apache.ignite.internal.storage.RowId;
-import org.apache.ignite.internal.storage.index.BinaryTupleComparator;
 import org.apache.ignite.internal.storage.index.IndexRow;
 import org.apache.ignite.internal.storage.index.IndexRowImpl;
 import org.apache.ignite.internal.storage.index.PeekCursor;
 import org.apache.ignite.internal.storage.index.SortedIndexStorage;
 import org.apache.ignite.internal.storage.index.StorageSortedIndexDescriptor;
+import org.apache.ignite.internal.storage.util.StorageUtils;
 import org.apache.ignite.internal.util.TransformingIterator;
 import org.jetbrains.annotations.Nullable;
 
@@ -54,7 +54,7 @@ public class TestSortedIndexStorage extends AbstractTestIndexStorage implements
     public TestSortedIndexStorage(int partitionId, StorageSortedIndexDescriptor descriptor) {
         super(partitionId, descriptor);
 
-        BinaryTupleComparator binaryTupleComparator = new BinaryTupleComparator(descriptor.columns());
+        var binaryTupleComparator = StorageUtils.binaryTupleComparator(descriptor.columns());
 
         this.descriptor = descriptor;
         this.index = new ConcurrentSkipListSet<>(
diff --git a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/sorted/PageMemorySortedIndexStorage.java b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/sorted/PageMemorySortedIndexStorage.java
index 2c58b405c7..13e635e651 100644
--- a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/sorted/PageMemorySortedIndexStorage.java
+++ b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/sorted/PageMemorySortedIndexStorage.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.storage.pagememory.index.sorted;
 import static org.apache.ignite.internal.storage.util.StorageUtils.throwExceptionIfStorageInProgressOfRebalance;
 
 import java.nio.ByteBuffer;
+import java.util.Comparator;
 import java.util.Objects;
 import org.apache.ignite.internal.binarytuple.BinaryTupleCommon;
 import org.apache.ignite.internal.lang.IgniteInternalCheckedException;
@@ -30,7 +31,6 @@ import org.apache.ignite.internal.schema.BinaryTuple;
 import org.apache.ignite.internal.schema.BinaryTuplePrefix;
 import org.apache.ignite.internal.storage.RowId;
 import org.apache.ignite.internal.storage.StorageException;
-import org.apache.ignite.internal.storage.index.BinaryTupleComparator;
 import org.apache.ignite.internal.storage.index.IndexRow;
 import org.apache.ignite.internal.storage.index.IndexRowImpl;
 import org.apache.ignite.internal.storage.index.PeekCursor;
@@ -251,7 +251,7 @@ public class PageMemorySortedIndexStorage extends AbstractPageMemoryIndexStorage
             SortedIndexRowKey upper = createBound(upperBound, includeUpper);
 
             return new ScanCursor<IndexRow>(lower) {
-                private final BinaryTupleComparator comparator = localTree.getBinaryTupleComparator();
+                private final Comparator<ByteBuffer> comparator = localTree.getBinaryTupleComparator();
 
                 @Override
                 public IndexRow map(SortedIndexRow value) {
diff --git a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/sorted/SortedIndexTree.java b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/sorted/SortedIndexTree.java
index c1ebb88bd9..c10ccf73c9 100644
--- a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/sorted/SortedIndexTree.java
+++ b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/sorted/SortedIndexTree.java
@@ -29,12 +29,13 @@ import org.apache.ignite.internal.pagememory.tree.BplusTree;
 import org.apache.ignite.internal.pagememory.tree.io.BplusIo;
 import org.apache.ignite.internal.pagememory.util.PageLockListener;
 import org.apache.ignite.internal.schema.BinaryTuple;
-import org.apache.ignite.internal.storage.index.BinaryTupleComparator;
+import org.apache.ignite.internal.schema.BinaryTupleComparator;
 import org.apache.ignite.internal.storage.index.StorageSortedIndexDescriptor;
 import org.apache.ignite.internal.storage.pagememory.index.sorted.io.SortedIndexTreeInnerIo;
 import org.apache.ignite.internal.storage.pagememory.index.sorted.io.SortedIndexTreeIo;
 import org.apache.ignite.internal.storage.pagememory.index.sorted.io.SortedIndexTreeLeafIo;
 import org.apache.ignite.internal.storage.pagememory.index.sorted.io.SortedIndexTreeMetaIo;
+import org.apache.ignite.internal.storage.util.StorageUtils;
 import org.jetbrains.annotations.Nullable;
 
 /**
@@ -90,7 +91,7 @@ public class SortedIndexTree extends BplusTree<SortedIndexRowKey, SortedIndexRow
                 ? binaryTupleInlineSize(pageSize(), ITEM_SIZE_WITHOUT_COLUMNS, indexDescriptor)
                 : readInlineSizeFromMetaIo();
         this.dataPageReader = new DataPageReader(pageMem, grpId, statisticsHolder());
-        this.binaryTupleComparator = new BinaryTupleComparator(indexDescriptor.columns());
+        this.binaryTupleComparator = StorageUtils.binaryTupleComparator(indexDescriptor.columns());
 
         init(initNew);
     }
diff --git a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/sorted/io/SortedIndexTreeIo.java b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/sorted/io/SortedIndexTreeIo.java
index f677d8aeb7..aaf72b5fd3 100644
--- a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/sorted/io/SortedIndexTreeIo.java
+++ b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/sorted/io/SortedIndexTreeIo.java
@@ -32,6 +32,7 @@ import static org.apache.ignite.internal.storage.pagememory.index.InlineUtils.ca
 import static org.apache.ignite.internal.util.GridUnsafe.wrapPointer;
 
 import java.nio.ByteBuffer;
+import java.util.Comparator;
 import java.util.UUID;
 import org.apache.ignite.internal.lang.IgniteInternalCheckedException;
 import org.apache.ignite.internal.pagememory.datapage.DataPageReader;
@@ -39,7 +40,6 @@ import org.apache.ignite.internal.pagememory.tree.io.BplusIo;
 import org.apache.ignite.internal.pagememory.util.PageUtils;
 import org.apache.ignite.internal.schema.BinaryTuple;
 import org.apache.ignite.internal.storage.RowId;
-import org.apache.ignite.internal.storage.index.BinaryTupleComparator;
 import org.apache.ignite.internal.storage.pagememory.index.freelist.IndexColumns;
 import org.apache.ignite.internal.storage.pagememory.index.freelist.ReadIndexColumnsValue;
 import org.apache.ignite.internal.storage.pagememory.index.sorted.SortedIndexRow;
@@ -164,7 +164,7 @@ public interface SortedIndexTreeIo {
      */
     default int compare(
             DataPageReader dataPageReader,
-            BinaryTupleComparator binaryTupleComparator,
+            Comparator<ByteBuffer> binaryTupleComparator,
             int partitionId,
             long pageAddr,
             int idx,
diff --git a/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/index/RocksDbBinaryTupleComparator.java b/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/index/RocksDbBinaryTupleComparator.java
index cad20f2958..a70162fccd 100644
--- a/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/index/RocksDbBinaryTupleComparator.java
+++ b/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/index/RocksDbBinaryTupleComparator.java
@@ -18,10 +18,11 @@
 package org.apache.ignite.internal.storage.rocksdb.index;
 
 import java.nio.ByteBuffer;
+import java.util.Comparator;
 import java.util.List;
 import org.apache.ignite.internal.schema.BinaryTuple;
-import org.apache.ignite.internal.storage.index.BinaryTupleComparator;
 import org.apache.ignite.internal.storage.index.StorageSortedIndexDescriptor.StorageSortedIndexColumnDescriptor;
+import org.apache.ignite.internal.storage.util.StorageUtils;
 import org.rocksdb.AbstractComparator;
 import org.rocksdb.ComparatorOptions;
 
@@ -29,7 +30,7 @@ import org.rocksdb.ComparatorOptions;
  * {@link AbstractComparator} implementation that compares Binary Tuples.
  */
 public class RocksDbBinaryTupleComparator extends AbstractComparator {
-    private final BinaryTupleComparator comparator;
+    private final Comparator<ByteBuffer> comparator;
 
     /** Options needed for resource management. */
     private final ComparatorOptions options;
@@ -47,7 +48,7 @@ public class RocksDbBinaryTupleComparator extends AbstractComparator {
         super(options);
 
         this.options = options;
-        this.comparator = new BinaryTupleComparator(columns);
+        this.comparator = StorageUtils.binaryTupleComparator(columns);
     }
 
     @Override
diff --git a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/PartitionReplicaListener.java b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/PartitionReplicaListener.java
index 0a4669c1b9..5e09c14e2b 100644
--- a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/PartitionReplicaListener.java
+++ b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/PartitionReplicaListener.java
@@ -171,11 +171,11 @@ import org.apache.ignite.internal.storage.MvPartitionStorage;
 import org.apache.ignite.internal.storage.PartitionTimestampCursor;
 import org.apache.ignite.internal.storage.ReadResult;
 import org.apache.ignite.internal.storage.RowId;
-import org.apache.ignite.internal.storage.index.BinaryTupleComparator;
 import org.apache.ignite.internal.storage.index.IndexRow;
 import org.apache.ignite.internal.storage.index.IndexRowImpl;
 import org.apache.ignite.internal.storage.index.IndexStorage;
 import org.apache.ignite.internal.storage.index.SortedIndexStorage;
+import org.apache.ignite.internal.storage.util.StorageUtils;
 import org.apache.ignite.internal.table.RowIdGenerator;
 import org.apache.ignite.internal.table.distributed.IndexLocker;
 import org.apache.ignite.internal.table.distributed.SortedIndexLocker;
@@ -1367,7 +1367,7 @@ public class PartitionReplicaListener implements ReplicaListener {
 
         int flags = request.flags();
 
-        var comparator = new BinaryTupleComparator(indexStorage.indexDescriptor().columns());
+        var comparator = StorageUtils.binaryTupleComparator(indexStorage.indexDescriptor().columns());
 
         Predicate<IndexRow> isUpperBoundAchieved = indexRow -> {
             if (indexRow == null) {