diff --git a/databind/src/main/java/tech/ydb/yoj/databind/schema/reflect/KotlinDataClassComponent.java b/databind/src/main/java/tech/ydb/yoj/databind/schema/reflect/KotlinDataClassComponent.java index c73bb8bb..811e6cda 100644 --- a/databind/src/main/java/tech/ydb/yoj/databind/schema/reflect/KotlinDataClassComponent.java +++ b/databind/src/main/java/tech/ydb/yoj/databind/schema/reflect/KotlinDataClassComponent.java @@ -1,7 +1,9 @@ package tech.ydb.yoj.databind.schema.reflect; +import com.google.common.reflect.TypeToken; import kotlin.jvm.JvmClassMappingKt; import kotlin.reflect.KCallable; +import kotlin.reflect.KClass; import kotlin.reflect.jvm.KCallablesJvm; import kotlin.reflect.jvm.ReflectJvmMapping; import tech.ydb.yoj.databind.FieldValueType; @@ -22,7 +24,7 @@ public final class KotlinDataClassComponent implements ReflectField { private final Class type; private final FieldValueType valueType; private final Column column; - + private final ReflectType reflectType; public KotlinDataClassComponent(Reflector reflector, String name, KCallable callable) { @@ -33,7 +35,15 @@ public KotlinDataClassComponent(Reflector reflector, String name, KCallable c var kReturnType = callable.getReturnType(); this.genericType = ReflectJvmMapping.getJavaType(kReturnType); - this.type = JvmClassMappingKt.getJavaClass(kReturnType); + + var kClassifier = kReturnType.getClassifier(); + if (kClassifier instanceof KClass kClass) { + this.type = JvmClassMappingKt.getJavaClass(kClass); + } else { + // fallback to Guava's TypeToken if kotlin-reflect returns unpredictable results ;-) + this.type = TypeToken.of(genericType).getRawType(); + } + this.column = type.getAnnotation(Column.class); this.valueType = FieldValueType.forJavaType(genericType, column); this.reflectType = reflector.reflectFieldType(genericType, valueType); diff --git a/repository/pom.xml b/repository/pom.xml index ba4117f8..0af379a0 100644 --- a/repository/pom.xml +++ b/repository/pom.xml @@ -39,6 +39,18 @@ simpleclient + + + org.jetbrains.kotlin + kotlin-reflect + true + + + org.jetbrains.kotlin + kotlin-stdlib + true + + org.eclipse.collections eclipse-collections @@ -70,4 +82,31 @@ test + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + compileTests + process-test-sources + + test-compile + + + + src/test/java + src/test/resources + src/test/kotlin + target/generated-sources/annotations + + 17 + + + + + + diff --git a/repository/src/test/java/tech/ydb/yoj/repository/hybrid/ChangeMetadata.java b/repository/src/test/java/tech/ydb/yoj/repository/hybrid/ChangeMetadata.java new file mode 100644 index 00000000..938061a1 --- /dev/null +++ b/repository/src/test/java/tech/ydb/yoj/repository/hybrid/ChangeMetadata.java @@ -0,0 +1,15 @@ +package tech.ydb.yoj.repository.hybrid; + +import java.time.Instant; + +public record ChangeMetadata( + ChangeKind kind, + Instant time, + long version +) { + enum ChangeKind { + CREATE, + UPDATE, + DELETE, + } +} diff --git a/repository/src/test/java/tech/ydb/yoj/repository/hybrid/Chronology.java b/repository/src/test/java/tech/ydb/yoj/repository/hybrid/Chronology.java new file mode 100644 index 00000000..35bdde0e --- /dev/null +++ b/repository/src/test/java/tech/ydb/yoj/repository/hybrid/Chronology.java @@ -0,0 +1,9 @@ +package tech.ydb.yoj.repository.hybrid; + +import java.time.Instant; + +public record Chronology( + Instant createdAt, + Instant updatedAt +) { +} diff --git a/repository/src/test/kotlin/tech/ydb/yoj/repository/hybrid/Account.kt b/repository/src/test/kotlin/tech/ydb/yoj/repository/hybrid/Account.kt new file mode 100644 index 00000000..253d6ecd --- /dev/null +++ b/repository/src/test/kotlin/tech/ydb/yoj/repository/hybrid/Account.kt @@ -0,0 +1,28 @@ +package tech.ydb.yoj.repository.hybrid + +import tech.ydb.yoj.repository.db.Entity + +data class Account( + private val id: Id, + private val version: Long, + private val chronology: Chronology, + val login: String, + val description: String? = null, +) : Entity { + override fun getId() = id + + data class Id(val value: String) : Entity.Id + + data class Snapshot( + private val id: Id, + private val entity: Account?, + val meta: ChangeMetadata, + ) : Entity { + override fun getId() = id + + data class Id( + private val id: Account.Id, + private val version: Long + ) : Entity.Id + } +} diff --git a/repository/src/test/kotlin/tech/ydb/yoj/repository/hybrid/HybridSchemaTest.kt b/repository/src/test/kotlin/tech/ydb/yoj/repository/hybrid/HybridSchemaTest.kt new file mode 100644 index 00000000..a24bbd09 --- /dev/null +++ b/repository/src/test/kotlin/tech/ydb/yoj/repository/hybrid/HybridSchemaTest.kt @@ -0,0 +1,45 @@ +package tech.ydb.yoj.repository.hybrid + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import tech.ydb.yoj.repository.db.EntitySchema +import java.time.Instant + +/** + * Test for Schema using both Kotlin data classes and JVM records. + */ +class HybridSchemaTest { + @Test + fun testIdFlattening() { + val now = Instant.now() + val account = Account( + id = Account.Id("hahaha"), + version = 1L, + chronology = Chronology(now, now), + login = "logIn", + description = null + ) + val accountSnapshot = Account.Snapshot( + id = Account.Snapshot.Id(account.id, 1L), + entity = account, + meta = ChangeMetadata(ChangeMetadata.ChangeKind.CREATE, now, 1L) + ) + + val schema = EntitySchema.of(Account.Snapshot::class.java) + + val flattened = schema.flatten(accountSnapshot) + assertThat(flattened).isEqualTo(mapOf( + "id_id" to "hahaha", + "id_version" to 1L, + "entity_id" to "hahaha", + "entity_version" to 1L, + "entity_chronology_createdAt" to now, + "entity_chronology_updatedAt" to now, + "entity_login" to "logIn", + "meta_kind" to ChangeMetadata.ChangeKind.CREATE, + "meta_time" to now, + "meta_version" to 1L + )) + assertThat(schema.newInstance(flattened)).isEqualTo(accountSnapshot) + } +}