Hi, a question about komapper, I have 2 tables wit...
# komapper
a
Hi, a question about komapper, I have 2 tables with an association table :
User(id: Int), Game(id: Int), UserGame(gameId: Int, userId: Int)
I want to use the association API to be able to do like
users.games()
and
game.members()
, but I don't quite understand the docs about this How can I do this ? I currently only created two data classes for my 2 tables, but none for the association table.
t
Hi, thanks for trying the association API! Define your entity classes as follows:
Copy code
@KomapperEntity
@KomapperOneToMany(UserGame::class)
@KomapperAggregateRoot("users")
data class User(@KomapperId val id: Int)

@KomapperEntity
@KomapperManyToOne(User::class)
@KomapperManyToOne(Game::class)
data class UserGame(
    @KomapperId val gameId: Int,
    @KomapperId val userId: Int
)

@KomapperEntity
@KomapperOneToMany(UserGame::class)
data class Game(@KomapperId val id: Int)
If you want to hide access to the association table from your application, you need to define extension functions:
Copy code
context(org.komapper.core.dsl.query.EntityStoreContext)
fun User.gemes(): Set<Game> {
    return this.userGame().mapNotNull { it.game() }.toSet()
}

context(org.komapper.core.dsl.query.EntityStoreContext)
fun Game.members(): Set<User> {
    return this.userGame().mapNotNull { it.user() }.toSet()
}
Now you can achieve what you want:
Copy code
val store: EntityStore = db.runQuery(...)

with(store.asContext()) {
    val users: Set<User> = store.users()
    for (user in users) {
        val games: Set<Game> = user.gemes()
        for (game in games) {
            val memabers: Set<User> = game.members()
        }
    }
}
a
Hi, thanks for the quick and complete response ! I'll discuss with my team whether we use the API or not :) Is it planned to simplify the usage of this API also ? I know it's for Context Receivers and it's complex in Kotlin to works with all of this, I already worked on similar context-dependant APIs, but we find the API pretty complex to use with the
store: EntityStore
and
with(store.asContext())
etc A more direct
Copy code
val user = db.runQuery(...)
val userGames = user.games()
API would be very cool !!
Or even just extension val properties if it's not doing any transaction internally 👀
t
Is it planned to simplify the usage of this API also ?
If we can simplify it, that would be great. Do you have any ideas? From my point of view, using Context Receiver to get association data is a simpler way than using Lazy Loading.
Or even just extension val properties if it’s not doing any transaction internally
What does this mean? Let me hear a little more.
a
Just using
.games
of
.games()
, as in Kotlin we rarely use methods for returning a value except when it does some internal computing But I don't know what it currently does in the current API :)
I don't know how much you can do with KSP, but a "perfect" API, in my opinion, wouldn't use Metamodels nor QueryDSL at all Like you just do
User().insert()
, and
User.select { where { it.id greaterThan 5 } }.first()
etc I know it's pretty far from the current API, but it's just how I would design and love to use it As I said, I don't know the capabilities of KSP and if it's possible at all If I had to be closer to the current API, I would write instead
Copy code
QueryDSL.insert(User).values(User(), User())
QueryDSL.select(User).where { ... }.first()
You could only use
@KomapperEntity
to QueryDSL methods
For the Association API, as I said, something like this would be cool to use :
Copy code
val users = QueryDSL.from(...) // List<User>
val games = users.games // List<Game>
users.first().userGames.first().delete()
val latestGame = games.last().also { it.userGames += UserGame(it.id, 2) }
I've written
List
, but I think a custom implementation should be used, so that it can perform easily the SQL request, like when using +=
And I've not written MutableList because I think as it will also execute SQL requests, you will only get a new list and not the same list modified when using
.delete()
and other operations
t
Just using
.games
of
.games()
, as in Kotlin we rarely use methods for returning a value except when it does some internal computing
The reason Komapper uses extended functions rather than extended properties is to avoid conflicts with user-defined properties.
Thanks for sharing your ideas. However, the API you present seems to go beyond the constraints of Kotlin syntax and KSP capabilities. For example, it is not possible to reference User as done in
QueryDSL.insert(User)
. For the Association API, we need to resolve how to hold and propagate association entities. In the current API, EntityStoreContext and Context Receivers play those roles.
a
Okay I see, why exactly can't you do that ? What exactly is blocking this from KSP ?
t
Copy code
data class User(val id: Int) {
    companion object: EntityMetadamodel<User, Int, Companion> {
        ...
    }
}
If KSP allows bytecode enhancement and can generate a comanion object as described above, we can write something like
QueryDSL.insert(User)
. But KSP can only generate source code and can not generate a companion object. I think it is not a blocking, but an appropriate constraint. Komapper’s current API allows us to write
QueryDsl.insert(Meta.user)
, which is not much different than writing
QueryDSL.insert(User)
.
a
Okay I see, maybe one day :>