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

Using CriteriaBuilder with embedded IDs #3125

Open
casey-marshall-yolabs opened this issue Sep 14, 2024 · 1 comment
Open

Using CriteriaBuilder with embedded IDs #3125

casey-marshall-yolabs opened this issue Sep 14, 2024 · 1 comment

Comments

@casey-marshall-yolabs
Copy link

Expected Behavior

I'm trying to write an update criteria that updates a row with a composite key. An abbreviated version of the classes involved:

@Embeddable
data class MemberProfileId(
    @field:MappedProperty("family_id") val familyId: String,
    @field:MappedProperty("member_id") val memberId: String
)

@MappedEntity
data class MemberProfileDto(
    @field:EmbeddedId val id: MemberProfileId,
    @field:MappedProperty("given_name") val givenName: String? = null,
    // more fields omitted
)

And I'm trying to write an UpdateSpecification<MemberProfileDto>:

override fun toPredicate(
    root: Root<MemberProfileDto>, query: CriteriaUpdate<*>, criteriaBuilder: CriteriaBuilder
): Predicate? {
    givenName?.let { query.set(root.get("givenName"), it) }
    // update more fields conditionally
   return criteriaBuilder.equals(root.get<MemberProfileId>("id"), targetId)
}

Actual Behaviour

From logs it looks like the SQL generated is correct, but the arguments bound are not:

2024-09-14T14:19:34.695Z [reactor-tcp-nio-14] DEBUG io.micronaut.data.query - Executing Query: UPDATE "member_profile" SET "given_name"=$1 WHERE ("family_id" = $2 AND "member_id" = $3) []
2024-09-14T14:19:34.695Z [reactor-tcp-nio-14] TRACE io.micronaut.data.query - Binding parameter at position 0 to value foo2 with data type: STRING []
2024-09-14T14:19:34.695Z [reactor-tcp-nio-14] TRACE io.micronaut.data.query - Binding parameter at position 1 to value MemberProfileId(familyId=MOb9rKxVrTQX_mUs6P8YN, memberId=pV5H2vV1d8kdtPp0LTIA1) with data type: STRING []
2024-09-14T14:19:34.695Z [reactor-tcp-nio-14] TRACE io.micronaut.data.query - Binding parameter at position 2 to value MemberProfileId(familyId=MOb9rKxVrTQX_mUs6P8YN, memberId=pV5H2vV1d8kdtPp0LTIA1) with data type: STRING []

The bound values appear to just be toString of the MemberProfileId class, not the individual components.

I've tried a fair number of things to try working around this, but nothing has been successful so far. Is there a hook into the conversion to string I could use to perform the correct value binding?

I saw a discussion about a similar issue from > 2 years ago, with no answer. I'm figuring this is a bug, or that I've missed something.

Steps To Reproduce

  1. Attempt to write an update query with CriteriaBuilder that refers to an embedded ID.

Environment Information

  • Micronaut 4.6.1
  • Micronaut-data 4.9.3
  • r2dbc 1.0.0-RELEASE, r2dbc-postgresql 1.0.5.RELEASE

Example Application

No response

Version

4.6.1

@casey-marshall-yolabs
Copy link
Author

I did hack out a workaround by implementing a (terrible, ugly) PersistentPropertyPath:

package mypackage

import io.micronaut.data.model.Association
import io.micronaut.data.model.PersistentEntity
import io.micronaut.data.model.PersistentProperty
import io.micronaut.data.model.jpa.criteria.PersistentEntityPath
import io.micronaut.data.model.jpa.criteria.PersistentPropertyPath
import io.micronaut.data.model.jpa.criteria.impl.ExpressionVisitor
import jakarta.persistence.criteria.Expression
import jakarta.persistence.criteria.Path
import jakarta.persistence.criteria.Root
import jakarta.persistence.metamodel.Bindable
import jakarta.persistence.metamodel.MapAttribute
import jakarta.persistence.metamodel.PluralAttribute
import jakarta.persistence.metamodel.SingularAttribute

internal class BasicPersistentPropertyPath<T>(
    val name: String,
    private val alias: String,
    private val type: Class<T>,
    private val parent: Root<*>
) : PersistentPropertyPath<T> {
    override fun getJavaType(): Class<out T> = type

    override fun getModel(): Bindable<T> {
        TODO("Not yet implemented")
    }

    override fun getParentPath(): Path<*> = parent

    override fun <Y : Any?> get(attributeName: String?): Path<Y> {
        TODO("Not yet implemented")
    }

    override fun type(): Expression<Class<out T>> {
        TODO("Not yet implemented")
    }

    override fun visitExpression(expressionVisitor: ExpressionVisitor?) {
        TODO("Not yet implemented")
    }

    override fun getProperty(): PersistentProperty = object : PersistentProperty {
        override fun getName(): String = this@BasicPersistentPropertyPath.name
        override fun getPersistedName(): String = this@BasicPersistentPropertyPath.alias

        override fun getTypeName(): String {
            TODO("Not yet implemented")
        }

        override fun getOwner(): PersistentEntity {
            return (parent as PersistentEntityPath<*>).persistentEntity ?:
                throw IllegalStateException("No owner found for property: $name")
        }

        override fun isAssignable(type: String?): Boolean {
            TODO("Not yet implemented")
        }
    }

    override fun getAssociations(): MutableList<Association> = mutableListOf()

    override fun <K : Any?, V : Any?, M : MutableMap<K, V>?> get(map: MapAttribute<T, K, V>?): Expression<M> {
        TODO("Not yet implemented")
    }

    override fun <E : Any?, C : MutableCollection<E>?> get(collection: PluralAttribute<T, C, E>?): Expression<C> {
        TODO("Not yet implemented")
    }

    override fun <Y : Any?> get(attribute: SingularAttribute<in T, Y>?): Path<Y> {
        TODO("Not yet implemented")
    }
}

Usage in my case:

return criteriaBuilder.and(
    criteriaBuilder.equal(
        BasicPersistentPropertyPath<String>("familyId", "family_id", String::class.java, root),
        id.familyId),
    criteriaBuilder.equal(BasicPersistentPropertyPath<String>(
        "memberId", "member_id", String::class.java, root),
        id.memberId)
)

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

1 participant