90
90
*/
91
91
class QraphQLJpaBaseDataFetcher implements DataFetcher <Object > {
92
92
93
+ private static final String OPTIONAL = "optional" ;
94
+
93
95
// "__typename" is part of the graphql introspection spec and has to be ignored
94
96
private static final String TYPENAME = "__typename" ;
95
97
@@ -167,24 +169,35 @@ protected final List<Argument> getFieldArguments(Field field, CriteriaQuery<?> q
167
169
// Process where arguments clauses.
168
170
arguments .addAll (selectedField .getArguments ()
169
171
.stream ()
170
- .filter (it -> !isOrderByArgument (it ))
172
+ .filter (it -> !isOrderByArgument (it ) && ! isOptionalArgument ( it ) )
171
173
.map (it -> new Argument (selectedField .getName () + "." + it .getName (), it .getValue ()))
172
174
.collect (Collectors .toList ()));
173
175
174
- // Check if it's an object and the foreign side is One. Then we can eagerly fetch causing an inner join instead of 2 queries
176
+ // 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
175
177
if (fieldPath .getModel () instanceof SingularAttribute ) {
176
178
SingularAttribute <?,?> attribute = (SingularAttribute <?,?>) fieldPath .getModel ();
177
179
if (attribute .getPersistentAttributeType () == Attribute .PersistentAttributeType .MANY_TO_ONE
178
180
|| attribute .getPersistentAttributeType () == Attribute .PersistentAttributeType .ONE_TO_ONE
179
181
) {
180
- reuseJoin (from , selectedField .getName (), false );
182
+ // Let's apply left outer join to retrieve optional associations
183
+ Optional <Argument > optionalArgument = getArgument (selectedField , OPTIONAL );
184
+
185
+ // Let's do fugly conversion
186
+ Boolean isOptional = optionalArgument .map (it -> getArgumentValue (environment , it , Boolean .class ))
187
+ .orElse (attribute .isOptional ());
188
+
189
+ reuseJoin (from , selectedField .getName (), isOptional );
181
190
}
182
191
}
183
- } else {
184
- // We must add plural attributes with explicit fetch to avoid Hibernate error:
192
+ } else {
193
+ // We must add plural attributes with explicit join to avoid Hibernate error:
185
194
// "query specified join fetching, but the owner of the fetched association was not present in the select list"
186
- // TODO Let's try detect optional relation and apply join type
187
- reuseJoin (from , selectedField .getName (), false );
195
+ PluralAttribute <?, ?, ?> attribute = getAttribute (selectedField .getName ());
196
+
197
+ // Let's apply left outer join to retrieve optional many-to-many associations
198
+ boolean isOptional = (PersistentAttributeType .MANY_TO_MANY == attribute .getPersistentAttributeType ());
199
+
200
+ reuseJoin (from , selectedField .getName (), isOptional );
188
201
}
189
202
}
190
203
}
@@ -251,6 +264,22 @@ protected boolean isOrderByArgument(Argument argument) {
251
264
return GraphQLJpaSchemaBuilder .ORDER_BY_PARAM_NAME .equals (argument .getName ());
252
265
}
253
266
267
+ protected boolean isOptionalArgument (Argument argument ) {
268
+ return OPTIONAL .equals (argument .getName ());
269
+ }
270
+
271
+ protected Optional <Argument > getArgument (Field selectedField , String argumentName ) {
272
+ return selectedField .getArguments ()
273
+ .stream ()
274
+ .filter (it -> it .getName ()
275
+ .equals (argumentName ))
276
+ .findFirst ();
277
+ }
278
+
279
+ protected <R extends Attribute <?,?>> R getAttribute (String attributeName ) {
280
+ return (R ) entityType .getAttribute (attributeName );
281
+ }
282
+
254
283
@ SuppressWarnings ( "unchecked" )
255
284
protected Predicate getPredicate (CriteriaBuilder cb , Root <?> from , From <?,?> path , DataFetchingEnvironment environment , Argument argument ) {
256
285
@@ -259,6 +288,7 @@ protected Predicate getPredicate(CriteriaBuilder cb, Root<?> from, From<?,?> pat
259
288
260
289
// If the argument is a list, let's assume we need to join and do an 'in' clause
261
290
if (argumentEntityAttribute instanceof PluralAttribute ) {
291
+ // Apply left outer join to retrieve optional associations
262
292
return reuseJoin (from , argument .getName (), false )
263
293
.in (convertValue (environment , argument , argument .getValue ()));
264
294
}
@@ -272,7 +302,7 @@ protected Predicate getPredicate(CriteriaBuilder cb, Root<?> from, From<?,?> pat
272
302
} else {
273
303
String fieldName = argument .getName ().split ("\\ ." )[0 ];
274
304
275
- From <?,?> join = getCompoundJoin (path , argument .getName (), false );
305
+ From <?,?> join = getCompoundJoin (path , argument .getName (), true );
276
306
Argument where = new Argument ("where" , argument .getValue ());
277
307
Map <String , Object > variables = Optional .ofNullable (environment .getContext ())
278
308
.filter (it -> it instanceof Map )
@@ -388,13 +418,17 @@ private Predicate getFieldPredicate(String fieldName, CriteriaBuilder cb, From<?
388
418
this .getObjectType (environment , argument ),
389
419
new Field (fieldName ));
390
420
Map <String , Object > arguments = new LinkedHashMap <>();
421
+ boolean isOptional = false ;
391
422
392
- if (Logical .names ().contains (argument .getName ()))
423
+ if (Logical .names ().contains (argument .getName ())) {
393
424
arguments .put (logical .name (), environment .getArgument (argument .getName ()));
394
- else
425
+ } else {
395
426
arguments .put (logical .name (), environment .getArgument (fieldName ));
427
+
428
+ isOptional = isOptionalAttribute (getAttribute (environment , argument ));
429
+ }
396
430
397
- return getArgumentPredicate (cb , reuseJoin (path , fieldName , false ),
431
+ return getArgumentPredicate (cb , reuseJoin (path , fieldName , isOptional ),
398
432
wherePredicateEnvironment (environment , fieldDefinition , arguments ),
399
433
new Argument (logical .name (), expressionValue ));
400
434
}
@@ -519,9 +553,7 @@ private Join<?,?> reuseJoin(From<?, ?> path, String fieldName, boolean outer) {
519
553
520
554
for (Join <?,?> join : path .getJoins ()) {
521
555
if (join .getAttribute ().getName ().equals (fieldName )) {
522
- if ((join .getJoinType () == JoinType .LEFT ) == outer ) {
523
- return join ;
524
- }
556
+ return join ;
525
557
}
526
558
}
527
559
return outer ? path .join (fieldName , JoinType .LEFT ) : path .join (fieldName );
@@ -629,6 +661,14 @@ private Attribute<?,?> getAttribute(DataFetchingEnvironment environment, Argumen
629
661
return entityType .getAttribute (argument .getName ());
630
662
}
631
663
664
+ private boolean isOptionalAttribute (Attribute <?,?> attribute ) {
665
+ if (SingularAttribute .class .isInstance (attribute )) {
666
+ return SingularAttribute .class .cast (attribute ).isOptional ();
667
+ }
668
+
669
+ return false ;
670
+ }
671
+
632
672
/**
633
673
* Resolve JPA model entity type from GraphQL objectType
634
674
*
@@ -777,14 +817,38 @@ protected final Stream<Field> flatten(Field field) {
777
817
778
818
779
819
@ SuppressWarnings ( "unchecked" )
780
- protected final <R extends Value > R getObjectFieldValue (ObjectValue objectValue , String fieldName ) {
820
+ protected final <R extends Value <?> > R getObjectFieldValue (ObjectValue objectValue , String fieldName ) {
781
821
return (R ) getObjectField (objectValue , fieldName ).map (it -> it .getValue ())
782
822
.orElse (new NullValue ());
783
823
}
784
824
785
825
@ SuppressWarnings ( "unchecked" )
786
- protected final <R > R getArgumentValue (Argument argument ) {
787
- return (R ) argument .getValue ();
826
+ protected final <T > T getArgumentValue (DataFetchingEnvironment environment , Argument argument , Class <T > type ) {
827
+ Value <?> value = argument .getValue ();
828
+
829
+ if (VariableReference .class .isInstance (value )) {
830
+ return (T )
831
+ environment .getExecutionContext ()
832
+ .getVariables ()
833
+ .get (VariableReference .class .cast (value ).getName ());
834
+ }
835
+ else if (BooleanValue .class .isInstance (value )) {
836
+ return (T ) new Boolean (BooleanValue .class .cast (value ).isValue ());
837
+ }
838
+ else if (IntValue .class .isInstance (value )) {
839
+ return (T ) IntValue .class .cast (value ).getValue ();
840
+ }
841
+ else if (StringValue .class .isInstance (value )) {
842
+ return (T ) StringValue .class .cast (value ).getValue ();
843
+ }
844
+ else if (FloatValue .class .isInstance (value )) {
845
+ return (T ) FloatValue .class .cast (value ).getValue ();
846
+ }
847
+ else if (NullValue .class .isInstance (value )) {
848
+ return (T ) null ;
849
+ }
850
+
851
+ throw new IllegalArgumentException ("Not supported" );
788
852
}
789
853
790
854
protected final Optional <ObjectField > getObjectField (ObjectValue objectValue , String fieldName ) {
0 commit comments