From eb41e686591d0b142a1929c3d335d96a14a21032 Mon Sep 17 00:00:00 2001 From: Ramin Gharib Date: Thu, 10 Sep 2026 15:18:56 +0200 Subject: [PATCH] [FLINK-40556][table] Support UUID value literals backed by byte[] values UuidType allows both UUID and byte[] as conversion classes, so a ValueLiteralExpression can legally hold a UUID literal as a byte[] value. ValueLiteralExpression#getValueAs(UUID.class) did not handle that case and returned empty, so asSerializableString and ExpressionConverter threw NoSuchElementException or IllegalStateException instead of producing a literal. Add a byte[] to UUID conversion arm to getValueAs, matching the existing coercion pattern for LocalDate, Instant, and other multi-representation types. This single fix covers both call sites since they both go through getValueAs. --- .../expressions/ValueLiteralExpression.java | 12 ++++++ .../table/expressions/ExpressionTest.java | 40 +++++++++++++++---- .../converter/ExpressionConverterTest.java | 26 ++++++++++++ 3 files changed, 70 insertions(+), 8 deletions(-) diff --git a/flink-table/flink-table-common/src/main/java/org/apache/flink/table/expressions/ValueLiteralExpression.java b/flink-table/flink-table-common/src/main/java/org/apache/flink/table/expressions/ValueLiteralExpression.java index d0d6a1389ed613..10e7e879c2fe68 100644 --- a/flink-table/flink-table-common/src/main/java/org/apache/flink/table/expressions/ValueLiteralExpression.java +++ b/flink-table/flink-table-common/src/main/java/org/apache/flink/table/expressions/ValueLiteralExpression.java @@ -41,6 +41,7 @@ import javax.annotation.Nullable; import java.math.BigDecimal; +import java.nio.ByteBuffer; import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; @@ -138,6 +139,8 @@ public Optional getValueAs(Class clazz) { convertedValue = convertToInstant(value, valueClass); } else if (clazz == BigDecimal.class) { convertedValue = convertToBigDecimal(value); + } else if (clazz == UUID.class) { + convertedValue = convertToUuid(value, valueClass); } } @@ -216,6 +219,15 @@ public Optional getValueAs(Class clazz) { return null; } + private @Nullable UUID convertToUuid(Object value, Class valueClass) { + if (valueClass == byte[].class) { + final ByteBuffer buffer = ByteBuffer.wrap((byte[]) value); + return new UUID(buffer.getLong(), buffer.getLong()); + } + + return null; + } + @Override public DataType getOutputDataType() { return dataType; diff --git a/flink-table/flink-table-common/src/test/java/org/apache/flink/table/expressions/ExpressionTest.java b/flink-table/flink-table-common/src/test/java/org/apache/flink/table/expressions/ExpressionTest.java index 63748ccdc2a052..947daf0157e1ff 100644 --- a/flink-table/flink-table-common/src/test/java/org/apache/flink/table/expressions/ExpressionTest.java +++ b/flink-table/flink-table-common/src/test/java/org/apache/flink/table/expressions/ExpressionTest.java @@ -30,6 +30,7 @@ import org.junit.jupiter.params.provider.MethodSource; import java.math.BigDecimal; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.sql.Date; import java.sql.Time; @@ -245,14 +246,18 @@ void testInstantValueLiteralExtraction() { .isEqualTo(instant.minusMillis(100)); } - @Test - void testUuidValueLiteralExtraction() { - final UUID uuid = UUID.fromString("550e8400-e29b-41d4-a716-446655440000"); - assertThat( - new ValueLiteralExpression(uuid) - .getValueAs(UUID.class) - .orElseThrow(AssertionError::new)) - .isEqualTo(uuid); + @ParameterizedTest(name = "{0}") + @MethodSource("uuidValueLiteralTestCases") + void testUuidValueLiteralExtraction( + String caseName, ValueLiteralExpression literal, UUID uuid) { + assertThat(literal.getValueAs(UUID.class).orElseThrow(AssertionError::new)).isEqualTo(uuid); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("uuidValueLiteralTestCases") + void testUuidAsSerializableString(String caseName, ValueLiteralExpression literal, UUID uuid) { + assertThat(literal.asSerializableString(DefaultSqlFactory.INSTANCE)) + .isEqualTo(String.format("UUID '%s'", uuid)); } @Test @@ -343,6 +348,25 @@ private static Expression createExpressionTree(Integer nestedValue) { DataTypes.BOOLEAN()); } + private static byte[] uuidToBytes(UUID uuid) { + return ByteBuffer.allocate(16) + .putLong(uuid.getMostSignificantBits()) + .putLong(uuid.getLeastSignificantBits()) + .array(); + } + + private static Stream uuidValueLiteralTestCases() { + final UUID uuid = UUID.fromString("550e8400-e29b-41d4-a716-446655440000"); + return Stream.of( + Arguments.of("UUID value", new ValueLiteralExpression(uuid), uuid), + Arguments.of( + "UUID bridged to byte[]", + new ValueLiteralExpression( + uuidToBytes(uuid), + DataTypes.UUID().notNull().bridgedTo(byte[].class)), + uuid)); + } + private static Stream timestampLtzPrecisionTestCases() { return Stream.of( // Precision 0-2: numeric variant wrapped in CAST to match data type diff --git a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/expressions/converter/ExpressionConverterTest.java b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/expressions/converter/ExpressionConverterTest.java index ffea6f95d5e2d3..fe2bc4ff716f2a 100644 --- a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/expressions/converter/ExpressionConverterTest.java +++ b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/expressions/converter/ExpressionConverterTest.java @@ -33,12 +33,14 @@ import org.junit.jupiter.api.Test; import java.math.BigDecimal; +import java.nio.ByteBuffer; 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.UUID; import static org.apache.flink.table.expressions.ApiExpressionUtils.valueLiteral; import static org.assertj.core.api.Assertions.assertThat; @@ -194,4 +196,28 @@ void testSymbolLiteral() { assertThat(((RexLiteral) rex).getValueAs(TimeUnit.class)).isEqualTo(TimeUnit.MICROSECOND); assertThat(rex.getType().getSqlTypeName()).isEqualTo(SqlTypeName.SYMBOL); } + + @Test + void testUuidLiteral() { + UUID uuid = UUID.fromString("550e8400-e29b-41d4-a716-446655440000"); + RexNode rex = converter.visit(valueLiteral(uuid)); + assertThat(((RexLiteral) rex).getValueAs(UUID.class)).isEqualTo(uuid); + assertThat(rex.getType().getSqlTypeName()).isEqualTo(SqlTypeName.UUID); + } + + @Test + void testUuidLiteralFromBytes() { + UUID uuid = UUID.fromString("550e8400-e29b-41d4-a716-446655440000"); + byte[] uuidBytes = + ByteBuffer.allocate(16) + .putLong(uuid.getMostSignificantBits()) + .putLong(uuid.getLeastSignificantBits()) + .array(); + RexNode rex = + converter.visit( + valueLiteral( + uuidBytes, DataTypes.UUID().notNull().bridgedTo(byte[].class))); + assertThat(((RexLiteral) rex).getValueAs(UUID.class)).isEqualTo(uuid); + assertThat(rex.getType().getSqlTypeName()).isEqualTo(SqlTypeName.UUID); + } }