Skip to content

Commit ca63c3f

Browse files
author
Martin Fekete
committed
move json query functions configuration to QuerySupport
1 parent c6cfad3 commit ca63c3f

File tree

7 files changed

+73
-37
lines changed

7 files changed

+73
-37
lines changed

rsql-jpa/src/main/java/io/github/perplexhub/rsql/QuerySupport.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ public class QuerySupport {
2727
private Map<Class<?>, List<String>> propertyBlacklist;
2828
private Collection<String> procedureWhiteList;
2929
private Collection<String> procedureBlackList;
30+
/**
31+
* Postgresql {@code jsonb_path_exists} function to use
32+
*/
33+
private String jsonbPathExists;
34+
/**
35+
* Postgresql {@code jsonb_path_exists_tz} function to use
36+
*/
37+
private String jsonbPathExistsTz;
3038

3139
public static class QuerySupportBuilder {}
3240

rsql-jpa/src/main/java/io/github/perplexhub/rsql/RSQLJPAPredicateConverter.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ public class RSQLJPAPredicateConverter extends RSQLVisitorBase<Predicate, From>
3737
private final Collection<String> procedureBlackList;
3838
private final boolean strictEquality;
3939
private final Character likeEscapeCharacter;
40+
private final String jsonbPathExists;
41+
private final String jsonbPathExistsTz;
4042

4143
public RSQLJPAPredicateConverter(CriteriaBuilder builder, Map<String, String> propertyPathMapper) {
4244
this(builder, propertyPathMapper, null, null);
@@ -54,14 +56,27 @@ public RSQLJPAPredicateConverter(CriteriaBuilder builder, Map<String, String> pr
5456
this(builder, propertyPathMapper, customPredicates, joinHints, procedureWhiteList, procedureBlackList, false, null);
5557
}
5658

59+
public RSQLJPAPredicateConverter(CriteriaBuilder builder,
60+
Map<String, String> propertyPathMapper,
61+
List<RSQLCustomPredicate<?>> customPredicates,
62+
Map<String, JoinType> joinHints,
63+
Collection<String> proceduresWhiteList,
64+
Collection<String> proceduresBlackList,
65+
boolean strictEquality,
66+
Character likeEscapeCharacter) {
67+
this(builder, propertyPathMapper, customPredicates, joinHints, proceduresWhiteList, proceduresBlackList, strictEquality, likeEscapeCharacter, null, null);
68+
}
69+
5770
public RSQLJPAPredicateConverter(CriteriaBuilder builder,
5871
Map<String, String> propertyPathMapper,
5972
List<RSQLCustomPredicate<?>> customPredicates,
6073
Map<String, JoinType> joinHints,
6174
Collection<String> proceduresWhiteList,
6275
Collection<String> proceduresBlackList,
6376
boolean strictEquality,
64-
Character likeEscapeCharacter) {
77+
Character likeEscapeCharacter,
78+
String jsonbPathExists,
79+
String jsonbPathExistsTz) {
6580
this.builder = builder;
6681
this.propertyPathMapper = propertyPathMapper != null ? propertyPathMapper : Collections.emptyMap();
6782
this.customPredicates = customPredicates != null ? customPredicates.stream().collect(Collectors.toMap(RSQLCustomPredicate::getOperator, Function.identity(), (a, b) -> a)) : Collections.emptyMap();
@@ -70,6 +85,8 @@ public RSQLJPAPredicateConverter(CriteriaBuilder builder,
7085
this.procedureBlackList = proceduresBlackList != null ? proceduresBlackList : Collections.emptyList();
7186
this.strictEquality = strictEquality;
7287
this.likeEscapeCharacter = likeEscapeCharacter;
88+
this.jsonbPathExists = jsonbPathExists;
89+
this.jsonbPathExistsTz = jsonbPathExistsTz;
7390
}
7491

7592
RSQLJPAContext findPropertyPath(String propertyPath, Path startRoot) {
@@ -244,7 +261,7 @@ private ResolvedExpression resolveExpression(ComparisonNode node, From root, Sel
244261
String jsonbPath = JsonbSupport.jsonPathOfSelector(attribute, jsonSelector);
245262
if(jsonbPath.contains(".")) {
246263
ComparisonNode jsonbNode = node.withSelector(jsonbPath);
247-
return JsonbSupport.jsonbPathExistsExpression(builder, jsonbNode, path);
264+
return JsonbSupport.jsonbPathExistsExpression(builder, jsonbNode, path, jsonbPathExists, jsonbPathExistsTz);
248265
} else {
249266
final Expression expression;
250267
if (path instanceof JpaExpression jpaExpression) {

rsql-jpa/src/main/java/io/github/perplexhub/rsql/RSQLJPASupport.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@ public static <T> Specification<T> toSpecification(final QuerySupport querySuppo
127127
RSQLJPAPredicateConverter visitor = new RSQLJPAPredicateConverter(cb, querySupport.getPropertyPathMapper(),
128128
querySupport.getCustomPredicates(), querySupport.getJoinHints(),
129129
querySupport.getProcedureWhiteList(), querySupport.getProcedureBlackList(),
130-
querySupport.isStrictEquality(), querySupport.getLikeEscapeCharacter());
130+
querySupport.isStrictEquality(), querySupport.getLikeEscapeCharacter(),
131+
querySupport.getJsonbPathExists(), querySupport.getJsonbPathExistsTz());
131132

132133
visitor.setPropertyWhitelist(querySupport.getPropertyWhitelist());
133134
visitor.setPropertyBlacklist(querySupport.getPropertyBlacklist());

rsql-jpa/src/main/java/io/github/perplexhub/rsql/jsonb/JsonbExpressionBuilder.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
public class JsonbExpressionBuilder {
1717

18+
private final String jsonbPathExistsTz;
19+
private final String jsonbPathExists;
20+
1821
/**
1922
* The base json type.
2023
*/
@@ -161,11 +164,19 @@ public ArgValue convert(String s) {
161164
Map.entry(BETWEEN, "(%1$s >= %2$s && %1$s <= %3$s)")
162165
);
163166

167+
private static final String JSONB_PATH_EXISTS = "jsonb_path_exists";
168+
169+
private static final String JSONB_PATH_EXISTS_TZ = "jsonb_path_exists_tz";
170+
164171
private final ComparisonOperator operator;
165172
private final String keyPath;
166173
private final List<ArgValue> values;
167174

168175
JsonbExpressionBuilder(ComparisonOperator operator, String keyPath, List<String> args) {
176+
this(operator, keyPath, args, null, null);
177+
}
178+
179+
JsonbExpressionBuilder(ComparisonOperator operator, String keyPath, List<String> args, String jsonbPathExists, String jsonbPathExistsTz) {
169180
this.operator = Objects.requireNonNull(operator);
170181
this.keyPath = Objects.requireNonNull(keyPath);
171182
if(FORBIDDEN_NEGATION.contains(operator)) {
@@ -185,6 +196,8 @@ public ArgValue convert(String s) {
185196
throw new IllegalArgumentException("Operator " + operator + " requires at least one value");
186197
}
187198
this.values = findMoreTypes(operator, candidateValues);
199+
this.jsonbPathExistsTz = jsonbPathExistsTz == null ? JSONB_PATH_EXISTS_TZ : jsonbPathExistsTz;
200+
this.jsonbPathExists = jsonbPathExists == null ? JSONB_PATH_EXISTS : jsonbPathExists;
188201
}
189202

190203
/**
@@ -206,7 +219,7 @@ public JsonbPathExpression getJsonPathExpression() {
206219
List<String> templateArguments = new ArrayList<>();
207220
templateArguments.add(valueReference);
208221
templateArguments.addAll(valuesToCompare);
209-
var function = isDateTimeTz ? JSONB_PATH_EXISTS_TZ : JSONB_PATH_EXISTS;
222+
var function = isDateTimeTz ? jsonbPathExistsTz : jsonbPathExists;
210223
var expression = String.format("%s ? %s", targetPath, String.format(comparisonTemplate, templateArguments.toArray()));
211224
return new JsonbPathExpression(function, expression);
212225
}

rsql-jpa/src/main/java/io/github/perplexhub/rsql/jsonb/JsonbSupport.java

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@
1616
import io.github.perplexhub.rsql.ResolvedExpression;
1717
import jakarta.persistence.Column;
1818
import jakarta.persistence.criteria.CriteriaBuilder;
19-
import jakarta.persistence.criteria.Expression;
2019
import jakarta.persistence.criteria.Path;
21-
import jakarta.persistence.criteria.Predicate;
2220
import jakarta.persistence.metamodel.Attribute;
2321
import jakarta.persistence.metamodel.ManagedType;
2422
import org.springframework.orm.jpa.vendor.Database;
@@ -30,16 +28,6 @@ public class JsonbSupport {
3028

3129
public static boolean DATE_TIME_SUPPORT = false;
3230

33-
/**
34-
* Postgresql {@code jsonb_path_exists} function to use
35-
*/
36-
public static String JSONB_PATH_EXISTS = "jsonb_path_exists";
37-
38-
/**
39-
* Postgresql {@code jsonb_path_exists_tz} function to use
40-
*/
41-
public static String JSONB_PATH_EXISTS_TZ = "jsonb_path_exists_tz";
42-
4331
private static final Set<Database> JSON_SUPPORT = EnumSet.of(Database.POSTGRESQL);
4432

4533
private static final Map<ComparisonOperator, ComparisonOperator> NEGATE_OPERATORS =
@@ -73,9 +61,9 @@ record JsonbPathExpression(String jsonbFunction, String jsonbPath) {
7361
}
7462

7563

76-
public static ResolvedExpression jsonbPathExistsExpression(CriteriaBuilder builder, ComparisonNode node, Path<?> attrPath) {
64+
public static ResolvedExpression jsonbPathExistsExpression(CriteriaBuilder builder, ComparisonNode node, Path<?> attrPath, String jsonbPathExists, String jsonbPathExistsTz) {
7765
var mayBeInvertedOperator = Optional.ofNullable(NEGATE_OPERATORS.get(node.getOperator()));
78-
var jsb = new JsonbExpressionBuilder(mayBeInvertedOperator.orElse(node.getOperator()), node.getSelector(), node.getArguments());
66+
var jsb = new JsonbExpressionBuilder(mayBeInvertedOperator.orElse(node.getOperator()), node.getSelector(), node.getArguments(), jsonbPathExists, jsonbPathExistsTz);
7967
var expression = jsb.getJsonPathExpression();
8068
return ResolvedExpression.ofJson(builder.function(expression.jsonbFunction, Boolean.class, attrPath,
8169
builder.literal(expression.jsonbPath)), mayBeInvertedOperator.isPresent());

rsql-jpa/src/test/java/io/github/perplexhub/rsql/RSQLJPASupportPostgresJsonTest.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.springframework.boot.test.context.SpringBootTest;
1818
import org.springframework.orm.jpa.vendor.Database;
1919
import org.springframework.test.context.ActiveProfiles;
20+
import org.springframework.test.context.jdbc.Sql;
2021

2122
import java.util.Collection;
2223
import java.util.HashMap;
@@ -542,9 +543,9 @@ private static Stream<Arguments> meltedTimeZone() {
542543
var e3 = new PostgresJsonEntity(Map.of("a", "2020-01-01T00:00:00"));
543544
var allCases = List.of(e1, e2, e3);
544545
return Stream.of(
545-
arguments(allCases, "properties.a=ge=1970-01-02T00:00:00+00:00", List.of(e2, e3)),
546-
arguments(allCases, "properties.a=ge=1970-01-02T00:00:00+01:00", List.of(e2, e3)),
547-
arguments(allCases, "properties.a=lt=2022-01-01T00:00:00+01:00", List.of(e1, e2, e3)),
546+
arguments(allCases, "properties.a=ge=1970-01-02T00:00:00+00:00", List.of(e2, e3)),
547+
arguments(allCases, "properties.a=ge=1970-01-02T00:00:00+01:00", List.of(e2, e3)),
548+
arguments(allCases, "properties.a=lt=2022-01-01T00:00:00+01:00", List.of(e1, e2, e3)),
548549
null
549550
).filter(Objects::nonNull);
550551
}
@@ -721,4 +722,23 @@ private static Map<String, Object> nullMap(String key) {
721722
nullValue.put(key, null);
722723
return nullValue;
723724
}
725+
726+
@Sql(statements = "CREATE OR REPLACE FUNCTION my_jsonb_path_exists(arg1 jsonb,arg2 jsonpath) RETURNS boolean AS 'SELECT $1 @? $2' LANGUAGE 'sql' IMMUTABLE;")
727+
@Sql(statements = "DROP FUNCTION IF EXISTS my_jsonb_path_exists;", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
728+
@ParameterizedTest
729+
@MethodSource("data")
730+
void testJsonSearchCustomFunction(List<PostgresJsonEntity> entities, String rsql, List<PostgresJsonEntity> expected) {
731+
//given
732+
repository.saveAllAndFlush(entities);
733+
734+
//when
735+
List<PostgresJsonEntity> result = repository.findAll(toSpecification(new QuerySupport.QuerySupportBuilder().rsqlQuery(rsql).jsonbPathExists("my_jsonb_path_exists").build()));
736+
737+
//then
738+
assertThat(result)
739+
.hasSameSizeAs(expected)
740+
.containsExactlyInAnyOrderElementsOf(expected);
741+
742+
entities.forEach(e -> e.setId(null));
743+
}
724744
}

rsql-jpa/src/test/java/io/github/perplexhub/rsql/jsonb/JsonbExpressionBuilderTest.java

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,22 +40,11 @@ void testJsonbPathExpressionWithTemporal(ComparisonOperator operator, String key
4040
@ParameterizedTest
4141
@MethodSource("customized")
4242
void testJsonbPathExpressionCustomized(ComparisonOperator operator, String keyPath, List<String> arguments, String expectedJsonbFunction, String expectedJsonbPath) {
43-
String jsonbPathExists = JsonbSupport.JSONB_PATH_EXISTS;
44-
String jsonbPathExistsTz = JsonbSupport.JSONB_PATH_EXISTS_TZ;
45-
try {
46-
JsonbSupport.JSONB_PATH_EXISTS = "my_jsonb_path_exists";
47-
JsonbSupport.JSONB_PATH_EXISTS_TZ = "my_jsonb_path_exists_tz";
48-
JsonbSupport.DATE_TIME_SUPPORT = true;
49-
JsonbExpressionBuilder builder = new JsonbExpressionBuilder(operator, keyPath, arguments);
50-
var expression = builder.getJsonPathExpression();
51-
assertEquals(expectedJsonbFunction, expression.jsonbFunction());
52-
assertEquals(expectedJsonbPath, expression.jsonbPath());
53-
} catch (Exception e) {
54-
throw e;
55-
} finally {
56-
JsonbSupport.JSONB_PATH_EXISTS = jsonbPathExists;
57-
JsonbSupport.JSONB_PATH_EXISTS_TZ = jsonbPathExistsTz;
58-
}
43+
JsonbSupport.DATE_TIME_SUPPORT = true;
44+
JsonbExpressionBuilder builder = new JsonbExpressionBuilder(operator, keyPath, arguments, "my_jsonb_path_exists", "my_jsonb_path_exists_tz");
45+
var expression = builder.getJsonPathExpression();
46+
assertEquals(expectedJsonbFunction, expression.jsonbFunction());
47+
assertEquals(expectedJsonbPath, expression.jsonbPath());
5948
}
6049

6150
static Stream<Arguments> data() {

0 commit comments

Comments
 (0)