diff --git a/pinot-spi/src/main/java/org/apache/pinot/spi/data/FieldSpec.java b/pinot-spi/src/main/java/org/apache/pinot/spi/data/FieldSpec.java index 511d26744358..c5c3a491f6c1 100644 --- a/pinot-spi/src/main/java/org/apache/pinot/spi/data/FieldSpec.java +++ b/pinot-spi/src/main/java/org/apache/pinot/spi/data/FieldSpec.java @@ -37,7 +37,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.UUID; import javax.annotation.Nullable; import org.apache.pinot.spi.utils.BooleanUtils; import org.apache.pinot.spi.utils.ByteArray; @@ -419,8 +418,9 @@ public Object getDefaultNullValue() { return _defaultNullValue; } + @JsonIgnore public String getDefaultNullValueString() { - return _dataType != null ? _dataType.toString(_defaultNullValue) : getStringValue(_defaultNullValue); + return _dataType.toString(_defaultNullValue); } /// Returns the [String] representation of the given object. @@ -462,22 +462,13 @@ private void setDefaultNullValue(@Nullable JsonNode defaultNullValue) { @JsonIgnore public void setDefaultNullValue(@Nullable Object defaultNullValue) { if (defaultNullValue != null) { - _stringDefaultNullValue = getStringDefaultNullValue(defaultNullValue); + _stringDefaultNullValue = getStringValue(defaultNullValue); } if (_dataType != null) { _defaultNullValue = getDefaultNullValue(getFieldType(), _dataType, _stringDefaultNullValue); } } - // UUID default values must stringify to their canonical 8-4-4-4-12 form (not raw hex) so they round-trip back - // through getDefaultNullValue(...); all other types keep the generic getStringValue() form. - private String getStringDefaultNullValue(Object defaultNullValue) { - if (_dataType == DataType.UUID && !(defaultNullValue instanceof String)) { - return _dataType.toString(defaultNullValue); - } - return getStringValue(defaultNullValue); - } - public static Object getDefaultNullValue(FieldType fieldType, DataType dataType, @Nullable String stringDefaultNullValue) { if (stringDefaultNullValue != null) { @@ -748,9 +739,25 @@ public enum FieldType { DIMENSION, METRIC, TIME, DATE_TIME, COMPLEX } - /** - * The DataType enum is used to demonstrate the data type of a field. - */ + /// The `DataType` enum represents the data type of a field. + /// + /// A value of a given type is held in memory as a fixed Java class, and every value-handling method on this enum + /// (`convert`, `equals`, `hashCode`, `compare`, `toString`) expects and produces that representation: + /// - `INT` → [Integer] + /// - `LONG` → [Long] + /// - `FLOAT` → [Float] + /// - `DOUBLE` → [Double] + /// - `BIG_DECIMAL` → [BigDecimal] + /// - `BOOLEAN` → [Integer] (`0` or `1`) + /// - `TIMESTAMP` → [Long] (epoch millis) + /// - `STRING` / `JSON` → [String] + /// - `BYTES` → `byte[]` + /// - `UUID` → `byte[]` (fixed 16-byte big-endian form) + /// - `MAP` / `OPEN_STRUCT` → [Map] + /// - `LIST` → [List] + /// + /// `convertInternal` is the exception: it returns the internal storage form, which for `BYTES` and `UUID` is + /// [ByteArray] rather than `byte[]`. @SuppressWarnings("rawtypes") public enum DataType { // LIST is for complex lists which is different from multi-value column of primitives @@ -881,23 +888,16 @@ public Object convert(String value) { throw new IllegalStateException(); } } catch (Exception e) { - throw new IllegalArgumentException( - "Cannot convert value: '" + value + "' to type: " + this + " (" + e.getMessage() + ")", e); + throw new IllegalArgumentException("Cannot convert value: '" + value + "' to type: " + this); } } public boolean equals(Object value1, Object value2) { - if (this == UUID) { - return UuidUtils.equals(toBytesValue(value1), toBytesValue(value2)); - } - return this == BYTES ? Arrays.equals(toBytesValue(value1), toBytesValue(value2)) : value1.equals(value2); + return this == BYTES || this == UUID ? Arrays.equals((byte[]) value1, (byte[]) value2) : value1.equals(value2); } public int hashCode(Object value) { - if (this == UUID) { - return UuidUtils.hashCode(toBytesValue(value)); - } - return this == BYTES ? Arrays.hashCode(toBytesValue(value)) : value.hashCode(); + return this == BYTES || this == UUID ? Arrays.hashCode((byte[]) value) : value.hashCode(); } /** @@ -910,8 +910,10 @@ public int hashCode(Object value) { public int compare(Object value1, Object value2) { switch (this) { case INT: + case BOOLEAN: return Integer.compare((int) value1, (int) value2); case LONG: + case TIMESTAMP: return Long.compare((long) value1, (long) value2); case FLOAT: return Float.compare((float) value1, (float) value2); @@ -919,17 +921,12 @@ public int compare(Object value1, Object value2) { return Double.compare((double) value1, (double) value2); case BIG_DECIMAL: return ((BigDecimal) value1).compareTo((BigDecimal) value2); - case BOOLEAN: - return Boolean.compare((boolean) value1, (boolean) value2); - case TIMESTAMP: - return Long.compare((long) value1, (long) value2); case STRING: case JSON: return ((String) value1).compareTo((String) value2); case BYTES: - return ByteArray.compare(toBytesValue(value1), toBytesValue(value2)); case UUID: - return UuidUtils.compare(toBytesValue(value1), toBytesValue(value2)); + return ByteArray.compare((byte[]) value1, (byte[]) value2); case MAP: case OPEN_STRUCT: case LIST: @@ -946,14 +943,11 @@ public String toString(Object value) { if (this == BIG_DECIMAL) { return ((BigDecimal) value).toPlainString(); } - if (this == UUID) { - if (value instanceof UUID) { - return UuidUtils.toString((UUID) value); - } - return UuidUtils.toString(toBytesValue(value)); - } if (this == BYTES) { - return BytesUtils.toHexString(toBytesValue(value)); + return BytesUtils.toHexString((byte[]) value); + } + if (this == UUID) { + return UuidUtils.toString((byte[]) value); } if (this == MAP || this == OPEN_STRUCT || this == LIST) { try { @@ -1000,25 +994,8 @@ public Comparable convertInternal(String value) { throw new IllegalStateException(); } } catch (Exception e) { - throw new IllegalArgumentException( - "Cannot convert value: '" + value + "' to type: " + this + " (" + e.getMessage() + ")", e); - } - } - - private byte[] toBytesValue(Object value) { - if (value instanceof byte[]) { - return (byte[]) value; - } - if (value instanceof ByteArray) { - return ((ByteArray) value).getBytes(); - } - if (value instanceof UUID) { - return UuidUtils.toBytes((UUID) value); - } - if (value instanceof String && this == UUID) { - return UuidUtils.toBytes((String) value); + throw new IllegalArgumentException("Cannot convert value: '" + value + "' to type: " + this); } - throw new IllegalArgumentException("Unsupported value for " + this + ": " + value); } } diff --git a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/PinotDataType.java b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/PinotDataType.java index 15a950000e0a..5f987425aa6b 100644 --- a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/PinotDataType.java +++ b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/PinotDataType.java @@ -112,6 +112,11 @@ public byte[] toBytes(Object value) { throw new UnsupportedOperationException("Cannot convert value from BOOLEAN to BYTES"); } + @Override + public UUID toUUID(Object value) { + throw new UnsupportedOperationException("Cannot convert value from BOOLEAN to UUID"); + } + @Override public Boolean convert(Object value, PinotDataType sourceType) { return sourceType.toBoolean(value); @@ -168,6 +173,11 @@ public String toString(Object value) { public byte[] toBytes(Object value) { throw new UnsupportedOperationException("Cannot convert value from BYTE to BYTES"); } + + @Override + public UUID toUUID(Object value) { + throw new UnsupportedOperationException("Cannot convert value from BYTE to UUID"); + } }, CHARACTER { @@ -215,6 +225,11 @@ public String toString(Object value) { public byte[] toBytes(Object value) { throw new UnsupportedOperationException("Cannot convert value from CHARACTER to BYTES"); } + + @Override + public UUID toUUID(Object value) { + throw new UnsupportedOperationException("Cannot convert value from CHARACTER to UUID"); + } }, SHORT { @@ -262,6 +277,11 @@ public String toString(Object value) { public byte[] toBytes(Object value) { throw new UnsupportedOperationException("Cannot convert value from SHORT to BYTES"); } + + @Override + public UUID toUUID(Object value) { + throw new UnsupportedOperationException("Cannot convert value from SHORT to UUID"); + } }, INT { @@ -310,6 +330,11 @@ public byte[] toBytes(Object value) { throw new UnsupportedOperationException("Cannot convert value from INT to BYTES"); } + @Override + public UUID toUUID(Object value) { + throw new UnsupportedOperationException("Cannot convert value from INT to UUID"); + } + @Override public Integer convert(Object value, PinotDataType sourceType) { return sourceType.toInt(value); @@ -365,6 +390,11 @@ public byte[] toBytes(Object value) { throw new UnsupportedOperationException("Cannot convert value from LONG to BYTES"); } + @Override + public UUID toUUID(Object value) { + throw new UnsupportedOperationException("Cannot convert value from LONG to UUID"); + } + @Override public Long convert(Object value, PinotDataType sourceType) { return sourceType.toLong(value); @@ -419,6 +449,11 @@ public byte[] toBytes(Object value) { throw new UnsupportedOperationException("Cannot convert value from FLOAT to BYTES"); } + @Override + public UUID toUUID(Object value) { + throw new UnsupportedOperationException("Cannot convert value from FLOAT to UUID"); + } + @Override public Float convert(Object value, PinotDataType sourceType) { return sourceType.toFloat(value); @@ -473,6 +508,11 @@ public byte[] toBytes(Object value) { throw new UnsupportedOperationException("Cannot convert value from DOUBLE to BYTES"); } + @Override + public UUID toUUID(Object value) { + throw new UnsupportedOperationException("Cannot convert value from DOUBLE to UUID"); + } + @Override public Double convert(Object value, PinotDataType sourceType) { return sourceType.toDouble(value); @@ -525,6 +565,11 @@ public byte[] toBytes(Object value) { return BigDecimalUtils.serialize((BigDecimal) value); } + @Override + public UUID toUUID(Object value) { + throw new UnsupportedOperationException("Cannot convert value from BIG_DECIMAL to UUID"); + } + @Override public BigDecimal convert(Object value, PinotDataType sourceType) { return sourceType.toBigDecimal(value); @@ -588,6 +633,11 @@ public byte[] toBytes(Object value) { throw new UnsupportedOperationException("Cannot convert value from TIMESTAMP to BYTES"); } + @Override + public UUID toUUID(Object value) { + throw new UnsupportedOperationException("Cannot convert value from TIMESTAMP to UUID"); + } + @Override public Timestamp convert(Object value, PinotDataType sourceType) { return sourceType.toTimestamp(value); @@ -654,6 +704,11 @@ public byte[] toBytes(Object value) { throw new UnsupportedOperationException("Cannot convert value from DATE to BYTES"); } + @Override + public UUID toUUID(Object value) { + throw new UnsupportedOperationException("Cannot convert value from DATE to UUID"); + } + @Override public LocalDate convert(Object value, PinotDataType sourceType) { switch (sourceType) { @@ -741,6 +796,11 @@ public byte[] toBytes(Object value) { throw new UnsupportedOperationException("Cannot convert value from TIME to BYTES"); } + @Override + public UUID toUUID(Object value) { + throw new UnsupportedOperationException("Cannot convert value from TIME to UUID"); + } + @Override public LocalTime convert(Object value, PinotDataType sourceType) { switch (sourceType) { @@ -890,6 +950,17 @@ public byte[] toBytes(Object value) { } } + @Override + public UUID toUUID(Object value) { + // JSON-encoded UUID is a quoted JSON string; Jackson's UUIDDeserializer handles both the canonical-string form + // and 16-byte binary form natively. + try { + return JsonUtils.stringToObject(value.toString(), UUID.class); + } catch (IOException e) { + throw new RuntimeException("Cannot parse JSON value as UUID: " + value, e); + } + } + @Override public String convert(Object value, PinotDataType sourceType) { return sourceType.toJson(value); @@ -944,7 +1015,7 @@ public byte[] toBytes(Object value) { @Override public UUID toUUID(Object value) { - return UuidUtils.toUUID(value); + return UuidUtils.toUUID((byte[]) value); } @Override @@ -1001,52 +1072,22 @@ public Timestamp toTimestamp(Object value) { @Override public String toString(Object value) { - return UuidUtils.toUUID(value).toString(); + return value.toString(); } @Override public byte[] toBytes(Object value) { - return UuidUtils.toBytes(value); + return UuidUtils.toBytes((UUID) value); } @Override public UUID toUUID(Object value) { - return UuidUtils.toUUID(value); + return (UUID) value; } @Override public UUID convert(Object value, PinotDataType sourceType) { - switch (sourceType) { - case UUID: - return UuidUtils.toUUID(value); - case STRING: - return UuidUtils.toUUID(value.toString().trim()); - case JSON: - try { - // JSON-encoded UUID is a quoted JSON string; Jackson's UUIDDeserializer handles both the - // canonical-string form and 16-byte binary form natively. - return JsonUtils.stringToObject(value.toString(), UUID.class); - } catch (IOException e) { - throw new RuntimeException("Cannot parse JSON value as UUID: " + value, e); - } - case BYTES: - return UuidUtils.toUUID(value); - case OBJECT: - case COLLECTION: - case UUID_ARRAY: - case BYTES_ARRAY: - case STRING_ARRAY: - case OBJECT_ARRAY: - // OBJECT, collections and single-element arrays can carry a UUID (e.g., scalar-function arguments - // resolved by FunctionInvoker, or an MV source feeding an SV UUID column); these types have meaningful - // toUUID implementations. - return sourceType.toUUID(value); - default: - // Numeric and other types have no meaningful UUID interpretation; fail with an explicit message instead - // of the confusing "There is no single-value type ..." from the generic single-value fallback. - throw new UnsupportedOperationException( - "Cannot convert value from " + sourceType + " to UUID. Input value: " + value); - } + return sourceType.toUUID(value); } @Override @@ -1131,6 +1172,11 @@ public byte[] toBytes(Object value) { return MapUtils.serializeMap((Map) value); } + @Override + public UUID toUUID(Object value) { + throw new UnsupportedOperationException("Cannot convert value from MAP to UUID"); + } + @Override public Object convert(Object value, PinotDataType sourceType) { switch (sourceType) { @@ -1272,7 +1318,7 @@ public Integer[] toInternal(Object value) { TIMESTAMP_ARRAY { @Override - public Object convert(Object value, PinotDataType sourceType) { + public Timestamp[] convert(Object value, PinotDataType sourceType) { return sourceType.toTimestampArray(value); } @@ -1342,13 +1388,19 @@ public byte[][] convert(Object value, PinotDataType sourceType) { UUID_ARRAY { @Override - public byte[][] convert(Object value, PinotDataType sourceType) { - return sourceType.toUuidBytesArray(value); + public UUID[] convert(Object value, PinotDataType sourceType) { + return sourceType.toUuidArray(value); } @Override public byte[][] toInternal(Object value) { - return toUuidBytesArray(value); + UUID[] uuidArray = (UUID[]) value; + int length = uuidArray.length; + byte[][] bytesArray = new byte[length][]; + for (int i = 0; i < length; i++) { + bytesArray[i] = UuidUtils.toBytes(uuidArray[i]); + } + return bytesArray; } }, @@ -1707,28 +1759,6 @@ public byte[][] toBytesArray(Object value) { } } - public byte[][] toUuidBytesArray(Object value) { - if (value instanceof byte[][]) { - byte[][] values = (byte[][]) value; - byte[][] uuidBytes = new byte[values.length][]; - for (int i = 0; i < values.length; i++) { - uuidBytes[i] = UuidUtils.toBytes(values[i]); - } - return uuidBytes; - } - if (isSingleValue()) { - return new byte[][]{UuidUtils.toBytes(value)}; - } else { - Object[] valueArray = toObjectArray(value); - int length = valueArray.length; - byte[][] uuidBytes = new byte[length][]; - for (int i = 0; i < length; i++) { - uuidBytes[i] = UuidUtils.toBytes(valueArray[i]); - } - return uuidBytes; - } - } - public LocalDate[] toLocalDateArray(Object value) { if (value instanceof LocalDate[]) { return (LocalDate[]) value; @@ -2024,10 +2054,10 @@ public static PinotDataType getPinotDataTypeForIngestion(FieldSpec fieldSpec) { return JSON; } throw new IllegalStateException("There is no multi-value type for JSON"); - case UUID: - return fieldSpec.isSingleValueField() ? UUID : UUID_ARRAY; case BYTES: return fieldSpec.isSingleValueField() ? BYTES : BYTES_ARRAY; + case UUID: + return fieldSpec.isSingleValueField() ? UUID : UUID_ARRAY; case MAP: if (fieldSpec.isSingleValueField()) { return MAP; diff --git a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/UuidUtils.java b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/UuidUtils.java index 86365a49d08e..2da10eea3d0e 100644 --- a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/UuidUtils.java +++ b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/UuidUtils.java @@ -32,16 +32,13 @@ private UuidUtils() { } public static final int UUID_NUM_BYTES = 16; - private static final byte[] NULL_UUID_BYTES = new byte[UUID_NUM_BYTES]; // Gregorian-to-Unix offset in 100-nanosecond units. Matches RFC 4122 / RFC 9562. // This is the count of 100-ns intervals between 1582-10-15T00:00:00Z and 1970-01-01T00:00:00Z. private static final long GREGORIAN_TO_UNIX_OFFSET_100NS = 0x01b21dd213814000L; public static byte[] nullUuidBytes() { - byte[] uuidBytes = new byte[UUID_NUM_BYTES]; - System.arraycopy(NULL_UUID_BYTES, 0, uuidBytes, 0, UUID_NUM_BYTES); - return uuidBytes; + return new byte[UUID_NUM_BYTES]; } public static byte[] toBytes(long mostSignificantBits, long leastSignificantBits) { @@ -60,6 +57,14 @@ public static byte[] toBytes(String uuidString) { try { uuid = UUID.fromString(uuidString); } catch (Exception e) { + try { + // Try parsing the string as hex-encoded bytes + byte[] bytes = BytesUtils.toBytes(uuidString); + if (bytes.length == UUID_NUM_BYTES) { + return bytes; + } + } catch (Exception ignore) { + } throw new IllegalArgumentException("Invalid UUID value: '" + uuidString + "'", e); } String canonical = uuid.toString(); diff --git a/pinot-spi/src/test/java/org/apache/pinot/spi/data/FieldSpecTest.java b/pinot-spi/src/test/java/org/apache/pinot/spi/data/FieldSpecTest.java index fd47f0312dcd..b156bb848658 100644 --- a/pinot-spi/src/test/java/org/apache/pinot/spi/data/FieldSpecTest.java +++ b/pinot-spi/src/test/java/org/apache/pinot/spi/data/FieldSpecTest.java @@ -227,6 +227,7 @@ public void testFieldSpec() { @Test public void testUUIDConversions() { byte[] uuidBytes = UuidUtils.toBytes(UUID_VALUE); + byte[] equalUuidBytes = UuidUtils.toBytes(UUID_VALUE); byte[] largerUuidBytes = UuidUtils.toBytes(LARGER_UUID_VALUE); String mixedCaseUuidValue = UUID_VALUE.toUpperCase(); ByteArray uuidInternal = new ByteArray(uuidBytes); @@ -236,10 +237,9 @@ public void testUUIDConversions() { Assert.assertEquals(UUID.convertInternal(UUID_VALUE), uuidInternal); Assert.assertEquals(UUID.convertInternal(mixedCaseUuidValue), uuidInternal); Assert.assertEquals(UUID.toString(uuidBytes), UUID_VALUE); - Assert.assertEquals(UUID.toString(uuidInternal), UUID_VALUE); - Assert.assertTrue(UUID.equals(uuidBytes, uuidInternal)); - Assert.assertEquals(UUID.hashCode(uuidBytes), uuidInternal.hashCode()); - Assert.assertEquals(UUID.compare(uuidBytes, uuidInternal), 0); + Assert.assertTrue(UUID.equals(uuidBytes, equalUuidBytes)); + Assert.assertEquals(UUID.hashCode(uuidBytes), UUID.hashCode(equalUuidBytes)); + Assert.assertEquals(UUID.compare(uuidBytes, equalUuidBytes), 0); Assert.assertTrue(UUID.compare(uuidBytes, largerUuidBytes) < 0); Assert.assertTrue(UUID.compare(largerUuidBytes, uuidBytes) > 0); } @@ -248,7 +248,19 @@ public void testUUIDConversions() { public void testInvalidUUIDConversion() { IllegalArgumentException exception = Assert.expectThrows(IllegalArgumentException.class, () -> UUID.convert("not-a-uuid")); - assertThat(exception).hasMessageContaining("Invalid UUID"); + assertThat(exception).hasMessageContaining("Cannot convert value: 'not-a-uuid' to type: UUID"); + } + + @Test + public void testCompare() { + // BOOLEAN values are held as Integer (0/1) and TIMESTAMP as Long (epoch millis), so compare dispatches them + // through the INT and LONG cases respectively. + Assert.assertTrue(BOOLEAN.compare(0, 1) < 0); + Assert.assertTrue(BOOLEAN.compare(1, 0) > 0); + Assert.assertEquals(BOOLEAN.compare(1, 1), 0); + Assert.assertTrue(TIMESTAMP.compare(1000L, 2000L) < 0); + Assert.assertTrue(TIMESTAMP.compare(2000L, 1000L) > 0); + Assert.assertEquals(TIMESTAMP.compare(1000L, 1000L), 0); } /** diff --git a/pinot-spi/src/test/java/org/apache/pinot/spi/utils/PinotDataTypeTest.java b/pinot-spi/src/test/java/org/apache/pinot/spi/utils/PinotDataTypeTest.java index fe6f86c1fb0a..9d8b65697cae 100644 --- a/pinot-spi/src/test/java/org/apache/pinot/spi/utils/PinotDataTypeTest.java +++ b/pinot-spi/src/test/java/org/apache/pinot/spi/utils/PinotDataTypeTest.java @@ -160,14 +160,16 @@ public Object[][] conversions() { {BYTES_ARRAY, BYTES_ARRAY, new byte[][] { "foo".getBytes(UTF_8), "bar".getBytes(UTF_8) }, new byte[][] { "foo".getBytes(UTF_8), "bar".getBytes(UTF_8) }}, {STRING_ARRAY, UUID_ARRAY, new String[] {UUID_VALUE}, - new byte[][] {UuidUtils.toBytes(UUID_VALUE)}}, + new java.util.UUID[] {java.util.UUID.fromString(UUID_VALUE)}}, {UUID_ARRAY, UUID_ARRAY, new java.util.UUID[] {java.util.UUID.fromString(UUID_VALUE)}, - new byte[][] {UuidUtils.toBytes(UUID_VALUE)}}, + new java.util.UUID[] {java.util.UUID.fromString(UUID_VALUE)}}, {COLLECTION, STRING_ARRAY, Arrays.asList("test1", "test2"), new String[] {"test1", "test2"}}, - {COLLECTION, UUID_ARRAY, Arrays.asList(UUID_VALUE), new byte[][] {UuidUtils.toBytes(UUID_VALUE)}}, + {COLLECTION, UUID_ARRAY, Arrays.asList(UUID_VALUE), + new java.util.UUID[] {java.util.UUID.fromString(UUID_VALUE)}}, {COLLECTION, FLOAT_ARRAY, Arrays.asList(1.0f, 2.0f), new Float[] {1.0f, 2.0f}}, {OBJECT_ARRAY, STRING_ARRAY, new Object[] {"test1", "test2"}, new String[] {"test1", "test2"}}, - {OBJECT_ARRAY, UUID_ARRAY, new Object[] {UUID_VALUE}, new byte[][] {UuidUtils.toBytes(UUID_VALUE)}}, + {OBJECT_ARRAY, UUID_ARRAY, new Object[] {UUID_VALUE}, + new java.util.UUID[] {java.util.UUID.fromString(UUID_VALUE)}}, {OBJECT_ARRAY, FLOAT_ARRAY, new Object[] {1.0f, 2.0f}, new Float[] {1.0f, 2.0f}}, }; } @@ -336,14 +338,18 @@ public void testUuidArray() { UUID u1 = java.util.UUID.fromString("550e8400-e29b-41d4-a716-446655440000"); UUID u2 = java.util.UUID.fromString("00000000-0000-0000-0000-000000000001"); UUID[] uuids = {u1, u2}; - byte[][] convertedBytes = (byte[][]) UUID_ARRAY.convert(uuids, UUID_ARRAY); - assertEquals(UUID.convert(convertedBytes[0], BYTES), u1); - assertEquals(UUID.convert(convertedBytes[1], BYTES), u2); + UUID[] converted = (UUID[]) UUID_ARRAY.convert(uuids, UUID_ARRAY); + assertEquals(converted[0], u1); + assertEquals(converted[1], u2); // STRING_ARRAY → UUID_ARRAY: per-element parse. - byte[][] convertedStringBytes = (byte[][]) UUID_ARRAY.convert( + UUID[] convertedFromStrings = (UUID[]) UUID_ARRAY.convert( new String[]{"550e8400-e29b-41d4-a716-446655440000", "00000000-0000-0000-0000-000000000001"}, STRING_ARRAY); - assertEquals(UUID.convert(convertedStringBytes[0], BYTES), u1); - assertEquals(UUID.convert(convertedStringBytes[1], BYTES), u2); + assertEquals(convertedFromStrings[0], u1); + assertEquals(convertedFromStrings[1], u2); + // toInternal yields the 16-byte big-endian form per element. + byte[][] internal = (byte[][]) UUID_ARRAY.toInternal(uuids); + assertEquals(UUID.convert(internal[0], BYTES), u1); + assertEquals(UUID.convert(internal[1], BYTES), u2); // STRING_ARRAY destination: canonical strings. assertEquals(STRING_ARRAY.convert(uuids, UUID_ARRAY), new String[]{"550e8400-e29b-41d4-a716-446655440000", "00000000-0000-0000-0000-000000000001"}); diff --git a/pinot-spi/src/test/java/org/apache/pinot/spi/utils/UuidUtilsTest.java b/pinot-spi/src/test/java/org/apache/pinot/spi/utils/UuidUtilsTest.java index 2ccbfe52f51f..731c63bfab5a 100644 --- a/pinot-spi/src/test/java/org/apache/pinot/spi/utils/UuidUtilsTest.java +++ b/pinot-spi/src/test/java/org/apache/pinot/spi/utils/UuidUtilsTest.java @@ -41,7 +41,6 @@ public Object[][] invalidUuidStrings() { {"550e8400-e29b-41d4-a716-44665544000"}, {"550e8400-e29b-41d4-a716-4466554400000"}, {"550e8400-e29b-41d4-a716-44665544000g"}, - {"550e8400e29b41d4a716446655440000"}, {""} }; } @@ -72,6 +71,16 @@ public void testCharSequenceRoundTrips() { assertTrue(UuidUtils.isUuid(mixedCaseUuid)); } + @Test + public void testDashlessHexStringRoundTrips() { + String hexUuid = "550e8400e29b41d4a716446655440000"; + + assertEquals(UuidUtils.toBytes(hexUuid), UuidUtils.toBytes(UUID_VALUE)); + assertEquals(UuidUtils.toString(UuidUtils.toBytes(hexUuid)), UUID_VALUE); + assertEquals(UuidUtils.toUUID(hexUuid), UUID.fromString(UUID_VALUE)); + assertTrue(UuidUtils.isUuid(hexUuid)); + } + @Test(dataProvider = "invalidUuidStrings") public void testRejectsInvalidUuidStrings(String invalidUuid) { Assert.expectThrows(IllegalArgumentException.class, () -> UuidUtils.toBytes(invalidUuid));