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 <sebastian.firsching@bosch-si.com>
This commit is contained in:
Sebastian Firsching
2020-04-15 08:01:54 +02:00
committed by GitHub
parent 6df1e934ee
commit 0d52524202
13 changed files with 322 additions and 103 deletions

View File

@@ -36,4 +36,9 @@ public enum DistributionSetMetadataFields implements FieldNameProvider {
public String getFieldName() {
return fieldName;
}
@Override
public String identifierFieldName() {
return KEY.getFieldName();
}
}

View File

@@ -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";
}
}

View File

@@ -41,4 +41,9 @@ public enum SoftwareModuleMetadataFields implements FieldNameProvider {
public String getFieldName() {
return fieldName;
}
@Override
public String identifierFieldName() {
return KEY.getFieldName();
}
}

View File

@@ -33,4 +33,9 @@ public enum TargetMetadataFields implements FieldNameProvider {
public String getFieldName() {
return fieldName;
}
@Override
public String identifierFieldName() {
return KEY.getFieldName();
}
}

View File

@@ -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<A, T> jpqQueryRSQLVisitor = new JpqQueryRSQLVisitor<>(root, cb, enumType,
virtualPropertyReplacer, database);
virtualPropertyReplacer, database, query);
final List<Predicate> accept = rootNode.<List<Predicate>, String> accept(jpqQueryRSQLVisitor);
if (!CollectionUtils.isEmpty(accept)) {
@@ -206,29 +211,35 @@ public final class RSQLUtility {
private static final class JpqQueryRSQLVisitor<A extends Enum<A> & FieldNameProvider, T>
implements RSQLVisitor<List<Predicate>, String> {
private static final char ESCAPE_CHAR = '\\';
private static final List<String> NO_JOINS_OPERATOR = Lists.newArrayList("!=", "=out=");
public static final Character LIKE_WILDCARD = '*';
private final Root<T> root;
private final CriteriaBuilder cb;
private final CriteriaQuery<?> query;
private final Class<A> enumType;
private final VirtualPropertyReplacer virtualPropertyReplacer;
private int level;
private boolean isOrLevel;
private final Map<Integer, Set<Join<Object, Object>>> joinsInLevel = new HashMap<>(3);
private boolean joinsNeeded;
private final SimpleTypeConverter simpleTypeConverter;
private final Database database;
private JpqQueryRSQLVisitor(final Root<T> root, final CriteriaBuilder cb, final Class<A> 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<Object> getFieldPath(final A enumField, final String finalProperty) {
Path<Object> fieldPath = null;
final String[] split = finalProperty.split("\\" + FieldNameProvider.SUB_ATTRIBUTE_SEPERATOR);
private Path<Object> getFieldPath(final A enumField, final String finalProperty) {
return (Path<Object>) getFieldPath(root, getSubAttributesFrom(finalProperty), enumField.isMap(),
this::getJoinFieldPath);
}
private Path<?> getJoinFieldPath(final Path<?> fieldPath, final String fieldNameSplit) {
if (fieldPath instanceof PluralJoin) {
final Join<Object, ?> join = (Join<Object, ?>) fieldPath;
final From<?, Object> joinParent = join.getParent();
final Optional<Join<Object, Object>> 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<Object, Object> 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<Path<?>, 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<Object, ?> join = (Join<Object, ?>) fieldPath;
final From<?, Object> joinParent = join.getParent();
final Optional<Join<Object, Object>> 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<Object, Object> 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<String> values = node.getArguments();
final List<Object> transformedValue = new ArrayList<>();
final List<Object> transformedValues = new ArrayList<>();
final Path<Object> 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<Predicate> mapToPredicate(final ComparisonNode node, final Path<Object> fieldPath,
final List<String> values, final List<Object> 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<Object> fieldPath,
final List<Object> transformedValues, final Object transformedValue, final String value,
final Database database) {
switch (node.getOperator().getSymbol()) {
final List<Object> 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<Object> transformedValues, final Path<Object> fieldPath) {
final List<String> 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<Object> transformedValues, final String finalProperty,
final A enumField, final Path<Object> fieldPath) {
final String[] fieldNames = getSubAttributesFrom(finalProperty);
final List<String> 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<Object> fieldPath, final List<Object> transformedValues,
final List<String> outParams) {
final Path<String> 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<Object> transformedValues,
final A enumField, final List<String> outParams) {
final Function<Expression<String>, Predicate> inPredicateProvider = expressionToCompare -> outParams
.isEmpty() ? cb.upper(expressionToCompare).in(transformedValues)
: cb.upper(expressionToCompare).in(outParams);
return toNotExistsSubQueryPredicate(fieldNames, enumField, inPredicateProvider);
}
private Path<Object> getMapValueFieldPath(final A enumField, final Path<Object> 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<Object> fieldPath,
final Database database) {
private Predicate getEqualToPredicate(final Object transformedValue, final Path<Object> 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<Object> 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<Object> fieldPath) {
return cb.isNotNull(pathOfString(fieldPath));
}
private Predicate toNotEqualPredicate(final Path<Object> 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<Object> 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<Object> 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<Expression<String>, 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<Expression<String>, 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<String> 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<String> 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<String>) (((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")

View File

@@ -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

View File

@@ -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) {

View File

@@ -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

View File

@@ -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

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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<SoftwareModule> subqueryMock;
@Mock
private Root<SoftwareModule> 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.<String> 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);