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

rpuch 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 038f12f5ad IGNITE-23592 Align serialization methods of HybridTimestamp 
(#4670)
038f12f5ad is described below

commit 038f12f5adb907561c95ce3f56f6d57a668f7539
Author: Roman Puchkovskiy <[email protected]>
AuthorDate: Tue Nov 5 14:07:31 2024 +0400

    IGNITE-23592 Align serialization methods of HybridTimestamp (#4670)
---
 .../ignite/internal/hlc/HybridTimestamp.java       | 61 +++++++++++++++++-
 .../org/apache/ignite/internal/util/ByteUtils.java | 58 ++++++++++++++---
 .../apache/ignite/internal/util/VarIntUtils.java   | 24 +++++++
 .../ignite/internal/hlc/HybridTimestampTest.java   | 73 ++++++++++++++++++----
 4 files changed, 193 insertions(+), 23 deletions(-)

diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/hlc/HybridTimestamp.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/hlc/HybridTimestamp.java
index a7947892da..b132c82f1d 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/hlc/HybridTimestamp.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/hlc/HybridTimestamp.java
@@ -24,12 +24,26 @@ import java.io.Serializable;
 import java.time.Instant;
 import java.time.ZoneOffset;
 import org.apache.ignite.internal.util.ByteUtils;
+import org.apache.ignite.internal.util.VarIntUtils;
 import org.apache.ignite.internal.util.io.IgniteDataInput;
 import org.apache.ignite.internal.util.io.IgniteDataOutput;
 import org.jetbrains.annotations.Nullable;
 
 /**
  * A hybrid timestamp that combines physical clock and logical clock.
+ *
+ * <p>Serialization methods ({@link #toBytes()}, {@link 
#writeTo(IgniteDataOutput)}, {@link #write(HybridTimestamp, IgniteDataOutput)})
+ * write a timestamp in the following format:
+ *
+ * <pre>{@code
+ *   <TS> ::= <PHYSICAL_PART> <LOGICAL_PART>
+ *
+ *   <PHYSICAL_PART> ::= <PHYSICAL_HIGH> (4 bytes int) <PHYSICAL_LOW> (2 bytes 
short) // both are in little-endian
+ *
+ *   <LOGICAL_PART> ::= (varint)
+ * }</pre>
+ *
+ * <p>A null timestamp is serialized as both parts (physical and logical) 
equal to 0.
  */
 public final class HybridTimestamp implements Comparable<HybridTimestamp>, 
Serializable {
     /** Serial version UID. */
@@ -128,10 +142,24 @@ public final class HybridTimestamp implements 
Comparable<HybridTimestamp>, Seria
     /**
      * Restores a timestamp converted to bytes using {@link #toBytes()}.
      *
+     * <p>See the class javadoc for details on the serialization format.
+     *
      * @param bytes Byte array representing a timestamp.
+     * @see #toBytes()
      */
     public static HybridTimestamp fromBytes(byte[] bytes) {
-        return hybridTimestamp(ByteUtils.bytesToLong(bytes));
+        // Reversing bytes as ByteUtils works in BE, but we store in LE (as 
IgniteDataOutput uses LE and we want to be consistent between
+        // serialization methods).
+        long physicalHigh = Integer.reverseBytes(ByteUtils.bytesToInt(bytes, 
0));
+        int physicalLow = Short.reverseBytes(ByteUtils.bytesToShort(bytes, 
Integer.BYTES)) & 0xFFFF;
+
+        long physical = (physicalHigh << Short.SIZE) | physicalLow;
+        //noinspection NumericCastThatLosesPrecision
+        int logical = (int) VarIntUtils.readVarInt(bytes, Integer.BYTES + 
Short.BYTES);
+
+        assert physical != 0 || logical != 0;
+
+        return new HybridTimestamp(physical, logical);
     }
 
     /**
@@ -255,16 +283,36 @@ public final class HybridTimestamp implements 
Comparable<HybridTimestamp>, Seria
 
     /**
      * Returns a byte array representing this timestamp.
+     *
+     * <p>See the class javadoc for details on the serialization format.
+     *
+     * @see #fromBytes(byte[])
      */
+    @SuppressWarnings("NumericCastThatLosesPrecision")
     public byte[] toBytes() {
-        return ByteUtils.longToBytes(longValue());
+        long physical = getPhysical();
+        int logical = getLogical();
+
+        byte[] bytes = new byte[Integer.BYTES + Short.BYTES + 
VarIntUtils.varIntLength(logical)];
+
+        // Reversing bytes as ByteUtils works in BE, but we store in LE (as 
IgniteDataOutput uses LE and we want to be consistent between
+        // serialization methods).
+        ByteUtils.putIntToBytes(Integer.reverseBytes((int) (physical >> 
Short.SIZE)), bytes, 0);
+        ByteUtils.putShortToBytes(Short.reverseBytes((short) (physical & 
0xFFFF)), bytes, Integer.BYTES);
+
+        VarIntUtils.putVarIntToBytes(logical, bytes, Integer.BYTES + 
Short.BYTES);
+
+        return bytes;
     }
 
     /**
      * Writes this timestamp to the output.
      *
+     * <p>See the class javadoc for details on the serialization format.
+     *
      * @param out Output to write to.
      * @throws IOException If something goes wrong.
+     * @see #readFrom(IgniteDataInput)
      */
     public void writeTo(IgniteDataOutput out) throws IOException {
         long physical = getPhysical();
@@ -279,9 +327,12 @@ public final class HybridTimestamp implements 
Comparable<HybridTimestamp>, Seria
     /**
      * Writes a nullable timestamp to an output.
      *
+     * <p>See the class javadoc for details on the serialization format.
+     *
      * @param timestamp Timestamp to write
      * @param out Output to write to.
      * @throws IOException If something goes wrong.
+     * @see #readNullableFrom(IgniteDataInput)
      */
     public static void write(@Nullable HybridTimestamp timestamp, 
IgniteDataOutput out) throws IOException {
         if (timestamp == null) {
@@ -296,8 +347,11 @@ public final class HybridTimestamp implements 
Comparable<HybridTimestamp>, Seria
     /**
      * Reads a timestamp written with {@link #writeTo(IgniteDataOutput)}.
      *
+     * <p>See the class javadoc for details on the serialization format.
+     *
      * @param in Input from which to read.
      * @throws IOException If something goes wrong.
+     * @see #write(HybridTimestamp, IgniteDataOutput)
      */
     public static @Nullable HybridTimestamp readNullableFrom(IgniteDataInput 
in) throws IOException {
         long physicalHigh = in.readInt();
@@ -316,8 +370,11 @@ public final class HybridTimestamp implements 
Comparable<HybridTimestamp>, Seria
     /**
      * Reads a timestamp written with {@link #writeTo(IgniteDataOutput)}.
      *
+     * <p>See the class javadoc for details on the serialization format.
+     *
      * @param in Input from which to read.
      * @throws IOException If something goes wrong.
+     * @see #writeTo(IgniteDataOutput)
      */
     public static HybridTimestamp readFrom(IgniteDataInput in) throws 
IOException {
         HybridTimestamp ts = readNullableFrom(in);
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/util/ByteUtils.java 
b/modules/core/src/main/java/org/apache/ignite/internal/util/ByteUtils.java
index 745fb38605..d39ca490dc 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/util/ByteUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/ByteUtils.java
@@ -48,11 +48,11 @@ public class ByteUtils {
     public static long bytesToLong(byte[] bytes, int off) {
         assert bytes != null;
 
-        int bytesCnt = Math.min(Long.BYTES, bytes.length - off);
+        int bytesCount = Math.min(Long.BYTES, bytes.length - off);
 
         long res = 0;
 
-        for (int i = 0; i < bytesCnt; i++) {
+        for (int i = 0; i < bytesCount; i++) {
             res = (res << 8) | (0xffL & bytes[off + i]);
         }
 
@@ -131,11 +131,11 @@ public class ByteUtils {
     public static int bytesToInt(byte[] bytes, int off) {
         assert bytes != null;
 
-        int bytesCnt = Math.min(Integer.BYTES, bytes.length - off);
+        int bytesCount = Math.min(Integer.BYTES, bytes.length - off);
 
         int res = 0;
 
-        for (int i = 0; i < bytesCnt; i++) {
+        for (int i = 0; i < bytesCount; i++) {
             res = (res << 8) | (0xff & bytes[off + i]);
         }
 
@@ -163,6 +163,26 @@ public class ByteUtils {
         return bytesToInt(bytes) ^ 0x00808080;
     }
 
+    /**
+     * Constructs {@code short} from byte array in Big Endian order.
+     *
+     * @param bytes Array of bytes.
+     * @param off Offset in {@code bytes} array.
+     * @return Short value.
+     */
+    public static short bytesToShort(byte[] bytes, int off) {
+        assert bytes != null;
+
+        int bytesCount = Math.min(Short.BYTES, bytes.length - off);
+
+        int res = 0;
+
+        for (int i = 0; i < bytesCount; i++) {
+            res = (res << 8) | (0xff & bytes[off + i]);
+        }
+
+        return (short) res;
+    }
 
     /**
      * Converts a primitive {@code int} value to a byte array in Big Endian 
order.
@@ -194,17 +214,35 @@ public class ByteUtils {
      * @return The array.
      */
     public static byte[] putIntToBytes(int i, byte[] bytes, int off) {
-        assert bytes != null;
-        assert bytes.length >= off + Integer.BYTES;
+        putToBytes(i, Integer.BYTES, bytes, off);
 
-        for (int k = Integer.BYTES - 1; k >= 0; k--) {
-            bytes[off + k] = (byte) i;
-            i >>>= 8;
-        }
+        return bytes;
+    }
+
+    /**
+     * Converts a primitive {@code short} value to a byte array in Big Endian 
order and stores it in the specified byte array.
+     *
+     * @param s Unsigned int value.
+     * @param bytes Bytes array to write result to.
+     * @param off Offset in the target array to write result to.
+     * @return The array.
+     */
+    public static byte[] putShortToBytes(short s, byte[] bytes, int off) {
+        putToBytes(s, Short.BYTES, bytes, off);
 
         return bytes;
     }
 
+    private static void putToBytes(long value, int sizeInBytes, byte[] bytes, 
int off) {
+        assert bytes != null;
+        assert off + sizeInBytes <= bytes.length;
+
+        for (int k = sizeInBytes - 1; k >= 0; k--) {
+            bytes[off + k] = (byte) value;
+            value >>>= Byte.SIZE;
+        }
+    }
+
     /**
      * Converts a {@link Boolean} value to a single byte.
      *
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/util/VarIntUtils.java 
b/modules/core/src/main/java/org/apache/ignite/internal/util/VarIntUtils.java
index 6a43e19ab0..550cb56768 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/util/VarIntUtils.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/util/VarIntUtils.java
@@ -129,6 +129,30 @@ public class VarIntUtils {
         return res - 1;
     }
 
+    /**
+     * Reads a varint from a byte buffer.
+     *
+     * @param bytes Array from which to read.
+     * @param off Offset to start reading from.
+     * @return Long value that was encoded as a varint.
+     */
+    public static long readVarInt(byte[] bytes, int off) {
+        long res = 0;
+
+        int index = off;
+        for (int shift = 0; ; shift += 7) {
+            byte b = bytes[index++];
+
+            res |= ((long) b & 0x7F) << shift;
+
+            if (b >= 0) {
+                break;
+            }
+        }
+
+        return res - 1;
+    }
+
     /**
      * Reads a varint from an input.
      *
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/hlc/HybridTimestampTest.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/hlc/HybridTimestampTest.java
index 13739bf28d..96cfe7cf52 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/internal/hlc/HybridTimestampTest.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/hlc/HybridTimestampTest.java
@@ -34,6 +34,8 @@ import org.apache.ignite.internal.util.io.IgniteDataOutput;
 import org.apache.ignite.internal.util.io.IgniteUnsafeDataInput;
 import org.apache.ignite.internal.util.io.IgniteUnsafeDataOutput;
 import org.junit.jupiter.api.Test;
+import org.junitpioneer.jupiter.cartesian.CartesianTest;
+import org.junitpioneer.jupiter.cartesian.CartesianTest.Enum;
 
 /**
  * Tests of a hybrid timestamp implementation.
@@ -165,21 +167,14 @@ class HybridTimestampTest {
         assertThat(ts.roundUpToPhysicalTick(), is(new HybridTimestamp(2, 0)));
     }
 
-    @Test
-    void serializationAndDeserializationForNonNull() throws Exception {
+    @CartesianTest
+    void serializationAndDeserializationForNonNull(@Enum Serializer 
serializer, @Enum Deserializer deserializer) throws Exception {
         HybridTimestamp originalTs = new 
HybridTimestamp(System.currentTimeMillis(), 2);
 
-        IgniteDataOutput out = new IgniteUnsafeDataOutput(100);
-
-        originalTs.writeTo(out);
-
-        IgniteDataInput in = new IgniteUnsafeDataInput(out.array());
-
-        HybridTimestamp restoredTs = HybridTimestamp.readFrom(in);
+        byte[] bytes = serializer.serialize(originalTs);
+        HybridTimestamp restoredTs = deserializer.deserialize(bytes);
 
         assertThat(restoredTs, is(originalTs));
-
-        assertThat(in.available(), is(0));
     }
 
     @Test
@@ -195,6 +190,7 @@ class HybridTimestampTest {
         assertThat(in.available(), is(0));
     }
 
+    @SuppressWarnings("resource")
     @Test
     void readFromFailsWhenDeserializingNull() throws Exception {
         IgniteDataOutput out = new IgniteUnsafeDataOutput(100);
@@ -207,4 +203,59 @@ class HybridTimestampTest {
 
         assertThat(in.available(), is(0));
     }
+
+    private enum Serializer {
+        TO_BYTES(HybridTimestamp::toBytes),
+        WRITE_TO(ts -> {
+            IgniteDataOutput out = new IgniteUnsafeDataOutput(100);
+            ts.writeTo(out);
+            return out.array();
+        }),
+        WRITE(ts -> {
+            IgniteDataOutput out = new IgniteUnsafeDataOutput(100);
+            HybridTimestamp.write(ts, out);
+            return out.array();
+        });
+
+        private final IoFunction<HybridTimestamp, byte[]> transformation;
+
+        Serializer(IoFunction<HybridTimestamp, byte[]> transformation) {
+            this.transformation = transformation;
+        }
+
+        byte[] serialize(HybridTimestamp timestamp) throws IOException {
+            return transformation.apply(timestamp);
+        }
+    }
+
+    private enum Deserializer {
+        FROM_BYTES(HybridTimestamp::fromBytes),
+        READ_FROM(bytes -> {
+            IgniteDataInput in = new IgniteUnsafeDataInput(bytes);
+            HybridTimestamp ts = HybridTimestamp.readFrom(in);
+            assertThat(in.available(), is(0));
+            return ts;
+        }),
+        READ_NULLABLE_FROM(bytes -> {
+            IgniteDataInput in = new IgniteUnsafeDataInput(bytes);
+            HybridTimestamp ts = HybridTimestamp.readNullableFrom(in);
+            assertThat(in.available(), is(0));
+            return ts;
+        });
+
+        private final IoFunction<byte[], HybridTimestamp> transformation;
+
+        Deserializer(IoFunction<byte[], HybridTimestamp> transformation) {
+            this.transformation = transformation;
+        }
+
+        HybridTimestamp deserialize(byte[] bytes) throws IOException {
+            return transformation.apply(bytes);
+        }
+    }
+
+    @FunctionalInterface
+    private interface IoFunction<T, R> {
+        R apply(T t) throws IOException;
+    }
 }

Reply via email to