diff --git a/databind/src/main/java/tech/ydb/yoj/databind/schema/Column.java b/databind/src/main/java/tech/ydb/yoj/databind/schema/Column.java index da1a3a4b..1259f4d8 100644 --- a/databind/src/main/java/tech/ydb/yoj/databind/schema/Column.java +++ b/databind/src/main/java/tech/ydb/yoj/databind/schema/Column.java @@ -68,7 +68,7 @@ * * Defaults to {@code true} (flatten composite fields).
* Changing this parameter for a non-composite field has no effect. - *

Tip: Use the {@link ObjectColumn @ObjectColumn} annotation + *

Tip: Use the {@link tech.ydb.yoj.databind.converter.ObjectColumn @ObjectColumn} annotation * if you only need to override {@code @Column.flatten} to {@code false}. */ boolean flatten() default true; @@ -77,7 +77,39 @@ * Specifies custom conversion logic for this column, if any. * * @see CustomValueType + * @see tech.ydb.yoj.databind.converter.ValueConverter ValueConverter */ @ExperimentalApi(issue = "https://github.com/ydb-platform/yoj-project/issues/24") CustomValueType customValueType() default @CustomValueType(columnClass = Comparable.class, converter = NoConverter.class); + + /** + * Column naming policy, used by {@link tech.ydb.yoj.databind.schema.naming.AnnotationFirstNamingStrategy the default naming strategy}. + *
Determines how the column name is derived from this column's name and its parent column, if any. + */ + ColumnNaming columnNaming() default ColumnNaming.LEGACY; + + /** + * Column naming policy. + *
Determines how the column name is derived from this column's name and its parent column, if any. + */ + enum ColumnNaming { + /** + * Adds parent column name (if any) as prefix, but only if current (child) doesn't specify {@link #name()} explicitly. + *
It works both like {@link #RELATIVE} and {@link #ABSOLUTE} policy, depending on the presence of {@link #name()} annotation attribute + * for this column. + * + * @deprecated This column naming policy is deprecated, but will stay the default in YOJ 2.x. + * {@link #RELATIVE} will become the default policy in YOJ 3.0. + */ + @Deprecated LEGACY, + /** + * Uses this column's name as-is, without ever using consulting parent column's name (even if there is a parent column!) + */ + ABSOLUTE, + /** + * Always uses parent column name (if any) as prefix: {@code [_]}. + *
This policy will become the default in YOJ 3.0. + */ + RELATIVE + } } diff --git a/databind/src/main/java/tech/ydb/yoj/databind/schema/naming/AnnotationFirstNamingStrategy.java b/databind/src/main/java/tech/ydb/yoj/databind/schema/naming/AnnotationFirstNamingStrategy.java index dbba9ba2..b245de69 100644 --- a/databind/src/main/java/tech/ydb/yoj/databind/schema/naming/AnnotationFirstNamingStrategy.java +++ b/databind/src/main/java/tech/ydb/yoj/databind/schema/naming/AnnotationFirstNamingStrategy.java @@ -1,6 +1,7 @@ package tech.ydb.yoj.databind.schema.naming; import lombok.NonNull; +import tech.ydb.yoj.databind.schema.Column; import tech.ydb.yoj.databind.schema.Schema; import tech.ydb.yoj.databind.schema.Table; @@ -30,17 +31,34 @@ protected String getNameFromClass(Class entityClass) { private String getColumnName(Schema.JavaField field) { var annotation = field.getField().getColumn(); - if (annotation != null && !annotation.name().isEmpty()) { - return annotation.name(); + var parentName = (field.getParent() == null) ? null : field.getParent().getName(); + + if (annotation == null) { + var fieldName = field.getField().getName(); + return parentName == null ? fieldName : parentName + NAME_DELIMITER + fieldName; } - return getColumnNameFromField(field); + return getColumnNameWithNaming(field, annotation.name(), parentName, annotation.columnNaming()); } - protected String getColumnNameFromField(Schema.JavaField field) { - return (field.getParent() == null) - ? field.getField().getName() - : field.getParent().getName() + NAME_DELIMITER + field.getField().getName(); + private static String getColumnNameWithNaming( + Schema.JavaField field, + String annotationName, + String parentName, Column.ColumnNaming columnNaming + ) { + var name = (annotationName.isEmpty()) ? field.getField().getName() : annotationName; + + return switch (columnNaming) { + case ABSOLUTE -> name; + case RELATIVE -> parentName + NAME_DELIMITER + name; + case LEGACY -> { + if (!annotationName.isEmpty()) { + yield annotationName; + } else { + yield parentName == null ? name : parentName + NAME_DELIMITER + name; + } + } + }; } protected void propagateFieldNameToFlatSubfield(Schema.JavaField field) { diff --git a/databind/src/test/java/tech/ydb/yoj/databind/schema/naming/AnnotationFirstNamingStrategyTest.java b/databind/src/test/java/tech/ydb/yoj/databind/schema/naming/AnnotationFirstNamingStrategyTest.java index 472d7d0e..7fba9cab 100644 --- a/databind/src/test/java/tech/ydb/yoj/databind/schema/naming/AnnotationFirstNamingStrategyTest.java +++ b/databind/src/test/java/tech/ydb/yoj/databind/schema/naming/AnnotationFirstNamingStrategyTest.java @@ -37,7 +37,7 @@ public void testMixedEntityTableName() { @Test public void testMixedEntityFieldNameTest() { verifyFieldNames(MixedEntity.class, - "column_name", "subEntity_boolValue", "sfe_timestamp", "id_stringValue", "int.val"); + "column_name", "subEntity_boolValue", "absoluteBoolValue", "sfe_timestamp", "id_stringValue", "int.val", "prefix_sfe_relative_timestamp", "sfe_absolute_timestamp", "relativeWithoutColumnAnnotation_sfe_relative_timestamp"); } @Override diff --git a/databind/src/test/java/tech/ydb/yoj/databind/schema/naming/MixedEntity.java b/databind/src/test/java/tech/ydb/yoj/databind/schema/naming/MixedEntity.java index 5c8f60d8..2345feb4 100644 --- a/databind/src/test/java/tech/ydb/yoj/databind/schema/naming/MixedEntity.java +++ b/databind/src/test/java/tech/ydb/yoj/databind/schema/naming/MixedEntity.java @@ -12,6 +12,11 @@ public class MixedEntity { TestEntity.SubEntity subEntity; + SubEntityWithRelative relativeWithoutColumnAnnotation; + + @Column(name = "prefix") + SubEntityWithRelative relativeWithPrefix; + @Value private static class Id { String stringValue; @@ -19,4 +24,10 @@ private static class Id { @Column(name = "int.val") Integer intValue; } + + @Value + static class SubEntityWithRelative { + @Column(name = "sfe_relative", columnNaming = Column.ColumnNaming.RELATIVE) + SingleFieldEntity singleFieldEntityRelative; + } } diff --git a/databind/src/test/java/tech/ydb/yoj/databind/schema/naming/TestEntity.java b/databind/src/test/java/tech/ydb/yoj/databind/schema/naming/TestEntity.java index 8eac83e8..6324bbe7 100644 --- a/databind/src/test/java/tech/ydb/yoj/databind/schema/naming/TestEntity.java +++ b/databind/src/test/java/tech/ydb/yoj/databind/schema/naming/TestEntity.java @@ -18,7 +18,13 @@ private static class Id { static class SubEntity { boolean boolValue; + @Column(columnNaming = Column.ColumnNaming.ABSOLUTE) + boolean absoluteBoolValue; + @Column(name = "sfe") SingleFieldEntity singleFieldEntity; + + @Column(name = "sfe_absolute", columnNaming = Column.ColumnNaming.ABSOLUTE) + SingleFieldEntity singleFieldEntityAbsolute; } }