9393 */
9494class 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 )) {
0 commit comments