Skip to content

Commit 40c4ce9

Browse files
author
Martin Fekete
committed
JsonbExtractor
1 parent c1b8c39 commit 40c4ce9

File tree

9 files changed

+108
-48
lines changed

9 files changed

+108
-48
lines changed

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

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.github.perplexhub.rsql;
22

3+
import io.github.perplexhub.rsql.jsonb.JsonbExtractor;
34
import lombok.Builder;
45
import lombok.Data;
56

@@ -27,20 +28,14 @@ public class QuerySupport {
2728
private Map<Class<?>, List<String>> propertyBlacklist;
2829
private Collection<String> procedureWhiteList;
2930
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;
31+
@Builder.Default
32+
private JsonbExtractor jsonbExtractor = JsonbExtractor.DEFAULT;
3833

3934
public static class QuerySupportBuilder {}
4035

4136
@Override
4237
public String toString() {
43-
return String.format("%s,distinct:%b,propertyPathMapper:%s,customPredicates:%d,joinHints:%s,propertyWhitelist:%s,propertyBlacklist:%s,jsonbPathExists:%s,jsonbPathExistsTz:%s",
44-
rsqlQuery, distinct, propertyPathMapper, customPredicates == null ? 0 : customPredicates.size(), joinHints, propertyWhitelist, propertyBlacklist, jsonbPathExists, jsonbPathExistsTz);
38+
return String.format("%s,distinct:%b,propertyPathMapper:%s,customPredicates:%d,joinHints:%s,propertyWhitelist:%s,propertyBlacklist:%s,jsonbExtractor:%s",
39+
rsqlQuery, distinct, propertyPathMapper, customPredicates == null ? 0 : customPredicates.size(), joinHints, propertyWhitelist, propertyBlacklist, jsonbExtractor);
4540
}
4641
}

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

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.util.function.Function;
99
import java.util.stream.Collectors;
1010

11+
import io.github.perplexhub.rsql.jsonb.JsonbExtractor;
1112
import io.github.perplexhub.rsql.jsonb.JsonbSupport;
1213
import jakarta.persistence.criteria.*;
1314
import jakarta.persistence.metamodel.Attribute;
@@ -37,8 +38,7 @@ public class RSQLJPAPredicateConverter extends RSQLVisitorBase<Predicate, From>
3738
private final Collection<String> procedureBlackList;
3839
private final boolean strictEquality;
3940
private final Character likeEscapeCharacter;
40-
private final String jsonbPathExists;
41-
private final String jsonbPathExistsTz;
41+
private final JsonbExtractor jsonbExtractor;
4242

