Skip to content

Commit dcc0d07

Browse files
VladBanarvaldbanar5
authored andcommitted
[client] enum conversion functionality for typed writer/reader
1 parent cf2adb8 commit dcc0d07

10 files changed

Lines changed: 960 additions & 141 deletions

File tree

fluss-client/src/main/java/org/apache/fluss/client/converter/ColumnName.java

Lines changed: 0 additions & 39 deletions
This file was deleted.

fluss-client/src/main/java/org/apache/fluss/client/converter/ConverterCommons.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,15 @@ static void validateCompatibility(DataType fieldType, PojoType.Property prop) {
148148
}
149149
return;
150150
}
151+
if (actual.isEnum()) {
152+
if (typeRoot != DataTypeRoot.STRING) {
153+
throw new IllegalArgumentException(
154+
String.format(
155+
"Enum field '%s' must be a string type, got %s",
156+
prop.name, typeRoot));
157+
}
158+
return;
159+
}
151160

152161
Set<Class<?>> supported = SUPPORTED_TYPES.get(fieldType.getTypeRoot());
153162
if (supported == null) {

fluss-client/src/main/java/org/apache/fluss/client/converter/FlussTypeToPojoTypeConverter.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,30 @@
3131
import java.time.LocalTime;
3232
import java.time.OffsetDateTime;
3333
import java.time.ZoneOffset;
34+
import java.util.Arrays;
35+
import java.util.Collections;
36+
import java.util.HashMap;
37+
import java.util.Map;
38+
import java.util.concurrent.ConcurrentHashMap;
3439

3540
/** Shared utilities for Fluss type and Pojo type. */
3641
public class FlussTypeToPojoTypeConverter {
42+
43+
private static final Map<Class<?>, Map<String, Object>> ENUM_CONSTANTS_CACHE =
44+
new ConcurrentHashMap<>();
45+
46+
/**
47+
* Builds a map of enum name (uppercase) to enum constant for the given enum class. This map is
48+
* cached to avoid recreating it for every enum conversion.
49+
*/
50+
private static Map<String, Object> buildEnumConstantsMap(Class<?> enumClass) {
51+
Map<String, Object> map = new HashMap<>();
52+
for (Object constant : enumClass.getEnumConstants()) {
53+
map.put(constant.toString(), constant);
54+
}
55+
return Collections.unmodifiableMap(map);
56+
}
57+
3758
/**
3859
* Converts a text value (CHAR/STRING) read from an InternalRow into the target Java type
3960
* declared by the POJO property.
@@ -74,6 +95,18 @@ static Object convertTextValue(
7495
ConverterCommons.charLengthExceptionMessage(fieldName, v.length()));
7596
}
7697
return v.charAt(0);
98+
} else if (pojoType.isEnum()) {
99+
Map<String, Object> enumMap =
100+
ENUM_CONSTANTS_CACHE.computeIfAbsent(
101+
pojoType, FlussTypeToPojoTypeConverter::buildEnumConstantsMap);
102+
Object enumConstant = enumMap.get(v);
103+
if (enumConstant == null) {
104+
throw new IllegalArgumentException(
105+
String.format(
106+
"Could not parse value for enum %s. Expected one of: %s",
107+
pojoType, Arrays.toString(pojoType.getEnumConstants())));
108+
}
109+
return enumConstant;
77110
}
78111
throw new IllegalArgumentException(
79112
String.format(

fluss-client/src/main/java/org/apache/fluss/client/converter/PojoType.java

Lines changed: 11 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.apache.fluss.client.converter;
1919

2020
import javax.annotation.Nullable;
21+
2122
import java.lang.reflect.Constructor;
2223
import java.lang.reflect.Field;
2324
import java.lang.reflect.Method;
@@ -29,8 +30,6 @@
2930
import java.util.Map;
3031
import java.util.Objects;
3132

32-
import static org.apache.fluss.utils.Preconditions.checkArgument;
33-
3433
/**
3534
* Internal representation of a POJO type, used to validate POJO requirements and to provide unified
3635
* accessors for reading/writing properties.
@@ -74,53 +73,39 @@ static <T> PojoType<T> of(Class<T> pojoClass) {
7473

7574
Map<String, Property> props = new LinkedHashMap<>();
7675
for (Map.Entry<String, Field> e : allFields.entrySet()) {
77-
String fieldName = e.getKey();
76+
String name = e.getKey();
7877
Field field = e.getValue();
7978
// Enforce nullable fields: primitives are not allowed in POJO definitions.
8079
if (field.getType().isPrimitive()) {
8180
throw new IllegalArgumentException(
8281
String.format(
8382
"POJO class %s has primitive field '%s' of type %s. Primitive types are not allowed; all fields must be nullable (use wrapper types).",
84-
pojoClass.getName(), fieldName, field.getType().getName()));
83+
pojoClass.getName(), name, field.getType().getName()));
8584
}
86-
// Check for @ColumnName annotation to determine the mapped column name
87-
ColumnName columnNameAnnotation = field.getAnnotation(ColumnName.class);
88-
String mappedColumnName =
89-
columnNameAnnotation != null ? columnNameAnnotation.value() : fieldName;
90-
checkArgument(
91-
!mappedColumnName.isEmpty(),
92-
"Column name cannot be empty for field '%s' in POJO class %s",
93-
fieldName,
94-
pojoClass.getName());
95-
9685
// use boxed type as effective type
9786
Class<?> effectiveType = boxIfPrimitive(field.getType());
9887
boolean publicField = Modifier.isPublic(field.getModifiers());
99-
Method getter = getters.get(fieldName);
100-
Method setter = setters.get(fieldName);
88+
Method getter = getters.get(name);
89+
Method setter = setters.get(name);
10190
if (!publicField) {
10291
// When not a public field, require both getter and setter
10392
if (getter == null || setter == null) {
104-
final String capitalizedName = capitalize(fieldName);
93+
final String capitalizedName = capitalize(name);
10594
throw new IllegalArgumentException(
10695
String.format(
10796
"POJO class %s field '%s' must be public or have both getter and setter (get%s/set%s).",
108-
pojoClass.getName(),
109-
fieldName,
110-
capitalizedName,
111-
capitalizedName));
97+
pojoClass.getName(), name, capitalizedName, capitalizedName));
11298
}
11399
}
114100
props.put(
115-
mappedColumnName,
101+
name,
116102
new Property(
117-
fieldName,
103+
name,
118104
effectiveType,
119105
field.getGenericType(),
120106
publicField ? field : null,
121107
getter,
122-
setter,
123-
mappedColumnName));
108+
setter));
124109
}
125110

126111
return new PojoType<>(pojoClass, ctor, props);
@@ -284,19 +269,11 @@ private static Class<?> boxIfPrimitive(Class<?> type) {
284269
}
285270

286271
static final class Property {
287-
/** The name of the field in the POJO class (e.g. "userId"). */
288272
final String name;
289-
290273
final Class<?> type;
291274
/** The generic type of the field (e.g. {@code Map<String, AddressPojo>}). */
292275
final Type genericType;
293276

294-
/**
295-
* The name of the column in the Fluss table. This may differ from 'name' if a @ColumnName
296-
* annotation is present. Used for looking up the property by table column name.
297-
*/
298-
final String mappedName;
299-
300277
@Nullable final Field publicField;
301278
@Nullable final Method getter;
302279
@Nullable final Method setter;
@@ -307,12 +284,10 @@ static final class Property {
307284
Type genericType,
308285
@Nullable Field publicField,
309286
@Nullable Method getter,
310-
@Nullable Method setter,
311-
String mappedName) {
287+
@Nullable Method setter) {
312288
this.name = Objects.requireNonNull(name, "name");
313289
this.type = Objects.requireNonNull(type, "type");
314290
this.genericType = Objects.requireNonNull(genericType, "genericType");
315-
this.mappedName = Objects.requireNonNull(mappedName, "mappedName");
316291
this.publicField = publicField;
317292
this.getter = getter;
318293
this.setter = setter;

0 commit comments

Comments
 (0)