Exposed icon indicating copy to clipboard operation
Exposed copied to clipboard

DAO on compound/multiple primary key ?

Open scorsi opened this issue 5 years ago • 27 comments

Hello,

PostgreSQL can use multiple fields as Primary Key (doc) but how to handle that with Exposed DAO ?

I tried to use the DAO with a table which is quite complexe. Because example is better than words. Here the SQL :

CREATE TABLE table__contacts_first
(
    contact_id    UUID         NOT NULL,
    app_id        VARCHAR(128) NOT NULL,

    --- other fields ...

    FOREIGN KEY (contact_id) REFERENCES table__contacts (contact_id),
    PRIMARY KEY (contact_id, app_id)
);

As you can see, the table__contacts_first doesn't have any PK, but the pair contact_id + app_id acts as is. I have the following Exposed Table:

object ContactsFirstTable : Table(name = "table__contacts_first") {
    val contactId = reference("contact_id", ContactsTable).primaryKey()
    val appId = varchar("app_id", length = 128).primaryKey()
}

And now the Exposed DAO:

class ContactFirstDao(id: EntityID<UUID>) : UUIDEntity(id) {
    companion object : UUIDEntityClass<ContactFirstDao>(ContactsFirstTable)
}

Obviously, I got errors and in fact, I should construct my ContactFirstDao using contactId and appId.

Do you have any idea how I could achieve that ?

Thanks 👍

scorsi avatar Jun 17 '20 20:06 scorsi

Hi, maybe I didn’t understand it, but according to this guide we can specify two primary keys in the table definition and bind table fields to entity fields. Section "8.8. Many-to-Many Associations" https://www.baeldung.com/kotlin-exposed-persistence

michaelbrylevskii avatar Jul 02 '20 16:07 michaelbrylevskii

Hello @michaelbrylevskii thanks for the answer but it didn't answer to my question at all.

It is not a "many-to-many" associations but just a foreign key on a compound primary keys.

I have a table with two primary key, how could I create my Dao:

class MyDao(primaryKeyOne: EntityID<UUID>, primaryKeyTwo: EntityID<UUID>) : UUIDEntity(primaryKeyOne, primaryKeyTwo) {
    companion object : UUIDEntityClass<MyDao>(MyTable)
}
class MyDao(primaryKey EntityID<UUID, UUID>) : UUIDEntity(primaryKey) {
    companion object : UUIDEntityClass<MyDao>(MyTable)
}
class MyDao(primaryKey EntityID<Pair<UUID, UUID>>) : UUIDEntity(primaryKey) {
    companion object : UUIDEntityClass<MyDao>(MyTable)
}

All of thoses don't work. It is missing in the library.

Do you understand now ?

scorsi avatar Jul 02 '20 20:07 scorsi

Is there any update to this? I am looking for this feature as well

JulianBissekkou avatar Sep 10 '20 14:09 JulianBissekkou

I left Exposed caused of that important issue. Anyone in the staff behind the lib seams to be interested into this, it's still unassigned...

scorsi avatar Sep 15 '20 12:09 scorsi

This is something so basic at the SQL level, why is Exposed failing at addressing this? So strange.

PedroD avatar Oct 15 '20 12:10 PedroD

Agreed, very strange.

scorsi avatar Oct 15 '20 13:10 scorsi

I naively attempted to use a CompositeColumn as the ID column, but was blocked by this. Composite columns appear to inherit from Expression and not the requisite Column interface. So close!

data class BiCompositeId<T : Comparable<T>, U: Comparable<U>>(
    val first: T,
    val second: U
) : Comparable<BiCompositeId<T, U>> {
    override fun compareTo(other: BiCompositeId<T, U>): Int {
        return first.compareTo(other.first).let {
            if (it != 0) it else second.compareTo(other.second)
        }
    }
}

@Suppress("unchecked_cast")
class BiCompositeIdColumn<T : Comparable<T>, U: Comparable<U>>(
    val first: Column<T>,
    val second: Column<U>
) : BiCompositeColumn<T, U, BiCompositeId<T, U>?>(
    column1 = first,
    column2 = second,
    transformFromValue = { id -> id?.first to id?.second },
    transformToValue = { firstValue, secondValue ->
        if (firstValue != null && secondValue != null) {
            BiCompositeId(firstValue as T, secondValue as U)
        } else {
            null
        }
    }
)

abstract class BiCompositeIdTable<T : Comparable<T>, U: Comparable<U>>(name: String = "") : IdTable<BiCompositeId<T, U>>(name) {
    abstract val first: Column<T>
    abstract val second: Column<U>
    override val id by lazy { biCompositeIdColumn(first, second).entityId() } // sad-face :-(
    override val primaryKey by lazy { super.primaryKey ?: PrimaryKey(first, second) }
}

jnfeinstein avatar Nov 11 '20 16:11 jnfeinstein

I left Exposed caused of that important issue. Anyone in the staff behind the lib seams to be interested into this, it's still unassigned...

Hi, I was just wondering what library you are using now?

1fexd avatar Nov 26 '20 13:11 1fexd

