This is an automated email from the ASF dual-hosted git repository. zstan 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 0f5618fde0 IGNITE-21435: Sql. Catalog DefaultValue should not depend on Serializable. (#3627) 0f5618fde0 is described below commit 0f5618fde059370dc78b0d450d3c694313249922 Author: Max Zhuravkov <shh...@gmail.com> AuthorDate: Wed Apr 24 08:39:14 2024 +0300 IGNITE-21435: Sql. Catalog DefaultValue should not depend on Serializable. (#3627) --- .../internal/catalog/commands/DefaultValue.java | 220 ++++++++++++++++++++- .../descriptors/CatalogTableColumnDescriptor.java | 30 +-- .../storage/CatalogEntrySerializationTest.java | 130 ++++++++++-- .../ignite/internal/util/io/IgniteDataInput.java | 90 +++++++++ .../ignite/internal/util/io/IgniteDataOutput.java | 90 +++++++++ .../internal/util/io/IgniteUnsafeDataInput.java | 110 +++++++++++ .../internal/util/io/IgniteUnsafeDataOutput.java | 121 ++++++++++++ .../IgniteUnsafeDataInputOutputByteOrderTest.java | 120 ++++++++++- 8 files changed, 854 insertions(+), 57 deletions(-) diff --git a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/DefaultValue.java b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/DefaultValue.java index 7fbdfd6967..04f5eda1a2 100644 --- a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/DefaultValue.java +++ b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/DefaultValue.java @@ -17,16 +17,30 @@ package org.apache.ignite.internal.catalog.commands; -import java.io.Serializable; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Period; +import java.util.BitSet; import java.util.Objects; +import java.util.UUID; +import org.apache.ignite.internal.type.NativeType; +import org.apache.ignite.internal.type.NativeTypes; +import org.apache.ignite.internal.util.io.IgniteDataInput; +import org.apache.ignite.internal.util.io.IgniteDataOutput; +import org.apache.ignite.sql.ColumnType; import org.jetbrains.annotations.Nullable; /** * Definition of value provider to use as default. */ @SuppressWarnings("PublicInnerClass") -public class DefaultValue implements Serializable { - private static final long serialVersionUID = -3056041395340876711L; +public abstract class DefaultValue { /** * Defines value provider as functional provider. @@ -51,10 +65,20 @@ public class DefaultValue implements Serializable { /** Types of the defaults. */ public enum Type { /** Default is specified as a constant. */ - CONSTANT, + CONSTANT(0), /** Default is specified as a call to a function. */ - FUNCTION_CALL + FUNCTION_CALL(1); + + /** Represents absent of default value ({@code null}). */ + private static final int NULL_VALUE = -1; + + /** Type id used by serialization. */ + private final int typeId; + + Type(int typeId) { + this.typeId = typeId; + } } protected final Type type; @@ -70,7 +94,7 @@ public class DefaultValue implements Serializable { /** Defines default value provider as a function call. */ public static class FunctionCall extends DefaultValue { - private static final long serialVersionUID = -8166753714497411236L; + private final String functionName; private FunctionCall(String functionName) { @@ -108,16 +132,25 @@ public class DefaultValue implements Serializable { /** Defines default value provider as a constant. */ public static class ConstantValue extends DefaultValue { - private static final long serialVersionUID = -5909897953153236118L; - private final @Nullable Serializable value; + private final ColumnType columnType; + + private final @Nullable Object value; private ConstantValue(@Nullable Object value) { super(Type.CONSTANT); - this.value = (Serializable) value; + + NativeType nativeType = NativeTypes.fromObject(value); + + if (nativeType == null) { + columnType = ColumnType.NULL; + } else { + columnType = nativeType.spec().asColumnType(); + } + this.value = value; } /** Returns value to use as default. */ - public @Nullable Serializable value() { + public @Nullable Object value() { return value; } @@ -143,4 +176,171 @@ public class DefaultValue implements Serializable { return Objects.hash(type, value); } } + + /** + * Writes the given default value into output. + * + * @param val Default value or null. + * @param out Output. + * @throws IOException if thrown. + */ + public static void writeTo(@Nullable DefaultValue val, IgniteDataOutput out) throws IOException { + if (val == null) { + out.writeByte(Type.NULL_VALUE); + } else { + out.writeByte(val.type.typeId); + + if (val instanceof ConstantValue) { + ConstantValue constantValue = (ConstantValue) val; + + writeValue(constantValue.columnType, constantValue.value, out); + } else if (val instanceof FunctionCall) { + FunctionCall functionCall = (FunctionCall) val; + String functionName = functionCall.functionName(); + + out.writeUTF(functionName); + } else { + throw new IllegalArgumentException("Unknown or unexpected type: " + val); + } + } + } + + /** Reads default value or {@code null}. */ + public static @Nullable DefaultValue readFrom(IgniteDataInput in) throws IOException { + int typeId = in.readByte(); + if (typeId == Type.NULL_VALUE) { + return null; + } else if (typeId == Type.CONSTANT.typeId) { + Object val = readValue(in); + return new ConstantValue(val); + } else if (typeId == Type.FUNCTION_CALL.typeId) { + String functionName = in.readUTF(); + return new FunctionCall(functionName); + } else { + throw new IllegalArgumentException("Unexpected type: " + typeId); + } + } + + private static void writeValue(ColumnType columnType, @Nullable Object value, IgniteDataOutput out) throws IOException { + out.writeByte(columnType.id()); + if (value == null) { + return; + } + + switch (columnType) { + case NULL: + break; + case BOOLEAN: + out.writeBoolean((Boolean) value); + break; + case INT8: + out.writeByte((Byte) value); + break; + case INT16: + out.writeShort((Short) value); + break; + case INT32: + out.writeInt((Integer) value); + break; + case INT64: + out.writeLong((Long) value); + break; + case FLOAT: + out.writeFloat((Float) value); + break; + case DOUBLE: + out.writeDouble((Double) value); + break; + case DECIMAL: + out.writeBigDecimal((BigDecimal) value); + break; + case DATE: + out.writeLocalDate((LocalDate) value); + break; + case TIME: + out.writeLocalTime((LocalTime) value); + break; + case DATETIME: + out.writeLocalDateTime((LocalDateTime) value); + break; + case TIMESTAMP: + out.writeInstant((Instant) value); + break; + case UUID: + out.writeUuid((UUID) value); + break; + case BITMASK: + out.writeBitSet((BitSet) value); + break; + case STRING: + out.writeUTF((String) value); + break; + case BYTE_ARRAY: + byte[] bytes = (byte[]) value; + out.writeInt(bytes.length); + out.writeByteArray((byte[]) value); + break; + case PERIOD: + out.writePeriod((Period) value); + break; + case DURATION: + out.writeDuration((Duration) value); + break; + case NUMBER: + out.writeBigInteger((BigInteger) value); + break; + default: + throw new IllegalArgumentException("Unexpected column type: " + columnType); + } + } + + private static @Nullable Object readValue(IgniteDataInput in) throws IOException { + int typeId = in.readByte(); + ColumnType columnType = ColumnType.getById(typeId); + switch (columnType) { + case NULL: + return null; + case BOOLEAN: + return in.readBoolean(); + case INT8: + return in.readByte(); + case INT16: + return in.readShort(); + case INT32: + return in.readInt(); + case INT64: + return in.readLong(); + case FLOAT: + return in.readFloat(); + case DOUBLE: + return in.readDouble(); + case DECIMAL: + return in.readBigDecimal(); + case DATE: + return in.readLocalDate(); + case TIME: + return in.readLocalTime(); + case DATETIME: + return in.readLocalDateTime(); + case TIMESTAMP: + return in.readInstant(); + case UUID: + return in.readUuid(); + case BITMASK: + return in.readBitSet(); + case STRING: + return in.readUTF(); + case BYTE_ARRAY: + int bytesLength = in.readInt(); + return in.readByteArray(bytesLength); + case PERIOD: + return in.readPeriod(); + case DURATION: + return in.readDuration(); + case NUMBER: + return in.readBigInteger(); + default: + throw new IllegalArgumentException("Unexpected column type: " + columnType); + } + } } diff --git a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/descriptors/CatalogTableColumnDescriptor.java b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/descriptors/CatalogTableColumnDescriptor.java index 7d29d164b3..a6ce2dc4a7 100644 --- a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/descriptors/CatalogTableColumnDescriptor.java +++ b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/descriptors/CatalogTableColumnDescriptor.java @@ -18,12 +18,10 @@ package org.apache.ignite.internal.catalog.descriptors; import java.io.IOException; -import java.io.Serializable; import java.util.Objects; import org.apache.ignite.internal.catalog.commands.DefaultValue; import org.apache.ignite.internal.catalog.storage.serialization.CatalogObjectSerializer; import org.apache.ignite.internal.tostring.S; -import org.apache.ignite.internal.util.ByteUtils; import org.apache.ignite.internal.util.io.IgniteDataInput; import org.apache.ignite.internal.util.io.IgniteDataOutput; import org.apache.ignite.sql.ColumnType; @@ -147,8 +145,6 @@ public class CatalogTableColumnDescriptor { private static class TableColumnDescriptorSerializer implements CatalogObjectSerializer<CatalogTableColumnDescriptor> { @Override public CatalogTableColumnDescriptor readFrom(IgniteDataInput input) throws IOException { - // TODO https://issues.apache.org/jira/browse/IGNITE-21435 Should not depend on Serializable. - DefaultValue defaultValue = readSerializableObject(input); String name = input.readUTF(); int typeId = input.readInt(); ColumnType type = ColumnType.getById(typeId); @@ -160,41 +156,21 @@ public class CatalogTableColumnDescriptor { int scale = input.readInt(); int length = input.readInt(); + DefaultValue defaultValue = DefaultValue.readFrom(input); + return new CatalogTableColumnDescriptor(name, type, nullable, precision, scale, length, defaultValue); } @Override public void writeTo(CatalogTableColumnDescriptor descriptor, IgniteDataOutput output) throws IOException { - // TODO https://issues.apache.org/jira/browse/IGNITE-21435 Should not depend on Serializable. - writeSerializableObject(descriptor.defaultValue(), output); - output.writeUTF(descriptor.name()); output.writeInt(descriptor.type().id()); output.writeBoolean(descriptor.nullable()); output.writeInt(descriptor.precision()); output.writeInt(descriptor.scale()); output.writeInt(descriptor.length()); - } - - /** Reads {@link Serializable} object. */ - private static <T extends Serializable> @Nullable T readSerializableObject(IgniteDataInput input) throws IOException { - int blockSize = input.readInt(); - - return blockSize == -1 ? null : ByteUtils.fromBytes(input.readByteArray(blockSize)); - } - - /** Writes {@link Serializable} object. */ - private static void writeSerializableObject(@Nullable Serializable object, IgniteDataOutput output) throws IOException { - if (object == null) { - output.writeInt(-1); - - return; - } - - byte[] bytes = ByteUtils.toBytes(object); - output.writeInt(bytes.length); - output.writeByteArray(bytes); + DefaultValue.writeTo(descriptor.defaultValue(), output); } } } diff --git a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/storage/CatalogEntrySerializationTest.java b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/storage/CatalogEntrySerializationTest.java index 52c77d164a..ac0e23b8ed 100644 --- a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/storage/CatalogEntrySerializationTest.java +++ b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/storage/CatalogEntrySerializationTest.java @@ -21,13 +21,27 @@ import static org.apache.ignite.internal.catalog.commands.CatalogUtils.DEFAULT_F import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; -import java.io.Serializable; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.Base64; +import java.util.BitSet; import java.util.List; +import java.util.Random; import java.util.Set; +import java.util.UUID; +import java.util.stream.Stream; import org.apache.ignite.internal.catalog.Catalog; import org.apache.ignite.internal.catalog.commands.DefaultValue; +import org.apache.ignite.internal.catalog.commands.DefaultValue.ConstantValue; +import org.apache.ignite.internal.catalog.commands.DefaultValue.FunctionCall; import org.apache.ignite.internal.catalog.descriptors.CatalogColumnCollation; import org.apache.ignite.internal.catalog.descriptors.CatalogHashIndexDescriptor; import org.apache.ignite.internal.catalog.descriptors.CatalogIndexColumnDescriptor; @@ -45,20 +59,36 @@ import org.apache.ignite.internal.catalog.descriptors.CatalogZoneDescriptor; import org.apache.ignite.internal.catalog.storage.serialization.MarshallableEntryType; import org.apache.ignite.internal.catalog.storage.serialization.UpdateLogMarshallerImpl; import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest; -import org.apache.ignite.internal.tostring.S; +import org.apache.ignite.internal.type.NativeType; +import org.apache.ignite.internal.type.NativeTypes; +import org.apache.ignite.internal.util.io.IgniteUnsafeDataInput; +import org.apache.ignite.internal.util.io.IgniteUnsafeDataOutput; import org.apache.ignite.sql.ColumnType; import org.assertj.core.api.BDDAssertions; import org.jetbrains.annotations.Nullable; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.EnumSource.Mode; +import org.junit.jupiter.params.provider.MethodSource; /** * Tests to verify catalog storage entries serialization. */ public class CatalogEntrySerializationTest extends BaseIgniteAbstractTest { + private static final long SEED = System.nanoTime(); + + private static final Random RND = new Random(SEED); + private final UpdateLogMarshallerImpl marshaller = new UpdateLogMarshallerImpl(); + @BeforeEach + public void setup() { + log.info("Seed: {}", SEED); + } + @ParameterizedTest @EnumSource(value = MarshallableEntryType.class, names = "VERSIONED_UPDATE", mode = Mode.EXCLUDE) void test(MarshallableEntryType type) { @@ -144,6 +174,85 @@ public class CatalogEntrySerializationTest extends BaseIgniteAbstractTest { } } + @ParameterizedTest(name = "{0}") + @MethodSource("values") + public void testConstantDefaultAllTypes(ColumnType columnType, Object value) throws IOException { + ConstantValue val = (ConstantValue) DefaultValue.constant(value); + + log.info("{}: {}", columnType, value); + + try (IgniteUnsafeDataOutput os = new IgniteUnsafeDataOutput(128)) { + DefaultValue.writeTo(val, os); + + try (IgniteUnsafeDataInput in = new IgniteUnsafeDataInput(os.internalArray())) { + DefaultValue actual = DefaultValue.readFrom(in); + assertEquals(val, actual); + } + } + } + + private static Stream<Arguments> values() { + List<Object> list = new ArrayList<>(); + + list.add(null); + list.add(RND.nextBoolean()); + + list.add((byte) RND.nextInt()); + list.add((short) RND.nextInt()); + list.add(RND.nextInt()); + list.add(RND.nextLong()); + list.add((float) RND.nextDouble()); + list.add(RND.nextDouble()); + + list.add(BigDecimal.valueOf(RND.nextLong())); + list.add(BigDecimal.valueOf(RND.nextLong(), RND.nextInt(100))); + + list.add(BigInteger.valueOf(RND.nextLong())); + + list.add(LocalTime.of(RND.nextInt(24), RND.nextInt(60), RND.nextInt(60), RND.nextInt(100_000))); + list.add(LocalDate.of(RND.nextInt(4000) - 1000, RND.nextInt(12) + 1, RND.nextInt(27) + 1)); + list.add(LocalDateTime.of( + LocalDate.of(RND.nextInt(4000) - 1000, RND.nextInt(12) + 1, RND.nextInt(27) + 1), + LocalTime.of(RND.nextInt(24), RND.nextInt(60), RND.nextInt(60), RND.nextInt(100_000)) + )); + + byte[] bytes = new byte[RND.nextInt(1000)]; + RND.nextBytes(bytes); + list.add(Base64.getEncoder().encodeToString(bytes)); + + list.add(UUID.randomUUID()); + + // TODO Include ignored values to test after https://issues.apache.org/jira/browse/IGNITE-15200 + // list.add(Duration.of(11, ChronoUnit.HOURS)); + // list.add(Period.of(5, 4, 3)); + + BitSet bitSet = new BitSet(); + for (int i = 0; i < RND.nextInt(100); i++) { + int b = RND.nextInt(1024); + bitSet.set(b); + } + list.add(bitSet); + + return list.stream().map(val -> { + NativeType nativeType = NativeTypes.fromObject(val); + return Arguments.of(nativeType == null ? ColumnType.NULL : nativeType.spec().asColumnType(), val); + }); + } + + @Test + public void testFunctionCallDefault() throws IOException { + FunctionCall val = (FunctionCall) DefaultValue.functionCall("func"); + + try (IgniteUnsafeDataOutput os = new IgniteUnsafeDataOutput(128)) { + DefaultValue.writeTo(val, os); + + try (IgniteUnsafeDataInput in = new IgniteUnsafeDataInput(os.internalArray())) { + DefaultValue actual = DefaultValue.readFrom(in); + assertEquals(val, actual); + } + } + } + private void checkAlterZoneEntry() { CatalogStorageProfilesDescriptor profiles = new CatalogStorageProfilesDescriptor(List.of(new CatalogStorageProfileDescriptor("default"))); @@ -168,7 +277,7 @@ public class CatalogEntrySerializationTest extends BaseIgniteAbstractTest { private void checkAlterColumnEntry() { CatalogTableColumnDescriptor desc1 = newCatalogTableColumnDescriptor("c0", null); CatalogTableColumnDescriptor desc2 = - newCatalogTableColumnDescriptor("c1", DefaultValue.constant(new CustomDefaultValue(Integer.MAX_VALUE))); + newCatalogTableColumnDescriptor("c1", DefaultValue.constant(UUID.randomUUID())); CatalogTableColumnDescriptor desc3 = newCatalogTableColumnDescriptor("c2", DefaultValue.functionCall("function")); CatalogTableColumnDescriptor desc4 = newCatalogTableColumnDescriptor("c3", DefaultValue.constant(null)); @@ -373,19 +482,4 @@ public class CatalogEntrySerializationTest extends BaseIgniteAbstractTest { "default" ); } - - private static class CustomDefaultValue implements Serializable { - private static final long serialVersionUID = 0L; - - private final int field; - - CustomDefaultValue(int field) { - this.field = field; - } - - @Override - public String toString() { - return S.toString(this); - } - } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/io/IgniteDataInput.java b/modules/core/src/main/java/org/apache/ignite/internal/util/io/IgniteDataInput.java index f7f2d619e6..0722241f6e 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/io/IgniteDataInput.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/io/IgniteDataInput.java @@ -20,6 +20,16 @@ package org.apache.ignite.internal.util.io; import java.io.DataInput; import java.io.IOException; import java.io.InputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Period; +import java.util.BitSet; +import java.util.UUID; /** * Extended data input. @@ -149,6 +159,86 @@ public interface IgniteDataInput extends DataInput { */ char[] readCharArray(int length) throws IOException; + /** + * Reads big integer. + * + * @return Big integer. + * @throws IOException In case of error. + */ + BigInteger readBigInteger() throws IOException; + + /** + * Reads big decimal. + * + * @return Big decimal. + * @throws IOException In case of error. + */ + BigDecimal readBigDecimal() throws IOException; + + /** + * Reads local time. + * + * @return Local time. + * @throws IOException In case of error. + */ + LocalTime readLocalTime() throws IOException; + + /** + * Reads local date. + * + * @return Local date. + * @throws IOException In case of error. + */ + LocalDate readLocalDate() throws IOException; + + /** + * Reads local date time. + * + * @return Local date time. + * @throws IOException In case of error. + */ + LocalDateTime readLocalDateTime() throws IOException; + + /** + * Reads instant. + * + * @return Instant. + * @throws IOException In case of error. + */ + Instant readInstant() throws IOException; + + /** + * Reads duration. + * + * @return Duration. + * @throws IOException In case of error. + */ + Duration readDuration() throws IOException; + + /** + * Reads period. + * + * @return Period. + * @throws IOException In case of error. + */ + Period readPeriod() throws IOException; + + /** + * Reads uuid. + * + * @return UUID. + * @throws IOException In case of error. + */ + UUID readUuid() throws IOException; + + /** + * Reads bit set. + * + * @return Bit set. + * @throws IOException In case of error. + */ + BitSet readBitSet() throws IOException; + /** * Reads the requested number of bytes from the input stream into the given * byte array. This method blocks until {@code len} bytes of input data have diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/io/IgniteDataOutput.java b/modules/core/src/main/java/org/apache/ignite/internal/util/io/IgniteDataOutput.java index 7fd631f230..c1d54f24d8 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/io/IgniteDataOutput.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/io/IgniteDataOutput.java @@ -20,6 +20,16 @@ package org.apache.ignite.internal.util.io; import java.io.DataOutput; import java.io.IOException; import java.io.OutputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Period; +import java.util.BitSet; +import java.util.UUID; /** * Extended data output. @@ -129,6 +139,86 @@ public interface IgniteDataOutput extends DataOutput { */ void writeCharArray(char[] arr) throws IOException; + /** + * Writes big integer. + * + * @param val Big integer. + * @throws IOException In case of error. + */ + void writeBigInteger(BigInteger val) throws IOException; + + /** + * Writes decimal. + * + * @param val Big decimal. + * @throws IOException In case of error. + */ + void writeBigDecimal(BigDecimal val) throws IOException; + + /** + * Writes local time. + * + * @param val Local time. + * @throws IOException In case of error. + */ + void writeLocalTime(LocalTime val) throws IOException; + + /** + * Writes local date. + * + * @param date Local date. + * @throws IOException In case of error. + */ + void writeLocalDate(LocalDate date) throws IOException; + + /** + * Writes local date time. + * + * @param val Local date time. + * @throws IOException In case of error. + */ + void writeLocalDateTime(LocalDateTime val) throws IOException; + + /** + * Writes instant. + * + * @param val Instant. + * @throws IOException In case of error. + */ + void writeInstant(Instant val) throws IOException; + + /** + * Writes period. + * + * @param val Period. + * @throws IOException In case of error. + */ + void writePeriod(Period val) throws IOException; + + /** + * Writes duration. + * + * @param val Duration. + * @throws IOException In case of error. + */ + void writeDuration(Duration val) throws IOException; + + /** + * Writes uuid. + * + * @param val UUID. + * @throws IOException In case of error. + */ + void writeUuid(UUID val) throws IOException; + + /** + * Writes bit set. + * + * @param val Bit set. + * @throws IOException In case of error. + */ + void writeBitSet(BitSet val) throws IOException; + /** * Flushes the output. This flushes the interlying {@link OutputStream} (if exists). * diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/io/IgniteUnsafeDataInput.java b/modules/core/src/main/java/org/apache/ignite/internal/util/io/IgniteUnsafeDataInput.java index 012c9ecac6..d3df8eea62 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/io/IgniteUnsafeDataInput.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/io/IgniteUnsafeDataInput.java @@ -30,7 +30,18 @@ import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.UTFDataFormatException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Period; +import java.util.BitSet; import java.util.Objects; +import java.util.UUID; import org.apache.ignite.internal.tostring.IgniteToStringBuilder; import org.apache.ignite.internal.tostring.IgniteToStringExclude; import org.apache.ignite.internal.util.FastTimestamps; @@ -371,6 +382,105 @@ public class IgniteUnsafeDataInput extends InputStream implements IgniteDataInpu return arr; } + /** {@inheritDoc} */ + @Override + public BigInteger readBigInteger() throws IOException { + int length = readInt(); + byte[] bytes = readByteArray(length); + return new BigInteger(bytes); + } + + /** {@inheritDoc} */ + @Override + public BigDecimal readBigDecimal() throws IOException { + short scale = readShort(); + BigInteger bigInteger = readBigInteger(); + + return new BigDecimal(bigInteger, scale); + } + + /** {@inheritDoc} */ + @Override + public LocalTime readLocalTime() throws IOException { + byte hour = readByte(); + byte minute = readByte(); + byte second = readByte(); + int nano = readInt(); + + return LocalTime.of(hour, minute, second, nano); + } + + /** {@inheritDoc} */ + @Override + public LocalDate readLocalDate() throws IOException { + int year = readInt(); + short month = readShort(); + short day = readShort(); + + return LocalDate.of(year, month, day); + } + + /** {@inheritDoc} */ + @Override + public LocalDateTime readLocalDateTime() throws IOException { + int year = readInt(); + short month = readShort(); + short day = readShort(); + byte hour = readByte(); + byte minute = readByte(); + byte second = readByte(); + int nano = readInt(); + + return LocalDateTime.of(year, month, day, hour, minute, second, nano); + } + + /** {@inheritDoc} */ + @Override + public Instant readInstant() throws IOException { + long epochSecond = readLong(); + int nano = readInt(); + return Instant.ofEpochSecond(epochSecond, nano); + } + + + /** {@inheritDoc} */ + @Override + public Duration readDuration() throws IOException { + long seconds = readLong(); + int nano = readInt(); + + return Duration.ofSeconds(seconds, nano); + } + + /** {@inheritDoc} */ + @Override + public Period readPeriod() throws IOException { + int years = readInt(); + int months = readInt(); + int days = readInt(); + + return Period.of(years, months, days); + } + + /** {@inheritDoc} */ + @Override + public UUID readUuid() throws IOException { + int length = readByte(); + byte[] bytes = readByteArray(length); + + return UUID.fromString(new String(bytes, StandardCharsets.UTF_8)); + } + + /** {@inheritDoc} */ + @Override + public BitSet readBitSet() throws IOException { + int length = readInt(); + byte[] bytes = readByteArray(length); + + return BitSet.valueOf(bytes); + } + + /** {@inheritDoc} */ @Override public long[] readLongArray(int arrSize) throws IOException { diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/io/IgniteUnsafeDataOutput.java b/modules/core/src/main/java/org/apache/ignite/internal/util/io/IgniteUnsafeDataOutput.java index d7f93dd132..7dee8c054d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/io/IgniteUnsafeDataOutput.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/io/IgniteUnsafeDataOutput.java @@ -28,7 +28,18 @@ import static org.apache.ignite.internal.util.GridUnsafe.SHORT_ARR_OFF; import java.io.IOException; import java.io.OutputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Period; import java.util.Arrays; +import java.util.BitSet; +import java.util.UUID; import org.apache.ignite.internal.tostring.IgniteToStringBuilder; import org.apache.ignite.internal.util.FastTimestamps; import org.apache.ignite.internal.util.GridUnsafe; @@ -322,6 +333,116 @@ public class IgniteUnsafeDataOutput extends OutputStream implements IgniteDataOu onWrite(bytesToCp); } + /** {@inheritDoc} */ + @Override + public void writeBigInteger(BigInteger val) throws IOException { + byte[] bytes = val.toByteArray(); + writeInt(bytes.length); + writeByteArray(bytes); + } + + /** {@inheritDoc} */ + @Override + public void writeBigDecimal(BigDecimal val) throws IOException { + short scale = (short) val.scale(); + byte[] bytes = val.unscaledValue().toByteArray(); + + writeShort(scale); + writeInt(bytes.length); + writeByteArray(bytes); + } + + /** {@inheritDoc} */ + @Override + public void writeLocalTime(LocalTime val) throws IOException { + byte hour = (byte) val.getHour(); + byte minute = (byte) val.getMinute(); + byte second = (byte) val.getSecond(); + int nano = val.getNano(); + + writeByte(hour); + writeByte(minute); + writeByte(second); + writeInt(nano); + } + + /** {@inheritDoc} */ + @Override + public void writeLocalDate(LocalDate date) throws IOException { + int year = date.getYear(); + short month = (short) date.getMonth().getValue(); + short day = (short) date.getDayOfMonth(); + + writeInt(year); + writeShort(month); + writeShort(day); + } + + /** {@inheritDoc} */ + @Override + public void writeLocalDateTime(LocalDateTime val) throws IOException { + int year = val.getYear(); + short month = (short) val.getMonth().getValue(); + short day = (short) val.getDayOfMonth(); + + byte hour = (byte) val.getHour(); + byte minute = (byte) val.getMinute(); + byte second = (byte) val.getSecond(); + int nano = val.getNano(); + + writeInt(year); + writeShort(month); + writeShort(day); + + writeByte(hour); + writeByte(minute); + writeByte(second); + writeInt(nano); + } + + /** {@inheritDoc} */ + @Override + public void writeInstant(Instant val) throws IOException { + long epochSecond = val.getEpochSecond(); + int nano = val.getNano(); + + writeLong(epochSecond); + writeInt(nano); + } + + /** {@inheritDoc} */ + @Override + public void writePeriod(Period val) throws IOException { + writeInt(val.getYears()); + writeInt(val.getMonths()); + writeInt(val.getDays()); + } + + /** {@inheritDoc} */ + @Override + public void writeDuration(Duration val) throws IOException { + writeLong(val.getSeconds()); + writeInt(val.getNano()); + } + + /** {@inheritDoc} */ + @Override + public void writeUuid(UUID val) throws IOException { + byte[] bytes = val.toString().getBytes(StandardCharsets.US_ASCII); + + writeByte(bytes.length); + writeByteArray(bytes); + } + + /** {@inheritDoc} */ + @Override + public void writeBitSet(BitSet val) throws IOException { + byte[] bytes = val.toByteArray(); + + writeInt(bytes.length); + writeByteArray(bytes); + } + /** {@inheritDoc} */ @Override public void writeLongArray(long[] arr) throws IOException { diff --git a/modules/core/src/test/java/org/apache/ignite/internal/util/io/IgniteUnsafeDataInputOutputByteOrderTest.java b/modules/core/src/test/java/org/apache/ignite/internal/util/io/IgniteUnsafeDataInputOutputByteOrderTest.java index fabbb56ba9..34b1c804eb 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/util/io/IgniteUnsafeDataInputOutputByteOrderTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/util/io/IgniteUnsafeDataInputOutputByteOrderTest.java @@ -27,7 +27,19 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Period; +import java.util.BitSet; import java.util.Random; +import java.util.UUID; +import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -35,15 +47,22 @@ import org.junit.jupiter.api.Test; /** * Ignite unsafe data input/output byte order sanity tests. */ -class IgniteUnsafeDataInputOutputByteOrderTest { +class IgniteUnsafeDataInputOutputByteOrderTest extends BaseIgniteAbstractTest { /** Array length. */ private static final int ARR_LEN = 16; /** Length bytes. */ private static final int LEN_BYTES = 0; + private static final long SEED = System.nanoTime(); + /** Rnd. */ - private static final Random RND = new Random(); + private static final Random RND = new Random(SEED); + + @BeforeEach + public void setup() { + log.info("Seed: {}", SEED); + } /** Out. */ private IgniteUnsafeDataOutput out; @@ -237,4 +256,101 @@ class IgniteUnsafeDataInputOutputByteOrderTest { assertArrayEquals(arr, in.readDoubleArray(ARR_LEN), 0); } + + @Test + public void testLocalTime() throws IOException { + LocalTime val = LocalTime.of(RND.nextInt(24), RND.nextInt(60), RND.nextInt(60), RND.nextInt(10000)); + + out.writeLocalTime(val); + + assertEquals(val, in.readLocalTime()); + } + + @Test + public void testLocalDate() throws IOException { + LocalDate val = LocalDate.of(RND.nextInt(4000) - 1000, RND.nextInt(12) + 1, 1 + RND.nextInt(27)); + + out.writeLocalDate(val); + + assertEquals(val, in.readLocalDate()); + } + + @Test + public void testLocalDateTime() throws IOException { + LocalTime time = LocalTime.of(RND.nextInt(24), RND.nextInt(60), RND.nextInt(60), RND.nextInt(10000)); + LocalDate date = LocalDate.of(RND.nextInt(4000) - 1000, RND.nextInt(12) + 1, 1 + RND.nextInt(27)); + LocalDateTime val = LocalDateTime.of(date, time); + + out.writeLocalDateTime(val); + + assertEquals(val, in.readLocalDateTime()); + } + + @Test + public void testInstant() throws IOException { + Instant val = Instant.ofEpochMilli(RND.nextLong()); + + out.writeInstant(val); + + assertEquals(val, in.readInstant()); + } + + @Test + public void testBigInteger() throws IOException { + BigInteger val = BigInteger.valueOf(RND.nextLong()); + + out.writeBigInteger(val); + + assertEquals(val, in.readBigInteger()); + } + + @Test + public void testBigDecimal() throws IOException { + BigDecimal val = new BigDecimal(BigInteger.valueOf(RND.nextLong()), RND.nextInt(120)); + + out.writeBigDecimal(val); + + assertEquals(val, in.readBigDecimal()); + } + + @Test + public void testPeriod() throws IOException { + Period val = Period.of(RND.nextInt(10_000), RND.nextInt(10_000), RND.nextInt(10_000)); + + out.writePeriod(val); + + assertEquals(val, in.readPeriod()); + } + + @Test + public void testDuration() throws IOException { + Duration val = Duration.ofSeconds(RND.nextInt(100_000), RND.nextInt(100_000)); + + out.writeDuration(val); + + assertEquals(val, in.readDuration()); + } + + @Test + public void testUuid() throws IOException { + UUID val = UUID.randomUUID(); + + out.writeUuid(val); + + assertEquals(val, in.readUuid()); + } + + @Test + public void testBitSet() throws IOException { + BitSet val = new BitSet(); + + for (int i = 0; i < RND.nextInt(100); i++) { + int b = RND.nextInt(256); + val.set(Math.abs(b)); + } + + out.writeBitSet(val); + + assertEquals(val, in.readBitSet()); + } }