diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DateTimeCodec.java b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DateTimeCodec.java index 1044a59..3f4dc57 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DateTimeCodec.java +++ b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DateTimeCodec.java @@ -37,6 +37,8 @@ import static com.google.common.base.Preconditions.checkNotNull; * * * + * + * @see struct DateTime source */ public final class DateTimeCodec { @@ -62,8 +64,7 @@ public final class DateTimeCodec { * * @param bytes a {@link byte} array containing the serialized value to be decoded. * @return a new {@link OffsetDateTime}. - * @see - * struct DateTimeCodec source + * @see struct DateTime source */ public static OffsetDateTime decode(@Nonnull final byte[] bytes) { checkNotNull(bytes); @@ -75,8 +76,7 @@ public final class DateTimeCodec { * * @param in a {@link ByteBuf} containing the serialized value to be decoded. * @return a new {@link OffsetDateTime}. - * @see - * struct DateTimeCodec source + * @see struct DateTime source */ public static OffsetDateTime decode(@Nonnull final ByteBuf in) { @@ -101,8 +101,7 @@ public final class DateTimeCodec { * * @param offsetDateTime an {@link OffsetDateTime} to be encoded. * @return a new byte array containing the encoded {@code offsetDateTime}. - * @see - * struct DateTimeCodec source + * @see struct DateTime source */ public static byte[] encode(final OffsetDateTime offsetDateTime) { final byte[] bytes = new byte[BYTES]; @@ -115,8 +114,7 @@ public final class DateTimeCodec { * * @param offsetDateTime an {@link OffsetDateTime} to be encoded. * @param out an output {@link ByteBuf}. - * @see - * struct DateTimeCodec source + * @see struct DateTime source */ public static void encode(final OffsetDateTime offsetDateTime, final ByteBuf out) { diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DecimalCodec.java b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DecimalCodec.java index b9b1e1c..46e66ad 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DecimalCodec.java +++ b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DecimalCodec.java @@ -5,12 +5,14 @@ package com.azure.data.cosmos.serialization.hybridrow.codecs; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import io.netty.util.ByteProcessor; import javax.annotation.Nonnull; import java.math.BigDecimal; import java.math.BigInteger; import java.math.MathContext; import java.math.RoundingMode; +import java.util.Arrays; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -18,6 +20,16 @@ import static com.google.common.base.Preconditions.checkState; import static java.lang.Math.max; import static java.lang.Math.min; +/** + * Provides static methods for encoding and decoding {@link BigDecimal}s serialized as {@code System.Decimal}s + * + * The serialization format is lossy as the {@link BigDecimal} class represents arbitrary-precision signed decimal + * numbers while the binary representation of a {@code System.Decimal} value is constrained to a magnitude of 96-bits + * with a scaling factor of 10 and a scale value between 0 and 28. This yields a precision between 28 and 29 + * decimal digits. + * + * @see struct Decimal source + */ public final class DecimalCodec { public static final int BYTES = 4 * Integer.BYTES; @@ -26,21 +38,26 @@ public final class DecimalCodec { private static final int FLAGS_MASK_POWER = 0b00000000111111110000000000000000; private static final int FLAGS_MASK_SIGN = 0b10000000000000000000000000000000; - private static final BigInteger MAGNITUDE_MAX = new BigInteger(new byte[] { (byte)0x00, + private static final BigInteger MAGNITUDE_MAX = new BigInteger(new byte[] { + (byte)0x00, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF }); - private static final BigInteger MAGNITUDE_MIN = new BigInteger(new byte[] { (byte)0xFF, + private static final BigInteger MAGNITUDE_MIN = new BigInteger(new byte[] { + (byte)0xFF, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01 }); private static final MathContext REDUCED_PRECISION = new MathContext(28, RoundingMode.HALF_EVEN); + private static final int SCALE_MAX = 28; private static final int SCALE_MIN = 0; private static final int SCALE_SHIFT = 16; + private static final int VALUE_LENGTH = 3 * Integer.BYTES; + private static final int[] VALUE_OFFSETS = { /* hi */ 0, /* lo */ 2 * Integer.BYTES, /* mid */ Integer.BYTES }; private DecimalCodec() { } @@ -50,8 +67,7 @@ public final class DecimalCodec { * * @param bytes a {@link byte} array containing the serialized {@code System.Decimal} to be decoded. * @return a new {@link BigDecimal}. - * @see - * struct DecimalCodec source + * @see struct Decimal source */ public static BigDecimal decode(@Nonnull final byte[] bytes) { checkNotNull(bytes); @@ -63,8 +79,7 @@ public final class DecimalCodec { * * @param in a {@link ByteBuf} containing the serialized {@code System.Decimal} to be decoded. * @return a new {@link BigDecimal}. - * @see - * struct DecimalCodec source + * @see struct Decimal source */ public static BigDecimal decode(@Nonnull final ByteBuf in) { @@ -74,35 +89,40 @@ public final class DecimalCodec { BYTES, in.readableBytes()); - // The flags field is--and must be--interpreted as follows + // The flags field is interpreted as follows // // Bits Interpretation // ----- -------------------------------------------------------------------------------------------- - // 0-15 unused and must be zero + // 00-15 unused and must be zero // 16-23 a value between 0 and 28 specifying the number of digits to the right of the decimal point // 24-30 unused and must be zero - // 31 specifies the sign of the value, 1 meaning negative and 0 meaning non-negative + // 31-31 specifies the sign of the value, 1 meaning negative and 0 meaning non-negative final int flags = in.readIntLE(); checkState((flags & FLAGS_MASK_INVALID) == 0, "invalid flags field: %s", flags); - final int scale = (flags | FLAGS_MASK_POWER) >>> SCALE_SHIFT; + final int scale = (flags & FLAGS_MASK_POWER) >>> SCALE_SHIFT; checkState(scale <= SCALE_MAX); final int signum = (flags & FLAGS_MASK_SIGN) == 0 ? 1 : -1; - final byte[] buffer = new byte[VALUE_LENGTH]; - in.readBytes(buffer); + final byte[] magnitude = new byte[VALUE_LENGTH]; + final int source = in.readerIndex(); + int target = 0; - int i = VALUE_LENGTH; - int j = 0; - - while (i > j) { - final byte b = buffer[--i]; - buffer[i] = buffer[j]; - buffer[j++] = b; + for (int offset : VALUE_OFFSETS) { + final int start = target; + in.forEachByteDesc(source + offset, Integer.BYTES, new ByteProcessor() { + int index = start; + @Override + public boolean process(byte value) { + magnitude[this.index++] = value; + return true; + } + }); + target += Integer.BYTES; } - return new BigDecimal(new BigInteger(signum, buffer), scale); + return new BigDecimal(new BigInteger(signum, magnitude), scale); } /** @@ -110,13 +130,12 @@ public final class DecimalCodec { * * @param bigDecimal a {@link BigDecimal} to be encoded. * @return a new byte array containing the encoded {@code bigDecimal}. - * @see - * struct DecimalCodec source + * @see struct Decimal source */ public static byte[] encode(final BigDecimal bigDecimal) { - final byte[] bytes = new byte[BYTES]; - encode(bigDecimal, Unpooled.wrappedBuffer(bytes)); - return bytes; + ByteBuf buffer = Unpooled.wrappedBuffer(new byte[BYTES]).clear(); + encode(bigDecimal, buffer); + return buffer.array(); } /** @@ -124,8 +143,7 @@ public final class DecimalCodec { * * @param value a {@link BigDecimal} to be encoded. * @param out an output {@link ByteBuf}. - * @see - * struct DecimalCodec source + * @see struct Decimal source */ public static void encode(@Nonnull BigDecimal value, @Nonnull final ByteBuf out) { @@ -158,30 +176,26 @@ public final class DecimalCodec { unscaledValue = value.unscaledValue(); } - final byte[] bytes = unscaledValue.toByteArray(); - final byte[] buffer = new byte[VALUE_LENGTH]; + final byte[] decimalParts = new byte[VALUE_LENGTH]; + final byte[] bytes; final int flags; - if (signum == -1) { - flags = FLAGS_MASK_SIGN | value.scale() << SCALE_SHIFT; - int carry = 1; - for (int i = buffer.length, j = 0; i-- > 0; j++) { - final int n = ((bytes[i] ^ 0b1111_1111) & 0b1111_1111) + carry; - if ((n & 0b1_0000_0000) == 0) { - carry = 0; - } - buffer[j] = bytes[i]; - } - } else { + if (signum > 0) { flags = value.scale() << SCALE_SHIFT; - for (int i = buffer.length, j = 0; i-- > 0; j++) { - buffer[j] = bytes[i]; - } + bytes = unscaledValue.toByteArray(); + } else { + flags = value.scale() << SCALE_SHIFT | FLAGS_MASK_SIGN; + bytes = unscaledValue.abs().toByteArray(); } out.writeIntLE(flags); - out.writeBytes(buffer, 0, 4); - out.writeBytes(buffer, 8, 4); - out.writeBytes(buffer, 4, 4); + + for (int i = bytes.length, j = 0; i > 0 && j < decimalParts.length; ) { + decimalParts[j++] = bytes[--i]; + } + + out.writeBytes(decimalParts, 2 * Integer.BYTES, Integer.BYTES); // high + out.writeBytes(decimalParts, 0, Integer.BYTES); // low + out.writeBytes(decimalParts, Integer.BYTES, Integer.BYTES); // mid } } diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/GuidCodec.java b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/GuidCodec.java index 4ed4cb3..f49f416 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/GuidCodec.java +++ b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/GuidCodec.java @@ -12,6 +12,39 @@ import java.util.UUID; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +/** + * Provides static methods for encoding and decoding {@link UUID}s serialized as {@code System.Guid}s + * + * {@link UUID}s are serialized like {@code System.Guid}s read and written by {@code MemoryMarshal.Read} and + * {@code MemoryMarshal.Write}. + * + *
| + * Bits 00-31 + * | + * Contain an unsigned 32-bit int serialized in little-endian format. + * |
| + * Bits 32-47 + * | + * Contain an unsigned 16-bit int serialized in little-endian format. + * |
| + * Bits 48-63 + * | + * Contain an unsigned 16-bit int serialized in little-endian format. + * |
| + * Bits 64-127 + * | + * Contain an unsigned 64-bit int serialized in big-endian format. + * |
* Test data was generated from code that looks like this: * {@code + * using System.Runtime.InteropServices; * var buffer = new byte[16]; * var value = decimal.MaxValue; * MemoryMarshal.Write(buffer, ref value); - * Console.WriteLine($"new DecimalItem(new byte[] {{ (byte) {string.Join(", (byte) ", buffer )} }}, new BigDecimal(\"{value.ToString()}\"))"); * } + * Console.WriteLine($"new DecimalItem(new byte[] {{ (byte) {string.Join(", (byte) ", buffer )} }}, new BigDecimal(\" + * {value.ToString()}\"))"); + * } */ public class DecimalCodecTest { @@ -34,7 +37,7 @@ public class DecimalCodecTest { @Test(dataProvider = "decimalDataProvider") public void testDecodeByteBuf(byte[] buffer, BigDecimal value) { - ByteBuf byteBuf = Unpooled.wrappedBuffer(new byte[DecimalCodec.BYTES]); + ByteBuf byteBuf = Unpooled.wrappedBuffer(buffer); BigDecimal actual = DecimalCodec.decode(byteBuf); assertEquals(actual, value); } @@ -47,14 +50,23 @@ public class DecimalCodecTest { @Test(dataProvider = "decimalDataProvider") public void testEncodeByteBuf(byte[] buffer, BigDecimal value) { - ByteBuf actual = Unpooled.wrappedBuffer(new byte[DecimalCodec.BYTES]); + ByteBuf actual = Unpooled.wrappedBuffer(new byte[DecimalCodec.BYTES]).clear(); DecimalCodec.encode(value, actual); assertEquals(actual.array(), buffer); } @DataProvider(name = "decimalDataProvider") - private Iterator