diff --git a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/ql/SpecificationBuilder.java b/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/ql/SpecificationBuilder.java index bc471e3ca..976be4141 100644 --- a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/ql/SpecificationBuilder.java +++ b/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/ql/SpecificationBuilder.java @@ -26,6 +26,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; import jakarta.annotation.Nonnull; import jakarta.persistence.criteria.CriteriaBuilder; @@ -44,6 +45,8 @@ import jakarta.persistence.metamodel.MapAttribute; import jakarta.persistence.metamodel.PluralAttribute; import jakarta.persistence.metamodel.SetAttribute; +import lombok.Getter; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; @@ -117,9 +120,10 @@ public class SpecificationBuilder { .toList() .toArray(PREDICATES_ARRAY_0)); } else if (op == Logical.Operator.OR) { + final Map state = pathResolver.getState(); return cb.or(logical.getChildren().stream() .map(child -> { - pathResolver.reset(); + pathResolver.reset(state); return build(child); // for or path resolver joins could be reused }) .toList() @@ -324,12 +328,13 @@ public class SpecificationBuilder { private static Path deepGetPath(final Path path, final String subAttributeName) { return deepGetPath(path, subAttributeName.split("\\."), 0); } + private static Path deepGetPath(final Path path, final String[] subAttributeNameSplit, int startIndex) { final String subAttributeName = subAttributeNameSplit[startIndex++]; if (startIndex == subAttributeNameSplit.length) { return path.get(subAttributeName); } else { // else its a deeper path so request left join - if (path instanceof Join join) { + if (path instanceof Join join) { return deepGetPath(join.join(subAttributeName, JoinType.LEFT), subAttributeNameSplit, startIndex); } else { throw new RSQLParameterSyntaxException("Unexpected sub attribute " + subAttributeName); @@ -432,8 +437,13 @@ public class SpecificationBuilder { return getCollectionPathResolver(attribute.getName()).getJoinOnInner(value); } - private void reset() { - attributeToPathResolver.values().forEach(CollectionPathResolver::reset); + private Map getState() { + return attributeToPathResolver.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, resolver -> resolver.getValue().getPos())); + } + + private void reset(final Map toState) { + attributeToPathResolver.forEach((attribute, resolver) -> resolver.setPos(toState.getOrDefault(attribute, 0))); } @Nonnull @@ -445,6 +455,8 @@ public class SpecificationBuilder { private final String attributeName; private final List> paths = new ArrayList<>(); + @Getter + @Setter private int pos; private final Map> joinOnCache = new HashMap<>(); private final Map> joinOnInnerCache = new HashMap<>(); @@ -479,10 +491,6 @@ public class SpecificationBuilder { return mapPath; }); } - - private void reset() { - pos = 0; - } } } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/SpecificationsBuilder.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/SpecificationsBuilder.java index 7932cfa05..41200b028 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/SpecificationsBuilder.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/SpecificationsBuilder.java @@ -11,17 +11,16 @@ package org.eclipse.hawkbit.repository.jpa.specifications; import java.util.List; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import org.springframework.data.jpa.domain.Specification; /** * Helper class to easily combine {@link Specification} instances. */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) public final class SpecificationsBuilder { - private SpecificationsBuilder() { - - } - /** * Combine all given specification with and. The first specification is the * where clause. @@ -33,11 +32,10 @@ public final class SpecificationsBuilder { if (specList.isEmpty()) { return null; } - Specification specs = Specification.where(specList.get(0)); + Specification specs = specList.get(0); for (final Specification specification : specList.subList(1, specList.size())) { specs = specs.and(specification); } return specs; } - -} +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlToSqlTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlToSqlTest.java index e8c0c81eb..24e91af15 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlToSqlTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlToSqlTest.java @@ -102,6 +102,21 @@ class RsqlToSqlTest { print(JpaTarget.class, TargetFields.class, TargetFields.TAG.name() + "!=''"); } + @Test + void printComplex() { + print(JpaTarget.class, TargetFields.class, "attribute.key1==00 and (attribute.key2==02 or attribute.key2==01)"); + print(JpaTarget.class, TargetFields.class, "(attribute.key1==00 or attribute.key1==01) and (attribute.key2==02 or attribute.key2==01) and attribute.key3==01"); + print(JpaTarget.class, TargetFields.class, "(attribute.key1==00 or attribute.key1==01) and (attribute.key2==02 or attribute.key2==01) and attribute.key3==01 and updateStatus!=pending"); + print(JpaTarget.class, TargetFields.class, "((attribute.key1==00 or attribute.key1==01) and (attribute.key2==02 or attribute.key2==01) and attribute.key3==03 and updateStatus!=pending)"); + print(JpaTarget.class, TargetFields.class, "((attribute.key1==00 or attribute.key1==01) and (attribute.key2==02 or attribute.key2==01) and attribute.key3==01 and updateStatus!=pending)"); + } + + @Test + void printVeryComplex() { + print(JpaTarget.class, TargetFields.class, "(attribute.key1==00 or attribute.key1==01) and ((attribute.key2==02 or attribute.key2==01) and (attribute.key4==02 or attribute.key5==01)) and attribute.key3==01 and updateStatus!=pending"); + print(JpaTarget.class, TargetFields.class, "(attribute.key1==00 or attribute.key1==01) and ((attribute.key2==02 or attribute.key2==01) or (attribute.key4==02 or attribute.key5==01)) and attribute.key3==01 and updateStatus!=pending"); + } + private static String from(final String sql) { return sql.substring(sql.indexOf("FROM")); }