Skip to content

Commit

Permalink
AVRO-3918: Add UUID with fixed[16] (#2652)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
3 people committed Feb 2, 2024
1 parent e6eef2b commit b0e44bd
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 3 deletions.
16 changes: 16 additions & 0 deletions lang/java/avro/src/main/java/org/apache/avro/Conversions.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,22 @@ public UUID fromCharSequence(CharSequence value, Schema schema, LogicalType type
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> {
Expand Down
10 changes: 8 additions & 2 deletions lang/java/avro/src/main/java/org/apache/avro/LogicalTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -289,15 +289,21 @@ public static LocalTimestampNanos localTimestampNanos() {

/** 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);
}

@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");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ void uuidExtendsString() {
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)));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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()));
}

}

0 comments on commit b0e44bd

Please sign in to comment.