Skip to content

Commit 7ff25ae

Browse files
authoredApr 28, 2019
fix: Apply left outer join to retrieve optional associations [WIP] (#105)
* fix: Apply left outer join to retrieve optional associations * chore: polish GraphQLJPASchemaBuilder * chore: clean DriodFunction orphan * fix: change primaryFunction association type to @OnetoOne * feat: first crack at optional argument join handling * fix: set author association type to optional=false * fix: add optional unit tests * fix: compile error
1 parent 6e9da5b commit 7ff25ae

File tree

6 files changed

+341
-79
lines changed

6 files changed

+341
-79
lines changed
 

‎graphql-jpa-query-example-merge/src/main/java/com/introproventures/graphql/jpa/query/example/starwars/DroidFunction.java

-27
This file was deleted.

‎graphql-jpa-query-example-model-books/src/main/java/com/introproventures/graphql/jpa/query/schema/model/book/Book.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public class Book {
4343
@GraphQLIgnoreFilter
4444
String description;
4545

46-
@ManyToOne(fetch=FetchType.LAZY)
46+
@ManyToOne(fetch=FetchType.LAZY, optional = false)
4747
Author author;
4848

4949
@Enumerated(EnumType.STRING)

‎graphql-jpa-query-example-model-starwars/src/main/java/com/introproventures/graphql/jpa/query/schema/model/starwars/Droid.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,9 @@
1919
import javax.persistence.Entity;
2020
import javax.persistence.FetchType;
2121
import javax.persistence.JoinColumn;
22-
import javax.persistence.ManyToOne;
22+
import javax.persistence.OneToOne;
2323

2424
import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription;
25-
2625
import lombok.Data;
2726
import lombok.EqualsAndHashCode;
2827

@@ -32,7 +31,7 @@
3231
@EqualsAndHashCode(callSuper=true)
3332
public class Droid extends Character {
3433

35-
@ManyToOne(fetch = FetchType.LAZY)
34+
@OneToOne(fetch = FetchType.LAZY)
3635
@JoinColumn(name = "primary_function")
3736
DroidFunction primaryFunction;
3837

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

+46-28
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,13 @@
3636
import javax.persistence.Convert;
3737
import javax.persistence.EntityManager;
3838
import javax.persistence.Transient;
39-
import javax.persistence.metamodel.*;
39+
import javax.persistence.metamodel.Attribute;
40+
import javax.persistence.metamodel.EmbeddableType;
41+
import javax.persistence.metamodel.EntityType;
42+
import javax.persistence.metamodel.ManagedType;
43+
import javax.persistence.metamodel.PluralAttribute;
44+
import javax.persistence.metamodel.SingularAttribute;
45+
import javax.persistence.metamodel.Type;
4046

4147
import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription;
4248
import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnore;
@@ -221,20 +227,12 @@ private GraphQLArgument distinctArgument(EntityType<?> entityType) {
221227
}
222228

223229
private GraphQLArgument getWhereArgument(ManagedType<?> managedType) {
224-
String typeName="";
225-
if (managedType instanceof EmbeddableType){
226-
typeName = managedType.getJavaType().getSimpleName()+"EmbeddableType";
227-
} else if (managedType instanceof EntityType) {
228-
typeName = ((EntityType<?>)managedType).getName();
229-
}
230-
231-
String type = namingStrategy.pluralize(typeName)+"CriteriaExpression";
232-
233-
GraphQLArgument whereArgument = whereArgumentsMap.get(managedType.getJavaType());
234-
235-
if(whereArgument != null)
236-
return whereArgument;
237-
230+
return whereArgumentsMap.computeIfAbsent(managedType.getJavaType(), (javaType) -> computeWhereArgument(managedType));
231+
}
232+
233+
private GraphQLArgument computeWhereArgument(ManagedType<?> managedType) {
234+
String type=resolveWhereArgumentTypeName(managedType);
235+
238236
GraphQLInputObjectType whereInputObject = GraphQLInputObjectType.newInputObject()
239237
.name(type)
240238
.description("Where logical AND specification of the provided list of criteria expressions")
@@ -266,29 +264,38 @@ private GraphQLArgument getWhereArgument(ManagedType<?> managedType) {
266264
)
267265
.build();
268266

269-
whereArgument = GraphQLArgument.newArgument()
270-
.name(QUERY_WHERE_PARAM_NAME)
271-
.description("Where logical specification")
272-
.type(whereInputObject)
273-
.build();
274-
275-
whereArgumentsMap.put(managedType.getJavaType(), whereArgument);
276-
277-
return whereArgument;
267+
return GraphQLArgument.newArgument()
268+
.name(QUERY_WHERE_PARAM_NAME)
269+
.description("Where logical specification")
270+
.type(whereInputObject)
271+
.build();
278272

279273
}
280274

281-
private GraphQLInputObjectType getWhereInputType(ManagedType<?> managedType) {
282-
return inputObjectCache.computeIfAbsent(managedType, this::computeWhereInputType);
275+
private String resolveWhereArgumentTypeName(ManagedType<?> managedType) {
276+
String typeName=resolveTypeName(managedType);
277+
278+
return namingStrategy.pluralize(typeName)+"CriteriaExpression";
283279
}
284280

285-
private String resolveWhereInputTypeName(ManagedType<?> managedType) {
281+
private String resolveTypeName(ManagedType<?> managedType) {
286282
String typeName="";
283+
287284
if (managedType instanceof EmbeddableType){
288285
typeName = managedType.getJavaType().getSimpleName()+"EmbeddableType";
289286
} else if (managedType instanceof EntityType) {
290287
typeName = ((EntityType<?>)managedType).getName();
291288
}
289+
290+
return typeName;
291+
}
292+
293+
private GraphQLInputObjectType getWhereInputType(ManagedType<?> managedType) {
294+
return inputObjectCache.computeIfAbsent(managedType, this::computeWhereInputType);
295+
}
296+
297+
private String resolveWhereInputTypeName(ManagedType<?> managedType) {
298+
String typeName=resolveTypeName(managedType);
292299

293300
return namingStrategy.pluralize(typeName)+"RelationCriteriaExpression";
294301

@@ -633,6 +640,8 @@ && isNotIgnoredOrder(attribute) ) {
633640

634641
// TODO fix page count query
635642
arguments.add(getWhereArgument(foreignType));
643+
644+
arguments.add(optionalArgument(SingularAttribute.class.cast(attribute)));
636645

637646
} // Get Sub-Objects fields queries via DataFetcher
638647
else if (attribute instanceof PluralAttribute
@@ -644,7 +653,7 @@ else if (attribute instanceof PluralAttribute
644653
arguments.add(getWhereArgument(elementType));
645654
dataFetcher = new GraphQLJpaOneToManyDataFetcher(entityManager, baseEntity, (PluralAttribute) attribute);
646655
}
647-
656+
648657
return GraphQLFieldDefinition.newFieldDefinition()
649658
.name(attribute.getName())
650659
.description(getSchemaDescription(attribute.getJavaMember()))
@@ -653,6 +662,15 @@ else if (attribute instanceof PluralAttribute
653662
.argument(arguments)
654663
.build();
655664
}
665+
666+
private GraphQLArgument optionalArgument(SingularAttribute<?,?> attribute) {
667+
return GraphQLArgument.newArgument()
668+
.name("optional")
669+
.description("Optional association specification")
670+
.type(Scalars.GraphQLBoolean)
671+
.defaultValue(attribute.isOptional())
672+
.build();
673+
}
656674

657675
protected ManagedType<?> getForeignType(Attribute<?,?> attribute) {
658676
if(SingularAttribute.class.isInstance(attribute))

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

+81-17
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@
9090
*/
9191
class QraphQLJpaBaseDataFetcher implements DataFetcher<Object> {
9292

93+
private static final String OPTIONAL = "optional";
94+
9395
// "__typename" is part of the graphql introspection spec and has to be ignored
9496
private static final String TYPENAME = "__typename";
9597

@@ -167,24 +169,35 @@ protected final List<Argument> getFieldArguments(Field field, CriteriaQuery<?> q
167169
// Process where arguments clauses.
168170
arguments.addAll(selectedField.getArguments()
169171
.stream()
170-
.filter(it -> !isOrderByArgument(it))
172+
.filter(it -> !isOrderByArgument(it) && !isOptionalArgument(it))
171173
.map(it -> new Argument(selectedField.getName() + "." + it.getName(), it.getValue()))
172174
.collect(Collectors.toList()));
173175

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
175177
if (fieldPath.getModel() instanceof SingularAttribute) {
176178
SingularAttribute<?,?> attribute = (SingularAttribute<?,?>) fieldPath.getModel();
177179
if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.MANY_TO_ONE
178180
|| attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_ONE
179181
) {
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);
181190
}
182191
}
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:
185194
// "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);
188201
}
189202
}
190203
}
@@ -251,6 +264,22 @@ protected boolean isOrderByArgument(Argument argument) {
251264
return GraphQLJpaSchemaBuilder.ORDER_BY_PARAM_NAME.equals(argument.getName());
252265
}
253266

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+
254283
@SuppressWarnings( "unchecked" )
255284
protected Predicate getPredicate(CriteriaBuilder cb, Root<?> from, From<?,?> path, DataFetchingEnvironment environment, Argument argument) {
256285

@@ -259,6 +288,7 @@ protected Predicate getPredicate(CriteriaBuilder cb, Root<?> from, From<?,?> pat
259288

260289
// If the argument is a list, let's assume we need to join and do an 'in' clause
261290
if (argumentEntityAttribute instanceof PluralAttribute) {
291+
// Apply left outer join to retrieve optional associations
262292
return reuseJoin(from, argument.getName(), false)
263293
.in(convertValue(environment, argument, argument.getValue()));
264294
}
@@ -272,7 +302,7 @@ protected Predicate getPredicate(CriteriaBuilder cb, Root<?> from, From<?,?> pat
272302
} else {
273303
String fieldName = argument.getName().split("\\.")[0];
274304

275-
From<?,?> join = getCompoundJoin(path, argument.getName(), false);
305+
From<?,?> join = getCompoundJoin(path, argument.getName(), true);
276306
Argument where = new Argument("where", argument.getValue());
277307
Map<String, Object> variables = Optional.ofNullable(environment.getContext())
278308
.filter(it -> it instanceof Map)
@@ -388,13 +418,17 @@ private Predicate getFieldPredicate(String fieldName, CriteriaBuilder cb, From<?
388418
this.getObjectType(environment, argument),
389419
new Field(fieldName));
390420
Map<String, Object> arguments = new LinkedHashMap<>();
421+
boolean isOptional = false;
391422

392-
if(Logical.names().contains(argument.getName()))
423+
if(Logical.names().contains(argument.getName())) {
393424
arguments.put(logical.name(), environment.getArgument(argument.getName()));
394-
else
425+
} else {
395426
arguments.put(logical.name(), environment.getArgument(fieldName));
427+
428+
isOptional = isOptionalAttribute(getAttribute(environment, argument));
429+
}
396430

397-
return getArgumentPredicate(cb, reuseJoin(path, fieldName, false),
431+
return getArgumentPredicate(cb, reuseJoin(path, fieldName, isOptional),
398432
wherePredicateEnvironment(environment, fieldDefinition, arguments),
399433
new Argument(logical.name(), expressionValue));
400434
}
@@ -519,9 +553,7 @@ private Join<?,?> reuseJoin(From<?, ?> path, String fieldName, boolean outer) {
519553

520554
for (Join<?,?> join : path.getJoins()) {
521555
if (join.getAttribute().getName().equals(fieldName)) {
522-
if ((join.getJoinType() == JoinType.LEFT) == outer) {
523-
return join;
524-
}
556+
return join;
525557
}
526558
}
527559
return outer ? path.join(fieldName, JoinType.LEFT) : path.join(fieldName);
@@ -629,6 +661,14 @@ private Attribute<?,?> getAttribute(DataFetchingEnvironment environment, Argumen
629661
return entityType.getAttribute(argument.getName());
630662
}
631663

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+
632672
/**
633673
* Resolve JPA model entity type from GraphQL objectType
634674
*
@@ -777,14 +817,38 @@ protected final Stream<Field> flatten(Field field) {
777817

778818

779819
@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) {
781821
return (R) getObjectField(objectValue, fieldName).map(it-> it.getValue())
782822
.orElse(new NullValue());
783823
}
784824

785825
@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");
788852
}
789853

790854
protected final Optional<ObjectField> getObjectField(ObjectValue objectValue, String fieldName) {

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

+211-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static org.assertj.core.api.Assertions.assertThat;
2020

21+
import java.util.Collections;
2122
import java.util.HashMap;
2223
import java.util.List;
2324
import java.util.Map;
@@ -740,17 +741,126 @@ public void queryWithWhereInsideOneToManyRelations() {
740741
"}";
741742

742743
String expected = "{Humans={select=["
743-
+ "{id=1000, name=Luke Skywalker, favoriteDroid={name=C-3PO}, friends=[{name=C-3PO, appearsIn=[A_NEW_HOPE]}, {name=Han Solo, appearsIn=[A_NEW_HOPE]}, {name=Leia Organa, appearsIn=[A_NEW_HOPE]}, {name=R2-D2, appearsIn=[A_NEW_HOPE]}]}, "
744-
+ "{id=1001, name=Darth Vader, favoriteDroid={name=R2-D2}, friends=[{name=Wilhuff Tarkin, appearsIn=[A_NEW_HOPE]}]}"
744+
+ "{id=1000, name=Luke Skywalker, favoriteDroid={name=C-3PO}, friends=["
745+
+ "{name=C-3PO, appearsIn=[A_NEW_HOPE]}, "
746+
+ "{name=Han Solo, appearsIn=[A_NEW_HOPE]}, "
747+
+ "{name=Leia Organa, appearsIn=[A_NEW_HOPE]}, "
748+
+ "{name=R2-D2, appearsIn=[A_NEW_HOPE]}"
749+
+ "]}, "
750+
+ "{id=1001, name=Darth Vader, favoriteDroid={name=R2-D2}, friends=["
751+
+ "{name=Wilhuff Tarkin, appearsIn=[A_NEW_HOPE]}"
752+
+ "]}, "
753+
+ "{id=1002, name=Han Solo, favoriteDroid=null, friends=["
754+
+ "{name=Leia Organa, appearsIn=[A_NEW_HOPE]}, "
755+
+ "{name=Luke Skywalker, appearsIn=[A_NEW_HOPE]}, "
756+
+ "{name=R2-D2, appearsIn=[A_NEW_HOPE]}"
757+
+ "]}, "
758+
+ "{id=1003, name=Leia Organa, favoriteDroid=null, friends=["
759+
+ "{name=C-3PO, appearsIn=[A_NEW_HOPE]}, "
760+
+ "{name=Han Solo, appearsIn=[A_NEW_HOPE]}, "
761+
+ "{name=Luke Skywalker, appearsIn=[A_NEW_HOPE]}, "
762+
+ "{name=R2-D2, appearsIn=[A_NEW_HOPE]}"
763+
+ "]}, "
764+
+ "{id=1004, name=Wilhuff Tarkin, favoriteDroid=null, friends=["
765+
+ "{name=Darth Vader, appearsIn=[A_NEW_HOPE]}"
766+
+ "]}"
745767
+ "]}}";
746768

747769
//when:
748770
Object result = executor.execute(query).getData();
749771

750772
//then:
751773
assertThat(result.toString()).isEqualTo(expected);
752-
}
774+
}
775+
776+
@Test
777+
public void queryHumansWithFavoriteDroidDefaultOptionalTrue() {
778+
//given:
779+
String query = "query { "
780+
+ "Humans {" +
781+
" select {" +
782+
" id" +
783+
" name" +
784+
" homePlanet" +
785+
" favoriteDroid {" +
786+
" name" +
787+
" }" +
788+
" }" +
789+
" }" +
790+
"}";
791+
792+
String expected = "{Humans={select=["
793+
+ "{id=1000, name=Luke Skywalker, homePlanet=Tatooine, favoriteDroid={name=C-3PO}}, "
794+
+ "{id=1001, name=Darth Vader, homePlanet=Tatooine, favoriteDroid={name=R2-D2}}, "
795+
+ "{id=1002, name=Han Solo, homePlanet=null, favoriteDroid=null}, "
796+
+ "{id=1003, name=Leia Organa, homePlanet=Alderaan, favoriteDroid=null}, "
797+
+ "{id=1004, name=Wilhuff Tarkin, homePlanet=null, favoriteDroid=null}"
798+
+ "]}}";
799+
800+
//when:
801+
Object result = executor.execute(query).getData();
802+
803+
//then:
804+
assertThat(result.toString()).isEqualTo(expected);
805+
}
806+
807+
@Test
808+
public void queryHumansWittFavorideDroidExplicitOptionalFalse() {
809+
//given:
810+
String query = "query { "
811+
+ "Humans {" +
812+
" select {" +
813+
" id" +
814+
" name" +
815+
" homePlanet" +
816+
" favoriteDroid(optional: false) {" +
817+
" name" +
818+
" }" +
819+
" }" +
820+
" }" +
821+
"}";
822+
823+
String expected = "{Humans={select=["
824+
+ "{id=1000, name=Luke Skywalker, homePlanet=Tatooine, favoriteDroid={name=C-3PO}}, "
825+
+ "{id=1001, name=Darth Vader, homePlanet=Tatooine, favoriteDroid={name=R2-D2}}"
826+
+ "]}}";
827+
828+
//when:
829+
Object result = executor.execute(query).getData();
830+
831+
//then:
832+
assertThat(result.toString()).isEqualTo(expected);
833+
}
753834

835+
@Test
836+
public void queryHumansWittFavorideDroidExplicitOptionalFalseParameterBinding() {
837+
//given:
838+
String query = "query($optional: Boolean) { "
839+
+ "Humans {" +
840+
" select {" +
841+
" id" +
842+
" name" +
843+
" homePlanet" +
844+
" favoriteDroid(optional: $optional) {" +
845+
" name" +
846+
" }" +
847+
" }" +
848+
" }" +
849+
"}";
850+
851+
Map<String, Object> variables = Collections.singletonMap("optional", false);
852+
853+
String expected = "{Humans={select=["
854+
+ "{id=1000, name=Luke Skywalker, homePlanet=Tatooine, favoriteDroid={name=C-3PO}}, "
855+
+ "{id=1001, name=Darth Vader, homePlanet=Tatooine, favoriteDroid={name=R2-D2}}"
856+
+ "]}}";
857+
858+
//when:
859+
Object result = executor.execute(query, variables).getData();
860+
861+
//then:
862+
assertThat(result.toString()).isEqualTo(expected);
863+
}
754864

755865
@Test
756866
public void queryFilterManyToOneEmbdeddedCriteria() {
@@ -935,4 +1045,102 @@ public void queryFilterNestedManyToManyRelationCriteria() {
9351045
}
9361046

9371047

1048+
@Test
1049+
public void queryWithWhereInsideOneToManyRelationsShouldApplyFilterCriterias() {
1050+
//given:
1051+
String query = "query { "
1052+
+ " Humans(where: {"
1053+
+ "friends: {appearsIn: {IN: A_NEW_HOPE}} "
1054+
+ "favoriteDroid: {name: {EQ: \"C-3PO\"}} "
1055+
+ "}) {" +
1056+
" select {" +
1057+
" id" +
1058+
" name" +
1059+
" favoriteDroid {" +
1060+
" name" +
1061+
" }" +
1062+
" friends {" +
1063+
" name" +
1064+
" appearsIn" +
1065+
" }" +
1066+
" }" +
1067+
" }" +
1068+
"}";
1069+
1070+
String expected = "{Humans={select=["
1071+
+ "{id=1000, name=Luke Skywalker, favoriteDroid={name=C-3PO}, friends=["
1072+
+ "{name=C-3PO, appearsIn=[A_NEW_HOPE]}, "
1073+
+ "{name=Han Solo, appearsIn=[A_NEW_HOPE]}, "
1074+
+ "{name=Leia Organa, appearsIn=[A_NEW_HOPE]}, "
1075+
+ "{name=R2-D2, appearsIn=[A_NEW_HOPE]}"
1076+
+ "]}"
1077+
+ "]}}";
1078+
1079+
//when:
1080+
Object result = executor.execute(query).getData();
1081+
1082+
//then:
1083+
assertThat(result.toString()).isEqualTo(expected);
1084+
}
1085+
1086+
@Test
1087+
public void queryWithOneToManyRelationsShouldUseLeftOuterJoin() {
1088+
//given:
1089+
String query = "query { " +
1090+
" Humans {" +
1091+
" select {" +
1092+
" id" +
1093+
" name" +
1094+
" homePlanet" +
1095+
" favoriteDroid {" +
1096+
" name" +
1097+
" }" +
1098+
" }" +
1099+
" }" +
1100+
"}";
1101+
1102+
String expected = "{Humans={select=["
1103+
+ "{id=1000, name=Luke Skywalker, homePlanet=Tatooine, favoriteDroid={name=C-3PO}}, "
1104+
+ "{id=1001, name=Darth Vader, homePlanet=Tatooine, favoriteDroid={name=R2-D2}}, "
1105+
+ "{id=1002, name=Han Solo, homePlanet=null, favoriteDroid=null}, "
1106+
+ "{id=1003, name=Leia Organa, homePlanet=Alderaan, favoriteDroid=null}, "
1107+
+ "{id=1004, name=Wilhuff Tarkin, homePlanet=null, favoriteDroid=null}"
1108+
+ "]}}";
1109+
1110+
//when:
1111+
Object result = executor.execute(query).getData();
1112+
1113+
//then:
1114+
assertThat(result.toString()).isEqualTo(expected);
1115+
}
1116+
1117+
1118+
@Test
1119+
public void queryWithWhereOneToManyRelationsShouldUseLeftOuterJoinAndApplyCriteria() {
1120+
//given:
1121+
String query = "query { " +
1122+
" Humans(where: {favoriteDroid: {name: {EQ: \"C-3PO\"}}}) {" +
1123+
" select {" +
1124+
" id" +
1125+
" name" +
1126+
" homePlanet" +
1127+
" favoriteDroid {" +
1128+
" name" +
1129+
" }" +
1130+
" }" +
1131+
" }" +
1132+
"}";
1133+
1134+
String expected = "{Humans={select=["
1135+
+ "{id=1000, name=Luke Skywalker, homePlanet=Tatooine, favoriteDroid={name=C-3PO}}"
1136+
+ "]}}";
1137+
1138+
//when:
1139+
Object result = executor.execute(query).getData();
1140+
1141+
//then:
1142+
assertThat(result.toString()).isEqualTo(expected);
1143+
}
1144+
1145+
9381146
}

0 commit comments

Comments
 (0)
Please sign in to comment.