An amaizing library: SQL ! I stopped using libraries, ORMs, ... Just SQL is enough. But you can take a look at Ktorm (https://ktorm.liuwj.me/).

I left that issue open because it's an importante missing feature.

scorsi avatar Nov 26 '20 13:11 scorsi

An amaizing library: SQL ! I stopped using libraries, ORMs, ... Just SQL is enough. But you can take a look at Ktorm (https://ktorm.liuwj.me/).

I left that issue open because it's an importante missing feature.

That's been my approach since ever, but I got tired of making all CRUD methods manually for every new project. Waste of time, prone to error, and more code to maintain.

PedroD avatar Dec 17 '20 13:12 PedroD

I left that issue open because it's an importante missing feature.

If Ktorm can do this, then I'm sold: https://github.com/kotlin-orm/ktorm/issues/220

PedroD avatar Dec 17 '20 13:12 PedroD

Hello there

Just created an extension library for that (modifying exposed-dao part). Hope it will help you https://gitlab.com/sickfar/exposed-dao-composite

sickfar avatar Apr 11 '21 15:04 sickfar

So, no composite keys for Exposed?

I'm not calling it ORM anymore then. Composite keys are pretty basic!

rodrigogs avatar Oct 13 '21 14:10 rodrigogs

So it's almost a year and still nothing? :(

szakerx avatar Nov 04 '21 10:11 szakerx

So it's almost a year and still nothing? :(

I ended up going with ktorm.

It's pretty good, but I have to be honest...

I, more than anybody, aim to avoid using Spring and hibernate for everything, just because I don't believe that almost an entire community should rely basically on a single toolset. The problem is: Kotlin libraries are amazing, but most of them are not mature like good old Java libs that ppl have been using for decades now. I'm also facing problems with ktorm, it's not all roses.

My suggestions are:

  • if you are developing something critical, go for hibernate or something like it
  • if you are toying around with code or developing some PoC, you might like to struggle a little bit with ktorm
  • Exposed is not an option since it does not have basic mapping features such as composite keys

rodrigogs avatar Nov 04 '21 20:11 rodrigogs

I know this issue is old now but this seems to been solved.

object myTable : Table() {
    val column1 = varchar("column1", 256)
    val column2 = varchar("column2", 256)

    override val primaryKey = PrimaryKey(arrayOf(column1, column2), "compositeKey")
}

Sir, we are talking about dao, not sql dsl

szakerx avatar Jan 19 '23 21:01 szakerx

I just reached this point today. I'll try figure out how to bypass it.

rafaz182 avatar Mar 03 '23 11:03 rafaz182

Any luck with that? @rafaz182

rChaoz avatar Apr 14 '23 20:04 rChaoz

Are there any updates on this topic?

BierDav avatar May 11 '23 11:05 BierDav

Same issue here

VitSun2001 avatar Jun 07 '23 14:06 VitSun2001

any updates?

mairs8 avatar Jul 21 '23 18:07 mairs8

Any updates on composite primary keys?

Splitframe avatar Sep 07 '23 11:09 Splitframe

We also need this feature. Are you planning do implement it?

fengelniederhammer avatar Sep 25 '23 11:09 fengelniederhammer

While migrating from Hibernate, I came across this issue as well. Use DAO with MySQL and it works great, until now I need to use a table with composite primary key (2 int fields). Seems pretty vital?

Update I did work around this by using the DSL instead of DAO approach for these 2 tables. It was a bit of work, but not that much of a problem for this case.

peterdk avatar Jan 03 '24 12:01 peterdk

I think it has been achieved. You can pass multiple values as a primary key like below:

import org.jetbrains.exposed.sql.*

data class Article(val title: String, val body: String)

object Articles : Table() {
    val title = varchar("title", 128)
    val body = varchar("body", 1024)

    override val primaryKey = PrimaryKey(title, body)
}

The result:

image

You can check the rest of the code to make sure the work by following the official short tutorial: https://ktor.io/docs/interactive-website-add-persistence.html

Edit:

Sir, we are talking about dao, not sql dsl

Okey, I understood. But I'm not sure what is the use-case about this issue. You can declare the entity like the official doc I put above:

data class Article(val id: Int, val title: String, val body: String)

...and convert queries like this (from the doc):

class DAOFacadeImpl : DAOFacade {
    private fun resultRowToArticle(row: ResultRow) = Article(
        id = row[Articles.id],
        title = row[Articles.title],
        body = row[Articles.body],
    )

    override suspend fun addNewArticle(title: String, body: String): Article? = dbQuery {
        val insertStatement = Articles.insert {
            it[Articles.title] = title
            it[Articles.body] = body
        }
        insertStatement.resultedValues?.singleOrNull()?.let(::resultRowToArticle)
    }
}

But I'm not a Kotlin guy, maybe that is a reason I'm not sure why Kotlin experts want to declare the primary key as a companion object.

sundaycrafts avatar Feb 19 '24 12:02 sundaycrafts

The reason to use dao is to not have to write this boilerplate code, of course it is possible to implement a dao like thing with dsl, but it will introduce a lot of disgusting code duplication

BierDav avatar Feb 19 '24 13:02 BierDav

Thank you. I don't intend to argue against your opinion at all, but my primary personal reason for using DAO is to code type-safe applications, not to remove boilerplate. However, I completely agree that the DAOFacade implementation appears redundant upon first impression. I haven't considered DAO carefully, though.

(To be honest, in the first place, I lack a clear picture of how resolving this issue would eliminate the need for that boilerplate)

sundaycrafts avatar Feb 20 '24 10:02 sundaycrafts