Soft Module metadata as complex map value (#2568)

---------

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-07-30 17:29:02 +03:00
committed by GitHub
parent 4a8e60764f
commit 08cacf9034
38 changed files with 392 additions and 962 deletions

View File

@@ -149,17 +149,16 @@ public class SpecificationBuilder<T> {
if (comparison.getValue() == null) {
// map entry with key is null (doesn't exist) / is not null (exists) - use left join with on
return switch (op) {
case EQ -> cb.isNull(pathResolver.getJoinOn(attribute, split[1]));
case NE -> cb.isNotNull(pathResolver.getJoinOn(attribute, split[1]));
case EQ -> cb.isNull(toMapValuePath(pathResolver.getJoinOn(attribute, split[1])));
case NE -> cb.isNotNull(toMapValuePath(pathResolver.getJoinOn(attribute, split[1])));
default -> throw new RSQLParameterSyntaxException(
String.format("Operator %s is not supported for map fields with value null", op));
};
} else {
final MapJoin<?, ?, ?> mapPath = (MapJoin<?, ?, ?>) pathResolver.getPath(attribute);
final Path<String> valuePath = (Path<String>) mapPath.value();
return isNot(op)
? compare(comparison, pathResolver.getJoinOnInner(attribute, split[1]))
: cb.and(equal(mapPath.key(), split[1]), compare(comparison, valuePath));
? compare(comparison, toMapValuePath(pathResolver.getJoinOnInner(attribute, split[1])))
: cb.and(equal(mapPath.key(), split[1]), compare(comparison, toMapValuePath(mapPath)));
}
} else if (attribute instanceof SetAttribute<?, ?> setAttribute) {
if (split.length < 2 || ObjectUtils.isEmpty(split[1])) {
@@ -185,6 +184,12 @@ public class SpecificationBuilder<T> {
}
}
@SuppressWarnings("unchecked")
private static Path<String> toMapValuePath(final Path<?> mapJoin) {
final Path<?> valuePath = ((MapJoin<?, ?, ?>) mapJoin).value();
return valuePath.getJavaType() == String.class ? (Path<String>) valuePath : valuePath.get("value");
}
private Predicate compare(final Comparison comparison, final Path<?> fieldPath) {
final List<Object> values = getValues(comparison, fieldPath.getJavaType());
final Object firstValue = values.get(0);

View File

@@ -343,8 +343,7 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & RsqlQueryField, T> extends
final Predicate mapPredicate = mapToMapPredicate(fieldPath, queryPath);
final Predicate valuePredicate = addOperatorPredicate(node, fieldPath, transformedValues, value, queryPath);
final Predicate valuePredicate = addOperatorPredicate(node, queryPath.getEnumValue().isMap() ? (Path)toMapValuePath(fieldPath) : fieldPath, transformedValues, value, queryPath);
return toSingleList(mapPredicate != null ? cb.and(mapPredicate, valuePredicate) : valuePredicate);
}
@@ -380,6 +379,16 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & RsqlQueryField, T> extends
}
}
@SuppressWarnings("unchecked")
private static Path<String> toMapValuePath(final Path<?> fieldPath) {
final Path<?> mapValuePath = ((MapJoin<?, ?, ?>) pathOfString(fieldPath)).value();
if (mapValuePath.getJavaType() == String.class) {
return (Path<String>) mapValuePath;
} else {
return mapValuePath.get("value");
}
}
private Predicate getOutPredicate(
final List<Object> transformedValues,
final QueryPath queryPath, final Path<Object> fieldPath) {
@@ -496,8 +505,7 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & RsqlQueryField, T> extends
@SuppressWarnings({ "rawtypes", "unchecked" })
private Expression<String> getExpressionToCompare(final Path innerFieldPath, final A enumField) {
if (enumField.isMap()){
// Currently we support only string key. So below cast is safe.
return (Expression<String>) (((MapJoin<?, ?, ?>) pathOfString(innerFieldPath)).value());
return toMapValuePath(innerFieldPath);
} else {
return pathOfString(innerFieldPath);
}

View File

@@ -129,7 +129,7 @@ public class JpaQueryRsqlVisitorG2<A extends Enum<A> & RsqlQueryField, T>
if (values.get(0) == null) {
// IS operator for maps and null value is treated as doesn't exist correspondingly
((PluralJoin<?, ?, ?>) fieldPath).on(toMapEntryKeyPredicate(queryPath, fieldPath));
return cb.isNull(fieldPath);
return cb.isNull(toMapValuePath(fieldPath));
}
} else if (node.getOperator() == NOT) {
if (values.size() != 1) {
@@ -139,10 +139,10 @@ public class JpaQueryRsqlVisitorG2<A extends Enum<A> & RsqlQueryField, T>
((PluralJoin<?, ?, ?>) fieldPath).on(toMapEntryKeyPredicate(queryPath, fieldPath));
if (values.get(0) == null) {
// special handling of "exists"
return cb.isNotNull(fieldPath);
return cb.isNotNull(toMapValuePath(fieldPath));
} else {
// special handling or "not equal" or null (same as != but with possible optimized join - no subquery)
return toNotEqualToPredicate(queryPath, fieldPath, values.get(0));
return toNotEqualToPredicate(queryPath, toMapValuePath(fieldPath), values.get(0));
}
}
mapEntryKeyPredicate = toMapEntryKeyPredicate(queryPath, fieldPath);
@@ -150,11 +150,21 @@ public class JpaQueryRsqlVisitorG2<A extends Enum<A> & RsqlQueryField, T>
mapEntryKeyPredicate = null;
}
final Predicate valuePredicate = toOperatorAndValuePredicate(node, queryPath, fieldPath, values);
final Predicate valuePredicate = toOperatorAndValuePredicate(node, queryPath, queryPath.getEnumValue().isMap() ? toMapValuePath(fieldPath) : fieldPath, values);
return mapEntryKeyPredicate == null ? valuePredicate : cb.and(mapEntryKeyPredicate, valuePredicate);
}
@SuppressWarnings("unchecked")
private static Path<String> toMapValuePath(final Path<?> fieldPath) {
final Path<?> mapValuePath = ((MapJoin<?, ?, ?>) pathOfString(fieldPath)).value();
if (mapValuePath.getJavaType() == String.class) {
return (Path<String>) mapValuePath;
} else {
return mapValuePath.get("value");
}
}
@SuppressWarnings("unchecked")
private Predicate toMapEntryKeyPredicate(final QueryPath queryPath, final Path<?> fieldPath) {
final String[] graph = queryPath.getJpaPath();
@@ -341,7 +351,7 @@ public class JpaQueryRsqlVisitorG2<A extends Enum<A> & RsqlQueryField, T>
private Path<String> getExpressionToCompare(final A enumField, final Path fieldPath) {
if (enumField.isMap()) {
// Currently we support only string key. So below cast is safe.
return (Path<String>) (((MapJoin<?, ?, ?>) fieldPath).value());
return toMapValuePath(fieldPath);
} else {
return pathOfString(fieldPath);
}