Skip to content

Commit 148e129

Browse files
committed
fix: Apply left outer join to retrieve optional associations
1 parent 5dd999a commit 148e129

File tree

2 files changed

+133
-13
lines changed

2 files changed

+133
-13
lines changed

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@
5252

5353
import com.introproventures.graphql.jpa.query.annotation.GraphQLDefaultOrderBy;
5454
import com.introproventures.graphql.jpa.query.schema.impl.PredicateFilter.Criteria;
55-
5655
import graphql.GraphQLException;
5756
import graphql.execution.ValuesResolver;
5857
import graphql.language.Argument;
@@ -178,14 +177,15 @@ protected final List<Argument> getFieldArguments(Field field, CriteriaQuery<?> q
178177
if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.MANY_TO_ONE
179178
|| attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_ONE
180179
) {
181-
reuseJoin(from, selectedField.getName(), false);
180+
// Apply left outer join to retrieve optional associations
181+
reuseJoin(from, selectedField.getName(), true);
182182
}
183183
}
184184
} else {
185185
// We must add plural attributes with explicit join to avoid Hibernate error:
186186
// "query specified join fetching, but the owner of the fetched association was not present in the select list"
187-
// TODO Let's try detect many-to-many relation and reuse outer join
188-
reuseJoin(from, selectedField.getName(), false);
187+
// Apply left outer join to retrieve optional associations
188+
reuseJoin(from, selectedField.getName(), true);
189189
}
190190
}
191191
}
@@ -260,7 +260,8 @@ protected Predicate getPredicate(CriteriaBuilder cb, Root<?> from, From<?,?> pat
260260

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

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

397398
arguments.put(logical.name(), environment.getArgument(fieldName));
398-
399-
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),
400402
wherePredicateEnvironment(environment, fieldDefinition, arguments),
401403
new Argument(logical.name(), expressionValue));
402404
}

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

Lines changed: 124 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import javax.persistence.Query;
2727
import javax.transaction.Transactional;
2828

29+
import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaExecutor;
30+
import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaSchemaBuilder;
2931
import org.junit.Test;
3032
import org.junit.runner.RunWith;
3133
import org.springframework.beans.factory.annotation.Autowired;
@@ -35,9 +37,6 @@
3537
import org.springframework.test.context.TestPropertySource;
3638
import org.springframework.test.context.junit4.SpringRunner;
3739

38-
import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaExecutor;
39-
import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaSchemaBuilder;
40-
4140
@RunWith(SpringRunner.class)
4241
@SpringBootTest
4342
@TestPropertySource({"classpath:hibernate.properties"})
@@ -740,15 +739,134 @@ public void queryWithWhereInsideOneToManyRelations() {
740739
"}";
741740

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

747767
//when:
748768
Object result = executor.execute(query).getData();
749769

750770
//then:
751771
assertThat(result.toString()).isEqualTo(expected);
752-
}
772+
}
773+
774+
@Test
775+
public void queryWithWhereInsideOneToManyRelationsShouldApplyFilterCriterias() {
776+
//given:
777+
String query = "query { "
778+
+ " Humans(where: {"
779+
+ "friends: {appearsIn: {IN: A_NEW_HOPE}} "
780+
+ "favoriteDroid: {name: {EQ: \"C-3PO\"}} "
781+
+ "}) {" +
782+
" select {" +
783+
" id" +
784+
" name" +
785+
" favoriteDroid {" +
786+
" name" +
787+
" }" +
788+
" friends {" +
789+
" name" +
790+
" appearsIn" +
791+
" }" +
792+
" }" +
793+
" }" +
794+
"}";
795+
796+
String expected = "{Humans={select=["
797+
+ "{id=1000, name=Luke Skywalker, favoriteDroid={name=C-3PO}, friends=["
798+
+ "{name=C-3PO, appearsIn=[A_NEW_HOPE]}, "
799+
+ "{name=Han Solo, appearsIn=[A_NEW_HOPE]}, "
800+
+ "{name=Leia Organa, appearsIn=[A_NEW_HOPE]}, "
801+
+ "{name=R2-D2, appearsIn=[A_NEW_HOPE]}"
802+
+ "]}"
803+
+ "]}}";
804+
805+
//when:
806+
Object result = executor.execute(query).getData();
807+
808+
//then:
809+
assertThat(result.toString()).isEqualTo(expected);
810+
}
811+
812+
@Test
813+
public void queryWithOneToManyRelationsShouldUseLeftOuterJoin() {
814+
//given:
815+
String query = "query { " +
816+
" Humans {" +
817+
" select {" +
818+
" id" +
819+
" name" +
820+
" homePlanet" +
821+
" favoriteDroid {" +
822+
" name" +
823+
" }" +
824+
" }" +
825+
" }" +
826+
"}";
827+
828+
String expected = "{Humans={select=["
829+
+ "{id=1000, name=Luke Skywalker, homePlanet=Tatooine, favoriteDroid={name=C-3PO}}, "
830+
+ "{id=1001, name=Darth Vader, homePlanet=Tatooine, favoriteDroid={name=R2-D2}}, "
831+
+ "{id=1002, name=Han Solo, homePlanet=null, favoriteDroid=null}, "
832+
+ "{id=1003, name=Leia Organa, homePlanet=Alderaan, favoriteDroid=null}, "
833+
+ "{id=1004, name=Wilhuff Tarkin, homePlanet=null, favoriteDroid=null}"
834+
+ "]}}";
835+
836+
//when:
837+
Object result = executor.execute(query).getData();
838+
839+
//then:
840+
assertThat(result.toString()).isEqualTo(expected);
841+
}
842+
843+
844+
@Test
845+
public void queryWithWhereOneToManyRelationsShouldUseLeftOuterJoinAndApplyCriteria() {
846+
//given:
847+
String query = "query { " +
848+
" Humans(where: {favoriteDroid: {name: {EQ: \"C-3PO\"}}}) {" +
849+
" select {" +
850+
" id" +
851+
" name" +
852+
" homePlanet" +
853+
" favoriteDroid {" +
854+
" name" +
855+
" }" +
856+
" }" +
857+
" }" +
858+
"}";
859+
860+
String expected = "{Humans={select=["
861+
+ "{id=1000, name=Luke Skywalker, homePlanet=Tatooine, favoriteDroid={name=C-3PO}}"
862+
+ "]}}";
863+
864+
//when:
865+
Object result = executor.execute(query).getData();
866+
867+
//then:
868+
assertThat(result.toString()).isEqualTo(expected);
869+
}
870+
753871

754872
}

0 commit comments

Comments
 (0)