Skip to content

Commit 1c72160

Browse files
committed
fix: Apply left outer join to retrieve optional associations
1 parent efc5278 commit 1c72160

File tree

2 files changed

+131
-9
lines changed

2 files changed

+131
-9
lines changed

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -177,14 +177,15 @@ protected final List<Argument> getFieldArguments(Field field, CriteriaQuery<?> q
177177
if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.MANY_TO_ONE
178178
|| attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_ONE
179179
) {
180-
reuseJoin(from, selectedField.getName(), false);
180+
// Apply left outer join to retrieve optional associations
181+
reuseJoin(from, selectedField.getName(), true);
181182
}
182183
}
183184
} else {
184185
// We must add plural attributes with explicit fetch to avoid Hibernate error:
185186
// "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);
187+
// Apply left outer join to retrieve optional associations
188+
reuseJoin(from, selectedField.getName(), true);
188189
}
189190
}
190191
}
@@ -259,7 +260,8 @@ protected Predicate getPredicate(CriteriaBuilder cb, Root<?> from, From<?,?> pat
259260

260261
// If the argument is a list, let's assume we need to join and do an 'in' clause
261262
if (argumentEntityAttribute instanceof PluralAttribute) {
262-
return reuseJoin(from, argument.getName(), false)
263+
// Apply left outer join to retrieve optional associations
264+
return reuseJoin(from, argument.getName(), true)
263265
.in(convertValue(environment, argument, argument.getValue()));
264266
}
265267

@@ -394,8 +396,9 @@ private Predicate getFieldPredicate(String fieldName, CriteriaBuilder cb, From<?
394396
Map<String, Object> arguments = new LinkedHashMap<>();
395397

396398
arguments.put(logical.name(), environment.getArgument(fieldName));
397-
398-
return getArgumentPredicate(cb, reuseJoin(path, fieldName, false),
399+
400+
// Apply left outer join to retrieve optional associations
401+
return getArgumentPredicate(cb, reuseJoin(path, fieldName, true),
399402
wherePredicateEnvironment(environment, fieldDefinition, arguments),
400403
new Argument(logical.name(), expressionValue));
401404
}

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

Lines changed: 122 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -740,16 +740,37 @@ public void queryWithWhereInsideOneToManyRelations() {
740740
"}";
741741

742742
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]}]}"
743+
+ "{id=1000, name=Luke Skywalker, favoriteDroid={name=C-3PO}, friends=["
744+
+ "{name=C-3PO, appearsIn=[A_NEW_HOPE]}, "
745+
+ "{name=Han Solo, appearsIn=[A_NEW_HOPE]}, "
746+
+ "{name=Leia Organa, appearsIn=[A_NEW_HOPE]}, "
747+
+ "{name=R2-D2, appearsIn=[A_NEW_HOPE]}"
748+
+ "]}, "
749+
+ "{id=1001, name=Darth Vader, favoriteDroid={name=R2-D2}, friends=["
750+
+ "{name=Wilhuff Tarkin, appearsIn=[A_NEW_HOPE]}"
751+
+ "]}, "
752+
+ "{id=1002, name=Han Solo, favoriteDroid=null, friends=["
753+
+ "{name=Leia Organa, appearsIn=[A_NEW_HOPE]}, "
754+
+ "{name=Luke Skywalker, appearsIn=[A_NEW_HOPE]}, "
755+
+ "{name=R2-D2, appearsIn=[A_NEW_HOPE]}"
756+
+ "]}, "
757+
+ "{id=1003, name=Leia Organa, favoriteDroid=null, friends=["
758+
+ "{name=C-3PO, appearsIn=[A_NEW_HOPE]}, "
759+
+ "{name=Han Solo, appearsIn=[A_NEW_HOPE]}, "
760+
+ "{name=Luke Skywalker, appearsIn=[A_NEW_HOPE]}, "
761+
+ "{name=R2-D2, appearsIn=[A_NEW_HOPE]}"
762+
+ "]}, "
763+
+ "{id=1004, name=Wilhuff Tarkin, favoriteDroid=null, friends=["
764+
+ "{name=Darth Vader, appearsIn=[A_NEW_HOPE]}"
765+
+ "]}"
745766
+ "]}}";
746767

