From 37e7399320cdad238c388bcaba7e44f18009bf57 Mon Sep 17 00:00:00 2001 From: Nikolai Amelichev Date: Thu, 14 Mar 2024 21:11:52 +0100 Subject: [PATCH] #24: `@CustomValueType` and `FieldValueType` improvements - Add `@Deprecated(forRemoval=true)` and "will be removed in YOJ 3.0.0" warnings to `FieldValueType` pitfalls (`BINARY`, `isSortable()` etc.) - Remove `@CustomValueType.columnValueType` because it can always be deduced from `@CustomValueType.columnClass` instead. - Disallow *recursive* custom value types (that is, types whose converter in turn produce custom value types). - Allow preconverted values to be of a subclass of the column class. This is valuable if columnClass is a enum class, because Java makes anonymous inner classes of enums. Remove fast-path for postconverted values because it is never used by any real code (preconvert fast-path is used by `FieldValue`). - Add "map enum by ordinal" custom value type converter as a demonstration. Custom value types might become a viable alternatives for `@Column(dbQualifier="...")`. --- .../ydb/yoj/databind/CustomValueType.java | 19 +++-- .../ydb/yoj/databind/CustomValueTypes.java | 41 +++++---- .../tech/ydb/yoj/databind/FieldValueType.java | 85 +++++++++++-------- .../converter/EnumOrdinalConverter.java | 52 ++++++++++++ .../converter/StringValueConverter.java | 3 +- .../databind/converter/ValueConverter.java | 29 ++++++- .../yoj/databind/expression/FieldValue.java | 22 ++--- .../tech/ydb/yoj/databind/schema/Column.java | 2 +- .../tech/ydb/yoj/databind/schema/Schema.java | 2 - .../yoj/repository/test/RepositoryTest.java | 9 +- .../test/sample/model/NetworkAppliance.java | 6 +- .../test/sample/model/TypeFreak.java | 5 +- .../test/sample/model/UpdateFeedEntry.java | 15 +++- .../test/sample/model/VersionedEntity.java | 3 - .../repository/ydb/yql/YqlPrimitiveType.java | 4 - .../repository/ydb/yql/YqlPrimitiveType.java | 4 - 16 files changed, 201 insertions(+), 100 deletions(-) create mode 100644 databind/src/main/java/tech/ydb/yoj/databind/converter/EnumOrdinalConverter.java diff --git a/databind/src/main/java/tech/ydb/yoj/databind/CustomValueType.java b/databind/src/main/java/tech/ydb/yoj/databind/CustomValueType.java index b06c5b5b..f2ce736b 100644 --- a/databind/src/main/java/tech/ydb/yoj/databind/CustomValueType.java +++ b/databind/src/main/java/tech/ydb/yoj/databind/CustomValueType.java @@ -2,6 +2,7 @@ import tech.ydb.yoj.ExperimentalApi; import tech.ydb.yoj.databind.converter.ValueConverter; +import tech.ydb.yoj.databind.schema.Column; import tech.ydb.yoj.databind.schema.Schema; import java.lang.annotation.Inherited; @@ -16,9 +17,12 @@ /** * Annotates the class, or entity field/record component as having a custom {@link ValueConverter value converter}. *
The specified converter will be used by YOJ instead of the default (Database column↔Java field) mapping. - *

Annotation on entity field/record component has priority over annotation on the field's/record component's class. + *

