ufoss-org / kotysa

The idiomatic way to write type-safe SQL in Kotlin
The Unlicense
117 stars 1 forks source link

Added supporter for custom mappers column to entityand entity to column #128

Open guitcastro opened 1 year ago

guitcastro commented 1 year ago

Hi! First of all thanks for the amazing project! I am evaluating it, and loving it =)

My goals is have a small orm to mapper tables entity to domain entities and vice-versa, and if possible without an intermediate entities. Per example, the following domain class:


data class Foo(val id: Id, val name: String) {
    @JvmInline
    value class Id(val value: UUID = UUID.randomUUID())
}

Would be really coll if this was possible:


object FooSchema : PostgresqlTable<Foo> {
    val id = uuid({ it.id.value }).primaryKey()
    val name = varchar(Foo::name)
}

This does not work because getters are restricted to KCallable:


// Reflection.kt
public fun <T : Any> ((T) -> Any?).toCallable(): KCallable<Any?> =
        when (this) {
            is KProperty1<T, *> -> this
            is KFunction<*> -> this
            else -> throw RuntimeException("Wrong type for $this, support only KProperty1 and KFunction")
        }

I've done the follow workaround and it worked partially:


object FooSchema : PostgresqlTable<Foo>() {
    val id = uuid(Foo::id).primaryKey()
}
private fun Foo.id() = this.id.value

I mean partially, because it's possible to insert rows, but this breaks reading entities from database. The exception occur at:

callBy(args)

because: kotysaTable.columns.indexOf(column) is an UUID and not a column.columnClass.javaObjectType

  args[param] =
              row.getWithOffset(kotysaTable.columns.indexOf(column), column.columnClass.javaObjectType)

So if we add a property to KotysaColumnDb<T : Any, U : Any> such as :

public val rowToEntityConverter: (U -> T), would be possible to use custom mapper (at least from row to entities) using:


  val rawColumnValue = row.getWithOffset(kotysaTable.columns.indexOf(column), column.columnClass.javaObjectType)
  args[param] =  column.rowToEntityConverter.call(rawColumnValue)

If this is in the scope of the project and I am in the right track to implement it, I can open a PR to fix it.

pull-vert commented 1 year ago

Hi @guitcastro , first thanks a lot for the kind words about Kotysa :slightly_smiling_face:

An issue was opened a few weeks ago, I think you can take a look the 2 solutions I gave at that time. I think they could solve your case without needing a new feature in Kotysa : #100

A custom converter could be a great feature for Kotysa, but Kotlin language has already very nice native features like the by keyword so I am not sure this is needed.

If the solutions I gave in #100 drive you to a clean answer for your particular case, maybe you can reply in a new comment with the code you ended with ? It would provide an example that I can share in Kotysa documentation.

guitcastro commented 1 year ago

I've looked, and using the by keyword in a extension enforces me to add a default value to my domain constructor, which is not desired. I tried to use noArg kotlin plugin, however the constructor is generated only for java Class and not for KClass. One option is to verify if the class has a java 0 args constructor and if so, use it and initialize all other properties later.

This would fill my needs, as I am adding a noArg but it only can be used by reflection. So the domain remains clean.

pull-vert commented 1 year ago

Hi @guitcastro

I am currently working on Kotysa 3.0, with nice and big technical changes. I will have a look at this issue after that :)