747768
//when:
748769
Object result = executor.execute(query).getData();
749770

750771
//then:
751772
assertThat(result.toString()).isEqualTo(expected);
752-
}
773+
}
753774

754775

755776
@Test
@@ -935,4 +956,102 @@ public void queryFilterNestedManyToManyRelationCriteria() {
935956
}
936957

937958

959+
@Test
960+
public void queryWithWhereInsideOneToManyRelationsShouldApplyFilterCriterias() {
961+
//given:
962+
String query = "query { "
963+
+ " Humans(where: {"
964+
+ "friends: {appearsIn: {IN: A_NEW_HOPE}} "
965+
+ "favoriteDroid: {name: {EQ: \"C-3PO\"}} "
966+
+ "}) {" +
967+
" select {" +
968+
" id" +
969+
" name" +
970+
" favoriteDroid {" +
971+
" name" +
972+
" }" +
973+
" friends {" +
974+
" name" +
975+
" appearsIn" +
976+
" }" +
977+
" }" +
978+
" }" +
979+
"}";
980+
981+
String expected = "{Humans={select=["
982+
+ "{id=1000, name=Luke Skywalker, favoriteDroid={name=C-3PO}, friends=["
983+
+ "{name=C-3PO, appearsIn=[A_NEW_HOPE]}, "
984+
+ "{name=Han Solo, appearsIn=[A_NEW_HOPE]}, "
985+
+ "{name=Leia Organa, appearsIn=[A_NEW_HOPE]}, "
986+
+ "{name=R2-D2, appearsIn=[A_NEW_HOPE]}"
987+
+ "]}"
988+
+ "]}}";
989+
990+
//when:
991+
Object result = executor.execute(query).getData();
992+
993+
//then:
994+
assertThat(result.toString()).isEqualTo(expected);
995+
}
996+
997+
@Test
998+
public void queryWithOneToManyRelationsShouldUseLeftOuterJoin() {
999+
//given:
1000+
String query = "query { " +
1001+
" Humans {" +
1002+
" select {" +
1003+
" id" +
1004+
" name" +
1005+
" homePlanet" +
1006+
" favoriteDroid {" +
1007+
" name" +
1008+
" }" +
1009+
" }" +
1010+
" }" +
1011+
"}";
1012+
1013+
String expected = "{Humans={select=["
1014+
+ "{id=1000, name=Luke Skywalker, homePlanet=Tatooine, favoriteDroid={name=C-3PO}}, "
1015+
+ "{id=1001, name=Darth Vader, homePlanet=Tatooine, favoriteDroid={name=R2-D2}}, "
1016+
+ "{id=1002, name=Han Solo, homePlanet=null, favoriteDroid=null}, "
1017+
+ "{id=1003, name=Leia Organa, homePlanet=Alderaan, favoriteDroid=null}, "
1018+
+ "{id=1004, name=Wilhuff Tarkin, homePlanet=null, favoriteDroid=null}"
1019+
+ "]}}";
1020+
1021+
//when:
1022+
Object result = executor.execute(query).getData();
1023+
1024+
//then:
1025+
assertThat(result.toString()).isEqualTo(expected);
1026+
}
1027+
1028+
1029+
@Test
1030+
public void queryWithWhereOneToManyRelationsShouldUseLeftOuterJoinAndApplyCriteria() {
1031+
//given:
1032+
String query = "query { " +
1033+
" Humans(where: {favoriteDroid: {name: {EQ: \"C-3PO\"}}}) {" +
1034+
" select {" +
1035+
" id" +
1036+
" name" +
1037+
" homePlanet" +
1038+
" favoriteDroid {" +
1039+
" name" +
1040+
" }" +
1041+
" }" +
1042+
" }" +
1043+
"}";
1044+
1045+
String expected = "{Humans={select=["
1046+
+ "{id=1000, name=Luke Skywalker, homePlanet=Tatooine, favoriteDroid={name=C-3PO}}"
1047+
+ "]}}";
1048+
1049+
//when:
1050+
Object result = executor.execute(query).getData();
1051+
1052+
//then:
1053+
assertThat(result.toString()).isEqualTo(expected);
1054+
}
1055+
1056+
9381057
}

0 commit comments

Comments
 (0)