You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by pt...@apache.org on 2022/09/19 14:56:16 UTC

[ignite-3] branch main updated: IGNITE-17632 Support decimal scale in client schema (#1097)

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

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


The following commit(s) were added to refs/heads/main by this push:
     new fc959a42eb IGNITE-17632 Support decimal scale in client schema (#1097)
fc959a42eb is described below

commit fc959a42eb21987feffa0319a99aa63fed2dc00e
Author: Pavel Tupitsyn <pt...@apache.org>
AuthorDate: Mon Sep 19 17:56:10 2022 +0300

    IGNITE-17632 Support decimal scale in client schema (#1097)
    
    * Require `scale` in `BinaryTupleBuilder#appendDecimalNotNull` to ensure correct value round-trip.
    * Add decimal scale to client schema to fix serialization and deserialization of `BigDecimal` values.
---
 .../internal/binarytuple/BinaryTupleBuilder.java   | 11 +++---
 .../internal/binarytuple/BinaryTupleTest.java      | 20 ++++++++++-
 .../client/proto/ClientBinaryTupleUtils.java       |  6 ++--
 .../handler/requests/table/ClientTableCommon.java  | 13 +++++--
 .../ignite/internal/client/table/ClientColumn.java | 27 ++++++++++++++
 .../ignite/internal/client/table/ClientSchema.java |  2 +-
 .../ignite/internal/client/table/ClientTable.java  |  7 ++--
 .../client/table/ClientTupleSerializer.java        | 16 ++++-----
 .../marshaller/ClientMarshallerReader.java         |  5 ++-
 .../marshaller/ClientMarshallerWriter.java         |  4 +--
 .../ignite/client/ClientKeyValueViewTest.java      |  4 +--
 .../apache/ignite/client/ClientRecordViewTest.java |  4 +--
 .../ignite/client/fakes/FakeIgniteTables.java      |  2 +-
 .../ignite/internal/marshaller/FieldAccessor.java  | 41 +++++++++++++++-------
 .../internal/marshaller/MarshallerColumn.java      | 14 ++++++--
 .../internal/marshaller/MarshallerReader.java      |  2 +-
 .../internal/marshaller/MarshallerWriter.java      |  4 +--
 .../internal/marshaller/FieldAccessorTest.java     |  4 +--
 .../ignite/internal/schema/BinaryConverter.java    |  2 +-
 .../index/impl/BinaryTupleRowSerializer.java       |  2 +-
 20 files changed, 135 insertions(+), 55 deletions(-)

diff --git a/modules/binary-tuple/src/main/java/org/apache/ignite/internal/binarytuple/BinaryTupleBuilder.java b/modules/binary-tuple/src/main/java/org/apache/ignite/internal/binarytuple/BinaryTupleBuilder.java
index 873ba1d3f4..7f09077556 100644
--- a/modules/binary-tuple/src/main/java/org/apache/ignite/internal/binarytuple/BinaryTupleBuilder.java
+++ b/modules/binary-tuple/src/main/java/org/apache/ignite/internal/binarytuple/BinaryTupleBuilder.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.binarytuple;
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.math.RoundingMode;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.CharBuffer;
@@ -326,10 +327,11 @@ public class BinaryTupleBuilder {
      * Append a value for the current element.
      *
      * @param value Element value.
+     * @param scale Decimal scale.
      * @return {@code this} for chaining.
      */
-    public BinaryTupleBuilder appendDecimalNotNull(@NotNull BigDecimal value) {
-        putBytes(value.unscaledValue().toByteArray());
+    public BinaryTupleBuilder appendDecimalNotNull(@NotNull BigDecimal value, int scale) {
+        putBytes(value.setScale(scale, RoundingMode.HALF_UP).unscaledValue().toByteArray());
         return proceed();
     }
 
@@ -337,10 +339,11 @@ public class BinaryTupleBuilder {
      * Append a value for the current element.
      *
      * @param value Element value.
+     * @param scale Decimal scale.
      * @return {@code this} for chaining.
      */
-    public BinaryTupleBuilder appendDecimal(BigDecimal value) {
-        return value == null ? appendNull() : appendDecimalNotNull(value);
+    public BinaryTupleBuilder appendDecimal(BigDecimal value, int scale) {
+        return value == null ? appendNull() : appendDecimalNotNull(value, scale);
     }
 
     /**
diff --git a/modules/binary-tuple/src/test/java/org/apache/ignite/internal/binarytuple/BinaryTupleTest.java b/modules/binary-tuple/src/test/java/org/apache/ignite/internal/binarytuple/BinaryTupleTest.java
index 95b50bf8b7..969ba0acf6 100644
--- a/modules/binary-tuple/src/test/java/org/apache/ignite/internal/binarytuple/BinaryTupleTest.java
+++ b/modules/binary-tuple/src/test/java/org/apache/ignite/internal/binarytuple/BinaryTupleTest.java
@@ -354,12 +354,30 @@ public class BinaryTupleTest {
         BigDecimal value = new BigDecimal(BigInteger.valueOf(12345), 100);
 
         BinaryTupleBuilder builder = BinaryTupleBuilder.create(1, false);
-        ByteBuffer bytes = builder.appendDecimal(value).build();
+        ByteBuffer bytes = builder.appendDecimal(value, 100).build();
 
         BinaryTupleReader reader = new BinaryTupleReader(1, bytes);
         assertEquals(value, reader.decimalValue(0, 100));
     }
 
+    /**
+     * Test big decimal value encoding with different scale in value and schema.
+     */
+    @Test
+    public void decimalScaleTest() {
+        int schemaScale = 10;
+        int valueScale = 3;
+        BigDecimal value = BigDecimal.valueOf(123456, valueScale);
+
+        BinaryTupleBuilder builder = BinaryTupleBuilder.create(1, false);
+        ByteBuffer bytes = builder.appendDecimal(value, schemaScale).build();
+
+        BinaryTupleReader reader = new BinaryTupleReader(1, bytes);
+        BigDecimal res = reader.decimalValue(0, schemaScale);
+
+        assertEquals(value.doubleValue(), res.doubleValue());
+    }
+
     /**
      * Test string value encoding.
      */
diff --git a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientBinaryTupleUtils.java b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientBinaryTupleUtils.java
index 5d28a5667d..1038532ddd 100644
--- a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientBinaryTupleUtils.java
+++ b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientBinaryTupleUtils.java
@@ -41,7 +41,8 @@ public class ClientBinaryTupleUtils {
             int readerIndex,
             Tuple tuple,
             String columnName,
-            int clientDataType) {
+            int clientDataType,
+            int decimalScale) {
         if (reader.hasNullValue(readerIndex)) {
             tuple.set(columnName, null);
             return;
@@ -73,8 +74,7 @@ public class ClientBinaryTupleUtils {
                 break;
 
             case ClientDataType.DECIMAL:
-                // TODO IGNITE-17632: Get scale from schema.
-                tuple.set(columnName, reader.decimalValue(readerIndex, 100));
+                tuple.set(columnName, reader.decimalValue(readerIndex, decimalScale));
                 break;
 
             case ClientDataType.UUID:
diff --git a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/table/ClientTableCommon.java b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/table/ClientTableCommon.java
index 5c8452d9c6..1ab66e4521 100644
--- a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/table/ClientTableCommon.java
+++ b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/table/ClientTableCommon.java
@@ -40,6 +40,8 @@ import org.apache.ignite.internal.client.proto.ClientMessagePacker;
 import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
 import org.apache.ignite.internal.client.proto.TuplePart;
 import org.apache.ignite.internal.schema.Column;
+import org.apache.ignite.internal.schema.DecimalNativeType;
+import org.apache.ignite.internal.schema.NativeType;
 import org.apache.ignite.internal.schema.NativeTypeSpec;
 import org.apache.ignite.internal.schema.SchemaAware;
 import org.apache.ignite.internal.schema.SchemaDescriptor;
@@ -81,11 +83,12 @@ public class ClientTableCommon {
         for (var colIdx = 0; colIdx < colCnt; colIdx++) {
             var col = schema.column(colIdx);
 
-            packer.packArrayHeader(4);
+            packer.packArrayHeader(5);
             packer.packString(col.name());
             packer.packInt(getClientDataType(col.type().spec()));
             packer.packBoolean(schema.isKeyColumn(colIdx));
             packer.packBoolean(col.nullable());
+            packer.packInt(getDecimalScale(col.type()));
         }
     }
 
@@ -285,7 +288,7 @@ public class ClientTableCommon {
 
             Column column = schema.column(i);
             ClientBinaryTupleUtils.readAndSetColumnValue(
-                    binaryTupleReader, i, tuple, column.name(), getClientDataType(column.type().spec()));
+                    binaryTupleReader, i, tuple, column.name(), getClientDataType(column.type().spec()), getDecimalScale(column.type()));
         }
 
         return tuple;
@@ -461,7 +464,7 @@ public class ClientTableCommon {
                 break;
 
             case DECIMAL:
-                builder.appendDecimalNotNull((BigDecimal) val);
+                builder.appendDecimalNotNull((BigDecimal) val, getDecimalScale(col.type()));
                 break;
 
             case NUMBER:
@@ -517,4 +520,8 @@ public class ClientTableCommon {
         packer.packBinaryHeader(buf.limit() - buf.position());
         packer.writePayload(buf);
     }
+
+    private static int getDecimalScale(NativeType type) {
+        return type instanceof DecimalNativeType ? ((DecimalNativeType) type).scale() : 0;
+    }
 }
diff --git a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientColumn.java b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientColumn.java
index dd7728cbe0..2a1368c3f2 100644
--- a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientColumn.java
+++ b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientColumn.java
@@ -38,6 +38,9 @@ public class ClientColumn {
     /** Index of the column in the schema. */
     private final int schemaIndex;
 
+    /** value scale, if applicable. */
+    private final int scale;
+
     /**
      * Constructor.
      *
@@ -48,6 +51,20 @@ public class ClientColumn {
      * @param schemaIndex Index of the column in the schema.
      */
     public ClientColumn(String name, int type, boolean nullable, boolean isKey, int schemaIndex) {
+        this(name, type, nullable, isKey, schemaIndex, 0);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param name        Column name.
+     * @param type        Column type code.
+     * @param nullable    Nullable flag.
+     * @param isKey       Key column flag.
+     * @param schemaIndex Index of the column in the schema.
+     * @param scale       Scale of the column, if applicable.
+     */
+    public ClientColumn(String name, int type, boolean nullable, boolean isKey, int schemaIndex, int scale) {
         assert name != null;
         assert schemaIndex >= 0;
 
@@ -56,6 +73,7 @@ public class ClientColumn {
         this.nullable = nullable;
         this.isKey = isKey;
         this.schemaIndex = schemaIndex;
+        this.scale = scale;
     }
 
     public String name() {
@@ -97,4 +115,13 @@ public class ClientColumn {
     public int schemaIndex() {
         return schemaIndex;
     }
+
+    /**
+     * Gets the decimal scale of the column, if applicable.
+     *
+     * @return Decimal scale.
+     */
+    public int scale() {
+        return scale;
+    }
 }
diff --git a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientSchema.java b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientSchema.java
index 04e30bab4a..89fe3345b2 100644
--- a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientSchema.java
+++ b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientSchema.java
@@ -164,7 +164,7 @@ public class ClientSchema {
         for (int i = 0; i < colCount; i++) {
             var col = columns[i  + firstColIdx];
 
-            cols[i] = new MarshallerColumn(col.name(), mode(col.type()));
+            cols[i] = new MarshallerColumn(col.name(), mode(col.type()), null, col.scale());
         }
 
         return Marshaller.createMarshaller(cols, mapper, part == TuplePart.KEY);
diff --git a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTable.java b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTable.java
index 47b3bc11b2..2d1ec8da30 100644
--- a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTable.java
+++ b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTable.java
@@ -175,17 +175,18 @@ public class ClientTable implements Table {
         for (int i = 0; i < colCnt; i++) {
             var propCnt = in.unpackArrayHeader();
 
-            assert propCnt >= 4;
+            assert propCnt >= 5;
 
             var name = in.unpackString();
             var type = in.unpackInt();
             var isKey = in.unpackBoolean();
             var isNullable = in.unpackBoolean();
+            var scale = in.unpackInt();
 
             // Skip unknown extra properties, if any.
-            in.skipValues(propCnt - 4);
+            in.skipValues(propCnt - 5);
 
-            var column = new ClientColumn(name, type, isNullable, isKey, i);
+            var column = new ClientColumn(name, type, isNullable, isKey, i, scale);
             columns[i] = column;
         }
 
diff --git a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTupleSerializer.java b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTupleSerializer.java
index 3397d24dc2..79b30172d9 100644
--- a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTupleSerializer.java
+++ b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTupleSerializer.java
@@ -241,7 +241,7 @@ public class ClientTupleSerializer {
 
         for (var i = 0; i < colCnt; i++) {
             ClientColumn column = schema.columns()[i];
-            ClientBinaryTupleUtils.readAndSetColumnValue(binTuple, i, tuple, column.name(), column.type());
+            ClientBinaryTupleUtils.readAndSetColumnValue(binTuple, i, tuple, column.name(), column.type(), column.scale());
         }
 
         return tuple;
@@ -257,7 +257,8 @@ public class ClientTupleSerializer {
             if (i < schema.keyColumnCount()) {
                 tuple.setInternal(i, keyTuple.value(col.name()));
             } else {
-                ClientBinaryTupleUtils.readAndSetColumnValue(binTuple, i - schema.keyColumnCount(), tuple, col.name(), col.type());
+                ClientBinaryTupleUtils.readAndSetColumnValue(
+                        binTuple, i - schema.keyColumnCount(), tuple, col.name(), col.type(), col.scale());
             }
         }
 
@@ -274,7 +275,7 @@ public class ClientTupleSerializer {
         for (var i = keyColCnt; i < colCnt; i++) {
             ClientColumn col = schema.columns()[i];
             ClientBinaryTupleUtils.readAndSetColumnValue(
-                    binTupleReader, i - keyColCnt, valTuple, col.name(), col.type());
+                    binTupleReader, i - keyColCnt, valTuple, col.name(), col.type(), col.scale());
         }
 
         return valTuple;
@@ -291,12 +292,9 @@ public class ClientTupleSerializer {
 
         for (var i = 0; i < colCnt; i++) {
             ClientColumn col = schema.columns()[i];
+            var targetTuple = i < keyColCnt ? keyTuple : valTuple;
 
-            if (i < keyColCnt) {
-                ClientBinaryTupleUtils.readAndSetColumnValue(binTuple, i, keyTuple, col.name(), col.type());
-            } else {
-                ClientBinaryTupleUtils.readAndSetColumnValue(binTuple, i, valTuple, col.name(), col.type());
-            }
+            ClientBinaryTupleUtils.readAndSetColumnValue(binTuple, i, targetTuple, col.name(), col.type(), col.scale());
         }
 
         return new IgniteBiTuple<>(keyTuple, valTuple);
@@ -395,7 +393,7 @@ public class ClientTupleSerializer {
                     return;
 
                 case ClientDataType.DECIMAL:
-                    builder.appendDecimalNotNull((BigDecimal) v);
+                    builder.appendDecimalNotNull((BigDecimal) v, col.scale());
                     return;
 
                 case ClientDataType.UUID:
diff --git a/modules/client/src/main/java/org/apache/ignite/internal/marshaller/ClientMarshallerReader.java b/modules/client/src/main/java/org/apache/ignite/internal/marshaller/ClientMarshallerReader.java
index 17576a8a11..dcfe309f2d 100644
--- a/modules/client/src/main/java/org/apache/ignite/internal/marshaller/ClientMarshallerReader.java
+++ b/modules/client/src/main/java/org/apache/ignite/internal/marshaller/ClientMarshallerReader.java
@@ -168,10 +168,9 @@ public class ClientMarshallerReader implements MarshallerReader {
 
     /** {@inheritDoc} */
     @Override
-    public BigDecimal readBigDecimal() {
-        // TODO IGNITE-17632: Get scale from schema.
+    public BigDecimal readBigDecimal(int scale) {
         var idx = index++;
-        return unpacker.hasNullValue(idx) ? null : unpacker.decimalValue(idx, 100);
+        return unpacker.hasNullValue(idx) ? null : unpacker.decimalValue(idx, scale);
     }
 
     /** {@inheritDoc} */
diff --git a/modules/client/src/main/java/org/apache/ignite/internal/marshaller/ClientMarshallerWriter.java b/modules/client/src/main/java/org/apache/ignite/internal/marshaller/ClientMarshallerWriter.java
index 7a1fc7ea86..84b9ad79bf 100644
--- a/modules/client/src/main/java/org/apache/ignite/internal/marshaller/ClientMarshallerWriter.java
+++ b/modules/client/src/main/java/org/apache/ignite/internal/marshaller/ClientMarshallerWriter.java
@@ -129,8 +129,8 @@ public class ClientMarshallerWriter implements MarshallerWriter {
 
     /** {@inheritDoc} */
     @Override
-    public void writeBigDecimal(BigDecimal val) {
-        packer.appendDecimal(val);
+    public void writeBigDecimal(BigDecimal val, int scale) {
+        packer.appendDecimal(val, scale);
     }
 
     /** {@inheritDoc} */
diff --git a/modules/client/src/test/java/org/apache/ignite/client/ClientKeyValueViewTest.java b/modules/client/src/test/java/org/apache/ignite/client/ClientKeyValueViewTest.java
index 222dd16860..371b5a8d59 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/ClientKeyValueViewTest.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/ClientKeyValueViewTest.java
@@ -136,7 +136,7 @@ public class ClientKeyValueViewTest extends AbstractClientTableTest {
         assertEquals("foo", res.zstring);
         assertArrayEquals(new byte[]{1, 2}, res.zbytes);
         assertEquals(BitSet.valueOf(new byte[]{32}), res.zbitmask);
-        assertEquals(21, res.zdecimal.unscaledValue().longValue()); // TODO: IGNITE-17632 check correct round-trip
+        assertEquals(21, res.zdecimal.longValue());
         assertEquals(22, res.znumber.longValue());
         assertEquals(uuid, res.zuuid);
     }
@@ -191,7 +191,7 @@ public class ClientKeyValueViewTest extends AbstractClientTableTest {
         assertEquals("119", res.stringValue("zstring"));
         assertEquals(120, ((byte[]) res.value("zbytes"))[0]);
         assertEquals(BitSet.valueOf(new byte[]{121}), res.bitmaskValue("zbitmask"));
-        assertEquals(122, ((BigDecimal) res.value("zdecimal")).unscaledValue().longValue()); // TODO: IGNITE-17632 check correct round-trip
+        assertEquals(122, ((BigDecimal) res.value("zdecimal")).longValue());
         assertEquals(BigInteger.valueOf(123), res.value("znumber"));
         assertEquals(uuid, res.uuidValue("zuuid"));
     }
diff --git a/modules/client/src/test/java/org/apache/ignite/client/ClientRecordViewTest.java b/modules/client/src/test/java/org/apache/ignite/client/ClientRecordViewTest.java
index 413370477b..d98a924f6c 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/ClientRecordViewTest.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/ClientRecordViewTest.java
@@ -136,7 +136,7 @@ public class ClientRecordViewTest extends AbstractClientTableTest {
         assertEquals("foo", res.zstring);
         assertArrayEquals(new byte[]{1, 2}, res.zbytes);
         assertEquals(BitSet.valueOf(new byte[]{32}), res.zbitmask);
-        assertEquals(21, res.zdecimal.unscaledValue().longValue()); // TODO: IGNITE-17632 check correct round-trip
+        assertEquals(21, res.zdecimal.longValue());
         assertEquals(22, res.znumber.longValue());
         assertEquals(uuid, res.zuuid);
     }
@@ -189,7 +189,7 @@ public class ClientRecordViewTest extends AbstractClientTableTest {
         assertEquals("119", res.stringValue("zstring"));
         assertEquals(120, ((byte[]) res.value("zbytes"))[0]);
         assertEquals(BitSet.valueOf(new byte[]{121}), res.bitmaskValue("zbitmask"));
-        assertEquals(122, ((BigDecimal) res.value("zdecimal")).unscaledValue().longValue());
+        assertEquals(122, ((BigDecimal) res.value("zdecimal")).longValue());
         assertEquals(BigInteger.valueOf(123), res.value("znumber"));
         assertEquals(uuid, res.uuidValue("zuuid"));
     }
diff --git a/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeIgniteTables.java b/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeIgniteTables.java
index 1ce0880260..3b430425ca 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeIgniteTables.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeIgniteTables.java
@@ -241,7 +241,7 @@ public class FakeIgniteTables implements IgniteTables, IgniteTablesInternal {
                         new Column("zbytes".toUpperCase(), NativeTypes.BYTES, true),
                         new Column("zuuid".toUpperCase(), NativeTypes.UUID, true),
                         new Column("zbitmask".toUpperCase(), NativeTypes.bitmaskOf(16), true),
-                        new Column("zdecimal".toUpperCase(), NativeTypes.decimalOf(20, 100), true),
+                        new Column("zdecimal".toUpperCase(), NativeTypes.decimalOf(20, 10), true),
                         new Column("znumber".toUpperCase(), NativeTypes.numberOf(24), true),
                 });
     }
diff --git a/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/FieldAccessor.java b/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/FieldAccessor.java
index 25da0cc471..6783ddfd6f 100644
--- a/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/FieldAccessor.java
+++ b/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/FieldAccessor.java
@@ -45,6 +45,9 @@ abstract class FieldAccessor {
      */
     protected final int colIdx;
 
+    /** Scale. */
+    protected final int scale;
+
     public Object get(Object obj) {
         return varHandle.get(obj);
     }
@@ -112,7 +115,7 @@ abstract class FieldAccessor {
                 case DATE:
                 case DATETIME:
                 case TIMESTAMP:
-                    return new ReferenceFieldAccessor(varHandle, colIdx, mode);
+                    return new ReferenceFieldAccessor(varHandle, colIdx, mode, col.scale());
 
                 default:
                     assert false : "Invalid mode " + mode;
@@ -176,7 +179,7 @@ abstract class FieldAccessor {
      * @param mode   Binary read mode.
      * @return Read value object.
      */
-    private static Object readRefValue(MarshallerReader reader, int colIdx, BinaryMode mode) {
+    private static Object readRefValue(MarshallerReader reader, int colIdx, BinaryMode mode, int scale) {
         assert reader != null;
         assert colIdx >= 0;
 
@@ -239,7 +242,7 @@ abstract class FieldAccessor {
                 break;
 
             case DECIMAL:
-                val = reader.readBigDecimal();
+                val = reader.readBigDecimal(scale);
 
                 break;
 
@@ -277,7 +280,7 @@ abstract class FieldAccessor {
      * @param writer Writer.
      * @param mode   Write binary mode.
      */
-    private static void writeRefObject(Object val, MarshallerWriter writer, BinaryMode mode) {
+    private static void writeRefObject(Object val, MarshallerWriter writer, BinaryMode mode, int scale) {
         assert writer != null;
 
         if (val == null) {
@@ -343,7 +346,7 @@ abstract class FieldAccessor {
                 break;
 
             case DECIMAL:
-                writer.writeBigDecimal((BigDecimal) val);
+                writer.writeBigDecimal((BigDecimal) val, scale);
 
                 break;
 
@@ -378,13 +381,26 @@ abstract class FieldAccessor {
      * @param varHandle Field var-handle.
      * @param colIdx    Column index.
      * @param mode      Read/write mode.
+     * @param scale     Scale.
      */
-    protected FieldAccessor(VarHandle varHandle, int colIdx, BinaryMode mode) {
+    protected FieldAccessor(VarHandle varHandle, int colIdx, BinaryMode mode, int scale) {
         assert colIdx >= 0;
 
         this.colIdx = colIdx;
         this.mode = Objects.requireNonNull(mode);
         this.varHandle = Objects.requireNonNull(varHandle);
+        this.scale = scale;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param varHandle Field var-handle.
+     * @param colIdx    Column index.
+     * @param mode      Read/write mode.
+     */
+    protected FieldAccessor(VarHandle varHandle, int colIdx, BinaryMode mode) {
+        this(varHandle, colIdx, mode, 0);
     }
 
     /**
@@ -399,6 +415,7 @@ abstract class FieldAccessor {
         this.colIdx = colIdx;
         this.mode = mode;
         varHandle = null;
+        scale = 0;
     }
 
     /**
@@ -522,7 +539,7 @@ abstract class FieldAccessor {
         /** {@inheritDoc} */
         @Override
         protected void write0(MarshallerWriter writer, Object obj) {
-            writeRefObject(obj, writer, mode);
+            writeRefObject(obj, writer, mode, scale);
         }
 
         /** {@inheritDoc} */
@@ -534,7 +551,7 @@ abstract class FieldAccessor {
         /** {@inheritDoc} */
         @Override
         public Object read(MarshallerReader reader) {
-            return readRefValue(reader, colIdx, mode);
+            return readRefValue(reader, colIdx, mode, scale);
         }
 
         /** {@inheritDoc} */
@@ -741,8 +758,8 @@ abstract class FieldAccessor {
          * @param colIdx    Column index.
          * @param mode      Read/write mode.
          */
-        ReferenceFieldAccessor(VarHandle varHandle, int colIdx, BinaryMode mode) {
-            super(Objects.requireNonNull(varHandle), colIdx, mode);
+        ReferenceFieldAccessor(VarHandle varHandle, int colIdx, BinaryMode mode, int scale) {
+            super(Objects.requireNonNull(varHandle), colIdx, mode, scale);
         }
 
         /** {@inheritDoc} */
@@ -759,13 +776,13 @@ abstract class FieldAccessor {
                 return;
             }
 
-            writeRefObject(val, writer, mode);
+            writeRefObject(val, writer, mode, scale);
         }
 
         /** {@inheritDoc} */
         @Override
         public void read0(MarshallerReader reader, Object obj) {
-            Object val = readRefValue(reader, colIdx, mode);
+            Object val = readRefValue(reader, colIdx, mode, scale);
 
             varHandle.set(obj, val);
         }
diff --git a/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/MarshallerColumn.java b/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/MarshallerColumn.java
index f2b782d4fe..73ec221f25 100644
--- a/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/MarshallerColumn.java
+++ b/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/MarshallerColumn.java
@@ -38,6 +38,11 @@ public class MarshallerColumn {
      */
     private final BinaryMode type;
 
+    /**
+     * Scale.
+     */
+    private final int scale;
+
     /**
      * Default value supplier.
      */
@@ -51,7 +56,7 @@ public class MarshallerColumn {
      * @param type      An instance of column data type.
      */
     public MarshallerColumn(String name, BinaryMode type) {
-        this(name, type, null);
+        this(name, type, null, 0);
     }
 
     /**
@@ -61,10 +66,11 @@ public class MarshallerColumn {
      * @param type      An instance of column data type.
      * @param defValSup Default value supplier.
      */
-    public MarshallerColumn(String name, BinaryMode type, @Nullable Supplier<Object> defValSup) {
+    public MarshallerColumn(String name, BinaryMode type, @Nullable Supplier<Object> defValSup, int scale) {
         this.name = name;
         this.type = type;
         this.defValSup = defValSup == null ? NULL_SUPPLIER : defValSup;
+        this.scale = scale;
     }
 
     public String name() {
@@ -78,4 +84,8 @@ public class MarshallerColumn {
     public Object defaultValue() {
         return defValSup.get();
     }
+
+    public int scale() {
+        return scale;
+    }
 }
diff --git a/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/MarshallerReader.java b/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/MarshallerReader.java
index 58f188b24a..654fe0b70f 100644
--- a/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/MarshallerReader.java
+++ b/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/MarshallerReader.java
@@ -159,7 +159,7 @@ public interface MarshallerReader {
      *
      * @return Value.
      */
-    BigDecimal readBigDecimal();
+    BigDecimal readBigDecimal(int scale);
 
     /**
      * Reads a date.
diff --git a/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/MarshallerWriter.java b/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/MarshallerWriter.java
index 8c966c3cd0..100a05b53f 100644
--- a/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/MarshallerWriter.java
+++ b/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/MarshallerWriter.java
@@ -125,7 +125,7 @@ public interface MarshallerWriter {
      *
      * @param val Value.
      */
-    void writeBigDecimal(BigDecimal val);
+    void writeBigDecimal(BigDecimal val, int scale);
 
     /**
      * Writes date.
@@ -245,7 +245,7 @@ public interface MarshallerWriter {
                 break;
             }
             case DECIMAL: {
-                writeBigDecimal((BigDecimal) val);
+                writeBigDecimal((BigDecimal) val, col.scale());
 
                 break;
             }
diff --git a/modules/marshaller-common/src/test/java/org/apache/ignite/internal/marshaller/FieldAccessorTest.java b/modules/marshaller-common/src/test/java/org/apache/ignite/internal/marshaller/FieldAccessorTest.java
index 69d556f755..7b74e941d9 100644
--- a/modules/marshaller-common/src/test/java/org/apache/ignite/internal/marshaller/FieldAccessorTest.java
+++ b/modules/marshaller-common/src/test/java/org/apache/ignite/internal/marshaller/FieldAccessorTest.java
@@ -266,7 +266,7 @@ public class FieldAccessorTest {
         Mockito.doAnswer(asmAnswer).when(mockedAsm).writeString(Mockito.anyString());
         Mockito.doAnswer(asmAnswer).when(mockedAsm).writeBytes(Mockito.any(byte[].class));
         Mockito.doAnswer(asmAnswer).when(mockedAsm).writeBigInt(Mockito.any(BigInteger.class));
-        Mockito.doAnswer(asmAnswer).when(mockedAsm).writeBigDecimal(Mockito.any(BigDecimal.class));
+        Mockito.doAnswer(asmAnswer).when(mockedAsm).writeBigDecimal(Mockito.any(BigDecimal.class), Mockito.anyInt());
 
         Mockito.doAnswer(asmAnswer).when(mockedAsm).writeDate(Mockito.any(LocalDate.class));
         Mockito.doAnswer(asmAnswer).when(mockedAsm).writeDateTime(Mockito.any(LocalDateTime.class));
@@ -296,7 +296,7 @@ public class FieldAccessorTest {
         Mockito.doAnswer(rowAnswer).when(mockedRow).readString();
         Mockito.doAnswer(rowAnswer).when(mockedRow).readBytes();
         Mockito.doAnswer(rowAnswer).when(mockedRow).readBigInt();
-        Mockito.doAnswer(rowAnswer).when(mockedRow).readBigDecimal();
+        Mockito.doAnswer(rowAnswer).when(mockedRow).readBigDecimal(0);
 
         return new Pair<>(mockedAsm, mockedRow);
     }
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryConverter.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryConverter.java
index 5b592216d7..259b82f2a3 100644
--- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryConverter.java
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryConverter.java
@@ -154,7 +154,7 @@ public class BinaryConverter {
                     builder.appendNumberNotNull(row.numberValue(columnIndex));
                     break;
                 case DECIMAL:
-                    builder.appendDecimalNotNull(row.decimalValue(columnIndex));
+                    builder.appendDecimalNotNull(row.decimalValue(columnIndex), elt.decimalScale);
                     break;
                 case STRING:
                 case BYTES:
diff --git a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/impl/BinaryTupleRowSerializer.java b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/impl/BinaryTupleRowSerializer.java
index 4dbb2cc66e..798d70bd22 100644
--- a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/impl/BinaryTupleRowSerializer.java
+++ b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/impl/BinaryTupleRowSerializer.java
@@ -173,7 +173,7 @@ public class BinaryTupleRowSerializer {
             case NUMBER:
                 return builder.appendNumberNotNull((BigInteger) value);
             case DECIMAL:
-                return builder.appendDecimalNotNull((BigDecimal) value);
+                return builder.appendDecimalNotNull((BigDecimal) value, element.decimalScale());
             case UUID:
                 return builder.appendUuidNotNull((UUID) value);
             case BYTES: