From 978a2ab7451d7dac409850c8bced8230289bdf04 Mon Sep 17 00:00:00 2001 From: Avgustin Marinov Date: Thu, 13 Nov 2025 14:38:32 +0200 Subject: [PATCH] Remove legacy RSQL to Specification builders (#2801) Signed-off-by: Avgustin Marinov --- .../hawkbit/repository/jpa/ql/QLSupport.java | 23 +- .../jpa/ql/utils/HawkbitQlToSql.java | 22 +- .../jpa/rsql/legacy/AbstractRSQLVisitor.java | 189 ------ .../jpa/rsql/legacy/JpaQueryRsqlVisitor.java | 563 ------------------ .../rsql/legacy/JpaQueryRsqlVisitorG2.java | 497 ---------------- .../legacy/SpecificationBuilderLegacy.java | 79 --- .../ql/SpecificationBuilderLegacyTest.java | 137 ----- .../jpa/ql/SpecificationBuilderTest.java | 174 ++---- .../jpa/management/TargetManagementTest.java | 6 - .../jpa/rsql/RsqlTargetFieldTest.java | 83 +-- .../repository/jpa/rsql/RsqlToSqlTest.java | 23 +- 11 files changed, 86 insertions(+), 1710 deletions(-) delete mode 100644 hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/AbstractRSQLVisitor.java delete mode 100644 hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/JpaQueryRsqlVisitor.java delete mode 100644 hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/JpaQueryRsqlVisitorG2.java delete mode 100644 hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/SpecificationBuilderLegacy.java delete mode 100644 hawkbit-repository/hawkbit-repository-jpa-ql/src/test/java/org/eclipse/hawkbit/repository/jpa/ql/SpecificationBuilderLegacyTest.java diff --git a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/ql/QLSupport.java b/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/ql/QLSupport.java index d5ea8c13c..7064bb3b1 100644 --- a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/ql/QLSupport.java +++ b/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/ql/QLSupport.java @@ -21,7 +21,6 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.NonNull; -import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.text.StrLookup; import org.eclipse.hawkbit.repository.QueryField; @@ -29,7 +28,6 @@ import org.eclipse.hawkbit.repository.exception.QueryException; import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.jpa.ql.Node.Comparison; -import org.eclipse.hawkbit.repository.jpa.rsql.legacy.SpecificationBuilderLegacy; import org.eclipse.hawkbit.repository.rsql.VirtualPropertyResolver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -80,12 +78,6 @@ public class QLSupport implements ApplicationListener { private static final QLSupport SINGLETON = new QLSupport(); - public enum SpecBuilder { - LEGACY_G1, // legacy RSQL visitor - LEGACY_G2, // G2 RSQL visitor - G3 // G3 specification builder - still experimental / yet default - } - /** * If QL comparison operators shall ignore the case. If ignore case is true "x == ax" will match "x == aX" */ @@ -101,15 +93,6 @@ public class QLSupport implements ApplicationListener { @Value("${hawkbit.ql.case-insensitive-db:false}") private boolean caseInsensitiveDB; - /** - * @deprecated in favour fixed final visitor / spec builder of G2 RSQL visitor / G3 spec builder. since 0.6.0 - */ - @Setter // for tests only - @Deprecated(forRemoval = true, since = "0.6.0") - @Value("${hawkbit.ql.spec-builder:G3}") // - private SpecBuilder specBuilder; - - @SuppressWarnings({ "rawtypes", "unchecked" }) private QueryParser parser; private List nodeTransformers; private EntityManager entityManager; @@ -166,12 +149,8 @@ public class QLSupport implements ApplicationListener { * @throws RSQLParameterSyntaxException if the RSQL syntax is wrong */ public & QueryField, T> Specification buildSpec(final String query, final Class queryFieldType) { - if (specBuilder == SpecBuilder.G3) { - return new SpecificationBuilder(ignoreCase && !caseInsensitiveDB) + return new SpecificationBuilder(ignoreCase && !caseInsensitiveDB) .specification(transform(parse(query, queryFieldType), queryFieldType)); - } else { - return new SpecificationBuilderLegacy(queryFieldType, virtualPropertyResolver).specification(query); - } } public & QueryField, T> Specification buildSpec(final Node query, final Class queryFieldType) { diff --git a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/ql/utils/HawkbitQlToSql.java b/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/ql/utils/HawkbitQlToSql.java index 0a0029573..8a2351533 100644 --- a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/ql/utils/HawkbitQlToSql.java +++ b/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/ql/utils/HawkbitQlToSql.java @@ -19,7 +19,6 @@ import jakarta.persistence.criteria.CriteriaQuery; import org.eclipse.hawkbit.repository.QueryField; import org.eclipse.hawkbit.repository.jpa.ql.QLSupport; -import org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder; public class HawkbitQlToSql { @@ -31,9 +30,8 @@ public class HawkbitQlToSql { isEclipselink = entityManager.getProperties().keySet().stream().anyMatch(key -> key.startsWith("eclipselink.")); } - public & QueryField> String toSQL( - final Class domainClass, final Class fieldsClass, final String rsql, final SpecBuilder rsqlToSpecBuilder) { - final CriteriaQuery query = createQuery(domainClass, fieldsClass, rsql, rsqlToSpecBuilder); + public & QueryField> String toSQL(final Class domainClass, final Class fieldsClass, final String rsql) { + final CriteriaQuery query = createQuery(domainClass, fieldsClass, rsql); final TypedQuery typedQuery = entityManager.createQuery(query); if (isEclipselink) { try { @@ -55,20 +53,10 @@ public class HawkbitQlToSql { } private & QueryField> CriteriaQuery createQuery( - final Class domainClass, final Class fieldsClass, final String rsql, final SpecBuilder rsqlToSpecBuilder) { + final Class domainClass, final Class fieldsClass, final String rsql) { final CriteriaQuery query = entityManager.getCriteriaBuilder().createQuery(domainClass); final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); - final SpecBuilder defaultRsqlToSpecBuilder = QLSupport.getInstance().getSpecBuilder(); - if (defaultRsqlToSpecBuilder != rsqlToSpecBuilder) { - QLSupport.getInstance().setSpecBuilder(rsqlToSpecBuilder); - } - try { - return query.where(QLSupport.getInstance(). buildSpec(rsql, fieldsClass) - .toPredicate(query.from(domainClass), cb.createQuery(domainClass), cb)); - } finally { - if (defaultRsqlToSpecBuilder != rsqlToSpecBuilder) { - QLSupport.getInstance().setSpecBuilder(defaultRsqlToSpecBuilder); - } - } + return query.where(QLSupport.getInstance(). buildSpec(rsql, fieldsClass) + .toPredicate(query.from(domainClass), cb.createQuery(domainClass), cb)); } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/AbstractRSQLVisitor.java b/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/AbstractRSQLVisitor.java deleted file mode 100644 index 320fb919e..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/AbstractRSQLVisitor.java +++ /dev/null @@ -1,189 +0,0 @@ -/** - * Copyright (c) 2025 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.eclipse.hawkbit.repository.jpa.rsql.legacy; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Stream; - -import jakarta.validation.constraints.NotNull; - -import cz.jirutka.rsql.parser.ast.ComparisonNode; -import cz.jirutka.rsql.parser.ast.ComparisonOperator; -import cz.jirutka.rsql.parser.ast.RSQLOperators; -import lombok.Value; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.hawkbit.repository.QueryField; -import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; -import org.eclipse.hawkbit.repository.jpa.ql.SpecificationBuilder; -import org.springframework.util.ObjectUtils; - -/** - * @deprecated Old implementation of RSQL Visitor (G2). Deprecated in favour of next gen implementation - {@link SpecificationBuilder}. - */ -@Deprecated(forRemoval = true, since = "0.9.0") -@Slf4j -public abstract class AbstractRSQLVisitor & QueryField> { - - static final ComparisonOperator IS = new ComparisonOperator("=is=", "=eq="); - static final ComparisonOperator NOT = new ComparisonOperator("=not=", "=ne="); - - static final Set OPERATORS; - - static { - final Set operators = new HashSet<>(RSQLOperators.defaultOperators()); - // == and != alternatives just treating "null" string as null not as a "null" - operators.add(IS); - operators.add(NOT); - OPERATORS = Collections.unmodifiableSet(operators); - } - - private final Class rsqlQueryFieldType; - - protected AbstractRSQLVisitor(final Class rsqlQueryFieldType) { - this.rsqlQueryFieldType = rsqlQueryFieldType; - } - - @SuppressWarnings("java:S1066") // java:S1066 - more readable with separate "if" statements - protected QueryPath getQueryPath(final ComparisonNode node) { - final int firstSeparatorIndex = node.getSelector().indexOf(QueryField.SUB_ATTRIBUTE_SEPARATOR); - final String enumName = (firstSeparatorIndex == -1 - ? node.getSelector() - : node.getSelector().substring(0, firstSeparatorIndex)).toUpperCase(); - log.debug("Get field identifier by name {} of enum type {}", enumName, rsqlQueryFieldType); - - try { - final A enumValue = Enum.valueOf(rsqlQueryFieldType, enumName); - String[] split = getSplit(enumValue, node.getSelector()); - - // validate - if (!enumValue.isMap()) { - // sub entity need minimum 1 dot - if (!enumValue.getSubEntityAttributes().isEmpty() && split.length < 2) { - final String defaultSubEntityAttribute = enumValue.getDefaultSubEntityAttribute(); - if (defaultSubEntityAttribute != null) { // single sub attribute - so add is as a default - split = new String[] { split[0], defaultSubEntityAttribute }; - } else { - throw createRSQLParameterUnsupportedException(node, null); - } - } - } - - // validate and normalize (replace enum name with JPA entity field name and format the rest) prepare jpa path - split[0] = enumValue.getJpaEntityFieldName(); - for (int i = 1; i < split.length; i++) { - split[i] = getFormattedSubEntityAttribute(enumValue, split[i]); - - if (!containsSubEntityAttribute(enumValue, split[i])) { - if (i != split.length - 1 || !enumValue.isMap()) { - throw createRSQLParameterUnsupportedException(node, null); - } // otherwise - the key of map is not in the sub entity attributes - } - } - - return new QueryPath(enumValue, split); - } catch (final IllegalArgumentException e) { - throw createRSQLParameterUnsupportedException(node, e); - } - } - - private String[] getSplit(final A enumValue, final String rsqlFieldName) { - if (enumValue.isMap()) { - final String[] split = rsqlFieldName.split(QueryField.SUB_ATTRIBUTE_SPLIT_REGEX, 2); - if (split.length != 2 || ObjectUtils.isEmpty(split[1])) { - throw new RSQLParameterUnsupportedFieldException( - "The syntax of the given map search parameter field {" + rsqlFieldName + "} is wrong. Syntax is: ."); - } - return split; - } else { - return rsqlFieldName.split(QueryField.SUB_ATTRIBUTE_SPLIT_REGEX); - } - } - - /** - * Contains the sub entity the given field. - * - * @param propertyField the given field - * @return true contains false contains not - */ - private boolean containsSubEntityAttribute(final A enumField, final String propertyField) { - final List subEntityAttributes = enumField.getSubEntityAttributes(); - if (subEntityAttributes.contains(propertyField)) { - return true; - } - - for (final String attribute : subEntityAttributes) { - final String[] graph = attribute.split(QueryField.SUB_ATTRIBUTE_SPLIT_REGEX); - for (final String subAttribute : graph) { - if (subAttribute.equalsIgnoreCase(propertyField)) { - return true; - } - } - } - - return false; - } - - /** - * @param node current processing node - * @param rootException in case there is a cause otherwise {@code null} - * @return Exception with prepared message extracted from the comparison node. - */ - private RSQLParameterUnsupportedFieldException createRSQLParameterUnsupportedException( - @NotNull final ComparisonNode node, final Exception rootException) { - return new RSQLParameterUnsupportedFieldException(String.format( - "The given search parameter field {%s} does not exist, must be one of the following fields %s", - node.getSelector(), getExpectedFieldList()), rootException); - } - - private String getFormattedSubEntityAttribute(final A propertyEnum, final String propertyField) { - return propertyEnum.getSubEntityAttributes().stream() - .filter(attr -> attr.equalsIgnoreCase(propertyField)) - .findFirst().orElse(propertyField); - } - - private List getExpectedFieldList() { - return Stream.concat( - Arrays.stream(rsqlQueryFieldType.getEnumConstants()) - .filter(enumField -> enumField.getSubEntityAttributes().isEmpty()).map(enumField -> { - final String enumFieldName = enumField.name().toLowerCase(); - if (enumField.isMap()) { - return enumFieldName + QueryField.SUB_ATTRIBUTE_SEPARATOR + "keyName"; - } else { - return enumFieldName; - } - }), - Arrays.stream(rsqlQueryFieldType.getEnumConstants()) - .filter(enumField -> !enumField.getSubEntityAttributes().isEmpty()).flatMap(enumField -> { - final List subEntity = enumField - .getSubEntityAttributes().stream().map(fieldName -> enumField.name().toLowerCase() - + QueryField.SUB_ATTRIBUTE_SEPARATOR + fieldName) - .toList(); - - return subEntity.stream(); - })) - .toList(); - } - - @Value - protected class QueryPath { - - A enumValue; - String[] jpaPath; - - private QueryPath(final A enumValue, final String[] jpaPath) { - this.enumValue = enumValue; - this.jpaPath = jpaPath; - } - } -} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/JpaQueryRsqlVisitor.java b/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/JpaQueryRsqlVisitor.java deleted file mode 100644 index bc9d3a8ba..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/JpaQueryRsqlVisitor.java +++ /dev/null @@ -1,563 +0,0 @@ -/** - * Copyright (c) 2025 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.eclipse.hawkbit.repository.jpa.rsql.legacy; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.BiFunction; -import java.util.function.Function; - -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.Expression; -import jakarta.persistence.criteria.From; -import jakarta.persistence.criteria.Join; -import jakarta.persistence.criteria.JoinType; -import jakarta.persistence.criteria.MapJoin; -import jakarta.persistence.criteria.Path; -import jakarta.persistence.criteria.PluralJoin; -import jakarta.persistence.criteria.Predicate; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Subquery; - -import cz.jirutka.rsql.parser.ast.AndNode; -import cz.jirutka.rsql.parser.ast.ComparisonNode; -import cz.jirutka.rsql.parser.ast.LogicalNode; -import cz.jirutka.rsql.parser.ast.Node; -import cz.jirutka.rsql.parser.ast.OrNode; -import cz.jirutka.rsql.parser.ast.RSQLVisitor; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.math.NumberUtils; -import org.eclipse.hawkbit.repository.QueryField; -import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; -import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; -import org.eclipse.hawkbit.repository.rsql.VirtualPropertyResolver; -import org.springframework.beans.SimpleTypeConverter; -import org.springframework.beans.TypeMismatchException; -import org.springframework.util.CollectionUtils; -import org.springframework.util.ObjectUtils; - -/** - * An implementation of the {@link RSQLVisitor} to visit the parsed tokens and - * build JPA where clauses. - * - * @param the enum for providing the field name of the entity field to filter on. - * @param the entity type referenced by the root - * @deprecated Old implementation of RSQL Visitor. Deprecated in favour of next gen implementation - {@link JpaQueryRsqlVisitorG2}. - * It will be kept for some time in order to keep backward compatibility and to allow for a smooth transition. Also, in case of - * problems with the new implementation, this one can be used as a fallback. - */ -@Deprecated(forRemoval = true, since = "0.6.0") -@Slf4j -public class JpaQueryRsqlVisitor & QueryField, T> extends AbstractRSQLVisitor - implements RSQLVisitor, String> { - - public static final Character LIKE_WILDCARD = '*'; - private static final char ESCAPE_CHAR = '\\'; - private static final List NO_JOINS_OPERATOR = List.of("!=", "=out="); - private static final String ESCAPE_CHAR_WITH_ASTERISK = ESCAPE_CHAR + "*"; - - private final Map>> joinsInLevel = new HashMap<>(3); - - private final CriteriaBuilder cb; - private final CriteriaQuery query; - private final boolean ensureIgnoreCase; - private final Root root; - private final VirtualPropertyResolver virtualPropertyResolver; - private final SimpleTypeConverter simpleTypeConverter; - - private int level; - private boolean isOrLevel; - private boolean joinsNeeded; - - public JpaQueryRsqlVisitor( - final Root root, final CriteriaBuilder cb, final Class enumType, - final VirtualPropertyResolver virtualPropertyResolver, - final CriteriaQuery query, final boolean ensureIgnoreCase) { - super(enumType); - this.root = root; - this.cb = cb; - this.query = query; - this.virtualPropertyResolver = virtualPropertyResolver; - this.simpleTypeConverter = new SimpleTypeConverter(); - this.ensureIgnoreCase = ensureIgnoreCase; - } - - @Override - public List visit(final AndNode node, final String param) { - beginLevel(false); - final List childs = acceptChildren(node); - endLevel(); - if (!childs.isEmpty()) { - return toSingleList(cb.and(childs.toArray(new Predicate[0]))); - } - return toSingleList(cb.conjunction()); - } - - @Override - public List visit(final OrNode node, final String param) { - beginLevel(true); - final List childs = acceptChildren(node); - endLevel(); - if (!childs.isEmpty()) { - return toSingleList(cb.or(childs.toArray(new Predicate[0]))); - } - return toSingleList(cb.conjunction()); - } - - @Override - // Exception squid:S2095 - see - // https://jira.sonarsource.com/browse/SONARJAVA-1478 - @SuppressWarnings({ "squid:S2095" }) - public List visit(final ComparisonNode node, final String param) { - final QueryPath queryPath = getQueryPath(node); - - final List values = node.getArguments(); - final List transformedValues = new ArrayList<>(); - final Path fieldPath = getFieldPath(queryPath.getEnumValue(), queryPath); - - for (final String value : values) { - transformedValues.add(convertValueIfNecessary(node, queryPath.getEnumValue(), value, fieldPath)); - } - - this.joinsNeeded = this.joinsNeeded || areJoinsNeeded(node); - - return mapToPredicate(node, fieldPath, node.getArguments(), transformedValues, queryPath); - } - - private static List toSingleList(final Predicate predicate) { - return Collections.singletonList(predicate); - } - - private static Optional> getFieldPath(final Root root, final String[] split, final boolean isMapKeyField, - final BiFunction, String, Path> joinFieldPathProvider) { - Path fieldPath = null; - for (int i = 0; i < split.length; i++) { - if (!(isMapKeyField && i == (split.length - 1))) { - final String fieldNameSplit = split[i]; - fieldPath = (fieldPath != null) ? fieldPath.get(fieldNameSplit) : root.get(fieldNameSplit); - fieldPath = joinFieldPathProvider.apply(fieldPath, fieldNameSplit); - } - } - return Optional.ofNullable(fieldPath); - } - - private static boolean areJoinsNeeded(final ComparisonNode node) { - return !NO_JOINS_OPERATOR.contains(node.getOperator().getSymbol()); - } - - // Exception squid:S2095 - see - // https://jira.sonarsource.com/browse/SONARJAVA-1478 - @SuppressWarnings({ "rawtypes", "unchecked", "squid:S2095" }) - private static Object transformEnumValue(final ComparisonNode node, final String value, final Class javaType) { - final Class tmpEnumType = (Class) javaType; - try { - return Enum.valueOf(tmpEnumType, value.toUpperCase()); - } catch (final IllegalArgumentException e) { - // we could not transform the given string value into the enum - // type, so ignore it and return null and do not filter - log.info("given value {} cannot be transformed into the correct enum type {}", value.toUpperCase(), - javaType); - log.debug("value cannot be transformed to an enum", e); - - throw new RSQLParameterUnsupportedFieldException("field {" + node.getSelector() - + "} must be one of the following values {" + Arrays.stream(tmpEnumType.getEnumConstants()) - .map(v -> v.name().toLowerCase()).toList() - + "}", e); - } - } - - private static boolean isSimpleField(final String[] split, final boolean isMapKeyField) { - return split.length == 1 || (split.length == 2 && isMapKeyField); - } - - private static Path getInnerFieldPath(final Root subqueryRoot, final String[] split, - final boolean isMapKeyField) { - return getFieldPath(subqueryRoot, split, isMapKeyField, - (fieldPath, fieldNameSplit) -> getInnerJoinFieldPath(subqueryRoot, fieldPath, fieldNameSplit)) - .orElseThrow(() -> new RSQLParameterUnsupportedFieldException("RSQL field path cannot be empty", - null)); - } - - private static Path getInnerJoinFieldPath(final Root subqueryRoot, final Path fieldPath, - final String fieldNameSplit) { - if (fieldPath instanceof Join) { - return subqueryRoot.join(fieldNameSplit, JoinType.INNER); - } - return fieldPath; - } - - private static boolean isPattern(final String transformedValue) { - if (transformedValue.contains(ESCAPE_CHAR_WITH_ASTERISK)) { - return transformedValue.replace(ESCAPE_CHAR_WITH_ASTERISK, "$").indexOf(LIKE_WILDCARD) != -1; - } else { - return transformedValue.indexOf(LIKE_WILDCARD) != -1; - } - } - - @SuppressWarnings("unchecked") - private static Path pathOfString(final Path path) { - return (Path) path; - } - - private void beginLevel(final boolean isOr) { - level++; - isOrLevel = isOr; - joinsInLevel.put(level, new HashSet<>(2)); - } - - private void endLevel() { - joinsInLevel.remove(level); - level--; - isOrLevel = false; - } - - private Set> getCurrentJoins() { - if (level > 0) { - return joinsInLevel.get(level); - } - return Collections.emptySet(); - } - - private Optional> findCurrentJoinOfType(final Class type) { - return getCurrentJoins().stream().filter(j -> type.equals(j.getJavaType())).findAny(); - } - - private void addCurrentJoin(final Join join) { - if (level > 0) { - getCurrentJoins().add(join); - } - } - - /** - * Resolves the Path for a field in the persistence layer and joins the - * required models. This operation is part of a tree traversal through an - * RSQL expression. It creates for every field that is not part of the root - * model a join to the foreign model. This behavior is optimized when - * several joins happen directly under an OR node in the traversed tree. The - * same foreign model is only joined once. - * - * Example: tags.name==M;(tags.name==A,tags.name==B,tags.name==C) This - * example joins the tags model only twice, because for the OR node in - * brackets only one join is used. - * - * @param enumField field from a FieldNameProvider to resolve on the persistence - * layer - * @param queryPath RSQL field - * @return the Path for a field - */ - @SuppressWarnings("unchecked") - private Path getFieldPath(final A enumField, final QueryPath queryPath) { - return (Path) getFieldPath(root, queryPath.getJpaPath(), enumField.isMap(), - this::getJoinFieldPath).orElseThrow( - () -> new RSQLParameterUnsupportedFieldException("RSQL field path cannot be empty", null)); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - private Path getJoinFieldPath(final Path fieldPath, final String fieldNameSplit) { - if (fieldPath instanceof PluralJoin join) { - final From joinParent = join.getParent(); - final Optional> currentJoinOfType = findCurrentJoinOfType(join.getJavaType()); - if (currentJoinOfType.isPresent() && isOrLevel) { - // remove the additional join and use the existing one - joinParent.getJoins().remove(join); - return currentJoinOfType.get(); - } else { - final Join newJoin = joinParent.join(fieldNameSplit, JoinType.LEFT); - addCurrentJoin(newJoin); - return newJoin; - } - } - return fieldPath; - } - - private Object convertValueIfNecessary(final ComparisonNode node, final A fieldName, final String value, - final Path fieldPath) { - // in case the value of an RSQL query e.g. type==application is an - // enum we need to handle it separately because JPA needs the - // correct java-type to build an expression. So String and numeric - // values JPA can do it by it's own but not for classes like enums. - // So we need to transform the given value string into the enum - // class. - final Class javaType = fieldPath.getJavaType(); - if (javaType != null && javaType.isEnum()) { - return transformEnumValue(node, value, javaType); - } - - if (Boolean.TYPE.equals(javaType)) { - return convertBooleanValue(node, value, javaType); - } - - return value; - } - - private Object convertBooleanValue(final ComparisonNode node, final String value, final Class javaType) { - try { - return simpleTypeConverter.convertIfNecessary(value, javaType); - } catch (final TypeMismatchException e) { - throw new RSQLParameterUnsupportedFieldException( - "The value of the given search parameter field {" + node.getSelector() - + "} is not well formed. Only a boolean (true or false) value will be expected", - e); - } - } - - private List mapToPredicate(final ComparisonNode node, final Path fieldPath, - final List values, final List transformedValues, final QueryPath queryPath) { - - String value = values.get(0); - // if lookup is available, replace macros ... - if (virtualPropertyResolver != null) { - value = virtualPropertyResolver.replace(value); - } - - final Predicate mapPredicate = mapToMapPredicate(fieldPath, queryPath); - - final Predicate valuePredicate = addOperatorPredicate(node, - queryPath.getEnumValue().isMap() ? (Path) toMapValuePath(fieldPath) : fieldPath, transformedValues, value, queryPath); - return toSingleList(mapPredicate != null ? cb.and(mapPredicate, valuePredicate) : valuePredicate); - } - - private Predicate addOperatorPredicate(final ComparisonNode node, final Path fieldPath, - final List transformedValues, final String value, final QueryPath queryPath) { - - // only 'equal' and 'notEqual' can handle transformed value like - // enums. The JPA API cannot handle object types for greaterThan etc - // methods. - final Object transformedValue = transformedValues.get(0); - final String operator = node.getOperator().getSymbol(); - - switch (operator) { - case "==": - return getEqualToPredicate(transformedValue, fieldPath); - case "!=": - return getNotEqualToPredicate(transformedValue, fieldPath, queryPath); - case "=gt=": - return cb.greaterThan(pathOfString(fieldPath), value); - case "=ge=": - return cb.greaterThanOrEqualTo(pathOfString(fieldPath), value); - case "=lt=": - return cb.lessThan(pathOfString(fieldPath), value); - case "=le=": - return cb.lessThanOrEqualTo(pathOfString(fieldPath), value); - case "=in=": - return in(pathOfString(fieldPath), transformedValues); - case "=out=": - return getOutPredicate(transformedValues, queryPath, fieldPath); - default: - throw new RSQLParameterSyntaxException( - "operator symbol {" + operator + "} is either not supported or not implemented"); - } - } - - @SuppressWarnings("unchecked") - private static Path toMapValuePath(final Path fieldPath) { - final Path mapValuePath = ((MapJoin) pathOfString(fieldPath)).value(); - if (mapValuePath.getJavaType() == String.class) { - return (Path) mapValuePath; - } else { - return mapValuePath.get("value"); - } - } - - private Predicate getOutPredicate( - final List transformedValues, - final QueryPath queryPath, final Path fieldPath) { - final String[] fieldNames = queryPath.getJpaPath(); - - if (isSimpleField(fieldNames, queryPath.getEnumValue().isMap())) { - final Path pathOfString = pathOfString(fieldPath); - return cb.or(cb.isNull(pathOfString), cb.not(in(pathOfString, transformedValues))); - } - - clearOuterJoinsIfNotNeeded(); - - return toNotExistsSubQueryPredicate(fieldNames, queryPath.getEnumValue(), - expressionToCompare -> in(expressionToCompare, transformedValues)); - } - - @SuppressWarnings("unchecked") - private Predicate mapToMapPredicate(final Path fieldPath, final QueryPath queryPath) { - if (!queryPath.getEnumValue().isMap()) { - return null; - } - - final String[] graph = queryPath.getJpaPath(); - final String keyValue = graph[graph.length - 1]; - // Currently we support only string key. So below cast is safe. - return equal((Expression) (((MapJoin) fieldPath).key()), keyValue); - } - - private Predicate getEqualToPredicate(final Object transformedValue, final Path fieldPath) { - if (transformedValue == null) { - return cb.isNull(pathOfString(fieldPath)); - } - - if ((transformedValue instanceof String transformedValueStr) && !NumberUtils.isCreatable(transformedValueStr)) { - if (ObjectUtils.isEmpty(transformedValue)) { - return cb.or(cb.isNull(pathOfString(fieldPath)), cb.equal(pathOfString(fieldPath), "")); - } - - if (isPattern(transformedValueStr)) { // a pattern, use like - return like(pathOfString(fieldPath), toSQL(transformedValueStr)); - } else { - return equal(pathOfString(fieldPath), transformedValueStr); - } - } - - return cb.equal(fieldPath, transformedValue); - } - - private Predicate getNotEqualToPredicate(final Object transformedValue, final Path fieldPath, - final QueryPath queryPath) { - - if (transformedValue == null) { - return cb.isNotNull(pathOfString(fieldPath)); - } - - if ((transformedValue instanceof String transformedValueStr) && !NumberUtils.isCreatable(transformedValueStr)) { - if (ObjectUtils.isEmpty(transformedValue)) { - return cb.and(cb.isNotNull(pathOfString(fieldPath)), cb.notEqual(pathOfString(fieldPath), "")); - } - - final String[] fieldNames = queryPath.getJpaPath(); - - if (isSimpleField(fieldNames, queryPath.getEnumValue().isMap())) { - if (isPattern(transformedValueStr)) { // a pattern, use like - return cb.or(cb.isNull(pathOfString(fieldPath)), notLike(pathOfString(fieldPath), toSQL(transformedValueStr))); - } else { - return toNullOrNotEqualPredicate(fieldPath, transformedValueStr); - } - } - - clearOuterJoinsIfNotNeeded(); - - if (isPattern(transformedValueStr)) { // a pattern, use like - return toNotExistsSubQueryPredicate(fieldNames, queryPath.getEnumValue(), - expressionToCompare -> like(expressionToCompare, toSQL(transformedValueStr))); - } else { - return toNotExistsSubQueryPredicate(fieldNames, queryPath.getEnumValue(), - expressionToCompare -> equal(expressionToCompare, transformedValueStr)); - } - } - - return toNullOrNotEqualPredicate(fieldPath, transformedValue); - } - - private void clearOuterJoinsIfNotNeeded() { - if (!joinsNeeded) { - root.getJoins().clear(); - } - } - - private Predicate toNullOrNotEqualPredicate(final Path fieldPath, final Object transformedValue) { - return cb.or( - cb.isNull(pathOfString(fieldPath)), - transformedValue instanceof String transformedValueStr - ? notEqual(pathOfString(fieldPath), transformedValueStr) - : cb.notEqual(fieldPath, transformedValue)); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private Predicate toNotExistsSubQueryPredicate(final String[] fieldNames, final A enumField, - final Function, Predicate> subQueryPredicateProvider) { - final Class javaType = root.getJavaType(); - final Subquery subquery = query.subquery(javaType); - final Root subqueryRoot = subquery.from(javaType); - final Predicate equalPredicate = cb.equal(root.get(enumField.getIdentifierFieldName()), - subqueryRoot.get(enumField.getIdentifierFieldName())); - final Path innerFieldPath = getInnerFieldPath(subqueryRoot, fieldNames, enumField.isMap()); - final Expression expressionToCompare = getExpressionToCompare(innerFieldPath, enumField); - final Predicate subQueryPredicate = subQueryPredicateProvider.apply(expressionToCompare); - subquery.select(subqueryRoot).where(cb.and(equalPredicate, subQueryPredicate)); - return cb.not(cb.exists(subquery)); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - private Expression getExpressionToCompare(final Path innerFieldPath, final A enumField) { - if (enumField.isMap()) { - return toMapValuePath(innerFieldPath); - } else { - return pathOfString(innerFieldPath); - } - } - - private String toSQL(final String transformedValue) { - final String escaped = transformedValue.replace("%", ESCAPE_CHAR + "%").replace("_", ESCAPE_CHAR + "_"); - return replaceIfRequired(escaped); - } - - private String replaceIfRequired(final String escapedValue) { - final String finalizedValue; - if (escapedValue.contains(ESCAPE_CHAR_WITH_ASTERISK)) { - finalizedValue = escapedValue.replace(ESCAPE_CHAR_WITH_ASTERISK, "$").replace(LIKE_WILDCARD, '%') - .replace("$", ESCAPE_CHAR_WITH_ASTERISK); - } else { - finalizedValue = escapedValue.replace(LIKE_WILDCARD, '%'); - } - return finalizedValue; - } - - private List acceptChildren(final LogicalNode node) { - final List children = node.getChildren(); - final List childs = new ArrayList<>(); - for (final Node node2 : children) { - final List accept = node2.accept(this); - if (!CollectionUtils.isEmpty(accept)) { - childs.addAll(accept); - } else { - log.debug("visit logical node children but could not parse it, ignoring {}", node2); - } - } - return childs; - } - - @SuppressWarnings("java:S1221") // java:S1221 - intentionally to match the SQL wording - private Predicate equal(final Expression expressionToCompare, final String sqlValue) { - return cb.equal(caseWise(cb, expressionToCompare), caseWise(sqlValue)); - } - - private Predicate notEqual(final Expression expressionToCompare, String transformedValueStr) { - return cb.notEqual(caseWise(cb, expressionToCompare), caseWise(transformedValueStr)); - } - - private Predicate like(final Expression expressionToCompare, final String sqlValue) { - return cb.like(caseWise(cb, expressionToCompare), caseWise(sqlValue), ESCAPE_CHAR); - } - - private Predicate notLike(final Expression expressionToCompare, final String sqlValue) { - return cb.notLike(caseWise(cb, expressionToCompare), caseWise(sqlValue), ESCAPE_CHAR); - } - - private Predicate in(final Expression expressionToCompare, final List transformedValues) { - final List inParams = transformedValues.stream().filter(String.class::isInstance) - .map(String.class::cast).map(this::caseWise).toList(); - return inParams.isEmpty() ? expressionToCompare.in(transformedValues) : caseWise(cb, expressionToCompare).in(inParams); - } - - private Expression caseWise(final CriteriaBuilder cb, final Expression expression) { - if (expression instanceof Path path && path.getJavaType() != String.class) { - return expression; // non string values doesn't support uppercase - } else { - return ensureIgnoreCase ? cb.upper(expression) : expression; - } - } - - private String caseWise(final String str) { - return ensureIgnoreCase ? str.toUpperCase() : str; - } -} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/JpaQueryRsqlVisitorG2.java b/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/JpaQueryRsqlVisitorG2.java deleted file mode 100644 index d6d143c36..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/JpaQueryRsqlVisitorG2.java +++ /dev/null @@ -1,497 +0,0 @@ -/** - * Copyright (c) 2025 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.eclipse.hawkbit.repository.jpa.rsql.legacy; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Function; - -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.JoinType; -import jakarta.persistence.criteria.MapJoin; -import jakarta.persistence.criteria.Path; -import jakarta.persistence.criteria.PluralJoin; -import jakarta.persistence.criteria.Predicate; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Subquery; -import jakarta.persistence.metamodel.Attribute; -import jakarta.persistence.metamodel.SingularAttribute; -import jakarta.persistence.metamodel.Type; - -import cz.jirutka.rsql.parser.ast.AndNode; -import cz.jirutka.rsql.parser.ast.ComparisonNode; -import cz.jirutka.rsql.parser.ast.ComparisonOperator; -import cz.jirutka.rsql.parser.ast.LogicalNode; -import cz.jirutka.rsql.parser.ast.Node; -import cz.jirutka.rsql.parser.ast.OrNode; -import cz.jirutka.rsql.parser.ast.RSQLOperators; -import cz.jirutka.rsql.parser.ast.RSQLVisitor; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.math.NumberUtils; -import org.eclipse.hawkbit.repository.QueryField; -import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; -import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; -import org.eclipse.hawkbit.repository.jpa.ql.SpecificationBuilder; -import org.eclipse.hawkbit.repository.rsql.VirtualPropertyResolver; -import org.springframework.util.CollectionUtils; -import org.springframework.util.ObjectUtils; - -/** - * An implementation of the {@link RSQLVisitor} to visit the parsed tokens and build JPA where clauses. - * - * @param the enum for providing the field name of the entity field to filter on. - * @param the entity type referenced by the root - * @deprecated Old implementation of RSQL Visitor (G2). Deprecated in favour of next gen implementation - - * {@link SpecificationBuilder}. - * It will be kept for some time in order to keep backward compatibility and to allow for a smooth transition. Also, in case of - * problems with the new implementation, this one can be used as a fallback. - */ -@Deprecated(forRemoval = true, since = "0.9.0") -@Slf4j -public class JpaQueryRsqlVisitorG2 & QueryField, T> - extends AbstractRSQLVisitor implements RSQLVisitor, String> { - - public static final Character LIKE_WILDCARD = '*'; - private static final char ESCAPE_CHAR = '\\'; - private static final String ESCAPE_CHAR_WITH_ASTERISK = ESCAPE_CHAR + "*"; - - private final Root root; - private final CriteriaQuery query; - private final CriteriaBuilder cb; - private final VirtualPropertyResolver virtualPropertyResolver; - private final boolean ensureIgnoreCase; - - private final Map> attributeToPath = new HashMap<>(); - private boolean inOr; - - public JpaQueryRsqlVisitorG2( - final Class enumType, - final Root root, final CriteriaQuery query, final CriteriaBuilder cb, - final VirtualPropertyResolver virtualPropertyResolver, final boolean ensureIgnoreCase) { - super(enumType); - this.root = root; - this.cb = cb; - this.query = query; - this.virtualPropertyResolver = virtualPropertyResolver; - this.ensureIgnoreCase = ensureIgnoreCase; - } - - @Override - public List visit(final AndNode node, final String param) { - final List children = acceptChildren(node); - return Collections.singletonList(children.isEmpty() ? cb.conjunction() : cb.and(children.toArray(new Predicate[0]))); - } - - @Override - public List visit(final OrNode node, final String param) { - inOr = true; - try { - final List children = acceptChildren(node); - return Collections.singletonList(children.isEmpty() ? cb.conjunction() : cb.or(children.toArray(new Predicate[0]))); - } finally { - inOr = false; - attributeToPath.clear(); - } - } - - @Override - public List visit(final ComparisonNode node, final String param) { - final QueryPath queryPath = getQueryPath(node); - final Path fieldPath = getFieldPath(root, queryPath); - return Collections.singletonList(toPredicate(node, queryPath, fieldPath, getValues(node, queryPath, fieldPath))); - } - - @SuppressWarnings("java:S3776") // java:S3776 - easier to read at one place - private Predicate toPredicate(final ComparisonNode node, final QueryPath queryPath, final Path fieldPath, final List values) { - final Predicate mapEntryKeyPredicate; - if (queryPath.getEnumValue().isMap()) { - if (node.getOperator() == IS) { - // special handling of "not-exists" - if (values.size() != 1) { - throw new RSQLParameterSyntaxException("The operator '" + IS + "' can only be used with one value"); - } - if (values.get(0) == null) { - // IS operator for maps and null value is treated as doesn't exist correspondingly - ((PluralJoin) fieldPath).on(toMapEntryKeyPredicate(queryPath, fieldPath)); - return cb.isNull(toMapValuePath(fieldPath)); - } - } else if (node.getOperator() == NOT) { - if (values.size() != 1) { - throw new RSQLParameterSyntaxException("The operator '" + NOT + "' can only be used with one value"); - } - // NOT operator for maps and null value is treated as does exist correspondingly - ((PluralJoin) fieldPath).on(toMapEntryKeyPredicate(queryPath, fieldPath)); - if (values.get(0) == null) { - // special handling of "exists" - return cb.isNotNull(toMapValuePath(fieldPath)); - } else { - // special handling or "not equal" or null (same as != but with possible optimized join - no subquery) - return toNotEqualToPredicate(queryPath, toMapValuePath(fieldPath), values.get(0)); - } - } - mapEntryKeyPredicate = toMapEntryKeyPredicate(queryPath, fieldPath); - } else { - mapEntryKeyPredicate = null; - } - - final Predicate valuePredicate = toOperatorAndValuePredicate(node, queryPath, - queryPath.getEnumValue().isMap() ? toMapValuePath(fieldPath) : fieldPath, values); - - return mapEntryKeyPredicate == null ? valuePredicate : cb.and(mapEntryKeyPredicate, valuePredicate); - } - - @SuppressWarnings("unchecked") - private static Path toMapValuePath(final Path fieldPath) { - final Path mapValuePath = ((MapJoin) pathOfString(fieldPath)).value(); - if (mapValuePath.getJavaType() == String.class) { - return (Path) mapValuePath; - } else { - return mapValuePath.get("value"); - } - } - - @SuppressWarnings("unchecked") - private Predicate toMapEntryKeyPredicate(final QueryPath queryPath, final Path fieldPath) { - final String[] graph = queryPath.getJpaPath(); - return equal((Path) ((MapJoin) fieldPath).key(), graph[graph.length - 1]); - } - - private Predicate toOperatorAndValuePredicate( - final ComparisonNode node, final QueryPath queryPath, final Path fieldPath, final List values) { - // only 'equal' and 'notEqual' can handle transformed value like enums. - // The JPA API cannot handle object types for greaterThan etc. methods. For them, it shall be a string. - final Object value = values.get(0); - final String operator = node.getOperator().getSymbol(); - return switch (operator) { - case "==", "=is=", "=eq=" -> toEqualToPredicate(fieldPath, value); - case "!=", "=not=", "=ne=" -> toNotEqualToPredicate(queryPath, fieldPath, value); - case "=gt=" -> cb.greaterThan(pathOfString(fieldPath), String.valueOf(value)); // JPA handles numbers - case "=ge=" -> cb.greaterThanOrEqualTo(pathOfString(fieldPath), String.valueOf(value)); - case "=lt=" -> cb.lessThan(pathOfString(fieldPath), String.valueOf(value)); - case "=le=" -> cb.lessThanOrEqualTo(pathOfString(fieldPath), String.valueOf(value)); - case "=in=" -> in(pathOfString(fieldPath), values); - case "=out=" -> toOutPredicate(queryPath, fieldPath, values); - default -> throw new RSQLParameterSyntaxException("Operator symbol {" + operator + "} is either not supported or not implemented"); - }; - } - - private Predicate toEqualToPredicate(final Path fieldPath, final Object value) { - if (value == null) { - return cb.isNull(fieldPath); - } - - if ((value instanceof String valueStr) && !NumberUtils.isCreatable(valueStr)) { - if (ObjectUtils.isEmpty(value)) { - return cb.or(cb.isNull(fieldPath), cb.equal(pathOfString(fieldPath), "")); - } - - final Path stringExpression = pathOfString(fieldPath); - if (isPattern(valueStr)) { // a pattern, use like - return like(stringExpression, toSQL(valueStr)); - } else { - return equal(stringExpression, valueStr); - } - } - - return cb.equal(fieldPath, value); - } - - // if value is null -> not null - // if value is not null -> null or not equal value - private Predicate toNotEqualToPredicate(final QueryPath queryPath, final Path fieldPath, final Object value) { - if (value == null) { - return cb.isNotNull(fieldPath); - } - - if (value instanceof String valueStr && !NumberUtils.isCreatable(valueStr)) { - if (ObjectUtils.isEmpty(value)) { - return cb.and(cb.isNotNull(fieldPath), cb.notEqual(pathOfString(fieldPath), "")); - } - - if (isSimpleField(queryPath)) { - if (isPattern(valueStr)) { // a pattern, use like - return cb.or(cb.isNull(fieldPath), notLike(pathOfString(fieldPath), toSQL(valueStr))); - } else { - return toNullOrNotEqualPredicate(fieldPath, valueStr); - } - } - - return toNotExistsSubQueryPredicate( - queryPath, fieldPath, expressionToCompare -> - isPattern(valueStr) - ? like(expressionToCompare, toSQL(valueStr)) // a pattern, use like - : equal(expressionToCompare, valueStr)); - } - - return toNullOrNotEqualPredicate(fieldPath, value); - } - - private Predicate toOutPredicate(final QueryPath queryPath, final Path fieldPath, final List values) { - if (isSimpleField(queryPath)) { - return cb.or(cb.isNull(fieldPath), cb.not(in(pathOfString(fieldPath), values))); - } - - return toNotExistsSubQueryPredicate(queryPath, fieldPath, expressionToCompare -> in(expressionToCompare, values)); - } - - private Path getFieldPath(final Root root, final QueryPath queryPath) { - final String[] split = queryPath.getJpaPath(); - Path fieldPath = null; - for (int i = 0, end = queryPath.getEnumValue().isMap() ? split.length - 1 : split.length; i < end; i++) { - final String fieldNameSplit = split[i]; - fieldPath = fieldPath == null ? getPath(root, fieldNameSplit) : fieldPath.get(fieldNameSplit); - } - if (fieldPath == null) { - throw new RSQLParameterUnsupportedFieldException("RSQL field path must not be null", null); - } - return fieldPath; - } - - @SuppressWarnings("java:S3776") // java:S3776 - easier to read at one place - private List getValues(final ComparisonNode node, final AbstractRSQLVisitor.QueryPath queryPath, final Path fieldPath) { - final List values = node.getArguments().stream() - // if lookup is available, replace macros ... - .map(value -> virtualPropertyResolver == null ? value : virtualPropertyResolver.replace(value)) - // converts value to the correct type - .map(value -> convertValueIfNecessary(node, queryPath.getEnumValue(), fieldPath, value)) - .toList(); - if (values.isEmpty()) { - throw new RSQLParameterSyntaxException("RSQL values must not be empty", null); - } else if (values.size() == 1) { - if (!(values.get(0) instanceof String)) { // enum or boolean or null - doesn's support >, >=, <, <= - final ComparisonOperator operator = node.getOperator(); - if (operator == RSQLOperators.GREATER_THAN || - operator == RSQLOperators.GREATER_THAN_OR_EQUAL || - operator == RSQLOperators.LESS_THAN || - operator == RSQLOperators.LESS_THAN_OR_EQUAL) { - final String errorMsg = values.get(0) == null ? "to null value" : "to enum or boolean field"; - throw new RSQLParameterSyntaxException(operator.getSymbol() + " operator could not be applied " + errorMsg, null); - } - } - } else { - final ComparisonOperator operator = node.getOperator(); - if (operator != RSQLOperators.IN && operator != RSQLOperators.NOT_IN) { - throw new RSQLParameterSyntaxException(operator.getSymbol() + " operator shall have exactly one value", null); - } - } - return values; - } - - // if root.get creates a join we call join directly in order to specify LEFT JOIN type, to include rows for missing in particular - // table / criteria (root.get creates INNER JOIN) (see org.eclipse.persistence.internal.jpa.querydef.FromImpl implementation - // for more details) otherwise delegate to root.get - @SuppressWarnings("java:S1066") // java:S1066 - better reading this way - private Path getPath(final Root root, final String fieldNameSplit) { - // see org.eclipse.persistence.internal.jpa.querydef.FromImpl implementation for more details when root.get creates a join - final Attribute attribute = root.getModel().getAttribute(fieldNameSplit); - if (!attribute.isCollection()) { - // it is a SingularAttribute and not join if it is of basic or entity persistence type - final Type.PersistenceType persistenceType = ((SingularAttribute) attribute).getType().getPersistenceType(); - if (persistenceType == Type.PersistenceType.BASIC) { - return root.get(fieldNameSplit); - } else if (persistenceType == Type.PersistenceType.ENTITY) { - return root.getJoins().stream() - .filter(join -> join.getAttribute().equals(attribute)) - .findFirst() - .orElseGet(() -> root.join(fieldNameSplit, JoinType.LEFT)); - } - } - // if a collection - it is a join - if (inOr && root == this.root) { // try to reuse join of the same "or" level and no subquery - return attributeToPath.computeIfAbsent(attribute.getName(), k -> root.join(fieldNameSplit, JoinType.LEFT)); - } else { - return root.join(fieldNameSplit, JoinType.LEFT); - } - } - - private Predicate toNullOrNotEqualPredicate(final Path fieldPath, final Object value) { - return cb.or( - cb.isNull(fieldPath), - value instanceof String valueStr ? notEqual(pathOfString(fieldPath), valueStr) : cb.notEqual(fieldPath, value)); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private Predicate toNotExistsSubQueryPredicate( - final QueryPath queryPath, final Path fieldPath, final Function, Predicate> subQueryPredicateProvider) { - // if a subquery the field's parent joins are not actually used - if (!inOr) { - // so, if not in or (hence not reused) we remove them. Parent shall be a Join - root.getJoins().remove(fieldPath.getParentPath()); - } - - final Class javaType = root.getJavaType(); - final Subquery subquery = query.subquery(javaType); - final Root subqueryRoot = subquery.from(javaType); - return cb.not(cb.exists( - subquery.select(subqueryRoot) - .where(cb.and( - cb.equal( - root.get(queryPath.getEnumValue().getIdentifierFieldName()), - subqueryRoot.get(queryPath.getEnumValue().getIdentifierFieldName())), - subQueryPredicateProvider.apply( - getExpressionToCompare(queryPath.getEnumValue(), getFieldPath(subqueryRoot, queryPath))))))); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - private Path getExpressionToCompare(final A enumField, final Path fieldPath) { - if (enumField.isMap()) { - // Currently we support only string key. So below cast is safe. - return toMapValuePath(fieldPath); - } else { - return pathOfString(fieldPath); - } - } - - // result is String, enum value, boolean or null - @SuppressWarnings({ "rawtypes", "unchecked" }) - private Object convertValueIfNecessary(final ComparisonNode node, final A enumValue, final Path fieldPath, final String value) { - // in case the value of an RSQL query is an enum we need to transform the given value to the correspondent java type object - final Class javaType = fieldPath.getJavaType(); - - if (javaType != null && javaType.isEnum()) { - return toEnumValue(node, javaType, value); - } - - if (boolean.class.equals(javaType) || Boolean.class.equals(javaType)) { - if ("true".equals(value) || "false".equals(value)) { - return Boolean.valueOf(value); - } else { - throw new RSQLParameterSyntaxException( - "The value of the given search parameter field {" + node.getSelector() + "} is not well formed. " + - "Only a boolean (true or false) value will be expected"); - } - } - - if ("null".equals(value)) { - final ComparisonOperator operator = node.getOperator(); - if (operator == IS || operator == NOT) { - return null; - } - } - - return value; - } - - private boolean isSimpleField(final QueryPath queryPath) { - return queryPath.getJpaPath().length == 1 || (queryPath.getJpaPath().length == 2 && queryPath.getEnumValue().isMap()); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - private static Object toEnumValue(final ComparisonNode node, final Class javaType, final String value) { - final Class tmpEnumType = (Class) javaType; - try { - return Enum.valueOf(tmpEnumType, value.toUpperCase()); - } catch (final IllegalArgumentException e) { - // we could not transform the given string value into the enum type, so ignore it and return null and do not filter - log.info("given value {} cannot be transformed into the correct enum type {}", value.toUpperCase(), javaType); - log.debug("value cannot be transformed to an enum", e); - throw new RSQLParameterUnsupportedFieldException( - "field '" + node.getSelector() + "' must be one of the following values " + - "{" + Arrays.stream(tmpEnumType.getEnumConstants()).map(Enum::name).map(String::toLowerCase).toList() + "}", - e); - } - } - - @SuppressWarnings("unchecked") - private static Path pathOfString(final Path path) { - return (Path) path; - } - - private static boolean isPattern(final String value) { - if (value.contains(ESCAPE_CHAR_WITH_ASTERISK)) { - return value.replace(ESCAPE_CHAR_WITH_ASTERISK, "$").indexOf(LIKE_WILDCARD) != -1; - } else { - return value.indexOf(LIKE_WILDCARD) != -1; - } - } - - private String toSQL(final String value) { - final String escaped = value.replace("%", ESCAPE_CHAR + "%").replace("_", ESCAPE_CHAR + "_"); - return replaceIfRequired(escaped); - } - - private static String replaceIfRequired(final String escapedValue) { - final String finalizedValue; - if (escapedValue.contains(ESCAPE_CHAR_WITH_ASTERISK)) { - finalizedValue = escapedValue.replace(ESCAPE_CHAR_WITH_ASTERISK, "$") - .replace(LIKE_WILDCARD, '%') - .replace("$", ESCAPE_CHAR_WITH_ASTERISK); - } else { - finalizedValue = escapedValue.replace(LIKE_WILDCARD, '%'); - } - return finalizedValue; - } - - private List acceptChildren(final LogicalNode node) { - final List children = new ArrayList<>(); - for (final Node child : node.getChildren()) { - final List accept = child.accept(this); - if (!CollectionUtils.isEmpty(accept)) { - children.addAll(accept); - } else { - log.debug("visit logical node children but could not parse it, ignoring {}", child); - } - } - return children; - } - - @SuppressWarnings("java:S1221") // java:S1221 - intentionally to match the SQL wording - private Predicate equal(final Path expressionToCompare, final String sqlValue) { - if (caseWise(expressionToCompare)) { - return cb.equal(cb.upper(expressionToCompare), sqlValue.toUpperCase()); - } else { - return cb.equal(expressionToCompare, sqlValue); - } - } - - private Predicate notEqual(final Path expressionToCompare, String valueStr) { - if (caseWise(expressionToCompare)) { - return cb.notEqual(cb.upper(expressionToCompare), valueStr.toUpperCase()); - } else { - return cb.notEqual(expressionToCompare, valueStr); - } - } - - private Predicate like(final Path expressionToCompare, final String sqlValue) { - if (caseWise(expressionToCompare)) { - return cb.like(cb.upper(expressionToCompare), sqlValue.toUpperCase(), ESCAPE_CHAR); - } else { - return cb.like(expressionToCompare, sqlValue, ESCAPE_CHAR); - } - } - - private Predicate notLike(final Path expressionToCompare, final String sqlValue) { - if (caseWise(expressionToCompare)) { - return cb.notLike(cb.upper(expressionToCompare), sqlValue.toUpperCase(), ESCAPE_CHAR); - } else { - return cb.notLike(expressionToCompare, sqlValue, ESCAPE_CHAR); - } - } - - private Predicate in(final Path expressionToCompare, final List values) { - if (ensureIgnoreCase && expressionToCompare.getJavaType() == String.class) { - final List inParams = values.stream() - .filter(String.class::isInstance) - .map(String.class::cast).map(String::toUpperCase).toList(); - return inParams.isEmpty() ? expressionToCompare.in(values) : cb.upper(expressionToCompare).in(inParams); - } else { - return expressionToCompare.in(values); - } - } - - private boolean caseWise(final Path fieldPath) { - return ensureIgnoreCase && fieldPath.getJavaType() == String.class; - } -} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/SpecificationBuilderLegacy.java b/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/SpecificationBuilderLegacy.java deleted file mode 100644 index b055b9b60..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/SpecificationBuilderLegacy.java +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright (c) 2025 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.eclipse.hawkbit.repository.jpa.rsql.legacy; - -import static org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder.LEGACY_G1; -import static org.eclipse.hawkbit.repository.jpa.rsql.legacy.AbstractRSQLVisitor.OPERATORS; - -import java.util.List; - -import jakarta.persistence.criteria.Predicate; - -import cz.jirutka.rsql.parser.RSQLParser; -import cz.jirutka.rsql.parser.RSQLParserException; -import cz.jirutka.rsql.parser.ast.Node; -import cz.jirutka.rsql.parser.ast.RSQLVisitor; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.hawkbit.repository.QueryField; -import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; -import org.eclipse.hawkbit.repository.jpa.ql.QLSupport; -import org.eclipse.hawkbit.repository.jpa.ql.SpecificationBuilder; -import org.eclipse.hawkbit.repository.rsql.VirtualPropertyResolver; -import org.springframework.data.jpa.domain.Specification; -import org.springframework.util.CollectionUtils; - -/** - * @deprecated Old implementation of RSQL Visitor (G2). Deprecated in favour of next gen implementation - {@link SpecificationBuilder}. - */ -@Deprecated(forRemoval = true, since = "0.9.0") -@Slf4j -public class SpecificationBuilderLegacy & QueryField, T> { - - private final Class rsqlQueryFieldType; - private final VirtualPropertyResolver virtualPropertyResolver; - - public SpecificationBuilderLegacy(final Class rsqlQueryFieldType, final VirtualPropertyResolver virtualPropertyResolver) { - this.rsqlQueryFieldType = rsqlQueryFieldType; - this.virtualPropertyResolver = virtualPropertyResolver; - } - - public Specification specification(final String rsql) { - return (root, query, cb) -> { - final QLSupport qlSupport = QLSupport.getInstance(); - - final Node rootNode = parseRsql(rsql, qlSupport); - query.distinct(true); - - final boolean ensureIgnoreCase = !qlSupport.isCaseInsensitiveDB() && qlSupport.isIgnoreCase(); - final RSQLVisitor, String> jpqQueryRSQLVisitor = - qlSupport.getSpecBuilder() == LEGACY_G1 - ? new JpaQueryRsqlVisitor<>(root, cb, rsqlQueryFieldType, virtualPropertyResolver, query, ensureIgnoreCase) - : new JpaQueryRsqlVisitorG2<>(rsqlQueryFieldType, root, query, cb, virtualPropertyResolver, ensureIgnoreCase); - final List accept = rootNode.accept(jpqQueryRSQLVisitor); - - if (CollectionUtils.isEmpty(accept)) { - return cb.conjunction(); - } else { - return cb.and(accept.toArray(new Predicate[0])); - } - }; - } - - private static Node parseRsql(final String rsql, final QLSupport rsqlUtility) { - log.debug("Parsing rsql string {}", rsql); - try { - return new RSQLParser(OPERATORS).parse(rsqlUtility.isCaseInsensitiveDB() || rsqlUtility.isIgnoreCase() ? rsql.toLowerCase() : rsql); - } catch (final IllegalArgumentException e) { - throw new RSQLParameterSyntaxException("RSQL filter must not be null", e); - } catch (final RSQLParserException e) { - throw new RSQLParameterSyntaxException(e); - } - } -} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa-ql/src/test/java/org/eclipse/hawkbit/repository/jpa/ql/SpecificationBuilderLegacyTest.java b/hawkbit-repository/hawkbit-repository-jpa-ql/src/test/java/org/eclipse/hawkbit/repository/jpa/ql/SpecificationBuilderLegacyTest.java deleted file mode 100644 index de482b890..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa-ql/src/test/java/org/eclipse/hawkbit/repository/jpa/ql/SpecificationBuilderLegacyTest.java +++ /dev/null @@ -1,137 +0,0 @@ -/** - * Copyright (c) 2025 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.eclipse.hawkbit.repository.jpa.ql; - -import static org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder.LEGACY_G1; -import static org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder.LEGACY_G2; - -import java.util.Arrays; -import java.util.List; - -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.hawkbit.repository.QueryField; -import org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder; -import org.eclipse.hawkbit.repository.jpa.rsql.legacy.SpecificationBuilderLegacy; -import org.junit.jupiter.api.Test; -import org.springframework.data.jpa.domain.Specification; - -@SuppressWarnings("java:S2699") // java:S2699 - assertions are un the super methods that are called -@Slf4j -class SpecificationBuilderLegacyTest extends SpecificationBuilderTest { - - private final SpecificationBuilderLegacy builder = new SpecificationBuilderLegacy<>(RootField.class, null); - - private static void runWithRsqlToSpecBuilder(final Runnable runnable, final SpecBuilder rsqlToSpecBuilder) { - final SpecBuilder defaultBuilder = QLSupport.getInstance().getSpecBuilder(); - QLSupport.getInstance().setSpecBuilder(rsqlToSpecBuilder); - try { - runnable.run(); - } finally { - QLSupport.getInstance().setSpecBuilder(defaultBuilder); - } - } - - @Test - void singularStringAttributeG1() { - runWithRsqlToSpecBuilder(super::singularStringAttribute, LEGACY_G1); - } - - @Override - @Test - void singularStringAttribute() { - runWithRsqlToSpecBuilder(super::singularStringAttribute, LEGACY_G2); - } - - @Test - void singularIntAttributeG1() { - runWithRsqlToSpecBuilder(super::singularIntAttribute, LEGACY_G1); - } - - @Override - @Test - void singularIntAttribute() { - runWithRsqlToSpecBuilder(super::singularIntAttribute, LEGACY_G2); - } - - @Test - void singularEntityAttributeG1() { - runWithRsqlToSpecBuilder(super::singularEntityAttribute, LEGACY_G1); - } - - @Override - @Test - void singularEntityAttribute() { - runWithRsqlToSpecBuilder(super::singularEntityAttribute, LEGACY_G2); - } - - @Test - void pluralSubSetAttributeG1() { - runWithRsqlToSpecBuilder(super::pluralSubSetAttribute, LEGACY_G1); - } - - @Override - @Test - void pluralSubSetAttribute() { - runWithRsqlToSpecBuilder(super::pluralSubSetAttribute, LEGACY_G2); - } - - // Legacy G1 doesn't support hibernate maps - @Override - @Test - void pluralSubMapAttribute() { - runWithRsqlToSpecBuilder(super::pluralSubMapAttribute, LEGACY_G2); - } - - @Test - void singularEntitySubSubAttributeG1() { - runWithRsqlToSpecBuilder(super::singularEntitySubSubAttribute, LEGACY_G1); - } - - @Override - @Test - void singularEntitySubSubAttribute() { - runWithRsqlToSpecBuilder(super::singularEntitySubSubAttribute, LEGACY_G2); - } - - @Override - @Test - void deapSearchSubSubSubSubAttribute() { - // legacy builders doesn't support deep search - } - - @Override - protected Specification getSpecification(final String rsql) { - return builder.specification(rsql); - } - - @Getter - private enum RootField implements QueryField { - - INTVALUE("intValue"), - STRVALUE("strValue"), - SUBENTITY("subEntity", "strValue", "intValue", "subSub"), - SUBSET("subSet", "strValue", "intValue"), - SUBMAP("subMap"); - - private final String jpaEntityFieldName; - private final List subEntityAttributes; - - RootField(final String jpaEntityFieldName, final String... subFields) { - this.jpaEntityFieldName = jpaEntityFieldName; - this.subEntityAttributes = Arrays.asList(subFields); - } - - @Override - public boolean isMap() { - return this == SUBMAP; - } - } -} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa-ql/src/test/java/org/eclipse/hawkbit/repository/jpa/ql/SpecificationBuilderTest.java b/hawkbit-repository/hawkbit-repository-jpa-ql/src/test/java/org/eclipse/hawkbit/repository/jpa/ql/SpecificationBuilderTest.java index e6d6d290a..1ddd24e8e 100644 --- a/hawkbit-repository/hawkbit-repository-jpa-ql/src/test/java/org/eclipse/hawkbit/repository/jpa/ql/SpecificationBuilderTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa-ql/src/test/java/org/eclipse/hawkbit/repository/jpa/ql/SpecificationBuilderTest.java @@ -10,9 +10,6 @@ package org.eclipse.hawkbit.repository.jpa.ql; import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder.G3; -import static org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder.LEGACY_G1; -import static org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder.LEGACY_G2; import java.util.List; import java.util.Map; @@ -22,7 +19,6 @@ import java.util.stream.StreamSupport; import jakarta.persistence.EntityManager; import lombok.extern.slf4j.Slf4j; -import org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder; import org.eclipse.hawkbit.repository.jpa.ql.utils.HawkbitQlToSql; import org.eclipse.hawkbit.repository.jpa.rsql.RsqlParser; import org.junit.jupiter.api.Test; @@ -32,7 +28,6 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.data.jpa.domain.Specification; -import org.springframework.orm.jpa.vendor.Database; @SuppressWarnings("java:S5961") // complex check because the matter is very complex @DataJpaTest(properties = { @@ -63,14 +58,10 @@ class SpecificationBuilderTest { assertThat(filter("strValue==rootx")).hasSize(2).containsExactlyInAnyOrder(root1, root2); assertThat(filter("strValue==nostr")).isEmpty(); - if (getRsqlToSpecBuilder() != LEGACY_G1) { - assertThat(filter("strValue=is=null")).hasSize(1).containsExactlyInAnyOrder(root5); - } + assertThat(filter("strValue=is=null")).hasSize(1).containsExactlyInAnyOrder(root5); assertThat(filter("strValue!=rootx")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5); assertThat(filter("strValue!=nostr")).hasSize(5); - if (getRsqlToSpecBuilder() != LEGACY_G1) { - assertThat(filter("strValue=not=null")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); - } + assertThat(filter("strValue=not=null")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); assertThat(filter("strValuerootx")).hasSize(2).containsExactlyInAnyOrder(root3, root4); @@ -89,20 +80,16 @@ class SpecificationBuilderTest { assertThat(filter("strValue==*")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); assertThat(filter("strValue!=*")).hasSize(1).containsExactlyInAnyOrder(root5); - if (getRsqlToSpecBuilder() != LEGACY_G1) { - // null checks - assertThat(filter("strValue=is=null")).hasSize(1).containsExactlyInAnyOrder(root5); - assertThat(filter("strValue=not=null")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); - } + // null checks + assertThat(filter("strValue=is=null")).hasSize(1).containsExactlyInAnyOrder(root5); + assertThat(filter("strValue=not=null")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); assertThat(filter("strValue==*tx and strValue==rooty")).isEmpty(); assertThat(filter("strValue==*tx and strValue!=rootx")).isEmpty(); assertThat(filter("strValue==*tx and strValue==rootx")).hasSize(2).containsExactlyInAnyOrder(root1, root2); assertThat(filter("strValue==*tx or strValue==rooty")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); assertThat(filter("strValue==*tx or strValue!=rootx")).hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); - if (getRsqlToSpecBuilder() != LEGACY_G1) { - assertThat(filter("strValue==*tx or strValue=is=null")).hasSize(3).containsExactlyInAnyOrder(root1, root2, root5); - } + assertThat(filter("strValue==*tx or strValue=is=null")).hasSize(3).containsExactlyInAnyOrder(root1, root2, root5); } @Test @@ -166,33 +153,25 @@ class SpecificationBuilderTest { assertThat(filter("subEntity.strValue==*")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); assertThat(filter("subEntity.strValue!=*")).hasSize(1).containsExactlyInAnyOrder(root5); - if (getRsqlToSpecBuilder() != LEGACY_G1) { - // null checks - assertThat(filter("subEntity.strValue=is=null")).hasSize(1).containsExactlyInAnyOrder(root5); - assertThat(filter("subEntity.strValue=not=null")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); - } + // null checks + assertThat(filter("subEntity.strValue=is=null")).hasSize(1).containsExactlyInAnyOrder(root5); + assertThat(filter("subEntity.strValue=not=null")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); assertThat(filter("subEntity.strValue==*bx and subEntity.strValue==suby")).isEmpty(); assertThat(filter("subEntity.strValue==*bx and subEntity.strValue!=subx")).isEmpty(); assertThat(filter("subEntity.strValue==*bx and subEntity.strValue==subx")).hasSize(2).containsExactlyInAnyOrder(root1, root2); assertThat(filter("subEntity.strValue==*bx or subEntity.strValue==suby")) .hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); - if (getRsqlToSpecBuilder() != LEGACY_G1) { - // G1 doesn't get null entity in not, and doesn't support =is= and =not= - assertThat(filter("subEntity.strValue==*bx or subEntity.strValue!=subx")) - .hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); - assertThat(filter("subEntity.strValue==*bx or subEntity.strValue=is=null")).hasSize(3) - .containsExactlyInAnyOrder(root1, root2, root5); - } + assertThat(filter("subEntity.strValue==*bx or subEntity.strValue!=subx")) + .hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); + assertThat(filter("subEntity.strValue==*bx or subEntity.strValue=is=null")).hasSize(3) + .containsExactlyInAnyOrder(root1, root2, root5); // by sub entity int assertThat(filter("subEntity.intValue==0")).hasSize(2).containsExactlyInAnyOrder(root1, root2); assertThat(filter("subEntity.intValue==2")).isEmpty(); - if (getRsqlToSpecBuilder() != LEGACY_G1) { - // G1 doesn't get null entity in not - assertThat(filter("subEntity.intValue!=0")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5); - assertThat(filter("subEntity.intValue!=2")).hasSize(5); - } + assertThat(filter("subEntity.intValue!=0")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5); + assertThat(filter("subEntity.intValue!=2")).hasSize(5); assertThat(filter("subEntity.intValue<1")).hasSize(2).containsExactlyInAnyOrder(root1, root2); assertThat(filter("subEntity.intValue<=1")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); assertThat(filter("subEntity.intValue>0")).hasSize(2).containsExactlyInAnyOrder(root3, root4); @@ -207,20 +186,14 @@ class SpecificationBuilderTest { assertThat(filter("subEntity.intValue==0 and subEntity.intValue!=1")).hasSize(2).containsExactlyInAnyOrder(root1, root2); assertThat(filter("subEntity.intValue==0 and subEntity.intValue==0")).hasSize(2).containsExactlyInAnyOrder(root1, root2); assertThat(filter("subEntity.intValue==0 or subEntity.intValue==1")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); - if (getRsqlToSpecBuilder() != LEGACY_G1) { - // G1 doesn't get null entity in not - assertThat(filter("subEntity.intValue==0 or subEntity.intValue!=1")).hasSize(3).containsExactlyInAnyOrder(root1, root2, root5); - } + assertThat(filter("subEntity.intValue==0 or subEntity.intValue!=1")).hasSize(3).containsExactlyInAnyOrder(root1, root2, root5); assertThat(filter("subEntity.strValue==subx and subEntity.intValue==1")).isEmpty(); assertThat(filter("subEntity.strValue==subx and subEntity.intValue!=1")).hasSize(2).containsExactlyInAnyOrder(root1, root2); assertThat(filter("subEntity.strValue==subx and subEntity.intValue==0")).hasSize(2).containsExactlyInAnyOrder(root1, root2); assertThat(filter("subEntity.strValue==subx or subEntity.intValue==1")).hasSize(4) .containsExactlyInAnyOrder(root1, root2, root3, root4); - if (getRsqlToSpecBuilder() != LEGACY_G1) { - // G1 doesn't get null entity in not - assertThat(filter("subEntity.strValue==subx or subEntity.intValue!=1")).hasSize(3).containsExactlyInAnyOrder(root1, root2, root5); - } + assertThat(filter("subEntity.strValue==subx or subEntity.intValue!=1")).hasSize(3).containsExactlyInAnyOrder(root1, root2, root5); } @Test @@ -257,34 +230,23 @@ class SpecificationBuilderTest { assertThat(filter("subSet.strValue==*")).hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); assertThat(filter("subSet.strValue!=*")).hasSize(1).containsExactlyInAnyOrder(root6); - if (getRsqlToSpecBuilder() != LEGACY_G1) { - // G1 treats it like same element shall match - which doesn't match to "has" semantic for sets - assertThat(filter("subSet.strValue==*bx and subSet.strValue==suby")).hasSize(2).containsExactlyInAnyOrder(root4, root5); - } + assertThat(filter("subSet.strValue==*bx and subSet.strValue==suby")).hasSize(2).containsExactlyInAnyOrder(root4, root5); assertThat(filter("subSet.strValue==*bx and subSet.strValue!=subx")).isEmpty(); assertThat(filter("subSet.strValue==*bx and subSet.strValue!=suby")).hasSize(1).containsExactlyInAnyOrder(root1); assertThat(filter("subSet.strValue==*bx or subSet.strValue==suby")) .hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); - if (getRsqlToSpecBuilder() != LEGACY_G1) { - // G1 doesn't get null - has which shall be included as doesn't have - assertThat(filter("subSet.strValue==*bx or subSet.strValue!=subx")) - .hasSize(6).containsExactlyInAnyOrder(root1, root2, root3, root4, root5, root6); - } + assertThat(filter("subSet.strValue==*bx or subSet.strValue!=subx")) + .hasSize(6).containsExactlyInAnyOrder(root1, root2, root3, root4, root5, root6); // by sub entity int assertThat(filter("subSet.intValue==0")).hasSize(4).containsExactlyInAnyOrder(root1, root3, root4, root5); assertThat(filter("subSet.intValue==2")).isEmpty(); - if (getRsqlToSpecBuilder() == G3) { - // the legacy builders has different (and wrong semantic) - // G1 - has element != x - // G2 - has element != x or has no elements - // accompanying G3 it is (as it should be) semantic of set != x - has no element with value x (including nhas no elements) - assertThat(filter("subSet.intValue!=0")).hasSize(2).containsExactlyInAnyOrder(root2, root6); - } - if (getRsqlToSpecBuilder() != LEGACY_G1) { - // G1 doesn't get null - has which shall be included as doesn't have - assertThat(filter("subSet.intValue!=2")).hasSize(6); - } + // the legacy builders has different (and wrong semantic) + // G1 - has element != x + // G2 - has element != x or has no elements + // accompanying G3 it is (as it should be) semantic of set != x - has no element with value x (including nhas no elements) + assertThat(filter("subSet.intValue!=0")).hasSize(2).containsExactlyInAnyOrder(root2, root6); + assertThat(filter("subSet.intValue!=2")).hasSize(6); assertThat(filter("subSet.intValue<1")).hasSize(4).containsExactlyInAnyOrder(root1, root3, root4, root5); assertThat(filter("subSet.intValue<=1")).hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); assertThat(filter("subSet.intValue>0")).hasSize(2).containsExactlyInAnyOrder(root2, root4); @@ -296,30 +258,19 @@ class SpecificationBuilderTest { assertThat(filter("subSet.intValue=out=(0, 1)")).hasSize(1).containsExactlyInAnyOrder(root6); assertThat(filter("subSet.intValue==0 and subSet.intValue==0")).hasSize(4).containsExactlyInAnyOrder(root1, root3, root4, root5); - if (getRsqlToSpecBuilder() == G3) { - assertThat(filter("subSet.intValue==0 and subSet.intValue!=1")).hasSize(3).containsExactlyInAnyOrder(root1, root3, root5); - } + assertThat(filter("subSet.intValue==0 and subSet.intValue!=1")).hasSize(3).containsExactlyInAnyOrder(root1, root3, root5); assertThat(filter("subSet.intValue==0 or subSet.intValue==1")) .hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); - if (getRsqlToSpecBuilder() != LEGACY_G1) { - // G1 doesn't get null - has which shall be included as doesn't have - assertThat(filter("subSet.intValue==0 or subSet.intValue!=1")) - .hasSize(5).containsExactlyInAnyOrder(root1, root3, root4, root5, root6); - } + assertThat(filter("subSet.intValue==0 or subSet.intValue!=1")) + .hasSize(5).containsExactlyInAnyOrder(root1, root3, root4, root5, root6); - if (getRsqlToSpecBuilder() != LEGACY_G1) { - // G1 treats it like same element shall match - which doesn't match to "has" semantic for sets - assertThat(filter("subSet.intValue==0 and subSet.strValue==suby")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5); - } + assertThat(filter("subSet.intValue==0 and subSet.strValue==suby")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5); assertThat(filter("subSet.intValue==0 and subSet.strValue!=subx")).hasSize(1).containsExactlyInAnyOrder(root3); assertThat(filter("subSet.intValue==0 and subSet.strValue!=suby")).hasSize(1).containsExactlyInAnyOrder(root1); assertThat(filter("subSet.intValue==0 or subSet.strValue==suby")) .hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); - if (getRsqlToSpecBuilder() != LEGACY_G1) { - // G1 doesn't get null - has which shall be included as doesn't have - assertThat(filter("subSet.intValue==0 or subSet.strValue!=subx")) - .hasSize(6).containsExactlyInAnyOrder(root1, root2, root3, root4, root5, root6); - } + assertThat(filter("subSet.intValue==0 or subSet.strValue!=subx")) + .hasSize(6).containsExactlyInAnyOrder(root1, root2, root3, root4, root5, root6); } @Test @@ -353,11 +304,9 @@ class SpecificationBuilderTest { assertThat(filter("subMap.x==*")).hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); assertThat(filter("subMap.x!=*")).isEmpty(); - if (getRsqlToSpecBuilder() != LEGACY_G1) { - // null checks - assertThat(filter("subMap.x=is=null")).hasSize(1).containsExactlyInAnyOrder(root6); - assertThat(filter("subMap.x=not=null")).hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); - } + // null checks + assertThat(filter("subMap.x=is=null")).hasSize(1).containsExactlyInAnyOrder(root6); + assertThat(filter("subMap.x=not=null")).hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); assertThat(filter("subMap.x==*tx and subMap.y==rooty")).hasSize(1).containsExactlyInAnyOrder(root1); assertThat(filter("subMap.x==*tx and subMap.y!=rootx")).hasSize(1).containsExactlyInAnyOrder(root1); @@ -402,13 +351,8 @@ class SpecificationBuilderTest { assertThat(filter("subEntity.subSub.strValue==*")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); assertThat(filter("subEntity.subSub.strValue!=*")).hasSize(1).containsExactlyInAnyOrder(root5); - if (getRsqlToSpecBuilder() != LEGACY_G1) { - // null checks - if (getRsqlToSpecBuilder() != LEGACY_G2) { - assertThat(filter("subEntity.subSub.strValue=is=null")).hasSize(1).containsExactlyInAnyOrder(root5); - } - assertThat(filter("subEntity.subSub.strValue=not=null")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); - } + assertThat(filter("subEntity.subSub.strValue=is=null")).hasSize(1).containsExactlyInAnyOrder(root5); + assertThat(filter("subEntity.subSub.strValue=not=null")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); assertThat(filter("subEntity.subSub.strValue==*bx and subEntity.subSub.strValue==suby")).isEmpty(); assertThat(filter("subEntity.subSub.strValue==*bx and subEntity.subSub.strValue!=subx")).isEmpty(); @@ -416,22 +360,16 @@ class SpecificationBuilderTest { .hasSize(2).containsExactlyInAnyOrder(root1, root2); assertThat(filter("subEntity.subSub.strValue==*bx or subEntity.subSub.strValue==suby")) .hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); - if (getRsqlToSpecBuilder() != LEGACY_G1 && getRsqlToSpecBuilder() != LEGACY_G2) { - // G1 doesn't get null entity in not, and doesn't support =is= and =not= - assertThat(filter("subEntity.subSub.strValue==*bx or subEntity.subSub.strValue!=subx")) - .hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); - assertThat(filter("subEntity.subSub.strValue==*bx or subEntity.subSub.strValue=is=null")) - .hasSize(3).containsExactlyInAnyOrder(root1, root2, root5); - } + assertThat(filter("subEntity.subSub.strValue==*bx or subEntity.subSub.strValue!=subx")) + .hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); + assertThat(filter("subEntity.subSub.strValue==*bx or subEntity.subSub.strValue=is=null")) + .hasSize(3).containsExactlyInAnyOrder(root1, root2, root5); // by sub entity int assertThat(filter("subEntity.subSub.intValue==0")).hasSize(2).containsExactlyInAnyOrder(root1, root2); assertThat(filter("subEntity.subSub.intValue==2")).isEmpty(); - if (getRsqlToSpecBuilder() != LEGACY_G1 && getRsqlToSpecBuilder() != LEGACY_G2) { - // G1 doesn't get null entity in not - assertThat(filter("subEntity.subSub.intValue!=0")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5); - assertThat(filter("subEntity.subSub.intValue!=2")).hasSize(5); - } + assertThat(filter("subEntity.subSub.intValue!=0")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5); + assertThat(filter("subEntity.subSub.intValue!=2")).hasSize(5); assertThat(filter("subEntity.subSub.intValue<1")).hasSize(2).containsExactlyInAnyOrder(root1, root2); assertThat(filter("subEntity.subSub.intValue<=1")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); assertThat(filter("subEntity.subSub.intValue>0")).hasSize(2).containsExactlyInAnyOrder(root3, root4); @@ -447,11 +385,8 @@ class SpecificationBuilderTest { assertThat(filter("subEntity.subSub.intValue==0 and subEntity.subSub.intValue==0")).hasSize(2).containsExactlyInAnyOrder(root1, root2); assertThat(filter("subEntity.subSub.intValue==0 or subEntity.subSub.intValue==1")).hasSize(4) .containsExactlyInAnyOrder(root1, root2, root3, root4); - if (getRsqlToSpecBuilder() != LEGACY_G1 && getRsqlToSpecBuilder() != LEGACY_G2) { - // G1 doesn't get null entity in not - assertThat(filter("subEntity.subSub.intValue==0 or subEntity.subSub.intValue!=1")) - .hasSize(3).containsExactlyInAnyOrder(root1, root2, root5); - } + assertThat(filter("subEntity.subSub.intValue==0 or subEntity.subSub.intValue!=1")) + .hasSize(3).containsExactlyInAnyOrder(root1, root2, root5); assertThat(filter("subEntity.subSub.strValue==subx and subEntity.subSub.intValue==1")).isEmpty(); assertThat(filter("subEntity.subSub.strValue==subx and subEntity.subSub.intValue!=1")).hasSize(2) @@ -460,11 +395,8 @@ class SpecificationBuilderTest { .hasSize(2).containsExactlyInAnyOrder(root1, root2); assertThat(filter("subEntity.subSub.strValue==subx or subEntity.subSub.intValue==1")) .hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); - if (getRsqlToSpecBuilder() != LEGACY_G1 && getRsqlToSpecBuilder() != LEGACY_G2) { - // G1 doesn't get null entity in not - assertThat(filter("subEntity.subSub.strValue==subx or subEntity.subSub.intValue!=1")) - .hasSize(3).containsExactlyInAnyOrder(root1, root2, root5); - } + assertThat(filter("subEntity.subSub.strValue==subx or subEntity.subSub.intValue!=1")) + .hasSize(3).containsExactlyInAnyOrder(root1, root2, root5); } @Test @@ -533,8 +465,10 @@ class SpecificationBuilderTest { assertThat(filter("subEntity.subSub.subSub.intValue=out=(0, 1)")).hasSize(1).containsExactlyInAnyOrder(root5); assertThat(filter("subEntity.subSub.subSub.intValue==0 and subEntity.subSub.intValue==1")).isEmpty(); - assertThat(filter("subEntity.subSub.subSub.intValue==0 and subEntity.subSub.intValue!=1")).hasSize(2).containsExactlyInAnyOrder(root1, root2); - assertThat(filter("subEntity.subSub.subSub.intValue==0 and subEntity.subSub.intValue==0")).hasSize(2).containsExactlyInAnyOrder(root1, root2); + assertThat(filter("subEntity.subSub.subSub.intValue==0 and subEntity.subSub.intValue!=1")).hasSize(2) + .containsExactlyInAnyOrder(root1, root2); + assertThat(filter("subEntity.subSub.subSub.intValue==0 and subEntity.subSub.intValue==0")).hasSize(2) + .containsExactlyInAnyOrder(root1, root2); assertThat(filter("subEntity.subSub.subSub.intValue==0 or subEntity.subSub.intValue==1")).hasSize(4) .containsExactlyInAnyOrder(root1, root2, root3, root4); assertThat(filter("subEntity.subSub.subSub.intValue==0 or subEntity.subSub.subSub.intValue!=1")) @@ -562,7 +496,7 @@ class SpecificationBuilderTest { } catch (final AssertionError e) { log.error( "Fail to get expected result for RSQL: {} with SQL query: {}", - rsql, new HawkbitQlToSql(entityManager).toSQL(Root.class, null, rsql, G3), + rsql, new HawkbitQlToSql(entityManager).toSQL(Root.class, null, rsql), e); throw e; } @@ -573,10 +507,6 @@ class SpecificationBuilderTest { return builder.specification(RsqlParser.parse(rsql)); } - private static SpecBuilder getRsqlToSpecBuilder() { - return QLSupport.getInstance().getSpecBuilder(); - } - @SpringBootConfiguration static class Config {} } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementTest.java index ea8bb1207..1af0c3544 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementTest.java @@ -12,7 +12,6 @@ package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.fail; -import static org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder.LEGACY_G1; import java.net.URI; import java.util.ArrayList; @@ -62,7 +61,6 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; -import org.eclipse.hawkbit.repository.jpa.ql.QLSupport; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.ActionStatusCreate; import org.eclipse.hawkbit.repository.model.Action.Status; @@ -636,10 +634,6 @@ class TargetManagementTest extends AbstractRepositoryManagementWithMetadataTest< */ @Test void findTargetsByRsqlWithTypeAndMetadata() { - if (QLSupport.getInstance().getSpecBuilder() == LEGACY_G1) { - // legacy visitor fail with that - return; - } final String controllerId1 = "target1"; final String controllerId2 = "target2"; createTargetWithMetadata(controllerId1, 2); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlTargetFieldTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlTargetFieldTest.java index 906f5009e..bf0824b1f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlTargetFieldTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlTargetFieldTest.java @@ -11,8 +11,6 @@ package org.eclipse.hawkbit.repository.jpa.rsql; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder.LEGACY_G2; -import static org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder.LEGACY_G1; import java.util.Arrays; import java.util.Map; @@ -178,11 +176,9 @@ class RsqlTargetFieldTest extends AbstractJpaIntegrationTest { assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.null==null", 1); // "null" check assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.n/a==null", 0); // "null" check - if (QLSupport.getInstance().getSpecBuilder() != LEGACY_G1) { - assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.dot=is=value.dot", 1); - assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.null=is=null", 5); // null check - assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.n/a=is=null", 1 + 5); // null check - } + assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.dot=is=value.dot", 1); + assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.null=is=null", 5); // null check + assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.n/a=is=null", 1 + 5); // null check assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.dot=eq=value.dot", 1); assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.null=eq=null", 5); // null check assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.n/a=eq=null", 1 + 5); // null check @@ -192,24 +188,14 @@ class RsqlTargetFieldTest extends AbstractJpaIntegrationTest { assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.null!=null2", 1); // value check assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.n/a!=null", 0); // "null" check - if (QLSupport.getInstance().getSpecBuilder() != LEGACY_G1) { - assertRSQLQuery( - TargetFields.ATTRIBUTE.name() + ".test.dot=not=value.dot", - QLSupport.getInstance().getSpecBuilder() == LEGACY_G2 ? 5 : 0); - assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.null=not=null", 1); // null check - assertRSQLQuery( - TargetFields.ATTRIBUTE.name() + ".test.null=not=null2", - 1 + (QLSupport.getInstance().getSpecBuilder() == LEGACY_G2 ? 5 : 0)); // value check - assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.n/a=not=null", 0); // null check - assertRSQLQuery( - TargetFields.ATTRIBUTE.name() + ".test.dot=ne=value.dot", - QLSupport.getInstance().getSpecBuilder() == LEGACY_G2 ? 5 : 0); - assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.null=ne=null", 1); // null check - assertRSQLQuery( - TargetFields.ATTRIBUTE.name() + ".test.null=ne=null2", - 1 + (QLSupport.getInstance().getSpecBuilder() == LEGACY_G2 ? 5 : 0)); // value check - assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.n/a=ne=null", 0); // null check - } + assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.dot=not=value.dot", 0); + assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.null=not=null", 1); // null check + assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.null=not=null2", 1); // value check + assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.n/a=not=null", 0); // null check + assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.dot=ne=value.dot", 0); + assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.null=ne=null", 1); // null check + assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.null=ne=null2", 1); // value check + assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.n/a=ne=null", 0); // null check assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".revision==1.1", 1); assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".revision!=1.1", 1); @@ -304,13 +290,11 @@ class RsqlTargetFieldTest extends AbstractJpaIntegrationTest { assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey==noExist*", 0); assertRSQLQuery(TargetFields.METADATA.name() + ".notExist==metaValue", 0); - if (QLSupport.getInstance().getSpecBuilder() != LEGACY_G1) { - assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=is=metaValue", 1); - assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=is=null", 4); // null check (1 of the initial five has) - assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=is=*v*", 2); - assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=is=noExist*", 0); - assertRSQLQuery(TargetFields.METADATA.name() + ".notExist=is=null", 1 + 5); // null check - } + assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=is=metaValue", 1); + assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=is=null", 4); // null check (1 of the initial five has) + assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=is=*v*", 2); + assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=is=noExist*", 0); + assertRSQLQuery(TargetFields.METADATA.name() + ".notExist=is=null", 1 + 5); // null check assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=eq=metaValue", 1); assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=eq=null", 4); // null check (1 of the initial five has) assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=eq=*v*", 2); @@ -322,28 +306,14 @@ class RsqlTargetFieldTest extends AbstractJpaIntegrationTest { assertRSQLQuery(TargetFields.METADATA.name() + ".notExist!=metaValue", 0); assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey!=notExist", 2); - if (QLSupport.getInstance().getSpecBuilder() != LEGACY_G1) { - assertRSQLQuery( - TargetFields.METADATA.name() + ".metaKey=not=metaValue", - QLSupport.getInstance().getSpecBuilder() == LEGACY_G2 ? 1 + 4 : 1); - assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=not=null", 2); // null check (2 of the initial five) - assertRSQLQuery( - TargetFields.METADATA.name() + ".notExist=not=metaValue", - QLSupport.getInstance().getSpecBuilder() == LEGACY_G2 ? 1 + 5 : 0); - assertRSQLQuery( - TargetFields.METADATA.name() + ".metaKey=not=notExist", - QLSupport.getInstance().getSpecBuilder() == LEGACY_G2 ? 2 + 4 : 2); - assertRSQLQuery( - TargetFields.METADATA.name() + ".metaKey=ne=metaValue", - QLSupport.getInstance().getSpecBuilder() == LEGACY_G2 ? 1 + 4 : 1); - assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=ne=null", 2); // null check (2 of the initial five) - assertRSQLQuery( - TargetFields.METADATA.name() + ".notExist=ne=metaValue", - QLSupport.getInstance().getSpecBuilder() == LEGACY_G2 ? 1 + 5 : 0); - assertRSQLQuery( - TargetFields.METADATA.name() + ".metaKey=ne=notExist", - QLSupport.getInstance().getSpecBuilder() == LEGACY_G2 ? 2 + 4 : 2); - } + assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=not=metaValue", 1); + assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=not=null", 2); // null check (2 of the initial five) + assertRSQLQuery(TargetFields.METADATA.name() + ".notExist=not=metaValue", 0); + assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=not=notExist", 2); + assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=ne=metaValue", 1); + assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=ne=null", 2); // null check (2 of the initial five) + assertRSQLQuery(TargetFields.METADATA.name() + ".notExist=ne=metaValue", 0); + assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=ne=notExist", 2); assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=in=(metaValue,notexist)", 1); assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=out=(metaValue,notexist)", 1); @@ -365,9 +335,8 @@ class RsqlTargetFieldTest extends AbstractJpaIntegrationTest { @Test void testFilterByComplexQueries() { assertRSQLQuery(TargetFields.NAME.name() + "!=targetName123" + AND + TargetFields.METADATA.name() + ".metaKey!=value", 0); - assertRSQLQuery( - "(" + TargetFields.TAG.name() + "!=TAG1" + OR + TargetFields.TAG.name() + "!=TAG2)" + - AND + TargetFields.CONTROLLERID.name() + "!=targetId1235", 4); + assertRSQLQuery("(" + TargetFields.TAG.name() + "!=TAG1" + OR + TargetFields.TAG.name() + "!=TAG2)" + + AND + TargetFields.CONTROLLERID.name() + "!=targetId1235", 4); } /** 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 a5ff91e11..fc2af4cc3 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 @@ -9,9 +9,6 @@ */ package org.eclipse.hawkbit.repository.jpa.rsql; -import static org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder.LEGACY_G2; -import static org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder.G3; -import static org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder.LEGACY_G1; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE; import jakarta.persistence.EntityManager; @@ -132,28 +129,12 @@ class RsqlToSqlTest { private & QueryField> void print(final Class domainClass, final Class fieldsClass, final String rsql) { System.out.println(rsql); - final String legacy = rsqlToSQL.toSQL(domainClass, fieldsClass, rsql, LEGACY_G1); - final String g2 = rsqlToSQL.toSQL(domainClass, fieldsClass, rsql, LEGACY_G2); - final String g3 = rsqlToSQL.toSQL(domainClass, fieldsClass, rsql, G3); - System.out.println("\tlegacy:\n\t\t" + useStar(legacy)); - System.out.println("\tG2:\n\t\t" + useStar(g2)); - System.out.println("\tG3:\n\t\t" + useStar(g3)); - if (!g2.equals(g3)) { - System.out.println("\tG2 != G3 for rsql: " + rsql); - } + System.out.println("\tSQL:\n\t\t" + useStar(rsqlToSQL.toSQL(domainClass, fieldsClass, rsql))); } private & QueryField> void printFrom(final Class domainClass, final Class fieldsClass, final String rsql) { System.out.println(rsql); - final String legacy = rsqlToSQL.toSQL(domainClass, fieldsClass, rsql, LEGACY_G1); - final String g2 = rsqlToSQL.toSQL(domainClass, fieldsClass, rsql, LEGACY_G2); - final String g3 = rsqlToSQL.toSQL(domainClass, fieldsClass, rsql, G3); - System.out.println("\tlegacy:\n\t\t" + from(legacy)); - System.out.println("\tG2:\n\t\t" + from(g2)); - System.out.println("\tG2:\n\t\t" + from(g3)); - if (!g2.equals(g3)) { - System.out.println("\tG2 != G3 for rsql: " + rsql); - } + System.out.println("\tSQL:\n\t\t" + from(rsqlToSQL.toSQL(domainClass, fieldsClass, rsql))); } private static String useStar(final String sql) {