{@link Column#customValueType() @Column(customValueType=...)} annotation on an entity field/record component + * has priority over annotation on the field's/record component's class. *

This annotation is inherited, so make sure that your {@link #converter() converter} either supports all * possible subclasses of your class, or restrict subclassing by making your class {@code final} or {@code sealed}. + *

Defining recursive custom value types is prohibited: that is, you cannot have a custom value type with + * a converter that returns value of {@link #columnClass() another custom value type}. */ @Inherited @Retention(RUNTIME) @@ -26,14 +30,11 @@ @ExperimentalApi(issue = "https://github.com/ydb-platform/yoj-project/issues/24") public @interface CustomValueType { /** - * Simple value type that the {@link #converter()} represents a custom value type as. - * Cannot be {@link FieldValueType#COMPOSITE} or {@link FieldValueType#UNKNOWN}. - */ - FieldValueType columnValueType(); - - /** - * Exact type of value that {@link #converter() converter's} {@link ValueConverter#toColumn(Schema.JavaField, Object) toColumn()} method returns. - * Must implement {@link Comparable}. + * Class of the values that the {@link ValueConverter#toColumn(Schema.JavaField, Object) toColumn()} method of the {@link #converter() converter} + * returns. + *

Column class itself cannot be a custom value type. It must be one of the {@link FieldValueType database column value types supported by YOJ} + * and it must implement {@link Comparable}. + *

It is allowed to return value of a subclass of {@code columnClass}, e.g. in case of {@code columnClass} being an {@code enum} class. */ @SuppressWarnings("rawtypes") Class columnClass(); diff --git a/databind/src/main/java/tech/ydb/yoj/databind/CustomValueTypes.java b/databind/src/main/java/tech/ydb/yoj/databind/CustomValueTypes.java index ccad0cfa..15afa734 100644 --- a/databind/src/main/java/tech/ydb/yoj/databind/CustomValueTypes.java +++ b/databind/src/main/java/tech/ydb/yoj/databind/CustomValueTypes.java @@ -24,8 +24,8 @@ private CustomValueTypes() { public static Object preconvert(@NonNull JavaField field, @NonNull Object value) { var cvt = field.getCustomValueType(); if (cvt != null) { - if (cvt.columnClass().equals(value.getClass())) { - // Already preconverted + if (cvt.columnClass().isInstance(value)) { + // Value is already preconverted return value; } @@ -41,11 +41,6 @@ public static Object preconvert(@NonNull JavaField field, @NonNull Object value) public static Object postconvert(@NonNull JavaField field, @NonNull Object value) { var cvt = field.getCustomValueType(); if (cvt != null) { - if (field.getRawType().equals(value.getClass())) { - // Already postconverted - return value; - } - value = createCustomValueTypeConverter(cvt).toJava(field, value); } return value; @@ -74,15 +69,31 @@ public static CustomValueType getCustomValueType(@NonNull Type type, @Nullable C var columnCvt = cvtAnnotation == null || cvtAnnotation.converter().equals(ValueConverter.NoConverter.class) ? null : cvtAnnotation; var cvt = columnCvt == null ? rawType.getAnnotation(CustomValueType.class) : columnCvt; if (cvt != null) { - Preconditions.checkArgument(!cvt.columnValueType().isComposite(), "@CustomValueType.columnValueType must be != COMPOSITE"); - Preconditions.checkArgument(!cvt.columnValueType().isUnknown(), "@CustomValueType.columnValueType must be != UNKNOWN"); + var columnClass = cvt.columnClass(); + + var recursiveCvt = getCustomValueType(columnClass, null); + Preconditions.checkArgument(recursiveCvt == null, + "Defining recursive custom value types is prohibited, but @CustomValueType.columnClass=%s is annotated with %s", + columnClass.getCanonicalName(), + recursiveCvt); + + Preconditions.checkArgument(!columnClass.isInterface() && !isAbstract(columnClass.getModifiers()), + "@CustomValueType.columnClass=%s must not be an interface or an abstract class", columnClass.getCanonicalName()); + + var fvt = FieldValueType.forJavaType(columnClass, null); + Preconditions.checkArgument(!fvt.isComposite(), + "@CustomValueType.columnClass=%s must not map to FieldValueType.COMPOSITE", columnClass.getCanonicalName()); + Preconditions.checkArgument(!fvt.isUnknown(), + "@CustomValueType.columnClass=%s must not map to FieldValueType.UNKNOWN", columnClass.getCanonicalName()); + + var converterClass = cvt.converter(); Preconditions.checkArgument( - !cvt.converter().equals(ValueConverter.NoConverter.class) - && !cvt.converter().isInterface() - && !isAbstract(cvt.converter().getModifiers()) - && (cvt.converter().getDeclaringClass() == null || isStatic(cvt.converter().getModifiers())), - "@CustomValueType.converter must not be an interface, abstract class, non-static inner class, or NoConverter.class, but got: %s", - cvt); + !converterClass.equals(ValueConverter.NoConverter.class) + && !converterClass.isInterface() + && !isAbstract(converterClass.getModifiers()) + && (converterClass.getDeclaringClass() == null || isStatic(converterClass.getModifiers())), + "@CustomValueType.converter=%s must not be an interface, abstract class, non-static inner class, or NoConverter.class", + converterClass.getCanonicalName()); } return cvt; diff --git a/databind/src/main/java/tech/ydb/yoj/databind/FieldValueType.java b/databind/src/main/java/tech/ydb/yoj/databind/FieldValueType.java index af5272d1..325b8a60 100644 --- a/databind/src/main/java/tech/ydb/yoj/databind/FieldValueType.java +++ b/databind/src/main/java/tech/ydb/yoj/databind/FieldValueType.java @@ -25,30 +25,29 @@ public enum FieldValueType { /** * Integer value. - * Java-side must either be a numeric primitive, or extend {@link Number java.lang.Number} and - * implement {@link Comparable java.lang.Comparable}. + * Java-side must be a {@code long}, {@code int}, {@code short} or {@code byte}, + * or an instance of their wrapper classes {@code Long}, {@code Integer}, {@code Short} or {@code Byte}. */ INTEGER, /** * Real (floating-point) number value. - * Java-side must either be a numeric primitive, or extend {@link Number java.lang.Number} and - * implement {@link Comparable java.lang.Comparable}. + * Java-side must be a {@code double} or a {@code float}, or an instance of their + * wrapper classes {@code Double} or {@code Float}. */ REAL, /** * String value. - * Java-side must be serialized to simple string value. + * Java-side must be a {@code String}. */ STRING, /** * Boolean value. - * Java-side must either be an instance of {@link Boolean java.lang.Boolean} or a {@code boolean} - * primitive. + * Java-side must either be a {@code boolean} primitive, or an instance of its + * wrapper class {@code Boolean}. */ BOOLEAN, /** - * Enum value. Java-side must be an instance of {@link Enum java.lang.Enum}.
- * Typically stored as {@link Enum#name() enum constant name} or its {@link Enum#ordinal() ordinal}. + * Enum value. Java-side must be a concrete subclass of {@link Enum java.lang.Enum}. */ ENUM, /** @@ -61,12 +60,14 @@ public enum FieldValueType { INTERVAL, /** * Binary value: just a stream of uninterpreted bytes. - * Java-side must be a byte array. + * Java-side must be a {@code byte[]}. *

- * @deprecated It is strongly recommended to use a {@link ByteArray} that is properly {@code Comparable} - * and has a sane {@code equals()}. + * + * @deprecated Support for mapping raw {@code byte[]} will be removed in YOJ 3.0.0. + * Even now, it is strongly recommended to use a {@link ByteArray}: it is properly {@code Comparable} + * and has a sane {@code equals()}, which ensures that queries will work the same for in-memory database and YDB. */ - @Deprecated + @Deprecated(forRemoval = true) BINARY, /** * Binary value: just a stream of uninterpreted bytes. @@ -75,19 +76,24 @@ public enum FieldValueType { BYTE_ARRAY, /** * Composite value. Can contain any other values, including other composite values.
- * Java-side must be an immutable POJO with all-args constructor, e.g. a Lombok {@code @Value}-annotated - * class. + * Java-side must be an immutable value reflectable by YOJ: a Java {@code Record}, + * a Kotlin {@code data class}, an immutable POJO with all-args constructor annotated with + * {@code @ConstructorProperties} etc. */ COMPOSITE, /** * Polymorphic object stored in an opaque form (i.e., individual fields cannot be accessed by data binding).
- * Serialized form strongly depends on the the marshalling mechanism (e.g., JSON, YAML, ...).
+ * Serialized form strongly depends on the the marshalling mechanism (e.g., JSON, YAML, ...). */ OBJECT, /** + * @deprecated This enum constant will be removed in YOJ 3.0.0; {@link #forJavaType(Type, Column)} will instead + * throw an {@code IllegalArgumentException} if an unmappable type is encountered. + *

* Value type is unknown.
* It might be supported by the data binding implementation, but relying on that fact is not recommended. */ + @Deprecated(forRemoval = true) UNKNOWN; private static final Set SORTABLE_VALUE_TYPES = Set.of( @@ -107,16 +113,13 @@ public enum FieldValueType { )); /** - * @deprecated It is recommended to use the {@link CustomValueType} annotation with a {@link StringValueConverter} - * instead of calling this method. - *

- * To register a class not in your code (e.g., {@code UUID} from the JDK) as a string-value type, use - * a {@link Column @Column(customValueType=@CustomValueType(...))} annotation on the specific field. - *

- * Future versions of YOJ might remove this method entirely. - * * @param clazz class to register as string-value. Must either be final or sealed with permissible final-only implementations. * All permissible implementations of a sealed class will be registered automatically. + * @deprecated This method will be removed in YOJ 3.0.0. + * Use the {@link CustomValueType} annotation with a {@link StringValueConverter} instead of calling this method. + *

+ * To register a class not in your code (e.g., {@code UUID} from the JDK) as a string-value type, use + * a {@link Column @Column(customValueType=@CustomValueType(...))} annotation on a specific field. */ @Deprecated(forRemoval = true) @ExperimentalApi(issue = "https://github.com/ydb-platform/yoj-project/issues/24") @@ -145,7 +148,7 @@ public static void registerStringValueType(@NonNull Class clazz) { public static FieldValueType forJavaType(Type type, Column columnAnnotation) { var cvt = CustomValueTypes.getCustomValueType(type, columnAnnotation); if (cvt != null) { - return cvt.columnValueType(); + type = cvt.columnClass(); } boolean flatten = columnAnnotation == null || columnAnnotation.flatten(); @@ -158,11 +161,6 @@ private static FieldValueType forJavaType(@NonNull Type type) { if (type instanceof ParameterizedType || type instanceof TypeVariable) { return OBJECT; } else if (type instanceof Class clazz) { - var cvt = CustomValueTypes.getCustomValueType(clazz, null); - if (cvt != null) { - return cvt.columnValueType(); - } - if (isStringValueType(clazz)) { return STRING; } else if (INTEGER_NUMERIC_TYPES.contains(clazz)) { @@ -206,10 +204,13 @@ private static boolean isStringValueType(Class clazz) { * Checks whether Java object of type {@code type} is mapped to a composite database value * (i.e. > 1 database field) * - * @deprecated This method does not properly take into account the customizations specified in the - * {@link Column @Column} annotation on the field. Please do not call it directly, instead use - * {@code FieldValueType.forJavaType(type, column).isComposite()} where {@code column} is the - * {@link Column @Column} annotation's value. + * @deprecated This method will be removed in YOJ 3.0.0. + * This method does not properly take into account the customizations specified in the + * {@link Column @Column} annotation on the field. + *
Please do not call this method directly, instead use + * {@link #forJavaType(Type, Column) FieldValueType.forJavaType(type, column).isComposite()} + * where {@code column} is the {@link Column @Column} annotation's value or {@code null} if + * there is no annotation/you explicitly don't care. * * @param type Java object type * @return {@code true} if {@code type} maps to a composite database value; {@code false} otherwise @@ -229,12 +230,28 @@ public boolean isComposite() { } /** + * @deprecated This method will be removed in YOJ 3.0.0 along with the {@link #UNKNOWN} enum constant. + * * @return {@code true} if there is no fitting database value type for the type provided; {@code false} otherwise */ + @Deprecated(forRemoval = true) public boolean isUnknown() { return this == UNKNOWN; } + /** + * @deprecated This method will be removed in YOJ 3.0.0. This method is misleadingly named and is not generally useful. + *

+ */ + @Deprecated(forRemoval = true) public boolean isSortable() { return SORTABLE_VALUE_TYPES.contains(this); } diff --git a/databind/src/main/java/tech/ydb/yoj/databind/converter/EnumOrdinalConverter.java b/databind/src/main/java/tech/ydb/yoj/databind/converter/EnumOrdinalConverter.java new file mode 100644 index 00000000..7ef1e2f0 --- /dev/null +++ b/databind/src/main/java/tech/ydb/yoj/databind/converter/EnumOrdinalConverter.java @@ -0,0 +1,52 @@ +package tech.ydb.yoj.databind.converter; + +import com.google.common.base.Preconditions; +import lombok.NonNull; +import tech.ydb.yoj.databind.schema.Schema.JavaField; + +/** + * A generic converter that can be applied to represent your enum values as their {@link Enum#ordinal() ordinal}s + * instead of their {@link Enum#name() constant name}s or {@link Enum#toString() string representation}s. + * You can use it in a {@link tech.ydb.yoj.databind.schema.Column @Column} annotation, like this: + *
+ * @Column(
+ *     customValueType=@CustomValueType(
+ *         columnClass=Integer.class,
+ *         converter=EnumOrdinalConverter.class
+ *     )
+ * )
+ * 
+ * or as a global default for some of your enum type, like this: + *
+ * @CustomValueType(
+ *      columnClass=Integer.class,
+ *      converter=EnumOrdinalConverter.class
+ * )
+ * public enum MyEnum {
+ *     FOO,
+ *     BAR,
+ * }
+ * 
+ * + * @param Java type + */ +public final class EnumOrdinalConverter> implements ValueConverter { + private EnumOrdinalConverter() { + } + + @Override + public @NonNull Integer toColumn(@NonNull JavaField field, @NonNull E value) { + return value.ordinal(); + } + + @Override + public @NonNull E toJava(@NonNull JavaField field, @NonNull Integer ordinal) { + @SuppressWarnings("unchecked") + E[] constants = (E[]) field.getRawType().getEnumConstants(); + Preconditions.checkState(constants != null, "Not an enum field: %s", field); + Preconditions.checkArgument(ordinal >= 0, "Negative ordinal %s for field %s", ordinal, field); + Preconditions.checkArgument(ordinal < constants.length, "Unknown enum ordinal %s for field %s", ordinal, field); + + return constants[ordinal]; + } +} diff --git a/databind/src/main/java/tech/ydb/yoj/databind/converter/StringValueConverter.java b/databind/src/main/java/tech/ydb/yoj/databind/converter/StringValueConverter.java index 56ef3581..e7f74a48 100644 --- a/databind/src/main/java/tech/ydb/yoj/databind/converter/StringValueConverter.java +++ b/databind/src/main/java/tech/ydb/yoj/databind/converter/StringValueConverter.java @@ -18,8 +18,7 @@ *
  * @Column(
  *     customValueType=@CustomValueType(
- *         columnValueType=STRING,
- *         columnClass=<your type>,
+ *         columnClass=String.class,
  *         converter=StringValueConverter.class
  *     )
  * )
diff --git a/databind/src/main/java/tech/ydb/yoj/databind/converter/ValueConverter.java b/databind/src/main/java/tech/ydb/yoj/databind/converter/ValueConverter.java
index bbbf07e9..679a2a8a 100644
--- a/databind/src/main/java/tech/ydb/yoj/databind/converter/ValueConverter.java
+++ b/databind/src/main/java/tech/ydb/yoj/databind/converter/ValueConverter.java
@@ -8,18 +8,41 @@
  * Custom conversion logic between database column values and Java field values.
  * 
Must have a no-args public constructor. * - * @param Java value type - * @param Database column value type + * @param Java field value type + * @param Database column value type. Must not be the same type as {@code }. */ @ExperimentalApi(issue = "https://github.com/ydb-platform/yoj-project/issues/24") public interface ValueConverter { + /** + * Converts a field value to a {@link tech.ydb.yoj.databind.FieldValueType database column value} supported by YOJ. + * + * @param field schema field + * @param v field value, guaranteed to not be {@code null} + * @return database column value corresponding to the Java field value, must not be {@code null} + * + * @see #toJava(JavaField, Object) + */ @NonNull C toColumn(@NonNull JavaField field, @NonNull J v); + /** + * Converts a database column value to a Java field value. + * + * @param field schema field + * @param c database column value, guaranteed to not be {@code null} + * @return Java field value corresponding to the database column value, must not be {@code null} + * + * @see #toColumn(JavaField, Object) + */ @NonNull J toJava(@NonNull JavaField field, @NonNull C c); - class NoConverter implements ValueConverter { + /** + * Represents "no custom converter is defined" for {@link tech.ydb.yoj.databind.CustomValueType @CustomValueType} + * annotation inside a {@link tech.ydb.yoj.databind.schema.Column @Column} annotation. + *

Non-instantiable, every method including the constructor throws {@link UnsupportedOperationException}. + */ + final class NoConverter implements ValueConverter { private NoConverter() { throw new UnsupportedOperationException("Not instantiable"); } diff --git a/databind/src/main/java/tech/ydb/yoj/databind/expression/FieldValue.java b/databind/src/main/java/tech/ydb/yoj/databind/expression/FieldValue.java index 807f7128..f79451b7 100644 --- a/databind/src/main/java/tech/ydb/yoj/databind/expression/FieldValue.java +++ b/databind/src/main/java/tech/ydb/yoj/databind/expression/FieldValue.java @@ -77,34 +77,34 @@ private static FieldValue ofByteArray(@NonNull ByteArray byteArray) { @NonNull public static FieldValue ofObj(@NonNull Object obj, @NonNull JavaField javaField) { FieldValueType fvt = FieldValueType.forJavaType(obj.getClass(), javaField.getField().getColumn()); - Object postconverted = CustomValueTypes.preconvert(javaField, obj); + obj = CustomValueTypes.preconvert(javaField, obj); switch (fvt) { case STRING -> { - return ofStr((String) postconverted); + return ofStr((String) obj); } case ENUM -> { - return ofStr(((Enum) postconverted).name()); + return ofStr(((Enum) obj).name()); } case INTEGER -> { - return ofNum(((Number) postconverted).longValue()); + return ofNum(((Number) obj).longValue()); } case REAL -> { - return ofReal(((Number) postconverted).doubleValue()); + return ofReal(((Number) obj).doubleValue()); } case BOOLEAN -> { - return ofBool((Boolean) postconverted); + return ofBool((Boolean) obj); } case BYTE_ARRAY -> { - return ofByteArray((ByteArray) postconverted); + return ofByteArray((ByteArray) obj); } case TIMESTAMP -> { - return ofTimestamp((Instant) postconverted); + return ofTimestamp((Instant) obj); } case COMPOSITE -> { - ObjectSchema schema = ObjectSchema.of(postconverted.getClass()); + ObjectSchema schema = ObjectSchema.of(obj.getClass()); List flatFields = schema.flattenFields(); - Map flattenedObj = schema.flatten(postconverted); + Map flattenedObj = schema.flatten(obj); List allFieldValues = flatFields.stream() .map(jf -> new JavaFieldValue(jf, flattenedObj.get(jf.getName()))) @@ -118,7 +118,7 @@ public static FieldValue ofObj(@NonNull Object obj, @NonNull JavaField javaField } default -> throw new UnsupportedOperationException( "Unsupported value type: not a string, integer, timestamp, enum, " - + "floating-point number, tuple or wrapper of the above" + + "floating-point number, byte array, tuple or wrapper of the above" ); } } 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 fe7b9e71..38c06aee 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 @@ -71,5 +71,5 @@ * @see CustomValueType */ @ExperimentalApi(issue = "https://github.com/ydb-platform/yoj-project/issues/24") - CustomValueType customValueType() default @CustomValueType(columnValueType = UNKNOWN, columnClass = Comparable.class, converter = NoConverter.class); + CustomValueType customValueType() default @CustomValueType(columnClass = Comparable.class, converter = NoConverter.class); } diff --git a/databind/src/main/java/tech/ydb/yoj/databind/schema/Schema.java b/databind/src/main/java/tech/ydb/yoj/databind/schema/Schema.java index f82068e6..f8ca6dd7 100644 --- a/databind/src/main/java/tech/ydb/yoj/databind/schema/Schema.java +++ b/databind/src/main/java/tech/ydb/yoj/databind/schema/Schema.java @@ -204,8 +204,6 @@ protected Schema(Schema schema, String subSchemaFieldPath) { } else { var cvt = subSchemaField.getCustomValueType(); if (cvt != null) { - Preconditions.checkArgument(!cvt.columnValueType().isComposite() && !cvt.columnValueType().isUnknown(), - "Cannot have a composite or unknown columnValueType() in @CustomValueType annotation"); var dummyField = new JavaField(new DummyCustomValueSubField(subSchemaField), subSchemaField, __ -> true); dummyField.setName(subSchemaField.getName()); fields = List.of(dummyField); diff --git a/repository-test/src/main/java/tech/ydb/yoj/repository/test/RepositoryTest.java b/repository-test/src/main/java/tech/ydb/yoj/repository/test/RepositoryTest.java index b69865a7..29ee24eb 100644 --- a/repository-test/src/main/java/tech/ydb/yoj/repository/test/RepositoryTest.java +++ b/repository-test/src/main/java/tech/ydb/yoj/repository/test/RepositoryTest.java @@ -2656,10 +2656,15 @@ public void readAndFailOnInconsistentDataSucceedOnRetry() { } @Test - public void stringValuedIdInsert() { + public void customValuedIds() { Map inserted = new HashMap<>(); for (int i = 0; i < 100; i++) { - var snap = new UpdateFeedEntry(UpdateFeedEntry.Id.generate("insert"), Instant.now(), "payload-" + i); + var snap = new UpdateFeedEntry( + UpdateFeedEntry.Id.generate("insert"), + Instant.now(), + "payload-" + i, + Math.random() < 0.5 ? UpdateFeedEntry.Status.ACTIVE : UpdateFeedEntry.Status.INACTIVE + ); db.tx(() -> db.updateFeedEntries().insert(snap)); inserted.put(snap.getId(), snap); } diff --git a/repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/NetworkAppliance.java b/repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/NetworkAppliance.java index 1adeda9d..b88c1f7d 100644 --- a/repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/NetworkAppliance.java +++ b/repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/NetworkAppliance.java @@ -6,7 +6,6 @@ import lombok.Value; import tech.ydb.yoj.databind.ByteArray; import tech.ydb.yoj.databind.CustomValueType; -import tech.ydb.yoj.databind.FieldValueType; import tech.ydb.yoj.databind.converter.ValueConverter; import tech.ydb.yoj.databind.schema.Column; import tech.ydb.yoj.databind.schema.Schema.JavaField; @@ -17,7 +16,6 @@ import java.net.UnknownHostException; import static tech.ydb.yoj.databind.DbType.UINT64; -import static tech.ydb.yoj.databind.FieldValueType.BYTE_ARRAY; public record NetworkAppliance( @NonNull Id id, @@ -29,7 +27,7 @@ public record Id(@NonNull String value) implements RecordEntity.Id { @Override public int intValue() { diff --git a/repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/TypeFreak.java b/repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/TypeFreak.java index c3746112..2c2ce323 100644 --- a/repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/TypeFreak.java +++ b/repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/TypeFreak.java @@ -6,7 +6,6 @@ import lombok.With; import tech.ydb.yoj.databind.CustomValueType; import tech.ydb.yoj.databind.DbType; -import tech.ydb.yoj.databind.FieldValueType; import tech.ydb.yoj.databind.converter.StringValueConverter; import tech.ydb.yoj.databind.schema.Column; import tech.ydb.yoj.repository.db.Entity; @@ -118,7 +117,7 @@ public static class StringView implements Table.ViewId { String stringEmbedded; } - @CustomValueType(columnValueType = FieldValueType.STRING, columnClass = String.class, converter = StringValueConverter.class) + @CustomValueType(columnClass = String.class, converter = StringValueConverter.class) public static final class StringValueWrapper { private final String value; @@ -147,7 +146,7 @@ public String toString() { * instance method. *

E.g., {@code "XYZ-100500" <=> new Ticket(queue="XYZ", num=100500)} */ - @CustomValueType(columnValueType = FieldValueType.STRING, columnClass = String.class, converter = StringValueConverter.class) + @CustomValueType(columnClass = String.class, converter = StringValueConverter.class) public record Ticket(@NonNull String queue, int num) { public Ticket { Preconditions.checkArgument(num >= 1, "ticket number must be >= 1"); diff --git a/repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/UpdateFeedEntry.java b/repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/UpdateFeedEntry.java index 7fe35204..22ca1a89 100644 --- a/repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/UpdateFeedEntry.java +++ b/repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/UpdateFeedEntry.java @@ -4,7 +4,7 @@ import lombok.RequiredArgsConstructor; import lombok.Value; import tech.ydb.yoj.databind.CustomValueType; -import tech.ydb.yoj.databind.FieldValueType; +import tech.ydb.yoj.databind.converter.EnumOrdinalConverter; import tech.ydb.yoj.databind.converter.StringValueConverter; import tech.ydb.yoj.databind.schema.Column; import tech.ydb.yoj.repository.DbTypeQualifier; @@ -25,15 +25,24 @@ public class UpdateFeedEntry implements Entity { String payload; - public UpdateFeedEntry(Id id, Instant updatedAt, String payload) { + Status status; + + public UpdateFeedEntry(Id id, Instant updatedAt, String payload, Status status) { this.id = id; this.updatedAt = updatedAt.truncatedTo(ChronoUnit.SECONDS); this.payload = payload; + this.status = status; + } + + @CustomValueType(columnClass = Integer.class, converter = EnumOrdinalConverter.class) + public enum Status { + ACTIVE, + INACTIVE, } @Value @RequiredArgsConstructor(access = PRIVATE) - @CustomValueType(columnValueType = FieldValueType.STRING, columnClass = String.class, converter = StringValueConverter.class) + @CustomValueType(columnClass = String.class, converter = StringValueConverter.class) public static class Id implements Entity.Id { UUID uuid; String reserved; diff --git a/repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/VersionedEntity.java b/repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/VersionedEntity.java index 4cf162ef..a4b0bc87 100644 --- a/repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/VersionedEntity.java +++ b/repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/VersionedEntity.java @@ -1,7 +1,6 @@ package tech.ydb.yoj.repository.test.sample.model; import tech.ydb.yoj.databind.CustomValueType; -import tech.ydb.yoj.databind.FieldValueType; import tech.ydb.yoj.databind.schema.Column; import tech.ydb.yoj.repository.db.Entity; import tech.ydb.yoj.repository.db.RecordEntity; @@ -10,7 +9,6 @@ public record VersionedEntity( Id id, @Column( customValueType = @CustomValueType( - columnValueType = FieldValueType.INTEGER, columnClass = Long.class, converter = Version.Converter.class ) @@ -21,7 +19,6 @@ public record Id( String value, @Column( customValueType = @CustomValueType( - columnValueType = FieldValueType.INTEGER, columnClass = Long.class, converter = Version.Converter.class ) diff --git a/repository-ydb-v1/src/main/java/tech/ydb/yoj/repository/ydb/yql/YqlPrimitiveType.java b/repository-ydb-v1/src/main/java/tech/ydb/yoj/repository/ydb/yql/YqlPrimitiveType.java index 66029b02..6d626704 100644 --- a/repository-ydb-v1/src/main/java/tech/ydb/yoj/repository/ydb/yql/YqlPrimitiveType.java +++ b/repository-ydb-v1/src/main/java/tech/ydb/yoj/repository/ydb/yql/YqlPrimitiveType.java @@ -365,10 +365,6 @@ public static YqlPrimitiveType of(JavaField column) { FieldValueType valueType = column.getValueType(); String qualifier = column.getDbTypeQualifier(); CustomValueType cvt = column.getCustomValueType(); - if (cvt != null && cvt.columnValueType() != valueType) { - throw new IllegalStateException("This should never happen: detected FieldValueType must == @CustomValueType.columnValueType(), but got: " - + valueType + " != " + cvt.columnValueType()); - } var underlyingType = resolveYqlType( cvt != null ? cvt.columnClass() : javaType, diff --git a/repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/yql/YqlPrimitiveType.java b/repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/yql/YqlPrimitiveType.java index 4c17f99e..0deaed40 100644 --- a/repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/yql/YqlPrimitiveType.java +++ b/repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/yql/YqlPrimitiveType.java @@ -438,10 +438,6 @@ public static YqlPrimitiveType of(JavaField column) { FieldValueType valueType = column.getValueType(); String qualifier = column.getDbTypeQualifier(); CustomValueType cvt = column.getCustomValueType(); - if (cvt != null && cvt.columnValueType() != valueType) { - throw new IllegalStateException("This should never happen: detected FieldValueType must == @CustomValueType.columnValueType(), but got: " - + valueType + " != " + cvt.columnValueType()); - } var underlyingType = resolveYqlType( cvt != null ? cvt.columnClass() : javaType,