ydb-platform / yoj-project

YDB ORM for Java (YOJ) is a lightweight ORM for immutable entities. It has native support for YDB and is battle-tested.
Apache License 2.0
13 stars 12 forks source link

Custom byte array and number conversion logic #24

Open nvamelichev opened 10 months ago

nvamelichev commented 10 months ago

Add converter logic & annotations to make your custom types convertible to a basic repertoire of YOJ types hard-coded in FieldValueType.

Note that at this time there is no attempt to make such types fully workable for complex scenarios, e.g. in-memory listing with filtering.

nvamelichev commented 9 months ago

Prototyping in https://github.com/ydb-platform/yoj-project/pull/22

nvamelichev commented 9 months ago

22 merged, now we can use this experimental feature and improve it in future versions :-)

nvamelichev commented 9 months ago

Bug: FieldValue.ofObj() does recognize the custom value type but does not call the preconvert()/postconvert() anywhere. This leads to ClassCastException at query-building time (db.myTable().query().where("customValueTypedField").eq(...)).

This was not detected because my test custom-value type implemented Number and/or was String-like and implemented a sane toString().

Failing test:

@Test
public void failingTest() {
    var ve = new VersionedEntity(new VersionedEntity.Id("heyhey"), new Version(100_500L));
    db.tx(() -> db.versionedEntities().insert(ve));

    // Throws ClassCastException on eq() call
    assertThat(db.tx(() -> db.versionedEntities().query()
            .where("version").eq(ve.version())
            .findOne()
    )).isEqualTo(ve);
}

Entity and cvt:

public record VersionedEntity(
        Id id,
        Version version
) implements RecordEntity<VersionedEntity> {
    public record Id(String value) implements Entity.Id<VersionedEntity> {
    }
}

@CustomValueType(
        columnValueType = FieldValueType.INTEGER,
        columnClass = Long.class,
        converter = Version.Converter.class
)
public record Version(long value) {
    public static final class Converter implements ValueConverter<Version, Long> {
        @Override
        public @NonNull Long toColumn(@NonNull JavaField field, Version v) {
            return v.value();
        }

        @Override
        public @NonNull Version toJava(@NonNull JavaField field, @NonNull Long value) {
            return new Version(value);
        }
    }
}
nvamelichev commented 8 months ago

Now we have meta-annotation support for @Column and @CustomValueType annotations (#50). You can e.g. write @StringColumn UUID myColumn in an entity and this is equivalent to @Column(customValueType=@CustomValueType(columnClass=String.class, converter=StringValueConverter.class)) UUID myColumn. Same is possible for annotating types with predefined @CustomValueType annotation, see e.g. @StringValueType.

nvamelichev commented 8 months ago

:tada: As of YOJ 2.2.10, custom value conversion logic is considered fairly well-tested and is OK performance wise, but API changes are still likely.

nvamelichev commented 2 months ago

:tada: As of YOJ 2.5.x, the custom value conversion logic has stabilized; further API changes are unlikely.

Custom conversion APIs will (most likely) stop being @ExperimentalApi in YOJ 3.0.0.