Fix comple attribute RSQL filters (#2564)
Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -26,6 +26,7 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import jakarta.annotation.Nonnull;
|
import jakarta.annotation.Nonnull;
|
||||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||||
@@ -44,6 +45,8 @@ import jakarta.persistence.metamodel.MapAttribute;
|
|||||||
import jakarta.persistence.metamodel.PluralAttribute;
|
import jakarta.persistence.metamodel.PluralAttribute;
|
||||||
import jakarta.persistence.metamodel.SetAttribute;
|
import jakarta.persistence.metamodel.SetAttribute;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
|
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
|
||||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
|
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
|
||||||
@@ -117,9 +120,10 @@ public class SpecificationBuilder<T> {
|
|||||||
.toList()
|
.toList()
|
||||||
.toArray(PREDICATES_ARRAY_0));
|
.toArray(PREDICATES_ARRAY_0));
|
||||||
} else if (op == Logical.Operator.OR) {
|
} else if (op == Logical.Operator.OR) {
|
||||||
|
final Map<String, Integer> state = pathResolver.getState();
|
||||||
return cb.or(logical.getChildren().stream()
|
return cb.or(logical.getChildren().stream()
|
||||||
.map(child -> {
|
.map(child -> {
|
||||||
pathResolver.reset();
|
pathResolver.reset(state);
|
||||||
return build(child); // for or path resolver joins could be reused
|
return build(child); // for or path resolver joins could be reused
|
||||||
})
|
})
|
||||||
.toList()
|
.toList()
|
||||||
@@ -324,12 +328,13 @@ public class SpecificationBuilder<T> {
|
|||||||
private static Path<?> deepGetPath(final Path<?> path, final String subAttributeName) {
|
private static Path<?> deepGetPath(final Path<?> path, final String subAttributeName) {
|
||||||
return deepGetPath(path, subAttributeName.split("\\."), 0);
|
return deepGetPath(path, subAttributeName.split("\\."), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Path<?> deepGetPath(final Path<?> path, final String[] subAttributeNameSplit, int startIndex) {
|
private static Path<?> deepGetPath(final Path<?> path, final String[] subAttributeNameSplit, int startIndex) {
|
||||||
final String subAttributeName = subAttributeNameSplit[startIndex++];
|
final String subAttributeName = subAttributeNameSplit[startIndex++];
|
||||||
if (startIndex == subAttributeNameSplit.length) {
|
if (startIndex == subAttributeNameSplit.length) {
|
||||||
return path.get(subAttributeName);
|
return path.get(subAttributeName);
|
||||||
} else { // else its a deeper path so request left join
|
} 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);
|
return deepGetPath(join.join(subAttributeName, JoinType.LEFT), subAttributeNameSplit, startIndex);
|
||||||
} else {
|
} else {
|
||||||
throw new RSQLParameterSyntaxException("Unexpected sub attribute " + subAttributeName);
|
throw new RSQLParameterSyntaxException("Unexpected sub attribute " + subAttributeName);
|
||||||
@@ -432,8 +437,13 @@ public class SpecificationBuilder<T> {
|
|||||||
return getCollectionPathResolver(attribute.getName()).getJoinOnInner(value);
|
return getCollectionPathResolver(attribute.getName()).getJoinOnInner(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset() {
|
private Map<String, Integer> getState() {
|
||||||
attributeToPathResolver.values().forEach(CollectionPathResolver::reset);
|
return attributeToPathResolver.entrySet().stream()
|
||||||
|
.collect(Collectors.toMap(Map.Entry::getKey, resolver -> resolver.getValue().getPos()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reset(final Map<String, Integer> toState) {
|
||||||
|
attributeToPathResolver.forEach((attribute, resolver) -> resolver.setPos(toState.getOrDefault(attribute, 0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@@ -445,6 +455,8 @@ public class SpecificationBuilder<T> {
|
|||||||
|
|
||||||
private final String attributeName;
|
private final String attributeName;
|
||||||
private final List<Path<?>> paths = new ArrayList<>();
|
private final List<Path<?>> paths = new ArrayList<>();
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
private int pos;
|
private int pos;
|
||||||
private final Map<Object, MapJoin<?, ?, ?>> joinOnCache = new HashMap<>();
|
private final Map<Object, MapJoin<?, ?, ?>> joinOnCache = new HashMap<>();
|
||||||
private final Map<Object, MapJoin<?, ?, ?>> joinOnInnerCache = new HashMap<>();
|
private final Map<Object, MapJoin<?, ?, ?>> joinOnInnerCache = new HashMap<>();
|
||||||
@@ -479,10 +491,6 @@ public class SpecificationBuilder<T> {
|
|||||||
return mapPath;
|
return mapPath;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset() {
|
|
||||||
pos = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,17 +11,16 @@ package org.eclipse.hawkbit.repository.jpa.specifications;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
import org.springframework.data.jpa.domain.Specification;
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class to easily combine {@link Specification} instances.
|
* Helper class to easily combine {@link Specification} instances.
|
||||||
*/
|
*/
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
public final class SpecificationsBuilder {
|
public final class SpecificationsBuilder {
|
||||||
|
|
||||||
private SpecificationsBuilder() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Combine all given specification with and. The first specification is the
|
* Combine all given specification with and. The first specification is the
|
||||||
* where clause.
|
* where clause.
|
||||||
@@ -33,11 +32,10 @@ public final class SpecificationsBuilder {
|
|||||||
if (specList.isEmpty()) {
|
if (specList.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Specification<T> specs = Specification.where(specList.get(0));
|
Specification<T> specs = specList.get(0);
|
||||||
for (final Specification<T> specification : specList.subList(1, specList.size())) {
|
for (final Specification<T> specification : specList.subList(1, specList.size())) {
|
||||||
specs = specs.and(specification);
|
specs = specs.and(specification);
|
||||||
}
|
}
|
||||||
return specs;
|
return specs;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
@@ -102,6 +102,21 @@ class RsqlToSqlTest {
|
|||||||
print(JpaTarget.class, TargetFields.class, TargetFields.TAG.name() + "!=''");
|
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) {
|
private static String from(final String sql) {
|
||||||
return sql.substring(sql.indexOf("FROM"));
|
return sql.substring(sql.indexOf("FROM"));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user