From 0d52524202d64ec15126317aba4dc10bbe066395 Mon Sep 17 00:00:00 2001 From: Sebastian Firsching <56029682+sebastian-firsching@users.noreply.github.com> Date: Wed, 15 Apr 2020 08:01:54 +0200 Subject: [PATCH] Fix not equal operator (#935) * Use nested query in getNotEqual Predicate * Refactor RSQL Utility + use identifierField for field enums * Don't join in case of not equal rsql operator * Bugfix for automatic cross join + small refactoring * Fix rsql out operator * Fix tests for out-operator + extend TargetFieldTest * Use inner join for subquery * Don't use subquery for simple rsql queries * Refactor RSQLUtility * Change some methods to static * Only use outer joins when they are needed * Add tests for empty tag names * Minor changes and refactoring for RSQLUtility * Refactor methods to remove duplications Signed-off-by: Sebastian Firsching --- .../DistributionSetMetadataFields.java | 5 + .../hawkbit/repository/FieldNameProvider.java | 9 + .../SoftwareModuleMetadataFields.java | 5 + .../repository/TargetMetadataFields.java | 5 + .../repository/jpa/rsql/RSQLUtility.java | 286 +++++++++++++----- .../rsql/RSQLDistributionSetFieldTest.java | 10 +- ...RSQLDistributionSetMetadataFieldsTest.java | 4 +- .../jpa/rsql/RSQLSoftwareModuleFieldTest.java | 4 +- .../RSQLSoftwareModuleMetadataFieldsTest.java | 4 +- .../jpa/rsql/RSQLTargetFieldTest.java | 44 ++- .../rsql/RSQLTargetFilterQueryFieldsTest.java | 4 +- .../rsql/RSQLTargetMetadataFieldsTest.java | 4 +- .../repository/jpa/rsql/RSQLUtilityTest.java | 41 ++- 13 files changed, 322 insertions(+), 103 deletions(-) diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/DistributionSetMetadataFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/DistributionSetMetadataFields.java index 8915e3725..abef365cf 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/DistributionSetMetadataFields.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/DistributionSetMetadataFields.java @@ -36,4 +36,9 @@ public enum DistributionSetMetadataFields implements FieldNameProvider { public String getFieldName() { return fieldName; } + + @Override + public String identifierFieldName() { + return KEY.getFieldName(); + } } diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/FieldNameProvider.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/FieldNameProvider.java index 58f31818f..d54a57011 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/FieldNameProvider.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/FieldNameProvider.java @@ -81,4 +81,13 @@ public interface FieldNameProvider { default boolean isMap() { return false; } + + /** + * Returns the name of the field, that identifies the entity. + * + * @return the name of the identifier, by default 'id' + */ + default String identifierFieldName() { + return "id"; + } } diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleMetadataFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleMetadataFields.java index 725d0f206..d3a86f165 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleMetadataFields.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleMetadataFields.java @@ -41,4 +41,9 @@ public enum SoftwareModuleMetadataFields implements FieldNameProvider { public String getFieldName() { return fieldName; } + + @Override + public String identifierFieldName() { + return KEY.getFieldName(); + } } diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetMetadataFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetMetadataFields.java index d85edbeec..a9fe24649 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetMetadataFields.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetMetadataFields.java @@ -33,4 +33,9 @@ public enum TargetMetadataFields implements FieldNameProvider { public String getFieldName() { return fieldName; } + + @Override + public String identifierFieldName() { + return KEY.getFieldName(); + } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLUtility.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLUtility.java index a23a382c2..a76d8e48f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLUtility.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLUtility.java @@ -18,6 +18,8 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; import java.util.stream.Collectors; import javax.persistence.criteria.CriteriaBuilder; @@ -31,6 +33,7 @@ import javax.persistence.criteria.Path; import javax.persistence.criteria.PluralJoin; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; +import javax.persistence.criteria.Subquery; import org.apache.commons.lang3.text.StrLookup; import org.eclipse.hawkbit.repository.FieldNameProvider; @@ -48,6 +51,8 @@ import org.springframework.orm.jpa.vendor.Database; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import com.google.common.collect.Lists; + import cz.jirutka.rsql.parser.RSQLParser; import cz.jirutka.rsql.parser.RSQLParserException; import cz.jirutka.rsql.parser.ast.AndNode; @@ -180,7 +185,7 @@ public final class RSQLUtility { query.distinct(true); final JpqQueryRSQLVisitor jpqQueryRSQLVisitor = new JpqQueryRSQLVisitor<>(root, cb, enumType, - virtualPropertyReplacer, database); + virtualPropertyReplacer, database, query); final List accept = rootNode., String> accept(jpqQueryRSQLVisitor); if (!CollectionUtils.isEmpty(accept)) { @@ -206,29 +211,35 @@ public final class RSQLUtility { private static final class JpqQueryRSQLVisitor & FieldNameProvider, T> implements RSQLVisitor, String> { private static final char ESCAPE_CHAR = '\\'; + private static final List NO_JOINS_OPERATOR = Lists.newArrayList("!=", "=out="); public static final Character LIKE_WILDCARD = '*'; private final Root root; private final CriteriaBuilder cb; + private final CriteriaQuery query; private final Class enumType; private final VirtualPropertyReplacer virtualPropertyReplacer; private int level; private boolean isOrLevel; private final Map>> joinsInLevel = new HashMap<>(3); + private boolean joinsNeeded; private final SimpleTypeConverter simpleTypeConverter; private final Database database; private JpqQueryRSQLVisitor(final Root root, final CriteriaBuilder cb, final Class enumType, - final VirtualPropertyReplacer virtualPropertyReplacer, final Database database) { + final VirtualPropertyReplacer virtualPropertyReplacer, final Database database, + final CriteriaQuery query) { this.root = root; this.cb = cb; + this.query = query; this.enumType = enumType; this.virtualPropertyReplacer = virtualPropertyReplacer; - simpleTypeConverter = new SimpleTypeConverter(); + this.simpleTypeConverter = new SimpleTypeConverter(); this.database = database; + this.joinsNeeded = false; } private void beginLevel(final boolean isOr) { @@ -288,7 +299,7 @@ public final class RSQLUtility { private String getAndValidatePropertyFieldName(final A propertyEnum, final ComparisonNode node) { - final String[] graph = node.getSelector().split("\\" + FieldNameProvider.SUB_ATTRIBUTE_SEPERATOR); + final String[] graph = getSubAttributesFrom(node.getSelector()); validateMapParameter(propertyEnum, node, graph); @@ -363,33 +374,40 @@ public final class RSQLUtility { * dot notated field path * @return the Path for a field */ - private Path getFieldPath(final A enumField, final String finalProperty) { - Path fieldPath = null; - final String[] split = finalProperty.split("\\" + FieldNameProvider.SUB_ATTRIBUTE_SEPERATOR); + private Path getFieldPath(final A enumField, final String finalProperty) { + return (Path) getFieldPath(root, getSubAttributesFrom(finalProperty), enumField.isMap(), + this::getJoinFieldPath); + } + + private Path getJoinFieldPath(final Path fieldPath, final String fieldNameSplit) { + if (fieldPath instanceof PluralJoin) { + final Join join = (Join) fieldPath; + 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 static Path 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++) { - final boolean isMapKeyField = enumField.isMap() && i == (split.length - 1); - if (isMapKeyField) { + if (isMapKeyField && i == (split.length - 1)) { return fieldPath; } - final String fieldNameSplit = split[i]; fieldPath = (fieldPath != null) ? fieldPath.get(fieldNameSplit) : root.get(fieldNameSplit); - if (fieldPath instanceof PluralJoin) { - final Join join = (Join) fieldPath; - 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); - fieldPath = currentJoinOfType.get(); - } else { - final Join newJoin = joinParent.join(fieldNameSplit, JoinType.LEFT); - addCurrentJoin(newJoin); - fieldPath = newJoin; - } - - } + fieldPath = joinFieldPathProvider.apply(fieldPath, fieldNameSplit); } return fieldPath; } @@ -413,14 +431,20 @@ public final class RSQLUtility { final String finalProperty = getAndValidatePropertyFieldName(fieldName, node); final List values = node.getArguments(); - final List transformedValue = new ArrayList<>(); + final List transformedValues = new ArrayList<>(); final Path fieldPath = getFieldPath(fieldName, finalProperty); for (final String value : values) { - transformedValue.add(convertValueIfNecessary(node, fieldName, value, fieldPath)); + transformedValues.add(convertValueIfNecessary(node, fieldName, value, fieldPath)); } - return mapToPredicate(node, fieldPath, node.getArguments(), transformedValue, fieldName, database); + this.joinsNeeded = this.joinsNeeded || areJoinsNeeded(node); + + return mapToPredicate(node, fieldPath, node.getArguments(), transformedValues, fieldName, finalProperty); + } + + private static boolean areJoinsNeeded(final ComparisonNode node) { + return !NO_JOINS_OPERATOR.contains(node.getOperator().getSymbol()); } // Exception squid:S2095 - see @@ -453,7 +477,7 @@ public final class RSQLUtility { private A getFieldEnumByName(final ComparisonNode node) { String enumName = node.getSelector(); - final String[] graph = enumName.split("\\" + FieldNameProvider.SUB_ATTRIBUTE_SEPERATOR); + final String[] graph = getSubAttributesFrom(enumName); if (graph.length != 0) { enumName = graph[0]; } @@ -484,8 +508,7 @@ public final class RSQLUtility { return value; } - private Object convertBooleanValue(final ComparisonNode node, final String value, - final Class javaType) { + private Object convertBooleanValue(final ComparisonNode node, final String value, final Class javaType) { try { return simpleTypeConverter.convertIfNecessary(value, javaType); } catch (final TypeMismatchException e) { @@ -533,11 +556,7 @@ public final class RSQLUtility { private List mapToPredicate(final ComparisonNode node, final Path fieldPath, final List values, final List transformedValues, final A enumField, - final Database database) { - // 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 finalProperty) { String value = values.get(0); // if lookup is available, replace macros ... @@ -548,19 +567,26 @@ public final class RSQLUtility { final Predicate mapPredicate = mapToMapPredicate(node, fieldPath, enumField); final Predicate valuePredicate = addOperatorPredicate(node, getMapValueFieldPath(enumField, fieldPath), - transformedValues, transformedValue, value, database); + transformedValues, value, finalProperty, enumField); return toSingleList(mapPredicate != null ? cb.and(mapPredicate, valuePredicate) : valuePredicate); } private Predicate addOperatorPredicate(final ComparisonNode node, final Path fieldPath, - final List transformedValues, final Object transformedValue, final String value, - final Database database) { - switch (node.getOperator().getSymbol()) { + final List transformedValues, final String value, final String finalProperty, + final A enumField) { + + // 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, database); + return getEqualToPredicate(transformedValue, fieldPath); case "!=": - return getNotEqualToPredicate(transformedValue, fieldPath, database); + return getNotEqualToPredicate(transformedValue, fieldPath, finalProperty, enumField); case "=gt=": return cb.greaterThan(pathOfString(fieldPath), value); case "=ge=": @@ -572,10 +598,10 @@ public final class RSQLUtility { case "=in=": return getInPredicate(transformedValues, fieldPath); case "=out=": - return getOutPredicate(transformedValues, fieldPath); + return getOutPredicate(transformedValues, finalProperty, enumField, fieldPath); default: - throw new RSQLParameterSyntaxException("operator symbol {" + node.getOperator().getSymbol() - + "} is either not supported or not implemented"); + throw new RSQLParameterSyntaxException( + "operator symbol {" + operator + "} is either not supported or not implemented"); } } @@ -594,19 +620,38 @@ public final class RSQLUtility { } } - private Predicate getOutPredicate(final List transformedValues, final Path fieldPath) { - final List outParams = new ArrayList<>(); - for (final Object param : transformedValues) { - if (param instanceof String) { - outParams.add(((String) param).toUpperCase()); - } - } - if (!outParams.isEmpty()) { - return cb.not(cb.upper(pathOfString(fieldPath)).in(outParams)); - } else { - return cb.not(fieldPath.in(transformedValues)); + private Predicate getOutPredicate(final List transformedValues, final String finalProperty, + final A enumField, final Path fieldPath) { + final String[] fieldNames = getSubAttributesFrom(finalProperty); + final List outParams = transformedValues.stream().filter(String.class::isInstance) + .map(String.class::cast).map(String::toUpperCase).collect(Collectors.toList()); + + if (isSimpleField(fieldNames, enumField.isMap())) { + return toNullOrNotInPredicate(fieldPath, transformedValues, outParams); } + + clearOuterJoinsIfNotNeeded(); + + return toOutWithSubQueryPredicate(fieldNames, transformedValues, enumField, outParams); + } + + private Predicate toNullOrNotInPredicate(final Path fieldPath, final List transformedValues, + final List outParams) { + + final Path pathOfString = pathOfString(fieldPath); + final Predicate inPredicate = outParams.isEmpty() ? fieldPath.in(transformedValues) + : cb.upper(pathOfString).in(outParams); + + return cb.or(cb.isNull(pathOfString), cb.not(inPredicate)); + } + + private Predicate toOutWithSubQueryPredicate(final String[] fieldNames, final List transformedValues, + final A enumField, final List outParams) { + final Function, Predicate> inPredicateProvider = expressionToCompare -> outParams + .isEmpty() ? cb.upper(expressionToCompare).in(transformedValues) + : cb.upper(expressionToCompare).in(outParams); + return toNotExistsSubQueryPredicate(fieldNames, enumField, inPredicateProvider); } private Path getMapValueFieldPath(final A enumField, final Path fieldPath) { @@ -625,7 +670,9 @@ public final class RSQLUtility { if (!enumField.isMap()) { return null; } - final String[] graph = node.getSelector().split("\\" + FieldNameProvider.SUB_ATTRIBUTE_SEPERATOR); + + final String[] graph = getSubAttributesFrom(node.getSelector()); + final String keyValue = graph[graph.length - 1]; if (fieldPath instanceof MapJoin) { // Currently we support only string key .So below cast is safe. @@ -640,15 +687,14 @@ public final class RSQLUtility { return cb.equal(cb.upper(fieldPath.get(keyFieldName)), keyValue.toUpperCase()); } - private Predicate getEqualToPredicate(final Object transformedValue, final Path fieldPath, - final Database database) { + private Predicate getEqualToPredicate(final Object transformedValue, final Path fieldPath) { if (transformedValue instanceof String) { if (StringUtils.isEmpty(transformedValue)) { return cb.or(cb.isNull(pathOfString(fieldPath)), cb.equal(pathOfString(fieldPath), "")); } - final String preFormattedValue = escapeValueToSQL((String) transformedValue, database, ESCAPE_CHAR); - return cb.like(cb.upper(pathOfString(fieldPath)), preFormattedValue.toUpperCase(), ESCAPE_CHAR); + final String sqlValue = toSQL((String) transformedValue); + return cb.like(cb.upper(pathOfString(fieldPath)), sqlValue, ESCAPE_CHAR); } if (transformedValue == null) { @@ -659,34 +705,124 @@ public final class RSQLUtility { } private Predicate getNotEqualToPredicate(final Object transformedValue, final Path fieldPath, - final Database database) { - if (transformedValue instanceof String) { - if (StringUtils.isEmpty(transformedValue)) { - return cb.and(cb.isNotNull(pathOfString(fieldPath)), cb.notEqual(pathOfString(fieldPath), "")); - } - - final String preFormattedValue = escapeValueToSQL((String) transformedValue, database, ESCAPE_CHAR); - return cb.notLike(cb.upper(pathOfString(fieldPath)), preFormattedValue.toUpperCase(), ESCAPE_CHAR); - } + final String finalProperty, final A enumField) { if (transformedValue == null) { - return cb.isNotNull(pathOfString(fieldPath)); + return toNotNullPredicate(fieldPath); } + if (transformedValue instanceof String) { + if (StringUtils.isEmpty(transformedValue)) { + return toNotNullAndNotEmptyPredicate(fieldPath); + } + + final String sqlValue = toSQL((String) transformedValue); + final String[] fieldNames = getSubAttributesFrom(finalProperty); + + if (isSimpleField(fieldNames, enumField.isMap())) { + return toNullOrNotLikePredicate(fieldPath, sqlValue); + } + + clearOuterJoinsIfNotNeeded(); + + return toNotEqualWithSubQueryPredicate(enumField, sqlValue, fieldNames); + } + + return toNotEqualPredicate(fieldPath, transformedValue); + } + + private void clearOuterJoinsIfNotNeeded() { + if (!joinsNeeded) { + root.getJoins().clear(); + } + } + + private Predicate toNotNullPredicate(final Path fieldPath) { + return cb.isNotNull(pathOfString(fieldPath)); + } + + private Predicate toNotEqualPredicate(final Path fieldPath, final Object transformedValue) { return cb.notEqual(fieldPath, transformedValue); } - private static String escapeValueToSQL(final String transformedValue, final Database database, - final char escapeChar) { + private Predicate toNullOrNotLikePredicate(final Path fieldPath, final String sqlValue) { + return cb.or(cb.isNull(pathOfString(fieldPath)), + cb.notLike(cb.upper(pathOfString(fieldPath)), sqlValue, ESCAPE_CHAR)); + } + + private Predicate toNotNullAndNotEmptyPredicate(final Path fieldPath) { + return cb.and(cb.isNotNull(pathOfString(fieldPath)), cb.notEqual(pathOfString(fieldPath), "")); + } + + private Predicate toNotEqualWithSubQueryPredicate(final A enumField, final String sqlValue, + final String[] fieldNames) { + final Function, Predicate> likePredicateProvider = expressionToCompare -> cb + .like(cb.upper(expressionToCompare), sqlValue); + return toNotExistsSubQueryPredicate(fieldNames, enumField, likePredicateProvider); + } + + @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.identifierFieldName()), + subqueryRoot.get(enumField.identifierFieldName())); + 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)); + } + + private static String[] getSubAttributesFrom(final String property) { + return property.split("\\" + FieldNameProvider.SUB_ATTRIBUTE_SEPERATOR); + } + + private static boolean isSimpleField(final String[] split, final boolean isMapKeyField) { + return split.length == 1 || (split.length == 2 && isMapKeyField); + } + + private Expression getExpressionToCompare(final Path innerFieldPath, final A enumField) { + if (!enumField.isMap()) { + return pathOfString(innerFieldPath); + } + if (innerFieldPath instanceof MapJoin) { + // Currently we support only string key .So below cast + // is safe. + return (Expression) (((MapJoin) pathOfString(innerFieldPath)).value()); + } + final String valueFieldName = enumField.getSubEntityMapTuple().map(Entry::getValue) + .orElseThrow(() -> new UnsupportedOperationException( + "For the fields, defined as Map, only Map java type or tuple in the form of SimpleImmutableEntry are allowed. Neither of those could be found!")); + return pathOfString(innerFieldPath).get(valueFieldName); + } + + private static Path getInnerFieldPath(final Root subqueryRoot, final String[] split, + final boolean isMapKeyField) { + return getFieldPath(subqueryRoot, split, isMapKeyField, + (fieldPath, fieldNameSplit) -> getInnerJoinFieldPath(subqueryRoot, fieldPath, fieldNameSplit)); + } + + 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 String toSQL(final String transformedValue) { final String escaped; if (database == Database.SQL_SERVER) { escaped = transformedValue.replace("%", "[%]").replace("_", "[_]"); } else { - escaped = transformedValue.replace("%", escapeChar + "%").replace("_", escapeChar + "_"); + escaped = transformedValue.replace("%", ESCAPE_CHAR + "%").replace("_", ESCAPE_CHAR + "_"); } - return escaped.replace(LIKE_WILDCARD, '%'); + return escaped.replace(LIKE_WILDCARD, '%').toUpperCase(); } @SuppressWarnings("unchecked") diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLDistributionSetFieldTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLDistributionSetFieldTest.java index 8aaf0d4db..d64f1c079 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLDistributionSetFieldTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLDistributionSetFieldTest.java @@ -79,12 +79,13 @@ public class RSQLDistributionSetFieldTest extends AbstractJpaIntegrationTest { assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==''", 1); assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "!=''", 4); assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==DS", 1); - assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "!=DS", 3); + assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "!=DS*", 3); + assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "!=DS", 4); assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==DS*", 2); assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==DS%", 1); assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==noExist*", 0); assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "=in=(DS,notexist)", 1); - assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "=out=(DS,notexist)", 3); + assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "=out=(DS,notexist)", 4); } @Test @@ -115,12 +116,11 @@ public class RSQLDistributionSetFieldTest extends AbstractJpaIntegrationTest { @Description("Test filter distribution set by tag name") public void testFilterByTag() { assertRSQLQuery(DistributionSetFields.TAG.name() + "==Tag1", 2); - // does not include untagged sets - assertRSQLQuery(DistributionSetFields.TAG.name() + "!=Tag1", 0); + assertRSQLQuery(DistributionSetFields.TAG.name() + "!=Tag1", 3); assertRSQLQuery(DistributionSetFields.TAG.name() + "==T*", 2); assertRSQLQuery(DistributionSetFields.TAG.name() + "==noExist*", 0); assertRSQLQuery(DistributionSetFields.TAG.name() + "=in=(Tag1,notexist)", 2); - assertRSQLQuery(DistributionSetFields.TAG.name() + "=out=(null)", 2); + assertRSQLQuery(DistributionSetFields.TAG.name() + "=out=(null)", 5); } @Test diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLDistributionSetMetadataFieldsTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLDistributionSetMetadataFieldsTest.java index 7780728ee..45e888004 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLDistributionSetMetadataFieldsTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLDistributionSetMetadataFieldsTest.java @@ -65,9 +65,9 @@ public class RSQLDistributionSetMetadataFieldsTest extends AbstractJpaIntegratio assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "==''", 1); assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "!=''", 5); assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "==1", 1); - assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "!=1", 4); + assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "!=1", 5); assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "=in=(1,2)", 2); - assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "=out=(1,2)", 3); + assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "=out=(1,2)", 4); } private void assertRSQLQuery(final String rsqlParam, final long expectedEntities) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLSoftwareModuleFieldTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLSoftwareModuleFieldTest.java index fd0f09007..d6b9d8048 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLSoftwareModuleFieldTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLSoftwareModuleFieldTest.java @@ -76,10 +76,10 @@ public class RSQLSoftwareModuleFieldTest extends AbstractJpaIntegrationTest { assertRSQLQuery(SoftwareModuleFields.DESCRIPTION.name() + "==''", 1); assertRSQLQuery(SoftwareModuleFields.DESCRIPTION.name() + "!=''", 4); assertRSQLQuery(SoftwareModuleFields.DESCRIPTION.name() + "==agent-hub", 1); - assertRSQLQuery(SoftwareModuleFields.DESCRIPTION.name() + "!=agent-hub", 3); + assertRSQLQuery(SoftwareModuleFields.DESCRIPTION.name() + "!=agent-hub", 4); assertRSQLQuery(SoftwareModuleFields.DESCRIPTION.name() + "==noExist*", 0); assertRSQLQuery(SoftwareModuleFields.DESCRIPTION.name() + "=in=(agent-hub,notexist)", 1); - assertRSQLQuery(SoftwareModuleFields.DESCRIPTION.name() + "=out=(agent-hub,notexist)", 3); + assertRSQLQuery(SoftwareModuleFields.DESCRIPTION.name() + "=out=(agent-hub,notexist)", 4); } @Test diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLSoftwareModuleMetadataFieldsTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLSoftwareModuleMetadataFieldsTest.java index edd9d6afb..e3cb26af9 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLSoftwareModuleMetadataFieldsTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLSoftwareModuleMetadataFieldsTest.java @@ -71,9 +71,9 @@ public class RSQLSoftwareModuleMetadataFieldsTest extends AbstractJpaIntegration assertRSQLQuery(SoftwareModuleMetadataFields.VALUE.name() + "==''", 1); assertRSQLQuery(SoftwareModuleMetadataFields.VALUE.name() + "!=''", 6); assertRSQLQuery(SoftwareModuleMetadataFields.VALUE.name() + "==1", 1); - assertRSQLQuery(SoftwareModuleMetadataFields.VALUE.name() + "!=1", 5); + assertRSQLQuery(SoftwareModuleMetadataFields.VALUE.name() + "!=1", 6); assertRSQLQuery(SoftwareModuleMetadataFields.VALUE.name() + "=in=(1,2)", 2); - assertRSQLQuery(SoftwareModuleMetadataFields.VALUE.name() + "=out=(1,2)", 4); + assertRSQLQuery(SoftwareModuleMetadataFields.VALUE.name() + "=out=(1,2)", 5); } @Test 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 767483b70..81678fb53 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 @@ -37,6 +37,9 @@ public class RSQLTargetFieldTest extends AbstractJpaIntegrationTest { private Target target; private Target target2; + private static final String OR = ","; + private static final String AND = ";"; + @Before public void setupBeforeTest() throws InterruptedException { @@ -73,7 +76,8 @@ public class RSQLTargetFieldTest extends AbstractJpaIntegrationTest { targetManagement.assignTag(Arrays.asList(target3.getControllerId(), target4.getControllerId()), targetTag2.getId()); - targetManagement.assignTag(Arrays.asList(target3.getControllerId(), target4.getControllerId()), + targetManagement.assignTag( + Arrays.asList(target.getControllerId(), target3.getControllerId(), target4.getControllerId()), targetTag3.getId()); assignDistributionSet(ds.getId(), target.getControllerId()); @@ -85,6 +89,7 @@ public class RSQLTargetFieldTest extends AbstractJpaIntegrationTest { assertRSQLQuery(TargetFields.ID.name() + "==targetId123", 1); assertRSQLQuery(TargetFields.ID.name() + "==target*", 5); assertRSQLQuery(TargetFields.ID.name() + "==noExist*", 0); + assertRSQLQuery(TargetFields.ID.name() + "!=targetId123", 4); assertRSQLQuery(TargetFields.ID.name() + "=in=(targetId123,notexist)", 1); assertRSQLQuery(TargetFields.ID.name() + "=out=(targetId123,notexist)", 4); } @@ -95,6 +100,7 @@ public class RSQLTargetFieldTest extends AbstractJpaIntegrationTest { assertRSQLQuery(TargetFields.NAME.name() + "==targetName123", 1); assertRSQLQuery(TargetFields.NAME.name() + "==target*", 5); assertRSQLQuery(TargetFields.NAME.name() + "==noExist*", 0); + assertRSQLQuery(TargetFields.NAME.name() + "!=targetName123", 4); assertRSQLQuery(TargetFields.NAME.name() + "=in=(targetName123,notexist)", 1); assertRSQLQuery(TargetFields.NAME.name() + "=out=(targetName123,notexist)", 4); } @@ -105,10 +111,11 @@ public class RSQLTargetFieldTest extends AbstractJpaIntegrationTest { assertRSQLQuery(TargetFields.DESCRIPTION.name() + "==''", 3); assertRSQLQuery(TargetFields.DESCRIPTION.name() + "!=''", 2); assertRSQLQuery(TargetFields.DESCRIPTION.name() + "==targetDesc123", 1); + assertRSQLQuery(TargetFields.DESCRIPTION.name() + "!=targetDesc123", 4); assertRSQLQuery(TargetFields.DESCRIPTION.name() + "==target*", 2); assertRSQLQuery(TargetFields.DESCRIPTION.name() + "==noExist*", 0); assertRSQLQuery(TargetFields.DESCRIPTION.name() + "=in=(targetDesc123,notexist)", 1); - assertRSQLQuery(TargetFields.DESCRIPTION.name() + "=out=(targetDesc123,notexist)", 1); + assertRSQLQuery(TargetFields.DESCRIPTION.name() + "=out=(targetDesc123,notexist)", 4); } @Test @@ -117,6 +124,7 @@ public class RSQLTargetFieldTest extends AbstractJpaIntegrationTest { assertRSQLQuery(TargetFields.CONTROLLERID.name() + "==targetId123", 1); assertRSQLQuery(TargetFields.CONTROLLERID.name() + "==target*", 5); assertRSQLQuery(TargetFields.CONTROLLERID.name() + "==noExist*", 0); + assertRSQLQuery(TargetFields.CONTROLLERID.name() + "!=targetId123", 4); assertRSQLQuery(TargetFields.CONTROLLERID.name() + "=in=(targetId123,notexist)", 1); assertRSQLQuery(TargetFields.CONTROLLERID.name() + "=out=(targetId123,notexist)", 4); } @@ -154,7 +162,7 @@ public class RSQLTargetFieldTest extends AbstractJpaIntegrationTest { assertRSQLQuery(TargetFields.ASSIGNEDDS.name() + ".name==A*", 1); assertRSQLQuery(TargetFields.ASSIGNEDDS.name() + ".name==noExist*", 0); assertRSQLQuery(TargetFields.ASSIGNEDDS.name() + ".name=in=(AssignedDs,notexist)", 1); - assertRSQLQuery(TargetFields.ASSIGNEDDS.name() + ".name=out=(AssignedDs,notexist)", 0); + assertRSQLQuery(TargetFields.ASSIGNEDDS.name() + ".name=out=(AssignedDs,notexist)", 4); } @Test @@ -166,28 +174,34 @@ public class RSQLTargetFieldTest extends AbstractJpaIntegrationTest { assertRSQLQuery( TargetFields.ASSIGNEDDS.name() + ".version=in=(" + TestdataFactory.DEFAULT_VERSION + ",notexist)", 1); assertRSQLQuery( - TargetFields.ASSIGNEDDS.name() + ".version=out=(" + TestdataFactory.DEFAULT_VERSION + ",notexist)", 0); + TargetFields.ASSIGNEDDS.name() + ".version=out=(" + TestdataFactory.DEFAULT_VERSION + ",notexist)", 4); } @Test @Description("Test filter target by tag name") public void testFilterByTag() { assertRSQLQuery(TargetFields.TAG.name() + "==Tag1", 2); - assertRSQLQuery(TargetFields.TAG.name() + "!=Tag1", 2); + assertRSQLQuery(TargetFields.TAG.name() + "!=Tag1", 3); assertRSQLQuery(TargetFields.TAG.name() + "==T*", 4); + assertRSQLQuery(TargetFields.TAG.name() + "!=T*", 1); assertRSQLQuery(TargetFields.TAG.name() + "==noExist*", 0); - assertRSQLQuery(TargetFields.TAG.name() + "!=notexist", 4); + assertRSQLQuery(TargetFields.TAG.name() + "!=notexist", 5); + assertRSQLQuery(TargetFields.TAG.name() + "==''", 1); + assertRSQLQuery(TargetFields.TAG.name() + "!=''", 4); assertRSQLQuery(TargetFields.TAG.name() + "=in=(Tag1,notexist)", 2); assertRSQLQuery(TargetFields.TAG.name() + "=in=(null)", 0); - assertRSQLQuery(TargetFields.TAG.name() + "=out=(Tag1,notexist)", 2); - assertRSQLQuery(TargetFields.TAG.name() + "=out=(null)", 4); + assertRSQLQuery(TargetFields.TAG.name() + "=out=(Tag1,notexist)", 3); + assertRSQLQuery(TargetFields.TAG.name() + "=out=(null)", 5); + assertRSQLQuery(TargetFields.TAG.name() + "==Tag1" + OR + TargetFields.TAG.name() + "==Tag2", 4); + assertRSQLQuery(TargetFields.TAG.name() + "!=Tag2" + AND + TargetFields.TAG.name() + "==Tag3", 1); + assertRSQLQuery(TargetFields.TAG.name() + "!=Tag2" + OR + TargetFields.TAG.name() + "!=Tag3", 3); } @Test @Description("Test filter target by lastTargetQuery") public void testFilterByLastTargetQuery() throws InterruptedException { assertRSQLQuery(TargetFields.LASTCONTROLLERREQUESTAT.name() + "==" + target.getLastTargetQuery(), 1); - assertRSQLQuery(TargetFields.LASTCONTROLLERREQUESTAT.name() + "!=" + target.getLastTargetQuery(), 1); + assertRSQLQuery(TargetFields.LASTCONTROLLERREQUESTAT.name() + "!=" + target.getLastTargetQuery(), 4); assertRSQLQuery(TargetFields.LASTCONTROLLERREQUESTAT.name() + "=lt=" + target.getLastTargetQuery(), 0); assertRSQLQuery(TargetFields.LASTCONTROLLERREQUESTAT.name() + "=lt=" + target2.getLastTargetQuery(), 1); assertRSQLQuery(TargetFields.LASTCONTROLLERREQUESTAT.name() + "=gt=" + target.getLastTargetQuery(), 1); @@ -205,7 +219,19 @@ public class RSQLTargetFieldTest extends AbstractJpaIntegrationTest { assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=in=(metaValue,notexist)", 1); assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=out=(metaValue,notexist)", 1); assertRSQLQuery(TargetFields.METADATA.name() + ".notExist==metaValue", 0); + assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey!=metaValue", 1); + assertRSQLQuery(TargetFields.METADATA.name() + ".notExist!=metaValue", 0); + assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey!=notExist", 2); + } + @Test + @Description("Test filter based on more complex RSQL queries") + public 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); } private void assertRSQLQuery(final String rsqlParam, final long expcetedTargets) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetFilterQueryFieldsTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetFilterQueryFieldsTest.java index 1490998ef..cb265d7db 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetFilterQueryFieldsTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetFilterQueryFieldsTest.java @@ -80,7 +80,7 @@ public class RSQLTargetFilterQueryFieldsTest extends AbstractJpaIntegrationTest assertRSQLQuery(TargetFilterQueryFields.AUTOASSIGNDISTRIBUTIONSET.name() + ".name=in=(" + filter1.getAutoAssignDistributionSet().getName() + ",notexist)", 1); assertRSQLQuery(TargetFilterQueryFields.AUTOASSIGNDISTRIBUTIONSET.name() + ".name=out=(" - + filter1.getAutoAssignDistributionSet().getName() + ",notexist)", 1); + + filter1.getAutoAssignDistributionSet().getName() + ",notexist)", 2); } @Test @@ -93,7 +93,7 @@ public class RSQLTargetFilterQueryFieldsTest extends AbstractJpaIntegrationTest assertRSQLQuery(TargetFilterQueryFields.AUTOASSIGNDISTRIBUTIONSET.name() + ".version=in=(" + TestdataFactory.DEFAULT_VERSION + ",notexist)", 2); assertRSQLQuery(TargetFilterQueryFields.AUTOASSIGNDISTRIBUTIONSET.name() + ".version=out=(" - + TestdataFactory.DEFAULT_VERSION + ",notexist)", 0); + + TestdataFactory.DEFAULT_VERSION + ",notexist)", 1); } private void assertRSQLQuery(final String rsqlParam, final long expectedFilterQueriesSize) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetMetadataFieldsTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetMetadataFieldsTest.java index b02050656..5b74f58f7 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetMetadataFieldsTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetMetadataFieldsTest.java @@ -64,9 +64,9 @@ public class RSQLTargetMetadataFieldsTest extends AbstractJpaIntegrationTest { assertRSQLQuery(TargetMetadataFields.VALUE.name() + "==''", 1); assertRSQLQuery(TargetMetadataFields.VALUE.name() + "!=''", 5); assertRSQLQuery(TargetMetadataFields.VALUE.name() + "==1", 1); - assertRSQLQuery(TargetMetadataFields.VALUE.name() + "!=1", 4); + assertRSQLQuery(TargetMetadataFields.VALUE.name() + "!=1", 5); assertRSQLQuery(TargetMetadataFields.VALUE.name() + "=in=(1,2)", 2); - assertRSQLQuery(TargetMetadataFields.VALUE.name() + "=out=(1,2)", 3); + assertRSQLQuery(TargetMetadataFields.VALUE.name() + "=out=(1,2)", 4); } private void assertRSQLQuery(final String rsqlParam, final long expectedEntities) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLUtilityTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLUtilityTest.java index ab5cb8971..d9d814eb8 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLUtilityTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLUtilityTest.java @@ -25,6 +25,7 @@ import javax.persistence.criteria.Expression; import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; +import javax.persistence.criteria.Subquery; import javax.persistence.metamodel.Attribute; import org.eclipse.hawkbit.repository.DistributionSetFields; @@ -75,6 +76,11 @@ public class RSQLUtilityTest { @Mock private CriteriaBuilder criteriaBuilderMock; + @Mock + private Subquery subqueryMock; + @Mock + private Root subqueryRootMock; + private final Database testDb = Database.H2; @Mock @@ -202,27 +208,54 @@ public class RSQLUtilityTest { } @Test - public void correctRsqlBuildsNotLikePredicate() { + public void correctRsqlBuildsSimpleNotLikePredicate() { reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock); final String correctRsql = "name!=abc"; when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock); when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class); + + when(criteriaBuilderMock.isNull(any(Expression.class))).thenReturn(mock(Predicate.class)); when(criteriaBuilderMock.notLike(any(Expression.class), anyString(), eq('\\'))) .thenReturn(mock(Predicate.class)); - when(criteriaBuilderMock. greaterThanOrEqualTo(any(Expression.class), any(String.class))) - .thenReturn(mock(Predicate.class)); when(criteriaBuilderMock.upper(eq(pathOfString(baseSoftwareModuleRootMock)))) .thenReturn(pathOfString(baseSoftwareModuleRootMock)); + // test RSQLUtility.parse(correctRsql, SoftwareModuleFields.class, null, testDb).toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock); // verification - verify(criteriaBuilderMock, times(1)).and(any(Predicate.class)); + verify(criteriaBuilderMock, times(1)).or(any(Predicate.class), any(Predicate.class)); + verify(criteriaBuilderMock, times(1)).isNull(eq(pathOfString(baseSoftwareModuleRootMock))); verify(criteriaBuilderMock, times(1)).notLike(eq(pathOfString(baseSoftwareModuleRootMock)), eq("abc".toUpperCase()), eq('\\')); } + @Test + public void correctRsqlBuildsNotSimpleNotLikePredicate() { + reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock); + // with this query a subquery has to be made, so it is no simple query + final String correctRsql = "type!=abc"; + when(baseSoftwareModuleRootMock.get(anyString())).thenReturn(baseSoftwareModuleRootMock); + when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class); + + when(subqueryRootMock.get(anyString())).thenReturn(mock(Path.class)); + + when(criteriaBuilderMock.and(any(), any())).thenReturn(mock(Predicate.class)); + + when(criteriaQueryMock.subquery(SoftwareModule.class)).thenReturn(subqueryMock); + + when(subqueryMock.from(SoftwareModule.class)).thenReturn(subqueryRootMock); + when(subqueryMock.select(subqueryRootMock)).thenReturn(subqueryMock); + + // test + RSQLUtility.parse(correctRsql, SoftwareModuleFields.class, null, testDb).toPredicate(baseSoftwareModuleRootMock, + criteriaQueryMock, criteriaBuilderMock); + + // verification + verify(criteriaBuilderMock, times(1)).not(criteriaBuilderMock.exists(eq(subqueryMock))); + } + @Test public void correctRsqlBuildsLikePredicateWithPercentage() { reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);