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

Fix binding embeded id parameter #3131

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,14 @@ import io.micronaut.data.jdbc.annotation.JdbcRepository
import io.micronaut.data.model.Sort
import io.micronaut.data.model.query.builder.sql.Dialect
import io.micronaut.data.repository.CrudRepository
import io.micronaut.data.repository.jpa.criteria.UpdateSpecification
import io.micronaut.data.tck.entities.Shipment
import io.micronaut.data.tck.entities.ShipmentId
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import jakarta.persistence.criteria.CriteriaBuilder
import jakarta.persistence.criteria.CriteriaUpdate
import jakarta.persistence.criteria.Predicate
import jakarta.persistence.criteria.Root
import spock.lang.Specification

import jakarta.inject.Inject
Expand Down Expand Up @@ -99,6 +104,33 @@ class H2EmbeddedIdSpec extends Specification {
then:"all is correct"
all.size() == 2

when:"Update specification by embedded id"
repository.updateAll(new UpdateSpecification<Shipment>() {
@Override
Predicate toPredicate(Root<Shipment> root, CriteriaUpdate<?> query, CriteriaBuilder criteriaBuilder) {
query.set("field", "test3-updated")
return criteriaBuilder.equal(root.get("shipmentId"), id3)
}
})
def updatedShipment = repository.findById(id3).orElse(null)
then:"Update is successful"
updatedShipment
updatedShipment.field == "test3-updated"

when:"Update specification by embedded id parts"
repository.updateAll(new UpdateSpecification<Shipment>() {
@Override
Predicate toPredicate(Root<Shipment> root, CriteriaUpdate<?> query, CriteriaBuilder criteriaBuilder) {
query.set("field", "test3")
return criteriaBuilder.and(criteriaBuilder.equal(root.join("shipmentId").get("country"), id3.country),
criteriaBuilder.equal(root.join("shipmentId").get("city"), id3.city))
}
})
updatedShipment = repository.findById(id3).orElse(null)
then:"Update is successful"
updatedShipment
updatedShipment.field == "test3"

when:"Find by country"
def foundByCountry = repository.findByShipmentIdCountry("g")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@

import io.micronaut.data.jdbc.annotation.JdbcRepository;
import io.micronaut.data.repository.PageableRepository;
import io.micronaut.data.repository.jpa.JpaSpecificationExecutor;
import io.micronaut.data.tck.entities.Shipment;
import io.micronaut.data.tck.entities.ShipmentId;
import io.micronaut.data.model.query.builder.sql.Dialect;

import java.util.List;

@JdbcRepository(dialect = Dialect.H2)
public interface ShipmentRepository extends PageableRepository<Shipment, ShipmentId> {
public interface ShipmentRepository extends PageableRepository<Shipment, ShipmentId>, JpaSpecificationExecutor<Shipment> {

Shipment findByShipmentIdCountry(String country);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.beans.BeanWrapper;
import io.micronaut.core.type.Argument;
import io.micronaut.data.model.DataType;
import io.micronaut.data.model.PersistentPropertyPath;
import io.micronaut.data.model.query.builder.QueryParameterBinding;
import io.micronaut.data.model.runtime.RuntimePersistentEntity;

/**
* The default parameter expression implementation.
Expand Down Expand Up @@ -48,6 +51,13 @@ public QueryParameterBinding bind(BindingContext bindingContext) {
if (outgoingQueryParameterProperty == null) {
return new SimpleParameterBinding(name, DataType.forType(paramClass), bindingContext.isExpandable(), value);
}
return new PropertyPathParameterBinding(name, outgoingQueryParameterProperty, bindingContext.isExpandable(), value);
Object parameterValue = value;
if (value != null && outgoingQueryParameterProperty.getProperty().getOwner().isEmbeddable()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dstepanov So here is the case that DefaultParameterExpression gets value as embeded id but binding parameters should be each field of embedded id. So, we need to extract value for the param if field belongs to embedded id and passed value is embedded id. Not sure if this is safe enough and if there is some better approach to extract correct value for embedded id part(s).

Copy link
Contributor

@dstepanov dstepanov Sep 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be expanded in the equals operator, similar to the composite identity:

public void visitEquals(Expression<?> leftExpression, Expression<?> rightExpression, boolean ignoreCase) {
if (leftExpression instanceof io.micronaut.data.model.jpa.criteria.PersistentPropertyPath<?> persistentPropertyPath) {
PersistentPropertyPath propertyPath = persistentPropertyPath.getPropertyPath();
PersistentProperty property = propertyPath.getProperty();
if (computePropertyPaths() && property instanceof Association) {
List<IPredicate> predicates = new ArrayList<>();
PersistentEntityUtils.traverse(propertyPath, pp ->
predicates.add(new BinaryPredicate(
new DefaultPersistentPropertyPath<>(pp, null),
rightExpression,
ignoreCase ? PredicateBinaryOp.EQUALS_IGNORE_CASE : PredicateBinaryOp.EQUALS
))
);
if (predicates.size() == 1) {

Copy link
Contributor Author

@radovanradic radovanradic Sep 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Predicate and criteria is ok, association is expanded and query generated is

UPDATE `Shipment1` SET `field`=? WHERE (`sp_country` = ? AND `sp_city` = ?)

but each of these 2 bindings for WHERE criteria is ShipmentId instead of each property of the embedded id getting from here

@NotNull
    @NextMajorVersion("Require non null y")
    private Predicate predicate(Expression<?> x, Object y, PredicateBinaryOp op) {
        if (x instanceof IdExpression) {
            return new ExpressionBinaryPredicate(x, literal(y), op);
        }
        return new PersistentPropertyBinaryPredicate<>(requireProperty(x), literal(y), op);
    }

This is why we need to get actual embedded id field value in the bind method above.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That should be done a bit differently:

  • We need to add PersistentPropertyPath parameterPropertyPath to DefaultParameterExpression like it's in SourceParameterExpressionImpl
  • When the property is expanded, we should try to recognize DefaultParameterExpression and clone it to a new parameter with parameterPropertyPath set the left property path
  • DefaultParameterExpression should use the same logic as in the source one to calculate parameterBindingPath
  • DefaultBindableParametersStoredQuery should support using getParameterBindingPath for cases when the value set

RuntimePersistentEntity runtimePersistentEntity = (RuntimePersistentEntity) outgoingQueryParameterProperty.getProperty().getOwner();
if (runtimePersistentEntity.getIntrospection().getBeanType().isAssignableFrom(value.getClass())) {
parameterValue = BeanWrapper.getWrapper(parameterValue).getRequiredProperty(outgoingQueryParameterProperty.getProperty().getName(), Argument.OBJECT_ARGUMENT);
}
}
return new PropertyPathParameterBinding(name, outgoingQueryParameterProperty, bindingContext.isExpandable(), parameterValue);
}
}
Loading