This is an automated email from the ASF dual-hosted git repository.
fokko pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/avro.git
The following commit(s) were added to refs/heads/main by this push:
new b0e44bdf6 AVRO-3918: Add UUID with `fixed[16]` (#2652)
b0e44bdf6 is described below
commit b0e44bdf6195714465c651e57b11747d68ad405c
Author: Christophe Le Saec <[email protected]>
AuthorDate: Fri Feb 2 12:19:13 2024 +0100
AVRO-3918: Add UUID with `fixed[16]` (#2652)
* AVRO-3918: add uuid with bytes and fixed
* AVRO-3918: add licence
* AVRO-3918: change spec
* AVRO-3918: force big endian mode for long value
* AVRO-3918: remove inefficient uuid bytes storage
* AVRO-3918: enforce network byte order
As stated in RFC 4122 section 4.1.2, UUIDs are in network byte order.
Also added a test for string based UUID conversion.
* Use buffer instead
---------
Co-authored-by: Oscar Westra van Holthe - Kind <[email protected]>
Co-authored-by: Fokko Driesprong <[email protected]>
---
.../src/main/java/org/apache/avro/Conversions.java | 16 +++++
.../main/java/org/apache/avro/LogicalTypes.java | 10 ++-
.../test/java/org/apache/avro/TestLogicalType.java | 2 +-
.../java/org/apache/avro/TestUuidConversions.java | 72 ++++++++++++++++++++++
4 files changed, 97 insertions(+), 3 deletions(-)
diff --git a/lang/java/avro/src/main/java/org/apache/avro/Conversions.java
b/lang/java/avro/src/main/java/org/apache/avro/Conversions.java
index 043ddfa07..99ad50064 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/Conversions.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/Conversions.java
@@ -68,6 +68,22 @@ public class Conversions {
public CharSequence toCharSequence(UUID value, Schema schema, LogicalType
type) {
return value.toString();
}
+
+ @Override
+ public UUID fromFixed(final GenericFixed value, final Schema schema, final
LogicalType type) {
+ ByteBuffer buffer = ByteBuffer.wrap(value.bytes());
+ long mostSigBits = buffer.getLong();
+ long leastSigBits = buffer.getLong();
+ return new UUID(mostSigBits, leastSigBits);
+ }
+
+ @Override
+ public GenericFixed toFixed(final UUID value, final Schema schema, final
LogicalType type) {
+ ByteBuffer buffer = ByteBuffer.allocate(2 * Long.BYTES);
+ buffer.putLong(value.getMostSignificantBits());
+ buffer.putLong(value.getLeastSignificantBits());
+ return new GenericData.Fixed(schema, buffer.array());
+ }
}
public static class DecimalConversion extends Conversion<BigDecimal> {
diff --git a/lang/java/avro/src/main/java/org/apache/avro/LogicalTypes.java
b/lang/java/avro/src/main/java/org/apache/avro/LogicalTypes.java
index 643760af9..6a894f051 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/LogicalTypes.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/LogicalTypes.java
@@ -289,6 +289,9 @@ public class LogicalTypes {
/** Uuid represents a uuid without a time */
public static class Uuid extends LogicalType {
+
+ private static final int UUID_BYTES = 2 * Long.BYTES;
+
private Uuid() {
super(UUID);
}
@@ -296,8 +299,11 @@ public class LogicalTypes {
@Override
public void validate(Schema schema) {
super.validate(schema);
- if (schema.getType() != Schema.Type.STRING) {
- throw new IllegalArgumentException("Uuid can only be used with an
underlying string type");
+ if (schema.getType() != Schema.Type.STRING && schema.getType() !=
Schema.Type.FIXED) {
+ throw new IllegalArgumentException("Uuid can only be used with an
underlying string or fixed type");
+ }
+ if (schema.getType() == Schema.Type.FIXED && schema.getFixedSize() !=
UUID_BYTES) {
+ throw new IllegalArgumentException("Uuid with fixed type must have a
size of " + UUID_BYTES + " bytes");
}
}
}
diff --git a/lang/java/avro/src/test/java/org/apache/avro/TestLogicalType.java
b/lang/java/avro/src/test/java/org/apache/avro/TestLogicalType.java
index 4476ac7db..733997db2 100644
--- a/lang/java/avro/src/test/java/org/apache/avro/TestLogicalType.java
+++ b/lang/java/avro/src/test/java/org/apache/avro/TestLogicalType.java
@@ -208,7 +208,7 @@ public class TestLogicalType {
assertEquals(LogicalTypes.uuid(), uuidSchema.getLogicalType());
assertThrows("UUID requires a string", IllegalArgumentException.class,
- "Uuid can only be used with an underlying string type",
+ "Uuid can only be used with an underlying string or fixed type",
() -> LogicalTypes.uuid().addToSchema(Schema.create(Schema.Type.INT)));
}
diff --git
a/lang/java/avro/src/test/java/org/apache/avro/TestUuidConversions.java
b/lang/java/avro/src/test/java/org/apache/avro/TestUuidConversions.java
new file mode 100644
index 000000000..640bf1a2b
--- /dev/null
+++ b/lang/java/avro/src/test/java/org/apache/avro/TestUuidConversions.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.avro;
+
+import org.apache.avro.generic.GenericFixed;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.math.BigInteger;
+import java.util.UUID;
+import java.util.stream.Stream;
+
+public class TestUuidConversions {
+
+ private Conversions.UUIDConversion uuidConversion = new
Conversions.UUIDConversion();
+
+ private Schema fixed = Schema.createFixed("fixed", "doc", "", Long.BYTES *
2);
+ private Schema fixedUuid = LogicalTypes.uuid().addToSchema(fixed);
+
+ private Schema string = Schema.createFixed("fixed", "doc", "", Long.BYTES *
2);
+ private Schema stringUuid = LogicalTypes.uuid().addToSchema(string);
+
+ @ParameterizedTest
+ @MethodSource("uuidData")
+ void uuidFixed(UUID uuid) {
+ GenericFixed value = uuidConversion.toFixed(uuid, fixedUuid,
LogicalTypes.uuid());
+
+ byte[] b = new byte[Long.BYTES];
+ System.arraycopy(value.bytes(), 0, b, 0, b.length);
+ Assertions.assertEquals(uuid.getMostSignificantBits(), new
BigInteger(b).longValue());
+ System.arraycopy(value.bytes(), Long.BYTES, b, 0, b.length);
+ Assertions.assertEquals(uuid.getLeastSignificantBits(), new
BigInteger(b).longValue());
+
+ UUID uuid1 = uuidConversion.fromFixed(value, fixedUuid,
LogicalTypes.uuid());
+ Assertions.assertEquals(uuid, uuid1);
+ }
+
+ @ParameterizedTest
+ @MethodSource("uuidData")
+ void uuidCharSequence(UUID uuid) {
+ CharSequence value = uuidConversion.toCharSequence(uuid, stringUuid,
LogicalTypes.uuid());
+
+ Assertions.assertEquals(uuid.toString(), value.toString());
+
+ UUID uuid1 = uuidConversion.fromCharSequence(value, stringUuid,
LogicalTypes.uuid());
+ Assertions.assertEquals(uuid, uuid1);
+ }
+
+ public static Stream<Arguments> uuidData() {
+ return Stream.of(Arguments.of(new UUID(Long.MIN_VALUE, Long.MAX_VALUE)),
Arguments.of(new UUID(-1, 0)),
+ Arguments.of(UUID.randomUUID()), Arguments.of(UUID.randomUUID()));
+ }
+
+}