4343
public RSQLJPAPredicateConverter(CriteriaBuilder builder, Map<String, String> propertyPathMapper) {
4444
this(builder, propertyPathMapper, null, null);
@@ -64,19 +64,18 @@ public RSQLJPAPredicateConverter(CriteriaBuilder builder,
6464
Collection<String> proceduresBlackList,
6565
boolean strictEquality,
6666
Character likeEscapeCharacter) {
67-
this(builder, propertyPathMapper, customPredicates, joinHints, proceduresWhiteList, proceduresBlackList, strictEquality, likeEscapeCharacter, null, null);
67+
this(builder, propertyPathMapper, customPredicates, joinHints, proceduresWhiteList, proceduresBlackList, strictEquality, likeEscapeCharacter, JsonbExtractor.DEFAULT);
6868
}
6969

7070
public RSQLJPAPredicateConverter(CriteriaBuilder builder,
71-
Map<String, String> propertyPathMapper,
72-
List<RSQLCustomPredicate<?>> customPredicates,
73-
Map<String, JoinType> joinHints,
74-
Collection<String> proceduresWhiteList,
75-
Collection<String> proceduresBlackList,
76-
boolean strictEquality,
77-
Character likeEscapeCharacter,
78-
String jsonbPathExists,
79-
String jsonbPathExistsTz) {
71+
Map<String, String> propertyPathMapper,
72+
List<RSQLCustomPredicate<?>> customPredicates,
73+
Map<String, JoinType> joinHints,
74+
Collection<String> proceduresWhiteList,
75+
Collection<String> proceduresBlackList,
76+
boolean strictEquality,
77+
Character likeEscapeCharacter,
78+
JsonbExtractor jsonbExtractor) {
8079
this.builder = builder;
8180
this.propertyPathMapper = propertyPathMapper != null ? propertyPathMapper : Collections.emptyMap();
8281
this.customPredicates = customPredicates != null ? customPredicates.stream().collect(Collectors.toMap(RSQLCustomPredicate::getOperator, Function.identity(), (a, b) -> a)) : Collections.emptyMap();
@@ -85,8 +84,7 @@ public RSQLJPAPredicateConverter(CriteriaBuilder builder,
8584
this.procedureBlackList = proceduresBlackList != null ? proceduresBlackList : Collections.emptyList();
8685
this.strictEquality = strictEquality;
8786
this.likeEscapeCharacter = likeEscapeCharacter;
88-
this.jsonbPathExists = jsonbPathExists;
89-
this.jsonbPathExistsTz = jsonbPathExistsTz;
87+
this.jsonbExtractor = jsonbExtractor;
9088
}
9189

9290
RSQLJPAContext findPropertyPath(String propertyPath, Path startRoot) {
@@ -261,7 +259,7 @@ private ResolvedExpression resolveExpression(ComparisonNode node, From root, Sel
261259
String jsonbPath = JsonbSupport.jsonPathOfSelector(attribute, jsonSelector);
262260
if(jsonbPath.contains(".")) {
263261
ComparisonNode jsonbNode = node.withSelector(jsonbPath);
264-
return JsonbSupport.jsonbPathExistsExpression(builder, jsonbNode, path, jsonbPathExists, jsonbPathExistsTz);
262+
return JsonbSupport.jsonbPathExistsExpression(builder, jsonbNode, path, jsonbExtractor);
265263
} else {
266264
final Expression expression;
267265
if (path instanceof JpaExpression jpaExpression) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ public static <T> Specification<T> toSpecification(final QuerySupport querySuppo
128128
querySupport.getCustomPredicates(), querySupport.getJoinHints(),
129129
querySupport.getProcedureWhiteList(), querySupport.getProcedureBlackList(),
130130
querySupport.isStrictEquality(), querySupport.getLikeEscapeCharacter(),
131-
querySupport.getJsonbPathExists(), querySupport.getJsonbPathExistsTz());
131+
querySupport.getJsonbExtractor());
132132

133133
visitor.setPropertyWhitelist(querySupport.getPropertyWhitelist());
134134
visitor.setPropertyBlacklist(querySupport.getPropertyBlacklist());

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public class JsonbExpressionBuilder {
1717

1818
private final String jsonbPathExistsTz;
1919
private final String jsonbPathExists;
20+
private final boolean useDateTime;
2021

2122
/**
2223
* The base json type.
@@ -173,10 +174,10 @@ public ArgValue convert(String s) {
173174
private final List<ArgValue> values;
174175

175176
JsonbExpressionBuilder(ComparisonOperator operator, String keyPath, List<String> args) {
176-
this(operator, keyPath, args, null, null);
177+
this(operator, keyPath, args, JsonbExtractor.DEFAULT);
177178
}
178179

179-
JsonbExpressionBuilder(ComparisonOperator operator, String keyPath, List<String> args, String jsonbPathExists, String jsonbPathExistsTz) {
180+
JsonbExpressionBuilder(ComparisonOperator operator, String keyPath, List<String> args, JsonbExtractor extractor) {
180181
this.operator = Objects.requireNonNull(operator);
181182
this.keyPath = Objects.requireNonNull(keyPath);
182183
if(FORBIDDEN_NEGATION.contains(operator)) {
@@ -195,9 +196,10 @@ public ArgValue convert(String s) {
195196
if(REQUIRE_AT_LEAST_ONE_ARGUMENT.contains(operator) && candidateValues.isEmpty()) {
196197
throw new IllegalArgumentException("Operator " + operator + " requires at least one value");
197198
}
199+
this.useDateTime = extractor.useDateTime();
200+
this.jsonbPathExistsTz = extractor.pathExistsTz();
201+
this.jsonbPathExists = extractor.pathExists();
198202
this.values = findMoreTypes(operator, candidateValues);
199-
this.jsonbPathExistsTz = jsonbPathExistsTz == null ? JSONB_PATH_EXISTS_TZ : jsonbPathExistsTz;
200-
this.jsonbPathExists = jsonbPathExists == null ? JSONB_PATH_EXISTS : jsonbPathExists;
201203
}
202204

203205
/**
@@ -243,7 +245,7 @@ private List<ArgValue> findMoreTypes(ComparisonOperator operator, List<String> v
243245
return values.stream().map(s -> new ArgValue(s, BaseJsonType.STRING)).toList();
244246
}
245247

246-
List<ArgConverter> argConverters = DATE_TIME_SUPPORT ?
248+
List<ArgConverter> argConverters = useDateTime ?
247249
List.of(DATE_TIME_CONVERTER, DATE_TIME_CONVERTER_TZ, NUMBER_CONVERTER, BOOLEAN_ARG_CONVERTER)
248250
: List.of(NUMBER_CONVERTER, BOOLEAN_ARG_CONVERTER);
249251
Optional<ArgConverter> candidateConverter = argConverters.stream()
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package io.github.perplexhub.rsql.jsonb;
2+
3+
/**
4+
* jsonb expression configuration
5+
*/
6+
public interface JsonbExtractor {
7+
8+
JsonbExtractor DEFAULT = new JsonbExtractor() {
9+
10+
@Override
11+
public String pathExists() {
12+
return "jsonb_path_exists";
13+
}
14+
15+
@Override
16+
public String pathExistsTz() {
17+
return "jsonb_path_exists_tz";
18+
}
19+
20+
@Override
21+
public boolean useDateTime() {
22+
return false;
23+
}
24+
25+
@Override
26+
public String toString() {
27+
return String.format("pathExists:%s,pathExistsTz:%s,useDateTime:%b", pathExists(), pathExistsTz(), useDateTime());
28+
}
29+
};
30+
31+
/**
32+
*
33+
* @return Postgresql {@code jsonb_path_exists} function to use
34+
*/
35+
String pathExists();
36+
37+
/**
38+
*
39+
* @return Postgresql {@code jsonb_path_exists_tz} function to use
40+
*/
41+
String pathExistsTz();
42+
43+
/**
44+
*
45+
* @return enable temporal values support
46+
*/
47+
boolean useDateTime();
48+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.github.perplexhub.rsql.jsonb;
2+
3+
import lombok.Builder;
4+
import lombok.Getter;
5+
import lombok.experimental.Accessors;
6+
7+
/**
8+
* convenient way to define configuration, based on default values
9+
*/
10+
@Builder
11+
@Accessors(fluent = true)
12+
@Getter
13+
public class JsonbExtractorSupport implements JsonbExtractor {
14+
15+
@Builder.Default
16+
private final String pathExists = JsonbExtractor.DEFAULT.pathExists();
17+
@Builder.Default
18+
private final String pathExistsTz = JsonbExtractor.DEFAULT.pathExistsTz();
19+
@Builder.Default
20+
private final boolean useDateTime = JsonbExtractor.DEFAULT.useDateTime();
21+
22+
@Override
23+
public String toString() {
24+
return String.format("pathExists:%s,pathExistsTz:%s,useDateTime:%b", pathExists, pathExistsTz, useDateTime);
25+
}
26+
}

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@
2626
*/
2727
public class JsonbSupport {
2828

29-
public static boolean DATE_TIME_SUPPORT = false;
30-
3129
private static final Set<Database> JSON_SUPPORT = EnumSet.of(Database.POSTGRESQL);
3230

3331
private static final Map<ComparisonOperator, ComparisonOperator> NEGATE_OPERATORS =
@@ -61,9 +59,9 @@ record JsonbPathExpression(String jsonbFunction, String jsonbPath) {
6159
}
6260

6361

64-
public static ResolvedExpression jsonbPathExistsExpression(CriteriaBuilder builder, ComparisonNode node, Path<?> attrPath, String jsonbPathExists, String jsonbPathExistsTz) {
62+
public static ResolvedExpression jsonbPathExistsExpression(CriteriaBuilder builder, ComparisonNode node, Path<?> attrPath, JsonbExtractor extractor) {
6563
var mayBeInvertedOperator = Optional.ofNullable(NEGATE_OPERATORS.get(node.getOperator()));
66-
var jsb = new JsonbExpressionBuilder(mayBeInvertedOperator.orElse(node.getOperator()), node.getSelector(), node.getArguments(), jsonbPathExists, jsonbPathExistsTz);
64+
var jsb = new JsonbExpressionBuilder(mayBeInvertedOperator.orElse(node.getOperator()), node.getSelector(), node.getArguments(), extractor);
6765
var expression = jsb.getJsonPathExpression();
6866
return ResolvedExpression.ofJson(builder.function(expression.jsonbFunction, Boolean.class, attrPath,
6967
builder.literal(expression.jsonbPath)), mayBeInvertedOperator.isPresent());

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package io.github.perplexhub.rsql;
22

3-
import io.github.perplexhub.rsql.jsonb.JsonbSupport;
3+
import io.github.perplexhub.rsql.jsonb.JsonbExtractorSupport;
44
import io.github.perplexhub.rsql.model.EntityWithJsonb;
55
import io.github.perplexhub.rsql.model.JsonbEntity;
66
import io.github.perplexhub.rsql.model.PostgresJsonEntity;
@@ -48,7 +48,6 @@ class RSQLJPASupportPostgresJsonTest {
4848
void setup(@Autowired EntityManager em) {
4949
RSQLVisitorBase.setEntityManagerDatabase(Map.of(em, Database.POSTGRESQL));
5050
clear();
51-
JsonbSupport.DATE_TIME_SUPPORT = false;
5251
}
5352

5453
@AfterEach
@@ -84,12 +83,11 @@ void testJsonSearch(List<PostgresJsonEntity> entities, String rsql, List<Postgre
8483
@ParameterizedTest
8584
@MethodSource("temporalData")
8685
void testJsonSearchOfTemporal(List<PostgresJsonEntity> entities, String rsql, List<PostgresJsonEntity> expected) {
87-
JsonbSupport.DATE_TIME_SUPPORT = true;
8886
//given
8987
repository.saveAllAndFlush(entities);
9088

9189
//when
92-
List<PostgresJsonEntity> result = repository.findAll(toSpecification(rsql));
90+
List<PostgresJsonEntity> result = repository.findAll(toSpecification(QuerySupport.builder().rsqlQuery(rsql).jsonbExtractor(JsonbExtractorSupport.builder().useDateTime(true).build()).build()));
9391

9492
//then
9593
assertThat(result)
@@ -162,7 +160,6 @@ void testJsonSearchOnMappedRelation(List<JsonbEntity> jsonbEntities, String rsql
162160
@ParameterizedTest
163161
@MethodSource("sortByRelation")
164162
void testJsonSortOnRelation(List<JsonbEntity> jsonbEntities, String rsql, List<JsonbEntity> expected) {
165-
JsonbSupport.DATE_TIME_SUPPORT = true;
166163
//given
167164
Collection<EntityWithJsonb> entitiesWithJsonb = jsonbEntityRepository.saveAllAndFlush(jsonbEntities).stream()
168165
.map(jsonbEntity -> EntityWithJsonb.builder().jsonb(jsonbEntity).build())
@@ -184,7 +181,6 @@ void testJsonSortOnRelation(List<JsonbEntity> jsonbEntities, String rsql, List<J
184181
@ParameterizedTest
185182
@MethodSource("sortByMappedRelation")
186183
void testJsonSortOnMappedRelation(List<JsonbEntity> jsonbEntities, String rsql, List<JsonbEntity> expected) {
187-
JsonbSupport.DATE_TIME_SUPPORT = true;
188184
//given
189185
Collection<EntityWithJsonb> entitiesWithJsonb = jsonbEntityRepository.saveAllAndFlush(jsonbEntities).stream()
190186
.map(jsonbEntity -> EntityWithJsonb.builder().jsonb(jsonbEntity).build())
@@ -732,7 +728,7 @@ void testJsonSearchCustomFunction(List<PostgresJsonEntity> entities, String rsql
732728
repository.saveAllAndFlush(entities);
733729

734730
//when
735-
List<PostgresJsonEntity> result = repository.findAll(toSpecification(new QuerySupport.QuerySupportBuilder().rsqlQuery(rsql).jsonbPathExists("my_jsonb_path_exists").build()));
731+
List<PostgresJsonEntity> result = repository.findAll(toSpecification(QuerySupport.builder().rsqlQuery(rsql).jsonbExtractor(JsonbExtractorSupport.builder().pathExists("my_jsonb_path_exists").build()).build()));
736732

737733
//then
738734
assertThat(result)

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ class JsonbExpressionBuilderTest {
2020
@ParameterizedTest
2121
@MethodSource("data")
2222
void testJsonbPathExpression(ComparisonOperator operator, String keyPath, List<String> arguments, String expectedJsonbFunction, String expectedJsonbPath) {
23-
JsonbSupport.DATE_TIME_SUPPORT = false;
24-
JsonbExpressionBuilder builder = new JsonbExpressionBuilder(operator, keyPath, arguments);
23+
JsonbExpressionBuilder builder = new JsonbExpressionBuilder(operator, keyPath, arguments, JsonbExtractor.DEFAULT);
2524
var expression = builder.getJsonPathExpression();
2625
assertEquals(expectedJsonbFunction, expression.jsonbFunction());
2726
assertEquals(expectedJsonbPath, expression.jsonbPath());
@@ -30,8 +29,7 @@ void testJsonbPathExpression(ComparisonOperator operator, String keyPath, List<S
3029
@ParameterizedTest
3130
@MethodSource("temporal")
3231
void testJsonbPathExpressionWithTemporal(ComparisonOperator operator, String keyPath, List<String> arguments, String expectedJsonbFunction, String expectedJsonbPath) {
33-
JsonbSupport.DATE_TIME_SUPPORT = true;
34-
JsonbExpressionBuilder builder = new JsonbExpressionBuilder(operator, keyPath, arguments);
32+
JsonbExpressionBuilder builder = new JsonbExpressionBuilder(operator, keyPath, arguments, JsonbExtractorSupport.builder().useDateTime(true).build());
3533
var expression = builder.getJsonPathExpression();
3634
assertEquals(expectedJsonbFunction, expression.jsonbFunction());
3735
assertEquals(expectedJsonbPath, expression.jsonbPath());
@@ -40,8 +38,7 @@ void testJsonbPathExpressionWithTemporal(ComparisonOperator operator, String key
4038
@ParameterizedTest
4139
@MethodSource("customized")
4240
void testJsonbPathExpressionCustomized(ComparisonOperator operator, String keyPath, List<String> arguments, String expectedJsonbFunction, String expectedJsonbPath) {
43-
JsonbSupport.DATE_TIME_SUPPORT = true;
44-
JsonbExpressionBuilder builder = new JsonbExpressionBuilder(operator, keyPath, arguments, "my_jsonb_path_exists", "my_jsonb_path_exists_tz");
41+
JsonbExpressionBuilder builder = new JsonbExpressionBuilder(operator, keyPath, arguments, JsonbExtractorSupport.builder().pathExists("my_jsonb_path_exists").pathExistsTz("my_jsonb_path_exists_tz").useDateTime(true).build());
4542
var expression = builder.getJsonPathExpression();
4643
assertEquals(expectedJsonbFunction, expression.jsonbFunction());
4744
assertEquals(expectedJsonbPath, expression.jsonbPath());

0 commit comments

Comments
 (0)