Improve building of SQL from an RSQL query (#1766)
* Improve building of SQL from an RSQL query * ignore case behavior could be disabled * like is used only when needed Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com> * Inlining of some methods and unified IN build + fix case Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com> * Implement more flexible ignore case configuration Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com> --------- Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 Bosch.IO GmbH and others
|
||||||
|
*
|
||||||
|
* 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.rsql;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class providing static access to the RSQL configuration as managed the ignoreCase and
|
||||||
|
* the {@link RsqlVisitorFactory} bean.
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
|
||||||
|
@Getter
|
||||||
|
public final class RsqlConfigHolder {
|
||||||
|
|
||||||
|
private static final RsqlConfigHolder SINGLETON = new RsqlConfigHolder();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If RSQL comparison operators shall ignore the case. If ignore case is <code>true</code>
|
||||||
|
* "x == ax" will match "x == aX"
|
||||||
|
*/
|
||||||
|
@Value("${hawkbit.rsql.ignoreCase:true}")
|
||||||
|
private boolean ignoreCase;
|
||||||
|
/**
|
||||||
|
* Declares if the database is case-insensitive, by default assumes <code>false</code>. In case it is case-sensitive and,
|
||||||
|
* {@link #ignoreCase} is set to <code>true</code> the SQL queries use upper case comparisons to ignore case.
|
||||||
|
*
|
||||||
|
* If the database is declared as case-sensitive and ignoreCase is set to <code>false</code> the RSQL queries shall use strict
|
||||||
|
* syntax - i.e. 'and' instead of 'AND' / 'aND'. Otherwise, the queries would be case-insensitive regarding operators.
|
||||||
|
*/
|
||||||
|
@Value("${hawkbit.rsql.caseInsensitiveDB:false}")
|
||||||
|
private boolean caseInsensitiveDB;
|
||||||
|
@Autowired
|
||||||
|
private RsqlVisitorFactory rsqlVisitorFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The holder singleton instance.
|
||||||
|
*/
|
||||||
|
public static RsqlConfigHolder getInstance() {
|
||||||
|
return SINGLETON;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2021 Bosch.IO GmbH and others
|
|
||||||
*
|
|
||||||
* 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.rsql;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class providing static access to the managed
|
|
||||||
* {@link RsqlVisitorFactory} bean.
|
|
||||||
*/
|
|
||||||
public final class RsqlVisitorFactoryHolder {
|
|
||||||
|
|
||||||
private static final RsqlVisitorFactoryHolder SINGLETON = new RsqlVisitorFactoryHolder();
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private RsqlVisitorFactory rsqlVisitorFactory;
|
|
||||||
|
|
||||||
private RsqlVisitorFactoryHolder() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The holder singleton instance.
|
|
||||||
*/
|
|
||||||
public static RsqlVisitorFactoryHolder getInstance() {
|
|
||||||
return SINGLETON;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The managed RsqlVisitorFactory bean
|
|
||||||
*/
|
|
||||||
public RsqlVisitorFactory getRsqlVisitorFactory() {
|
|
||||||
return rsqlVisitorFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -157,7 +157,7 @@ import org.eclipse.hawkbit.repository.model.helper.SystemSecurityContextHolder;
|
|||||||
import org.eclipse.hawkbit.repository.model.helper.TenantConfigurationManagementHolder;
|
import org.eclipse.hawkbit.repository.model.helper.TenantConfigurationManagementHolder;
|
||||||
import org.eclipse.hawkbit.repository.rsql.RsqlValidationOracle;
|
import org.eclipse.hawkbit.repository.rsql.RsqlValidationOracle;
|
||||||
import org.eclipse.hawkbit.repository.rsql.RsqlVisitorFactory;
|
import org.eclipse.hawkbit.repository.rsql.RsqlVisitorFactory;
|
||||||
import org.eclipse.hawkbit.repository.rsql.RsqlVisitorFactoryHolder;
|
import org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder;
|
||||||
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
|
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
|
||||||
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
||||||
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
|
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
|
||||||
@@ -1016,13 +1016,13 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains the {@link RsqlVisitorFactoryHolder} bean.
|
* Obtains the {@link RsqlConfigHolder} bean.
|
||||||
*
|
*
|
||||||
* @return The {@link RsqlVisitorFactoryHolder} singleton.
|
* @return The {@link RsqlConfigHolder} singleton.
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
RsqlVisitorFactoryHolder rsqlVisitorFactoryHolder() {
|
RsqlConfigHolder rsqlVisitorFactoryHolder() {
|
||||||
return RsqlVisitorFactoryHolder.getInstance();
|
return RsqlConfigHolder.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ import org.springframework.beans.SimpleTypeConverter;
|
|||||||
import org.springframework.beans.TypeMismatchException;
|
import org.springframework.beans.TypeMismatchException;
|
||||||
import org.springframework.orm.jpa.vendor.Database;
|
import org.springframework.orm.jpa.vendor.Database;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of the {@link RSQLVisitor} to visit the parsed tokens and
|
* An implementation of the {@link RSQLVisitor} to visit the parsed tokens and
|
||||||
@@ -75,6 +75,7 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & FieldNameProvider, T> exten
|
|||||||
private final CriteriaBuilder cb;
|
private final CriteriaBuilder cb;
|
||||||
private final CriteriaQuery<?> query;
|
private final CriteriaQuery<?> query;
|
||||||
private final Database database;
|
private final Database database;
|
||||||
|
private final boolean ensureIgnoreCase;
|
||||||
private final Root<T> root;
|
private final Root<T> root;
|
||||||
private final SimpleTypeConverter simpleTypeConverter;
|
private final SimpleTypeConverter simpleTypeConverter;
|
||||||
private final VirtualPropertyReplacer virtualPropertyReplacer;
|
private final VirtualPropertyReplacer virtualPropertyReplacer;
|
||||||
@@ -85,7 +86,7 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & FieldNameProvider, T> exten
|
|||||||
|
|
||||||
public JpaQueryRsqlVisitor(final Root<T> root, final CriteriaBuilder cb, final Class<A> enumType,
|
public JpaQueryRsqlVisitor(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) {
|
final CriteriaQuery<?> query, final boolean ensureIgnoreCase) {
|
||||||
super(enumType);
|
super(enumType);
|
||||||
this.root = root;
|
this.root = root;
|
||||||
this.cb = cb;
|
this.cb = cb;
|
||||||
@@ -93,7 +94,7 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & FieldNameProvider, T> exten
|
|||||||
this.virtualPropertyReplacer = virtualPropertyReplacer;
|
this.virtualPropertyReplacer = virtualPropertyReplacer;
|
||||||
this.simpleTypeConverter = new SimpleTypeConverter();
|
this.simpleTypeConverter = new SimpleTypeConverter();
|
||||||
this.database = database;
|
this.database = database;
|
||||||
this.joinsNeeded = false;
|
this.ensureIgnoreCase = ensureIgnoreCase;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void beginLevel(final boolean isOr) {
|
private void beginLevel(final boolean isOr) {
|
||||||
@@ -128,10 +129,10 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & FieldNameProvider, T> exten
|
|||||||
@Override
|
@Override
|
||||||
public List<Predicate> visit(final AndNode node, final String param) {
|
public List<Predicate> visit(final AndNode node, final String param) {
|
||||||
beginLevel(false);
|
beginLevel(false);
|
||||||
final List<Predicate> childs = acceptChilds(node);
|
final List<Predicate> childs = acceptChildren(node);
|
||||||
endLevel();
|
endLevel();
|
||||||
if (!childs.isEmpty()) {
|
if (!childs.isEmpty()) {
|
||||||
return toSingleList(cb.and(childs.toArray(new Predicate[childs.size()])));
|
return toSingleList(cb.and(childs.toArray(new Predicate[0])));
|
||||||
}
|
}
|
||||||
return toSingleList(cb.conjunction());
|
return toSingleList(cb.conjunction());
|
||||||
}
|
}
|
||||||
@@ -139,10 +140,10 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & FieldNameProvider, T> exten
|
|||||||
@Override
|
@Override
|
||||||
public List<Predicate> visit(final OrNode node, final String param) {
|
public List<Predicate> visit(final OrNode node, final String param) {
|
||||||
beginLevel(true);
|
beginLevel(true);
|
||||||
final List<Predicate> childs = acceptChilds(node);
|
final List<Predicate> childs = acceptChildren(node);
|
||||||
endLevel();
|
endLevel();
|
||||||
if (!childs.isEmpty()) {
|
if (!childs.isEmpty()) {
|
||||||
return toSingleList(cb.or(childs.toArray(new Predicate[childs.size()])));
|
return toSingleList(cb.or(childs.toArray(new Predicate[0])));
|
||||||
}
|
}
|
||||||
return toSingleList(cb.conjunction());
|
return toSingleList(cb.conjunction());
|
||||||
}
|
}
|
||||||
@@ -177,18 +178,17 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & FieldNameProvider, T> exten
|
|||||||
() -> new RSQLParameterUnsupportedFieldException("RSQL field path cannot be empty", null));
|
() -> new RSQLParameterUnsupportedFieldException("RSQL field path cannot be empty", null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
private Path<?> getJoinFieldPath(final Path<?> fieldPath, final String fieldNameSplit) {
|
private Path<?> getJoinFieldPath(final Path<?> fieldPath, final String fieldNameSplit) {
|
||||||
if (fieldPath instanceof PluralJoin) {
|
if (fieldPath instanceof PluralJoin join) {
|
||||||
final Join<Object, ?> join = (Join<Object, ?>) fieldPath;
|
final From joinParent = join.getParent();
|
||||||
final From<?, Object> joinParent = join.getParent();
|
|
||||||
final Optional<Join<Object, Object>> currentJoinOfType = findCurrentJoinOfType(join.getJavaType());
|
final Optional<Join<Object, Object>> currentJoinOfType = findCurrentJoinOfType(join.getJavaType());
|
||||||
if (currentJoinOfType.isPresent() && isOrLevel) {
|
if (currentJoinOfType.isPresent() && isOrLevel) {
|
||||||
// remove the additional join and use the existing one
|
// remove the additional join and use the existing one
|
||||||
joinParent.getJoins().remove(join);
|
joinParent.getJoins().remove(join);
|
||||||
return currentJoinOfType.get();
|
return currentJoinOfType.get();
|
||||||
} else {
|
} else {
|
||||||
final Join<Object, Object> newJoin = joinParent.join(fieldNameSplit, JoinType.LEFT);
|
final Join newJoin = joinParent.join(fieldNameSplit, JoinType.LEFT);
|
||||||
addCurrentJoin(newJoin);
|
addCurrentJoin(newJoin);
|
||||||
return newJoin;
|
return newJoin;
|
||||||
}
|
}
|
||||||
@@ -236,7 +236,7 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & FieldNameProvider, T> exten
|
|||||||
|
|
||||||
private Object convertValueIfNecessary(final ComparisonNode node, final A fieldName, final String value,
|
private Object convertValueIfNecessary(final ComparisonNode node, final A fieldName, final String value,
|
||||||
final Path<Object> fieldPath) {
|
final Path<Object> fieldPath) {
|
||||||
// in case the value of an rsql query e.g. type==application is an
|
// 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
|
// enum we need to handle it separately because JPA needs the
|
||||||
// correct java-type to build an expression. So String and numeric
|
// 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.
|
// values JPA can do it by it's own but not for classes like enums.
|
||||||
@@ -297,7 +297,7 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & FieldNameProvider, T> exten
|
|||||||
|
|
||||||
throw new RSQLParameterUnsupportedFieldException("field {" + node.getSelector()
|
throw new RSQLParameterUnsupportedFieldException("field {" + node.getSelector()
|
||||||
+ "} must be one of the following values {" + Arrays.stream(tmpEnumType.getEnumConstants())
|
+ "} must be one of the following values {" + Arrays.stream(tmpEnumType.getEnumConstants())
|
||||||
.map(v -> v.name().toLowerCase()).collect(Collectors.toList())
|
.map(v -> v.name().toLowerCase()).toList()
|
||||||
+ "}", e);
|
+ "}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -343,7 +343,7 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & FieldNameProvider, T> exten
|
|||||||
case "=le=":
|
case "=le=":
|
||||||
return cb.lessThanOrEqualTo(pathOfString(fieldPath), value);
|
return cb.lessThanOrEqualTo(pathOfString(fieldPath), value);
|
||||||
case "=in=":
|
case "=in=":
|
||||||
return getInPredicate(transformedValues, fieldPath);
|
return in(pathOfString(fieldPath), transformedValues);
|
||||||
case "=out=":
|
case "=out=":
|
||||||
return getOutPredicate(transformedValues, finalProperty, enumField, fieldPath);
|
return getOutPredicate(transformedValues, finalProperty, enumField, fieldPath);
|
||||||
default:
|
default:
|
||||||
@@ -352,53 +352,19 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & FieldNameProvider, T> exten
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Predicate getInPredicate(final List<Object> transformedValues, final Path<Object> fieldPath) {
|
private Predicate getOutPredicate(
|
||||||
final List<String> inParams = new ArrayList<>();
|
final List<Object> transformedValues, final String finalProperty,
|
||||||
for (final Object param : transformedValues) {
|
|
||||||
if (param instanceof String) {
|
|
||||||
inParams.add(((String) param).toUpperCase());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!inParams.isEmpty()) {
|
|
||||||
return cb.upper(pathOfString(fieldPath)).in(inParams);
|
|
||||||
} else {
|
|
||||||
return fieldPath.in(transformedValues);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Predicate getOutPredicate(final List<Object> transformedValues, final String finalProperty,
|
|
||||||
final A enumField, final Path<Object> fieldPath) {
|
final A enumField, final Path<Object> fieldPath) {
|
||||||
|
|
||||||
final String[] fieldNames = enumField.getSubAttributes(finalProperty);
|
final String[] fieldNames = enumField.getSubAttributes(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())) {
|
if (isSimpleField(fieldNames, enumField.isMap())) {
|
||||||
return toNullOrNotInPredicate(fieldPath, transformedValues, outParams);
|
final Path<String> pathOfString = pathOfString(fieldPath);
|
||||||
|
return cb.or(cb.isNull(pathOfString), cb.not(in(pathOfString, transformedValues)));
|
||||||
}
|
}
|
||||||
|
|
||||||
clearOuterJoinsIfNotNeeded();
|
clearOuterJoinsIfNotNeeded();
|
||||||
|
|
||||||
return toOutWithSubQueryPredicate(fieldNames, transformedValues, enumField, outParams);
|
return toNotExistsSubQueryPredicate(fieldNames, enumField, expressionToCompare -> in(expressionToCompare, transformedValues));
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
private Path<Object> getMapValueFieldPath(final A enumField, final Path<Object> fieldPath) {
|
||||||
@@ -420,16 +386,15 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & FieldNameProvider, T> exten
|
|||||||
|
|
||||||
final String keyValue = graph[graph.length - 1];
|
final String keyValue = graph[graph.length - 1];
|
||||||
if (fieldPath instanceof MapJoin) {
|
if (fieldPath instanceof MapJoin) {
|
||||||
// Currently we support only string key .So below cast is safe.
|
// Currently we support only string key. So below cast is safe.
|
||||||
return cb.equal(cb.upper((Expression<String>) (((MapJoin<?, ?, ?>) fieldPath).key())),
|
return equal((Expression<String>) (((MapJoin<?, ?, ?>) fieldPath).key()), keyValue);
|
||||||
keyValue.toUpperCase());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final String keyFieldName = enumField.getSubEntityMapTuple().map(Entry::getKey)
|
final String keyFieldName = enumField.getSubEntityMapTuple().map(Entry::getKey)
|
||||||
.orElseThrow(() -> new UnsupportedOperationException(
|
.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!"));
|
"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 cb.equal(cb.upper(fieldPath.get(keyFieldName)), keyValue.toUpperCase());
|
return equal(fieldPath.get(keyFieldName), keyValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Predicate getEqualToPredicate(final Object transformedValue, final Path<Object> fieldPath) {
|
private Predicate getEqualToPredicate(final Object transformedValue, final Path<Object> fieldPath) {
|
||||||
@@ -437,13 +402,16 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & FieldNameProvider, T> exten
|
|||||||
return cb.isNull(pathOfString(fieldPath));
|
return cb.isNull(pathOfString(fieldPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((transformedValue instanceof String) && !NumberUtils.isCreatable((String) transformedValue)) {
|
if ((transformedValue instanceof String transformedValueStr) && !NumberUtils.isCreatable(transformedValueStr)) {
|
||||||
if (StringUtils.isEmpty(transformedValue)) {
|
if (ObjectUtils.isEmpty(transformedValue)) {
|
||||||
return cb.or(cb.isNull(pathOfString(fieldPath)), cb.equal(pathOfString(fieldPath), ""));
|
return cb.or(cb.isNull(pathOfString(fieldPath)), cb.equal(pathOfString(fieldPath), ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
final String sqlValue = toSQL((String) transformedValue);
|
if (isPattern(transformedValueStr)) { // a pattern, use like
|
||||||
return cb.like(cb.upper(pathOfString(fieldPath)), sqlValue, ESCAPE_CHAR);
|
return like(pathOfString(fieldPath), toSQL(transformedValueStr));
|
||||||
|
} else {
|
||||||
|
return equal(pathOfString(fieldPath), transformedValueStr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb.equal(fieldPath, transformedValue);
|
return cb.equal(fieldPath, transformedValue);
|
||||||
@@ -453,24 +421,31 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & FieldNameProvider, T> exten
|
|||||||
final String finalProperty, final A enumField) {
|
final String finalProperty, final A enumField) {
|
||||||
|
|
||||||
if (transformedValue == null) {
|
if (transformedValue == null) {
|
||||||
return toNotNullPredicate(fieldPath);
|
return cb.isNotNull(pathOfString(fieldPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((transformedValue instanceof String) && !NumberUtils.isCreatable((String) transformedValue)) {
|
if ((transformedValue instanceof String transformedValueStr) && !NumberUtils.isCreatable(transformedValueStr)) {
|
||||||
if (StringUtils.isEmpty(transformedValue)) {
|
if (ObjectUtils.isEmpty(transformedValue)) {
|
||||||
return toNotNullAndNotEmptyPredicate(fieldPath);
|
return cb.and(cb.isNotNull(pathOfString(fieldPath)), cb.notEqual(pathOfString(fieldPath), ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
final String sqlValue = toSQL((String) transformedValue);
|
|
||||||
final String[] fieldNames = enumField.getSubAttributes(finalProperty);
|
final String[] fieldNames = enumField.getSubAttributes(finalProperty);
|
||||||
|
|
||||||
if (isSimpleField(fieldNames, enumField.isMap())) {
|
if (isSimpleField(fieldNames, enumField.isMap())) {
|
||||||
return toNullOrNotLikePredicate(fieldPath, sqlValue);
|
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();
|
clearOuterJoinsIfNotNeeded();
|
||||||
|
|
||||||
return toNotEqualWithSubQueryPredicate(enumField, sqlValue, fieldNames);
|
if (isPattern(transformedValueStr)) { // a pattern, use like
|
||||||
|
return toNotExistsSubQueryPredicate(fieldNames, enumField, expressionToCompare -> like(expressionToCompare, toSQL(transformedValueStr)));
|
||||||
|
} else {
|
||||||
|
return toNotExistsSubQueryPredicate(fieldNames, enumField, expressionToCompare -> equal(expressionToCompare, transformedValueStr));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return toNullOrNotEqualPredicate(fieldPath, transformedValue);
|
return toNullOrNotEqualPredicate(fieldPath, transformedValue);
|
||||||
@@ -482,28 +457,12 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & FieldNameProvider, T> exten
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Predicate toNotNullPredicate(final Path<Object> fieldPath) {
|
|
||||||
return cb.isNotNull(pathOfString(fieldPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
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 toNullOrNotEqualPredicate(final Path<Object> fieldPath, final Object transformedValue) {
|
private Predicate toNullOrNotEqualPredicate(final Path<Object> fieldPath, final Object transformedValue) {
|
||||||
return cb.or(cb.isNull(pathOfString(fieldPath)), cb.notEqual(fieldPath, transformedValue));
|
return cb.or(
|
||||||
}
|
cb.isNull(pathOfString(fieldPath)),
|
||||||
|
transformedValue instanceof String transformedValueStr
|
||||||
private Predicate toNotNullAndNotEmptyPredicate(final Path<Object> fieldPath) {
|
? notEqual(pathOfString(fieldPath), transformedValueStr)
|
||||||
return cb.and(cb.isNotNull(pathOfString(fieldPath)), cb.notEqual(pathOfString(fieldPath), ""));
|
: cb.notEqual(fieldPath, transformedValue));
|
||||||
}
|
|
||||||
|
|
||||||
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" })
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
@@ -556,6 +515,14 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & FieldNameProvider, T> exten
|
|||||||
return fieldPath;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String toSQL(final String transformedValue) {
|
private String toSQL(final String transformedValue) {
|
||||||
final String escaped;
|
final String escaped;
|
||||||
|
|
||||||
@@ -575,7 +542,7 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & FieldNameProvider, T> exten
|
|||||||
} else {
|
} else {
|
||||||
finalizedValue = escapedValue.replace(LIKE_WILDCARD, '%');
|
finalizedValue = escapedValue.replace(LIKE_WILDCARD, '%');
|
||||||
}
|
}
|
||||||
return finalizedValue.toUpperCase();
|
return finalizedValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -583,7 +550,7 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & FieldNameProvider, T> exten
|
|||||||
return (Path<Y>) path;
|
return (Path<Y>) path;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Predicate> acceptChilds(final LogicalNode node) {
|
private List<Predicate> acceptChildren(final LogicalNode node) {
|
||||||
final List<Node> children = node.getChildren();
|
final List<Node> children = node.getChildren();
|
||||||
final List<Predicate> childs = new ArrayList<>();
|
final List<Predicate> childs = new ArrayList<>();
|
||||||
for (final Node node2 : children) {
|
for (final Node node2 : children) {
|
||||||
@@ -597,4 +564,28 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & FieldNameProvider, T> exten
|
|||||||
return childs;
|
return childs;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
private Predicate equal(final Expression<String> expressionToCompare, final String sqlValue) {
|
||||||
|
return cb.equal(caseWise(cb, expressionToCompare), caseWise(sqlValue));
|
||||||
|
}
|
||||||
|
private Predicate notEqual(final Expression<String> expressionToCompare, String transformedValueStr) {
|
||||||
|
return cb.notEqual(caseWise(cb, expressionToCompare), caseWise(transformedValueStr));
|
||||||
|
}
|
||||||
|
private Predicate like(final Expression<String> expressionToCompare, final String sqlValue) {
|
||||||
|
return cb.like(caseWise(cb, expressionToCompare), caseWise(sqlValue), ESCAPE_CHAR);
|
||||||
|
}
|
||||||
|
private Predicate notLike(final Expression<String> expressionToCompare, final String sqlValue) {
|
||||||
|
return cb.notLike(caseWise(cb, expressionToCompare), caseWise(sqlValue), ESCAPE_CHAR);
|
||||||
|
}
|
||||||
|
private Predicate in(final Expression<String> expressionToCompare, final List<Object> transformedValues) {
|
||||||
|
final List<String> inParams = transformedValues.stream().filter(String.class::isInstance)
|
||||||
|
.map(String.class::cast).map(this::caseWise).collect(Collectors.toList());
|
||||||
|
return inParams.isEmpty() ? expressionToCompare.in(transformedValues) : caseWise(cb, expressionToCompare).in(inParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expression<String> caseWise(final CriteriaBuilder cb, final Expression<String> expression) {
|
||||||
|
return ensureIgnoreCase ? cb.upper(expression) : expression;
|
||||||
|
}
|
||||||
|
private String caseWise(final String str) {
|
||||||
|
return ensureIgnoreCase ? str.toUpperCase() : str;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.repository.jpa.rsql;
|
package org.eclipse.hawkbit.repository.jpa.rsql;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ import org.apache.commons.lang3.text.StrLookup;
|
|||||||
import org.eclipse.hawkbit.repository.FieldNameProvider;
|
import org.eclipse.hawkbit.repository.FieldNameProvider;
|
||||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
|
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
|
||||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
|
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
|
||||||
import org.eclipse.hawkbit.repository.rsql.RsqlVisitorFactoryHolder;
|
import org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder;
|
||||||
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
|
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
|
||||||
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyResolver;
|
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyResolver;
|
||||||
import org.springframework.data.jpa.domain.Specification;
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
@@ -80,62 +81,47 @@ public final class RSQLUtility {
|
|||||||
* query. The specification can be used to filter for JPA entities with the
|
* query. The specification can be used to filter for JPA entities with the
|
||||||
* given RSQL query.
|
* given RSQL query.
|
||||||
*
|
*
|
||||||
* @param rsql
|
* @param rsql the rsql query to be parsed
|
||||||
* the rsql query to be parsed
|
* @param fieldNameProvider the enum class type which implements the {@link FieldNameProvider}
|
||||||
* @param fieldNameProvider
|
* @param virtualPropertyReplacer holds the logic how the known macros have to be resolved; may be <code>null</code>
|
||||||
* the enum class type which implements the
|
* @param database database in use
|
||||||
* {@link FieldNameProvider}
|
|
||||||
* @param virtualPropertyReplacer
|
|
||||||
* holds the logic how the known macros have to be resolved; may
|
|
||||||
* be <code>null</code>
|
|
||||||
* @param database
|
|
||||||
* in use
|
|
||||||
*
|
|
||||||
* @return an specification which can be used with JPA
|
|
||||||
* @throws RSQLParameterUnsupportedFieldException
|
|
||||||
* if a field in the RSQL string is used but not provided by the
|
|
||||||
* given {@code fieldNameProvider}
|
|
||||||
* @throws RSQLParameterSyntaxException
|
|
||||||
* if the RSQL syntax is wrong
|
|
||||||
*
|
*
|
||||||
|
* @return a specification which can be used with JPA
|
||||||
|
* @throws RSQLParameterUnsupportedFieldException if a field in the RSQL string is used but not provided by the
|
||||||
|
* given {@code fieldNameProvider}
|
||||||
|
* @throws RSQLParameterSyntaxException if the RSQL syntax is wrong
|
||||||
*/
|
*/
|
||||||
public static <A extends Enum<A> & FieldNameProvider, T> Specification<T> buildRsqlSpecification(final String rsql,
|
public static <A extends Enum<A> & FieldNameProvider, T> Specification<T> buildRsqlSpecification(
|
||||||
final Class<A> fieldNameProvider, final VirtualPropertyReplacer virtualPropertyReplacer,
|
final String rsql, final Class<A> fieldNameProvider,
|
||||||
final Database database) {
|
final VirtualPropertyReplacer virtualPropertyReplacer, final Database database) {
|
||||||
return new RSQLSpecification<>(rsql, fieldNameProvider, virtualPropertyReplacer, database);
|
return new RSQLSpecification<>(rsql, fieldNameProvider, virtualPropertyReplacer, database);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the RSQL string
|
* Validates the RSQL string
|
||||||
*
|
*
|
||||||
* @param rsql
|
* @param rsql RSQL string to validate
|
||||||
* RSQL string to validate
|
|
||||||
* @param fieldNameProvider
|
* @param fieldNameProvider
|
||||||
*
|
*
|
||||||
* @throws RSQLParserException
|
* @throws RSQLParserException if RSQL syntax is invalid
|
||||||
* if RSQL syntax is invalid
|
* @throws RSQLParameterUnsupportedFieldException if RSQL key is not allowed
|
||||||
* @throws RSQLParameterUnsupportedFieldException
|
|
||||||
* if RSQL key is not allowed
|
|
||||||
*/
|
*/
|
||||||
public static <A extends Enum<A> & FieldNameProvider> void validateRsqlFor(final String rsql,
|
public static <A extends Enum<A> & FieldNameProvider> void validateRsqlFor(
|
||||||
final Class<A> fieldNameProvider) {
|
final String rsql, final Class<A> fieldNameProvider) {
|
||||||
final RSQLVisitor<Void, String> visitor = getValidationRsqlVisitor(fieldNameProvider);
|
final RSQLVisitor<Void, String> visitor =
|
||||||
|
RsqlConfigHolder.getInstance().getRsqlVisitorFactory().validationRsqlVisitor(fieldNameProvider);
|
||||||
final Node rootNode = parseRsql(rsql);
|
final Node rootNode = parseRsql(rsql);
|
||||||
rootNode.accept(visitor);
|
rootNode.accept(visitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <A extends Enum<A> & FieldNameProvider> RSQLVisitor<Void, String> getValidationRsqlVisitor(
|
|
||||||
final Class<A> fieldNameProvider) {
|
|
||||||
return RsqlVisitorFactoryHolder.getInstance().getRsqlVisitorFactory().validationRsqlVisitor(fieldNameProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Node parseRsql(final String rsql) {
|
private static Node parseRsql(final String rsql) {
|
||||||
|
log.debug("Parsing rsql string {}", rsql);
|
||||||
try {
|
try {
|
||||||
log.debug("Parsing rsql string {}", rsql);
|
|
||||||
final Set<ComparisonOperator> operators = RSQLOperators.defaultOperators();
|
final Set<ComparisonOperator> operators = RSQLOperators.defaultOperators();
|
||||||
return new RSQLParser(operators).parse(rsql.toLowerCase());
|
return new RSQLParser(operators).parse(
|
||||||
|
RsqlConfigHolder.getInstance().isCaseInsensitiveDB() || RsqlConfigHolder.getInstance().isIgnoreCase() ? rsql.toLowerCase() : rsql);
|
||||||
} catch (final IllegalArgumentException e) {
|
} catch (final IllegalArgumentException e) {
|
||||||
throw new RSQLParameterSyntaxException("rsql filter must not be null", e);
|
throw new RSQLParameterSyntaxException("RSQL filter must not be null", e);
|
||||||
} catch (final RSQLParserException e) {
|
} catch (final RSQLParserException e) {
|
||||||
throw new RSQLParameterSyntaxException(e);
|
throw new RSQLParameterSyntaxException(e);
|
||||||
}
|
}
|
||||||
@@ -143,6 +129,7 @@ public final class RSQLUtility {
|
|||||||
|
|
||||||
private static final class RSQLSpecification<A extends Enum<A> & FieldNameProvider, T> implements Specification<T> {
|
private static final class RSQLSpecification<A extends Enum<A> & FieldNameProvider, T> implements Specification<T> {
|
||||||
|
|
||||||
|
@Serial
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private final String rsql;
|
private final String rsql;
|
||||||
@@ -164,15 +151,14 @@ public final class RSQLUtility {
|
|||||||
query.distinct(true);
|
query.distinct(true);
|
||||||
|
|
||||||
final JpaQueryRsqlVisitor<A, T> jpqQueryRSQLVisitor = new JpaQueryRsqlVisitor<>(root, cb, enumType,
|
final JpaQueryRsqlVisitor<A, T> jpqQueryRSQLVisitor = new JpaQueryRsqlVisitor<>(root, cb, enumType,
|
||||||
virtualPropertyReplacer, database, query);
|
virtualPropertyReplacer, database, query,
|
||||||
final List<Predicate> accept = rootNode.<List<Predicate>, String> accept(jpqQueryRSQLVisitor);
|
!RsqlConfigHolder.getInstance().isCaseInsensitiveDB() && RsqlConfigHolder.getInstance().isIgnoreCase());
|
||||||
|
final List<Predicate> accept = rootNode.accept(jpqQueryRSQLVisitor);
|
||||||
|
|
||||||
if (!CollectionUtils.isEmpty(accept)) {
|
if (!CollectionUtils.isEmpty(accept)) {
|
||||||
return cb.and(accept.toArray(new Predicate[accept.size()]));
|
return cb.and(accept.toArray(new Predicate[0]));
|
||||||
}
|
}
|
||||||
return cb.conjunction();
|
return cb.conjunction();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
@@ -21,6 +21,7 @@ import org.eclipse.hawkbit.repository.model.Action;
|
|||||||
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
||||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.data.domain.PageRequest;
|
import org.springframework.data.domain.PageRequest;
|
||||||
@@ -43,34 +44,30 @@ public class RSQLActionFieldsTest extends AbstractJpaIntegrationTest {
|
|||||||
final DistributionSet dsA = testdataFactory.createDistributionSet("daA");
|
final DistributionSet dsA = testdataFactory.createDistributionSet("daA");
|
||||||
target = (JpaTarget) targetManagement
|
target = (JpaTarget) targetManagement
|
||||||
.create(entityFactory.target().create().controllerId("targetId123").description("targetId123"));
|
.create(entityFactory.target().create().controllerId("targetId123").description("targetId123"));
|
||||||
action = new JpaAction();
|
|
||||||
action.setActionType(ActionType.SOFT);
|
action = newJpaAction(dsA, false, null);
|
||||||
action.setDistributionSet(dsA);
|
for (int i = 0; i < 10; i++) {
|
||||||
action.setTarget(target);
|
newJpaAction(dsA, i % 2 == 0, i % 2 == 0 ? "extRef" : "extRef2");
|
||||||
action.setStatus(Status.RUNNING);
|
}
|
||||||
action.setWeight(45);
|
}
|
||||||
action.setInitiatedBy(tenantAware.getCurrentUsername());
|
|
||||||
|
private @NotNull JpaAction newJpaAction(final DistributionSet dsA, final boolean active, final String extRef) {
|
||||||
|
final JpaAction newAction = new JpaAction();
|
||||||
|
newAction.setActionType(ActionType.SOFT);
|
||||||
|
newAction.setDistributionSet(dsA);
|
||||||
|
newAction.setActive(active);
|
||||||
|
newAction.setStatus(Status.RUNNING);
|
||||||
|
newAction.setTarget(target);
|
||||||
|
newAction.setWeight(45);
|
||||||
|
newAction.setInitiatedBy(tenantAware.getCurrentUsername());
|
||||||
|
if (extRef != null) {
|
||||||
|
newAction.setExternalRef(extRef);
|
||||||
|
}
|
||||||
|
actionRepository.save(newAction);
|
||||||
|
|
||||||
target.addAction(action);
|
target.addAction(action);
|
||||||
|
|
||||||
actionRepository.save(action);
|
return newAction;
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
final JpaAction newAction = new JpaAction();
|
|
||||||
newAction.setActionType(ActionType.SOFT);
|
|
||||||
newAction.setDistributionSet(dsA);
|
|
||||||
newAction.setActive((i % 2) == 0);
|
|
||||||
newAction.setStatus(Status.RUNNING);
|
|
||||||
newAction.setTarget(target);
|
|
||||||
newAction.setWeight(45);
|
|
||||||
newAction.setInitiatedBy(tenantAware.getCurrentUsername());
|
|
||||||
if ((i % 2) == 0) {
|
|
||||||
newAction.setExternalRef("extRef");
|
|
||||||
} else {
|
|
||||||
newAction.setExternalRef("extRef2");
|
|
||||||
}
|
|
||||||
actionRepository.save(newAction);
|
|
||||||
target.addAction(newAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -116,11 +113,11 @@ public class RSQLActionFieldsTest extends AbstractJpaIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void assertRSQLQuery(final String rsqlParam, final long expectedEntities) {
|
private void assertRSQLQuery(final String rsqlParam, final long expectedEntities) {
|
||||||
|
final Slice<Action> findEntity = deploymentManagement.findActionsByTarget(rsqlParam, target.getControllerId(),
|
||||||
final Slice<Action> findEnitity = deploymentManagement.findActionsByTarget(rsqlParam, target.getControllerId(),
|
|
||||||
PageRequest.of(0, 100));
|
PageRequest.of(0, 100));
|
||||||
final long countAllEntities = deploymentManagement.countActionsByTarget(rsqlParam, target.getControllerId());
|
final long countAllEntities = deploymentManagement.countActionsByTarget(rsqlParam, target.getControllerId());
|
||||||
assertThat(findEnitity).isNotNull();
|
assertThat(findEntity).isNotNull();
|
||||||
|
assertThat(findEntity.getContent().size()).isEqualTo(expectedEntities);
|
||||||
assertThat(countAllEntities).isEqualTo(expectedEntities);
|
assertThat(countAllEntities).isEqualTo(expectedEntities);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
|
|||||||
import org.eclipse.hawkbit.repository.model.helper.SystemSecurityContextHolder;
|
import org.eclipse.hawkbit.repository.model.helper.SystemSecurityContextHolder;
|
||||||
import org.eclipse.hawkbit.repository.model.helper.TenantConfigurationManagementHolder;
|
import org.eclipse.hawkbit.repository.model.helper.TenantConfigurationManagementHolder;
|
||||||
import org.eclipse.hawkbit.repository.rsql.RsqlVisitorFactory;
|
import org.eclipse.hawkbit.repository.rsql.RsqlVisitorFactory;
|
||||||
import org.eclipse.hawkbit.repository.rsql.RsqlVisitorFactoryHolder;
|
import org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder;
|
||||||
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
|
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
|
||||||
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyResolver;
|
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyResolver;
|
||||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||||
@@ -116,8 +116,8 @@ public class RSQLUtilityTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
RsqlVisitorFactoryHolder rsqlVisitorFactoryHolder() {
|
RsqlConfigHolder rsqlVisitorFactoryHolder() {
|
||||||
return RsqlVisitorFactoryHolder.getInstance();
|
return RsqlConfigHolder.getInstance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,12 +284,36 @@ public class RSQLUtilityTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void correctRsqlBuildsSimpleNotLikePredicate() {
|
public void correctRsqlBuildsSimpleNotEqualPredicate() {
|
||||||
reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
||||||
final String correctRsql = "name!=abc";
|
final String correctRsql = "name!=abc";
|
||||||
when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock);
|
when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock);
|
||||||
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
|
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
|
||||||
|
|
||||||
|
when(criteriaBuilderMock.isNull(any(Expression.class))).thenReturn(mock(Predicate.class));
|
||||||
|
when(criteriaBuilderMock.notEqual(any(Expression.class), anyString()))
|
||||||
|
.thenReturn(mock(Predicate.class));
|
||||||
|
when(criteriaBuilderMock.upper(eq(pathOfString(baseSoftwareModuleRootMock))))
|
||||||
|
.thenReturn(pathOfString(baseSoftwareModuleRootMock));
|
||||||
|
|
||||||
|
// test
|
||||||
|
RSQLUtility.buildRsqlSpecification(correctRsql, SoftwareModuleFields.class, null, testDb)
|
||||||
|
.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
||||||
|
|
||||||
|
// verification
|
||||||
|
verify(criteriaBuilderMock, times(1)).or(any(Predicate.class), any(Predicate.class));
|
||||||
|
verify(criteriaBuilderMock, times(1)).isNull(eq(pathOfString(baseSoftwareModuleRootMock)));
|
||||||
|
verify(criteriaBuilderMock, times(1)).notEqual(eq(pathOfString(baseSoftwareModuleRootMock)),
|
||||||
|
eq("abc".toUpperCase()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
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.isNull(any(Expression.class))).thenReturn(mock(Predicate.class));
|
||||||
when(criteriaBuilderMock.notLike(any(Expression.class), anyString(), eq('\\')))
|
when(criteriaBuilderMock.notLike(any(Expression.class), anyString(), eq('\\')))
|
||||||
.thenReturn(mock(Predicate.class));
|
.thenReturn(mock(Predicate.class));
|
||||||
@@ -304,7 +328,7 @@ public class RSQLUtilityTest {
|
|||||||
verify(criteriaBuilderMock, times(1)).or(any(Predicate.class), 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)).isNull(eq(pathOfString(baseSoftwareModuleRootMock)));
|
||||||
verify(criteriaBuilderMock, times(1)).notLike(eq(pathOfString(baseSoftwareModuleRootMock)),
|
verify(criteriaBuilderMock, times(1)).notLike(eq(pathOfString(baseSoftwareModuleRootMock)),
|
||||||
eq("abc".toUpperCase()), eq('\\'));
|
eq("abc%".toUpperCase()), eq('\\'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -335,11 +359,32 @@ public class RSQLUtilityTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void correctRsqlBuildsLikePredicateWithPercentage() {
|
public void correctRsqlBuildsEqualPredicateWithPercentage() {
|
||||||
reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
||||||
final String correctRsql = "name==a%";
|
final String correctRsql = "name==a%";
|
||||||
when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock);
|
when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock);
|
||||||
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
|
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
|
||||||
|
when(criteriaBuilderMock.equal(any(Expression.class), anyString())).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.buildRsqlSpecification(correctRsql, SoftwareModuleFields.class, null, Database.H2)
|
||||||
|
.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
||||||
|
|
||||||
|
// verification
|
||||||
|
verify(criteriaBuilderMock, times(1)).and(any(Predicate.class));
|
||||||
|
verify(criteriaBuilderMock, times(1)).equal(eq(pathOfString(baseSoftwareModuleRootMock)),
|
||||||
|
eq("a%".toUpperCase()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void correctRsqlBuildsLikePredicateWithPercentage() {
|
||||||
|
reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
||||||
|
final String correctRsql = "name==a%*";
|
||||||
|
when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock);
|
||||||
|
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
|
||||||
when(criteriaBuilderMock.like(any(Expression.class), anyString(), eq('\\'))).thenReturn(mock(Predicate.class));
|
when(criteriaBuilderMock.like(any(Expression.class), anyString(), eq('\\'))).thenReturn(mock(Predicate.class));
|
||||||
when(criteriaBuilderMock.<String> greaterThanOrEqualTo(any(Expression.class), any(String.class)))
|
when(criteriaBuilderMock.<String> greaterThanOrEqualTo(any(Expression.class), any(String.class)))
|
||||||
.thenReturn(mock(Predicate.class));
|
.thenReturn(mock(Predicate.class));
|
||||||
@@ -352,13 +397,13 @@ public class RSQLUtilityTest {
|
|||||||
// verification
|
// verification
|
||||||
verify(criteriaBuilderMock, times(1)).and(any(Predicate.class));
|
verify(criteriaBuilderMock, times(1)).and(any(Predicate.class));
|
||||||
verify(criteriaBuilderMock, times(1)).like(eq(pathOfString(baseSoftwareModuleRootMock)),
|
verify(criteriaBuilderMock, times(1)).like(eq(pathOfString(baseSoftwareModuleRootMock)),
|
||||||
eq("a\\%".toUpperCase()), eq('\\'));
|
eq("a\\%%".toUpperCase()), eq('\\'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void correctRsqlBuildsLikePredicateWithPercentageSQLServer() {
|
public void correctRsqlBuildsLikePredicateWithPercentageSQLServer() {
|
||||||
reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
||||||
final String correctRsql = "name==a%";
|
final String correctRsql = "name==a%*";
|
||||||
when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock);
|
when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock);
|
||||||
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
|
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
|
||||||
when(criteriaBuilderMock.upper(eq(pathOfString(baseSoftwareModuleRootMock))))
|
when(criteriaBuilderMock.upper(eq(pathOfString(baseSoftwareModuleRootMock))))
|
||||||
@@ -374,7 +419,7 @@ public class RSQLUtilityTest {
|
|||||||
// verification
|
// verification
|
||||||
verify(criteriaBuilderMock, times(1)).and(any(Predicate.class));
|
verify(criteriaBuilderMock, times(1)).and(any(Predicate.class));
|
||||||
verify(criteriaBuilderMock, times(1)).like(eq(pathOfString(baseSoftwareModuleRootMock)),
|
verify(criteriaBuilderMock, times(1)).like(eq(pathOfString(baseSoftwareModuleRootMock)),
|
||||||
eq("a[%]".toUpperCase()), eq('\\'));
|
eq("a[%]%".toUpperCase()), eq('\\'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user