Skip to content

Commit 2065101

Browse files
authored
fix: add support for nested where criteria expressions (#148)
1 parent fcf9f12 commit 2065101

File tree

6 files changed

+150
-68
lines changed

6 files changed

+150
-68
lines changed

graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaOneToManyDataFetcher.java

+2-6
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
*/
4747
class GraphQLJpaOneToManyDataFetcher extends GraphQLJpaQueryDataFetcher {
4848

49-
protected static final String OPTIONAL = "optional";
5049
private final PluralAttribute<Object,Object,Object> attribute;
5150

5251
public GraphQLJpaOneToManyDataFetcher(EntityManager entityManager,
@@ -141,11 +140,8 @@ protected TypedQuery<?> getQuery(DataFetchingEnvironment environment, Field fiel
141140

142141
query.select(join.alias(attribute.getName()));
143142

144-
List<Predicate> predicates = getFieldArguments(field, query, cb, join, environment).stream()
145-
.filter(it -> !OPTIONAL.equals(it.getName()))
146-
.map(it -> getPredicate(cb, from, join, environment, it))
147-
.filter(it -> it != null)
148-
.collect(Collectors.toList());
143+
List<Predicate> predicates = getFieldPredicates(field, query, cb, from, join, environment);
144+
149145
query.where(
150146
predicates.toArray(new Predicate[predicates.size()])
151147
);

graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java

+8-6
Original file line numberDiff line numberDiff line change
@@ -661,12 +661,14 @@ else if (attribute instanceof PluralAttribute
661661

662662
// make it configurable via builder api
663663
arguments.add(optionalArgument(toManyDefaultOptional));
664-
665-
dataFetcher = new GraphQLJpaOneToManyDataFetcher(entityManager,
666-
baseEntity,
667-
toManyDefaultOptional,
668-
isDefaultDistinct,
669-
(PluralAttribute) attribute);
664+
665+
if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.MANY_TO_MANY) {
666+
dataFetcher = new GraphQLJpaOneToManyDataFetcher(entityManager,
667+
baseEntity,
668+
toManyDefaultOptional,
669+
isDefaultDistinct,
670+
(PluralAttribute) attribute);
671+
}
670672
}
671673

672674
return GraphQLFieldDefinition.newFieldDefinition()

graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/QraphQLJpaBaseDataFetcher.java

+58-46
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,11 @@
9393
*/
9494
class QraphQLJpaBaseDataFetcher implements DataFetcher<Object> {
9595

96-
private static final String OPTIONAL = "optional";
96+
private static final String WHERE = "where";
97+
98+
protected static final String OPTIONAL = "optional";
99+
100+
protected static final List<String> ARGUMENTS = Arrays.asList(OPTIONAL);
97101

98102
// "__typename" is part of the graphql introspection spec and has to be ignored
99103
private static final String TYPENAME = "__typename";
@@ -130,11 +134,7 @@ protected TypedQuery<?> getQuery(DataFetchingEnvironment environment, Field fiel
130134
from.alias(from.getModel().getName());
131135

132136
// Build predicates from query arguments
133-
List<Predicate> predicates = getFieldArguments(field, query, cb, from, environment)
134-
.stream()
135-
.map(it -> getPredicate(cb, from, from, environment, it))
136-
.filter(it -> it != null)
137-
.collect(Collectors.toList());
137+
List<Predicate> predicates = getFieldPredicates(field, query, cb,from, from, environment);
138138

139139
// Use AND clause to filter results
140140
if(!predicates.isEmpty())
@@ -146,9 +146,10 @@ protected TypedQuery<?> getQuery(DataFetchingEnvironment environment, Field fiel
146146
return entityManager.createQuery(query.distinct(isDistinct));
147147
}
148148

149-
protected final List<Argument> getFieldArguments(Field field, CriteriaQuery<?> query, CriteriaBuilder cb, From<?,?> from, DataFetchingEnvironment environment) {
149+
protected final List<Predicate> getFieldPredicates(Field field, CriteriaQuery<?> query, CriteriaBuilder cb, Root<?> root, From<?,?> from, DataFetchingEnvironment environment) {
150150

151151
List<Argument> arguments = new ArrayList<>();
152+
List<Predicate> predicates = new ArrayList<>();
152153

153154
// Loop through all of the fields being requested
154155
field.getSelectionSet().getSelections().forEach(selection -> {
@@ -161,6 +162,8 @@ protected final List<Argument> getFieldArguments(Field field, CriteriaQuery<?> q
161162
Path<?> fieldPath = from.get(selectedField.getName());
162163
From<?,?> fetch = null;
163164
Optional<Argument> optionalArgument = getArgument(selectedField, OPTIONAL);
165+
Optional<Argument> whereArgument = getArgument(selectedField, WHERE);
166+
Boolean isOptional = null;
164167

165168
// Build predicate arguments for singular attributes only
166169
if(fieldPath.getModel() instanceof SingularAttribute) {
@@ -176,46 +179,50 @@ protected final List<Argument> getFieldArguments(Field field, CriteriaQuery<?> q
176179
query.orderBy(cb.asc(fieldPath));
177180
}
178181

179-
// Process where arguments clauses.
180-
arguments.addAll(selectedField.getArguments()
181-
.stream()
182-
.filter(it -> !isOrderByArgument(it) && !isOptionalArgument(it))
183-
.map(it -> new Argument(selectedField.getName() + "." + it.getName(), it.getValue()))
184-
.collect(Collectors.toList()));
185-
186182
// Check if it's an object and the foreign side is One. Then we can eagerly join causing an inner join instead of 2 queries
187-
if (fieldPath.getModel() instanceof SingularAttribute) {
188-
SingularAttribute<?,?> attribute = (SingularAttribute<?,?>) fieldPath.getModel();
189-
if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.MANY_TO_ONE
190-
|| attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_ONE
191-
) {
192-
// Let's do fugly conversion
193-
Boolean isOptional = optionalArgument.map(it -> getArgumentValue(environment, it, Boolean.class))
194-
.orElse(attribute.isOptional());
195-
196-
// Let's apply left outer join to retrieve optional associations
197-
fetch = reuseFetch(from, selectedField.getName(), isOptional);
198-
}
183+
SingularAttribute<?,?> attribute = (SingularAttribute<?,?>) fieldPath.getModel();
184+
if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.MANY_TO_ONE
185+
|| attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_ONE
186+
) {
187+
// Let's do fugly conversion
188+
isOptional = optionalArgument.map(it -> getArgumentValue(environment, it, Boolean.class))
189+
.orElse(attribute.isOptional());
190+
191+
// Let's apply left outer join to retrieve optional associations
192+
fetch = reuseFetch(from, selectedField.getName(), isOptional);
193+
} else if(attribute.getPersistentAttributeType() == PersistentAttributeType.EMBEDDED) {
194+
// Process where arguments clauses.
195+
arguments.addAll(selectedField.getArguments()
196+
.stream()
197+
.filter(it -> !isOrderByArgument(it) && !isOptionalArgument(it))
198+
.map(it -> new Argument(selectedField.getName() + "." + it.getName(),
199+
it.getValue()))
200+
.collect(Collectors.toList()));
201+
199202
}
200203
} else {
201-
// We must add plural attributes with explicit join
204+
// We must add plural attributes with explicit join fetch
202205
// Let's do fugly conversion
203206
// the many end is a collection, and it is always optional by default (empty collection)
204-
Boolean isOptional = optionalArgument.map(it -> getArgumentValue(environment, it, Boolean.class))
207+
isOptional = optionalArgument.map(it -> getArgumentValue(environment, it, Boolean.class))
205208
.orElse(toManyDefaultOptional);
206-
209+
207210
// Let's apply join to retrieve associated collection
208211
fetch = reuseFetch(from, selectedField.getName(), isOptional);
209212

210-
// TODO add fetch argument parameter
211213
// Let's fetch element collections to avoid filtering their values used where search criteria
212-
from.fetch(selectedField.getName(),
213-
isOptional ? JoinType.LEFT : JoinType.INNER);
214+
GraphQLObjectType objectType = getObjectType(environment);
215+
EntityType<?> entityType = getEntityType(objectType);
216+
217+
PluralAttribute<?, ?, ?> attribute = (PluralAttribute<?, ?, ?>) entityType.getAttribute(selectedField.getName());
218+
219+
if(PersistentAttributeType.ELEMENT_COLLECTION == attribute.getPersistentAttributeType()) {
220+
from.fetch(selectedField.getName());
221+
}
214222
}
215-
216223
// Let's build join fetch graph to avoid Hibernate error:
217224
// "query specified join fetching, but the owner of the fetched association was not present in the select list"
218-
if(fetch != null && selectedField.getSelectionSet() != null) {
225+
if(selectedField.getSelectionSet() != null && fetch != null) {
219226
GraphQLFieldDefinition fieldDefinition = getFieldDef(environment.getGraphQLSchema(),
220227
this.getObjectType(environment),
221228
selectedField);
@@ -224,16 +231,21 @@ protected final List<Argument> getFieldArguments(Field field, CriteriaQuery<?> q
224231
DataFetchingEnvironment fieldEnvironment = wherePredicateEnvironment(environment,
225232
fieldDefinition,
226233
args);
227-
// TODO nested where criteria expressions
228-
getFieldArguments(selectedField, query, cb, fetch, fieldEnvironment);
234+
predicates.addAll(getFieldPredicates(selectedField, query, cb, root, fetch, fieldEnvironment));
229235
}
230236
}
231237
}
232238
});
233-
239+
234240
arguments.addAll(field.getArguments());
235241

236-
return arguments;
242+
arguments.stream()
243+
.filter(it -> !isOrderByArgument(it) && !isOptionalArgument(it))
244+
.map(it -> getPredicate(cb, root, from, environment, it))
245+
.filter(it -> it != null)
246+
.forEach(predicates::add);
247+
248+
return predicates;
237249
}
238250

239251
/**
@@ -331,7 +343,7 @@ protected Predicate getPredicate(CriteriaBuilder cb, Root<?> from, From<?,?> pat
331343
String fieldName = argument.getName().split("\\.")[0];
332344

333345
From<?,?> join = getCompoundJoin(path, argument.getName(), true);
334-
Argument where = new Argument("where", argument.getValue());
346+
Argument where = new Argument(WHERE, argument.getValue());
335347
Map<String, Object> variables = environment.getExecutionContext().getVariables();
336348

337349
GraphQLFieldDefinition fieldDef = getFieldDef(
@@ -342,7 +354,7 @@ protected Predicate getPredicate(CriteriaBuilder cb, Root<?> from, From<?,?> pat
342354

343355
Map<String, Object> arguments = (Map<String, Object>) new ValuesResolver()
344356
.getArgumentValues(fieldDef.getArguments(), Collections.singletonList(where), variables)
345-
.get("where");
357+
.get(WHERE);
346358

347359
return getWherePredicate(cb, from, join, wherePredicateEnvironment(environment, fieldDef, arguments), where);
348360
}
@@ -417,7 +429,7 @@ protected Predicate getArgumentPredicate(CriteriaBuilder cb, From<?,?> from,
417429
new Field(it.getName()));
418430
boolean isOptional = false;
419431

420-
return getArgumentPredicate(cb, reuseFetch(from, it.getName(), isOptional),
432+
return getArgumentPredicate(cb, reuseJoin(from, it.getName(), isOptional),
421433
wherePredicateEnvironment(environment, fieldDefinition, args),
422434
arg);
423435
}
@@ -502,7 +514,7 @@ protected Predicate getArgumentsPredicate(CriteriaBuilder cb,
502514
new Field(it.getName()));
503515
boolean isOptional = false;
504516

505-
return getArgumentPredicate(cb, reuseFetch(path, it.getName(), isOptional),
517+
return getArgumentPredicate(cb, reuseJoin(path, it.getName(), isOptional),
506518
wherePredicateEnvironment(environment, fieldDefinition, args),
507519
arg);
508520
}
@@ -631,7 +643,7 @@ private Predicate getLogicalPredicate(String fieldName, CriteriaBuilder cb, From
631643
isOptional = isOptionalAttribute(getAttribute(environment, argument));
632644
}
633645

634-
return getArgumentPredicate(cb, reuseFetch(path, fieldName, isOptional),
646+
return getArgumentPredicate(cb, reuseJoin(path, fieldName, isOptional),
635647
wherePredicateEnvironment(environment, fieldDefinition, args),
636648
arg);
637649
}
@@ -714,7 +726,7 @@ protected final DataFetchingEnvironment wherePredicateEnvironment(DataFetchingEn
714726
private From<?,?> getCompoundJoin(From<?,?> rootPath, String fieldName, boolean outer) {
715727
String[] compoundField = fieldName.split("\\.");
716728

717-
Join<?,?> join;
729+
From<?,?> join;
718730

719731
if (compoundField.length == 1) {
720732
return rootPath;
@@ -740,7 +752,7 @@ private From<?,?> getCompoundJoin(From<?,?> rootPath, String fieldName, boolean
740752
private Path<?> getCompoundJoinedPath(From<?,?> rootPath, String fieldName, boolean outer) {
741753
String[] compoundField = fieldName.split("\\.");
742754

743-
Join<?,?> join;
755+
From<?,?> join;
744756

745757
if (compoundField.length == 1) {
746758
return rootPath.get(compoundField[0]);
@@ -760,7 +772,7 @@ private Path<?> getCompoundJoinedPath(From<?,?> rootPath, String fieldName, bool
760772
}
761773

762774
// trying to find already existing joins to reuse
763-
private Join<?,?> reuseJoin(From<?, ?> from, String fieldName, boolean outer) {
775+
private From<?,?> reuseJoin(From<?, ?> from, String fieldName, boolean outer) {
764776

765777
for (Join<?,?> join : from.getJoins()) {
766778
if (join.getAttribute().getName().equals(fieldName)) {

graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/GraphQLEnumVariableBindingsTests.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,7 @@ public void queryEnumArrayVariableBindingInEmbeddedRelation() {
195195
+ "{id=1, name=Leo Tolstoy, books=["
196196
+ "{id=2, title=War and Peace, genre=NOVEL}, "
197197
+ "{id=3, title=Anna Karenina, genre=NOVEL}"
198-
+ "]}, "
199-
+ "{id=4, name=Anton Chekhov, books=[]}, "
200-
+ "{id=8, name=Igor Dianov, books=[]}"
198+
+ "]}"
201199
+ "]}}";
202200

203201
//when

0 commit comments

Comments
 (0)