Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion: Support for in-line row mapper #4

Open
arvearve opened this issue Nov 6, 2024 · 3 comments
Open

Suggestion: Support for in-line row mapper #4

arvearve opened this issue Nov 6, 2024 · 3 comments

Comments

@arvearve
Copy link

arvearve commented Nov 6, 2024

In many cases it would be nice to have an overload for queryOf<T>() / actionReturing<T>() that takes an extractor function / row mapper, e.g.

data class Statement {
    // ...
    inline fun <reified T> queryOf((ResultSet) -> T?): Query<T> = TODO()
}

Some times it’s nice to be allowed to access “under the hood”

If i understand correctly this would let you bypass the kotlinx.serialization loop and would be useful in complex cases where you would otherwise have to tailor your entity data classes a lot in order to make things work. E.g. custom Serializers, @contextual and @SerialName annotations,

Example envisioned usage:

// Business layer model
data class Member(val id: String, val name: String?, val createdAt: ZonedDateTime)

// No need for @Serializable entity class with @Contextual on dates, etc.
val query = Sql("...").queryOf<Member> { row -> Member(
    row.int("id"), 
    row.stringOrNull("name"), 
    row.zonedDateTime("created_at")
  )
}
query.runOn(db)

This suggestion is inspired by Kotliquery's implementation.

References:

@arvearve arvearve changed the title Support for in-line row mapper Suggestion: Support for in-line row mapper Nov 6, 2024
@deusaquilus
Copy link
Member

Yup. Good feature idea.

@deusaquilus
Copy link
Member

deusaquilus commented Dec 20, 2024

@arvearve If you're willing to use kotlin serialization you can build a custom serializer like this:

data class CustomPerson(val name: String, val age: Int)

object CustomPersonSerializer : KSerializer<CustomPerson> {
  override val descriptor =
    buildClassSerialDescriptor("CustomPerson") {
      element("name", serialDescriptor<String>())
      element("age", serialDescriptor<Int>())
    }

  override fun serialize(encoder: Encoder, value: CustomPerson) = 
    TODO("Not used by Terpal-SQL so don't care")

  override fun deserialize(decoder: Decoder): CustomPerson =
    decoder.decodeStructure(descriptor) {
      CustomPerson(
        decodeStringElement(descriptor, 0),
        decodeIntElement(descriptor, 1)
      )
    }
}

// Then invoke it like this:
val people = Sql("SELECT name, age FROM people").queryOf<CustomPerson>(CustomPersonSerializer).runOn(driver)
println(people)

This is obviously not the nicest API but its roughly what JetBrains is expecting everyone to use when they want to build custom composite serializer.

I have purposely not built a cross-database row-abstraction because there are ultimately too many nasty gotchas in the process (e.g. some row-implementations have metadata, others don't. Some row-implementations have async callbacks, etc...).

What I am interested in is exploring some kind of way to make writing this deserialize function in a more palatable way which is what I'm exploring now. Which API would you prefer, this one?

Sql("...").queryOf<Person>(
  shape = {
    fieldString("name")
    fieldInt("age")
  },
  run = {
    CustomPerson(readString(), readInt())
  }
)

Or this one?

Sql("...").queryOf<Person>(
  { fields -> 
    fields.string("name")
    fields.int("age")
  },
  { read -> 
    CustomPerson(read.string(), read.int())
  }
)

@arvearve
Copy link
Author

arvearve commented Jan 6, 2025

Hi. Personally I think the 2nd variant looks better 👍. However, it still feels quite like code repetition for the caller.
I can sort of see how for example async callbacks might make this unavoidable though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants