Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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
*
* http://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.fluss.client.converter;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation to specify a custom table column name for a POJO field. When present, the converter
* will map between the POJO field and the table column using the specified name.
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ColumnName {
/**
* The name of the table column that this field maps to.
*
* @return the column name in the Fluss table
*/
String value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,15 @@ static void validateCompatibility(DataType fieldType, PojoType.Property prop) {
}
return;
}
if (actual.isEnum()) {
if (typeRoot != DataTypeRoot.STRING) {
throw new IllegalArgumentException(
String.format(
"Enum field '%s' must be a string type, got %s",
prop.name, typeRoot));
}
return;
}

Set<Class<?>> supported = SUPPORTED_TYPES.get(fieldType.getTypeRoot());
if (supported == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.Arrays;

/** Shared utilities for Fluss type and Pojo type. */
public class FlussTypeToPojoTypeConverter {
Expand Down Expand Up @@ -74,6 +75,17 @@ static Object convertTextValue(
ConverterCommons.charLengthExceptionMessage(fieldName, v.length()));
}
return v.charAt(0);
} else if (pojoType.isEnum()) {
return Arrays.stream(pojoType.getEnumConstants())
.filter(e -> e.toString().equals(v.toUpperCase()))
.findAny()
.orElseThrow(
() ->
new IllegalArgumentException(
String.format(
"Could not parse value for enum %s. Expected one of: [%s]",
pojoType,
Arrays.toString(pojoType.getEnumConstants()))));
}
throw new IllegalArgumentException(
String.format(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import java.util.Map;
import java.util.Objects;

import static org.apache.fluss.utils.Preconditions.checkArgument;

/**
* Internal representation of a POJO type, used to validate POJO requirements and to provide unified
* accessors for reading/writing properties.
Expand Down Expand Up @@ -73,39 +75,53 @@ static <T> PojoType<T> of(Class<T> pojoClass) {

Map<String, Property> props = new LinkedHashMap<>();
for (Map.Entry<String, Field> e : allFields.entrySet()) {
String name = e.getKey();
String fieldName = e.getKey();
Field field = e.getValue();
// Enforce nullable fields: primitives are not allowed in POJO definitions.
if (field.getType().isPrimitive()) {
throw new IllegalArgumentException(
String.format(
"POJO class %s has primitive field '%s' of type %s. Primitive types are not allowed; all fields must be nullable (use wrapper types).",
pojoClass.getName(), name, field.getType().getName()));
pojoClass.getName(), fieldName, field.getType().getName()));
}
// Check for @ColumnName annotation to determine the mapped column name
ColumnName columnNameAnnotation = field.getAnnotation(ColumnName.class);
String mappedColumnName =
columnNameAnnotation != null ? columnNameAnnotation.value() : fieldName;
checkArgument(
!mappedColumnName.isEmpty(),
"Column name cannot be empty for field '%s' in POJO class %s",
fieldName,
pojoClass.getName());

// use boxed type as effective type
Class<?> effectiveType = boxIfPrimitive(field.getType());
boolean publicField = Modifier.isPublic(field.getModifiers());
Method getter = getters.get(name);
Method setter = setters.get(name);
Method getter = getters.get(fieldName);
Method setter = setters.get(fieldName);
if (!publicField) {
// When not a public field, require both getter and setter
if (getter == null || setter == null) {
final String capitalizedName = capitalize(name);
final String capitalizedName = capitalize(fieldName);
throw new IllegalArgumentException(
String.format(
"POJO class %s field '%s' must be public or have both getter and setter (get%s/set%s).",
pojoClass.getName(), name, capitalizedName, capitalizedName));
pojoClass.getName(),
fieldName,
capitalizedName,
capitalizedName));
}
}
props.put(
name,
mappedColumnName,
new Property(
name,
fieldName,
effectiveType,
field.getGenericType(),
publicField ? field : null,
getter,
setter));
setter,
mappedColumnName));
}

return new PojoType<>(pojoClass, ctor, props);
Expand Down Expand Up @@ -269,11 +285,19 @@ private static Class<?> boxIfPrimitive(Class<?> type) {
}

static final class Property {
/** The name of the field in the POJO class (e.g. "userId"). */
final String name;

final Class<?> type;
/** The generic type of the field (e.g. {@code Map<String, AddressPojo>}). */
final Type genericType;

/**
* The name of the column in the Fluss table. This may differ from 'name' if a @ColumnName
* annotation is present. Used for looking up the property by table column name.
*/
final String mappedName;

@Nullable final Field publicField;
@Nullable final Method getter;
@Nullable final Method setter;
Expand All @@ -284,10 +308,12 @@ static final class Property {
Type genericType,
@Nullable Field publicField,
@Nullable Method getter,
@Nullable Method setter) {
@Nullable Method setter,
String mappedName) {
this.name = Objects.requireNonNull(name, "name");
this.type = Objects.requireNonNull(type, "type");
this.genericType = Objects.requireNonNull(genericType, "genericType");
this.mappedName = Objects.requireNonNull(mappedName, "mappedName");
this.publicField = publicField;
this.getter = getter;
this.setter = setter;
Expand Down
Loading