From 4f05c3df38f1a670873d33a115d376b6d903c25f Mon Sep 17 00:00:00 2001 From: molexx Date: Tue, 5 Feb 2019 00:49:20 +0000 Subject: [PATCH 01/14] add DroidFunction to StarWars model for example and test, use as primaryFunction in Droid --- .../jpa/query/example/starwars/Droid.java | 7 +++++- .../query/example/model/DroidFunction.java | 25 +++++++++++++++++++ .../query/schema/model/starwars/Droid.java | 9 +++++-- .../schema/model/starwars/DroidFunction.java | 25 +++++++++++++++++++ 4 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/model/DroidFunction.java create mode 100644 graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/starwars/DroidFunction.java diff --git a/graphql-jpa-query-example-merge/src/main/java/com/introproventures/graphql/jpa/query/example/starwars/Droid.java b/graphql-jpa-query-example-merge/src/main/java/com/introproventures/graphql/jpa/query/example/starwars/Droid.java index 113ac8521..684d10258 100644 --- a/graphql-jpa-query-example-merge/src/main/java/com/introproventures/graphql/jpa/query/example/starwars/Droid.java +++ b/graphql-jpa-query-example-merge/src/main/java/com/introproventures/graphql/jpa/query/example/starwars/Droid.java @@ -17,6 +17,9 @@ package com.introproventures.graphql.jpa.query.example.starwars; import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription; @@ -30,6 +33,8 @@ public class Droid extends Character { @GraphQLDescription("Documents the primary purpose this droid serves") - String primaryFunction; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "primary_function") + DroidFunction primaryFunction; } diff --git a/graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/model/DroidFunction.java b/graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/model/DroidFunction.java new file mode 100644 index 000000000..e22b292ae --- /dev/null +++ b/graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/model/DroidFunction.java @@ -0,0 +1,25 @@ +package com.introproventures.graphql.jpa.query.example.model; + +import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.persistence.Entity; +import javax.persistence.Id; + + +@Entity(name = "droid_function") +@GraphQLDescription("Represents the functions a droid can have") +@Data +@EqualsAndHashCode() +public class DroidFunction { + + @Id + @GraphQLDescription("Primary Key for the DroidFunction Class") + String id; + + String function; + + + +} diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/starwars/Droid.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/starwars/Droid.java index b141ca9f0..12404e0c7 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/starwars/Droid.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/starwars/Droid.java @@ -17,6 +17,9 @@ package com.introproventures.graphql.jpa.query.schema.model.starwars; import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription; @@ -29,11 +32,13 @@ @EqualsAndHashCode(callSuper=true) public class Droid extends Character { - String primaryFunction; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "primary_function") + DroidFunction primaryFunction; //description moved to getter to test it gets picked up @GraphQLDescription("Documents the primary purpose this droid serves") - public String getPrimaryFunction() { + public DroidFunction getPrimaryFunction() { return(primaryFunction); } } diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/starwars/DroidFunction.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/starwars/DroidFunction.java new file mode 100644 index 000000000..56bcefa49 --- /dev/null +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/starwars/DroidFunction.java @@ -0,0 +1,25 @@ +package com.introproventures.graphql.jpa.query.schema.model.starwars; + +import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.persistence.Entity; +import javax.persistence.Id; + + +@Entity(name = "droid_function") +@GraphQLDescription("Represents the functions a droid can have") +@Data +@EqualsAndHashCode() +public class DroidFunction { + + @Id + @GraphQLDescription("Primary Key for the DroidFunction Class") + String id; + + String function; + + + +} From 063ac6ef9c8fbacc09746eba4dd29c357bc47278 Mon Sep 17 00:00:00 2001 From: molexx Date: Tue, 5 Feb 2019 00:50:04 +0000 Subject: [PATCH 02/14] update SQL for example and tests to create DroidFunction records and reference them in Droid records --- .../src/main/resources/data.sql | 213 +++++++++--------- .../src/test/resources/data.sql | 9 +- 2 files changed, 116 insertions(+), 106 deletions(-) diff --git a/graphql-jpa-query-example/src/main/resources/data.sql b/graphql-jpa-query-example/src/main/resources/data.sql index 39032979c..3d80c6295 100644 --- a/graphql-jpa-query-example/src/main/resources/data.sql +++ b/graphql-jpa-query-example/src/main/resources/data.sql @@ -1,104 +1,109 @@ --- Insert Code Lists -insert into code_list (id, type, code, description, sequence, active, parent_id) values - (0, 'org.crygier.graphql.model.starwars.Gender', 'Male', 'Male', 1, true, null), - (1, 'org.crygier.graphql.model.starwars.Gender', 'Female', 'Female', 2, true, null); - --- Insert Droids -insert into character (id, name, primary_function, dtype) values - ('2000', 'C-3PO', 'Protocol', 'Droid'), - ('2001', 'R2-D2', 'Astromech', 'Droid'); - --- Insert Humans -insert into character (id, name, home_planet, favorite_droid_id, dtype, gender_code_id) values - ('1000', 'Luke Skywalker', 'Tatooine', '2000', 'Human', 0), - ('1001', 'Darth Vader', 'Tatooine', '2001', 'Human', 0), - ('1002', 'Han Solo', NULL, NULL, 'Human', 0), - ('1003', 'Leia Organa', 'Alderaan', NULL, 'Human', 1), - ('1004', 'Wilhuff Tarkin', NULL, NULL, 'Human', 0); - --- Luke's friends -insert into character_friends (source_id, friend_id) values - ('1000', '1002'), - ('1000', '1003'), - ('1000', '2000'), - ('1000', '2001'); - --- Luke Appears in -insert into character_appears_in (character_id, appears_in) values - ('1000', 3), - ('1000', 4), - ('1000', 5), - ('1000', 6); - --- Vader's friends -insert into character_friends (source_id, friend_id) values - ('1001', '1004'); - --- Vader Appears in -insert into character_appears_in (character_id, appears_in) values - ('1001', 3), - ('1001', 4), - ('1001', 5); - --- Solo's friends -insert into character_friends (source_id, friend_id) values - ('1002', '1000'), - ('1002', '1003'), - ('1002', '2001'); - --- Solo Appears in -insert into character_appears_in (character_id, appears_in) values - ('1002', 3), - ('1002', 4), - ('1002', 5), - ('1002', 6); - --- Leia's friends -insert into character_friends (source_id, friend_id) values - ('1003', '1000'), - ('1003', '1002'), - ('1003', '2000'), - ('1003', '2001'); - --- Leia Appears in -insert into character_appears_in (character_id, appears_in) values - ('1003', 3), - ('1003', 4), - ('1003', 5), - ('1003', 6); - --- Wilhuff's friends -insert into character_friends (source_id, friend_id) values - ('1004', '1001'); - --- Wilhuff Appears in -insert into character_appears_in (character_id, appears_in) values - ('1004', 3); - --- C3PO's friends -insert into character_friends (source_id, friend_id) values - ('2000', '1000'), - ('2000', '1002'), - ('2000', '1003'), - ('2000', '2001'); - --- C3PO Appears in -insert into character_appears_in (character_id, appears_in) values - ('2000', 3), - ('2000', 4), - ('2000', 5), - ('2000', 6); - --- R2's friends -insert into character_friends (source_id, friend_id) values - ('2001', '1000'), - ('2001', '1002'), - ('2001', '1003'); - --- R2 Appears in -insert into character_appears_in (character_id, appears_in) values - ('2001', 3), - ('2001', 4), - ('2001', 5), - ('2001', 6); - +-- Insert Code Lists +insert into code_list (id, type, code, description, sequence, active, parent_id) values + (0, 'org.crygier.graphql.model.starwars.Gender', 'Male', 'Male', 1, true, null), + (1, 'org.crygier.graphql.model.starwars.Gender', 'Female', 'Female', 2, true, null); + +-- Insert Droid Functions +insert into droid_function(id, function) values +( '1000', 'Protocol'), +( '1001', 'Astromech'); + +-- Insert Droids +insert into character (id, name, primary_function, dtype) values + ('2000', 'C-3PO', '1000', 'Droid'), + ('2001', 'R2-D2', '1001', 'Droid'); + +-- Insert Humans +insert into character (id, name, home_planet, favorite_droid_id, dtype, gender_code_id) values + ('1000', 'Luke Skywalker', 'Tatooine', '2000', 'Human', 0), + ('1001', 'Darth Vader', 'Tatooine', '2001', 'Human', 0), + ('1002', 'Han Solo', NULL, NULL, 'Human', 0), + ('1003', 'Leia Organa', 'Alderaan', NULL, 'Human', 1), + ('1004', 'Wilhuff Tarkin', NULL, NULL, 'Human', 0); + +-- Luke's friends +insert into character_friends (source_id, friend_id) values + ('1000', '1002'), + ('1000', '1003'), + ('1000', '2000'), + ('1000', '2001'); + +-- Luke Appears in +insert into character_appears_in (character_id, appears_in) values + ('1000', 3), + ('1000', 4), + ('1000', 5), + ('1000', 6); + +-- Vader's friends +insert into character_friends (source_id, friend_id) values + ('1001', '1004'); + +-- Vader Appears in +insert into character_appears_in (character_id, appears_in) values + ('1001', 3), + ('1001', 4), + ('1001', 5); + +-- Solo's friends +insert into character_friends (source_id, friend_id) values + ('1002', '1000'), + ('1002', '1003'), + ('1002', '2001'); + +-- Solo Appears in +insert into character_appears_in (character_id, appears_in) values + ('1002', 3), + ('1002', 4), + ('1002', 5), + ('1002', 6); + +-- Leia's friends +insert into character_friends (source_id, friend_id) values + ('1003', '1000'), + ('1003', '1002'), + ('1003', '2000'), + ('1003', '2001'); + +-- Leia Appears in +insert into character_appears_in (character_id, appears_in) values + ('1003', 3), + ('1003', 4), + ('1003', 5), + ('1003', 6); + +-- Wilhuff's friends +insert into character_friends (source_id, friend_id) values + ('1004', '1001'); + +-- Wilhuff Appears in +insert into character_appears_in (character_id, appears_in) values + ('1004', 3); + +-- C3PO's friends +insert into character_friends (source_id, friend_id) values + ('2000', '1000'), + ('2000', '1002'), + ('2000', '1003'), + ('2000', '2001'); + +-- C3PO Appears in +insert into character_appears_in (character_id, appears_in) values + ('2000', 3), + ('2000', 4), + ('2000', 5), + ('2000', 6); + +-- R2's friends +insert into character_friends (source_id, friend_id) values + ('2001', '1000'), + ('2001', '1002'), + ('2001', '1003'); + +-- R2 Appears in +insert into character_appears_in (character_id, appears_in) values + ('2001', 3), + ('2001', 4), + ('2001', 5), + ('2001', 6); + diff --git a/graphql-jpa-query-schema/src/test/resources/data.sql b/graphql-jpa-query-schema/src/test/resources/data.sql index 589b9cc00..b6336cbff 100644 --- a/graphql-jpa-query-schema/src/test/resources/data.sql +++ b/graphql-jpa-query-schema/src/test/resources/data.sql @@ -3,10 +3,15 @@ insert into code_list (id, type, code, description, sequence, active, parent_id) (0, 'org.crygier.graphql.model.starwars.Gender', 'Male', 'Male', 1, true, null), (1, 'org.crygier.graphql.model.starwars.Gender', 'Female', 'Female', 2, true, null); +-- Insert Droid Functions +insert into droid_function(id, function) values +( '1000', 'Protocol'), +( '1001', 'Astromech'); + -- Insert Droids insert into character (id, name, primary_function, dtype) values - ('2000', 'C-3PO', 'Protocol', 'Droid'), - ('2001', 'R2-D2', 'Astromech', 'Droid'); + ('2000', 'C-3PO', '1000', 'Droid'), + ('2001', 'R2-D2', '1001', 'Droid'); -- Insert Humans insert into character (id, name, home_planet, favorite_droid_id, dtype, gender_code_id) values From 913d638dd310f2877a052af1881412ddbc703bd1 Mon Sep 17 00:00:00 2001 From: molexx Date: Tue, 5 Feb 2019 00:51:00 +0000 Subject: [PATCH 03/14] update test queryForDroidByName to expect DroidFunction to be nested --- .../graphql/jpa/query/schema/StarwarsQueryExecutorTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/StarwarsQueryExecutorTests.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/StarwarsQueryExecutorTests.java index bfcb4b0d0..ebc7ef0f4 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/StarwarsQueryExecutorTests.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/StarwarsQueryExecutorTests.java @@ -102,9 +102,9 @@ public void getsNamesOfAllDroids() { @Test public void queryForDroidByName() { //given: - String query = "query { Droids( where: { name: { EQ: \"C-3PO\"}}) { select {name, primaryFunction } } }"; + String query = "query { Droids( where: { name: { EQ: \"C-3PO\"}}) { select {name, primaryFunction {function} } } }"; - String expected = "{Droids={select=[{name=C-3PO, primaryFunction=Protocol}]}}"; + String expected = "{Droids={select=[{name=C-3PO, primaryFunction={function=Protocol}}]}}"; //when: Object result = executor.execute(query).getData(); From 9ba70cd4eef0d90299b450d2418202dccfb708f0 Mon Sep 17 00:00:00 2001 From: molexx Date: Tue, 5 Feb 2019 00:52:00 +0000 Subject: [PATCH 04/14] add tests for where clauses on many-to-one relationships, the nested one currently failing --- .../schema/StarwarsQueryExecutorTests.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/StarwarsQueryExecutorTests.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/StarwarsQueryExecutorTests.java index ebc7ef0f4..c49c706e4 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/StarwarsQueryExecutorTests.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/StarwarsQueryExecutorTests.java @@ -751,4 +751,79 @@ public void queryWithWhereInsideOneToManyRelations() { assertThat(result.toString()).isEqualTo(expected); } + + @Test + public void queryFilterManyToOne() { + //given: + String query = "query { Droids { select { name primaryFunction(where: {function: {EQ:\"Astromech\"}}) { function }}}}"; + + String expected = "{Droids={" + + "select=[{" + + "name=R2-D2, " + + "primaryFunction={function=Astromech}" + + "}]" + + "}}"; + + //when: + Object result = executor.execute(query).getData(); + + //then: + assertThat(result.toString()).isEqualTo(expected); + } + + + @Test + public void queryFilterNestedManyToOne() { + //given: + String query = "query {" + + " Humans {" + + " select {" + + " id" + + " name" + + " homePlanet" + + " favoriteDroid {" + + " name" + + " primaryFunction(where:{function:{EQ:\"Astromech\"}}) {" + + " function" + + " }" + + " }" + + " }" + + " }" + + "}"; + + String expected = "{Humans={" + + "select=[" + + "{" + + "id=1000, " + + "name=Luke Skywalker, " + + "homePlanet=Tatooine, " + + "favoriteDroid={" + + "name=C-3PO, " + + "primaryFunction={" + + "function=Protocol" + + "}" + + "}" + + /*"}, " + + "{" + + "id=1001, " + + "name=Darth Vader, " + + "homePlanet=Tatooine, " + + "favoriteDroid={" + + "name=R2-D2, " + + "primaryFunction={" + + "function=Astromech" + + "}" + + "}" + + */ + "}" + + "]" + + "}}"; + + //when: + Object result = executor.execute(query).getData(); + + //then: + assertThat(result.toString()).isEqualTo(expected); + } + } From 4db8a47f43e98347d5efc3c788c14b3f1de1b263 Mon Sep 17 00:00:00 2001 From: molexx Date: Tue, 5 Feb 2019 01:01:36 +0000 Subject: [PATCH 05/14] that was not the droid I was looking for --- .../jpa/query/schema/StarwarsQueryExecutorTests.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/StarwarsQueryExecutorTests.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/StarwarsQueryExecutorTests.java index c49c706e4..f744cfb3f 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/StarwarsQueryExecutorTests.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/StarwarsQueryExecutorTests.java @@ -793,7 +793,7 @@ public void queryFilterNestedManyToOne() { String expected = "{Humans={" + "select=[" + - "{" + + /*"{" + "id=1000, " + "name=Luke Skywalker, " + "homePlanet=Tatooine, " + @@ -803,7 +803,8 @@ public void queryFilterNestedManyToOne() { "function=Protocol" + "}" + "}" + - /*"}, " + + "}, " + + */ "{" + "id=1001, " + "name=Darth Vader, " + @@ -814,7 +815,6 @@ public void queryFilterNestedManyToOne() { "function=Astromech" + "}" + "}" + - */ "}" + "]" + "}}"; From b614ae13d55f2ad34c585d02a8b09b59387626ea Mon Sep 17 00:00:00 2001 From: molexx Date: Fri, 15 Mar 2019 00:55:32 +0000 Subject: [PATCH 06/14] move DroidFunction into starwars package --- .../graphql/jpa/query/example/model/DroidFunction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/model/DroidFunction.java b/graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/model/DroidFunction.java index e22b292ae..dc14da997 100644 --- a/graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/model/DroidFunction.java +++ b/graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/model/DroidFunction.java @@ -1,4 +1,4 @@ -package com.introproventures.graphql.jpa.query.example.model; +package com.introproventures.graphql.jpa.query.example.starwars; import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription; import lombok.Data; From cb4d16a4500fcae2878e2790bd7aaabeb8e72fdb Mon Sep 17 00:00:00 2001 From: molexx Date: Fri, 15 Mar 2019 00:57:55 +0000 Subject: [PATCH 07/14] restore DroidFunction into starwars package --- .../query/example/starwars/DroidFunction.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/starwars/DroidFunction.java diff --git a/graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/starwars/DroidFunction.java b/graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/starwars/DroidFunction.java new file mode 100644 index 000000000..dc14da997 --- /dev/null +++ b/graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/starwars/DroidFunction.java @@ -0,0 +1,25 @@ +package com.introproventures.graphql.jpa.query.example.starwars; + +import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.persistence.Entity; +import javax.persistence.Id; + + +@Entity(name = "droid_function") +@GraphQLDescription("Represents the functions a droid can have") +@Data +@EqualsAndHashCode() +public class DroidFunction { + + @Id + @GraphQLDescription("Primary Key for the DroidFunction Class") + String id; + + String function; + + + +} From 111a3385fb92928090e649f65550b498364c896d Mon Sep 17 00:00:00 2001 From: molexx Date: Fri, 15 Mar 2019 00:58:30 +0000 Subject: [PATCH 08/14] update example sql data to include droid_function entity --- .../src/main/resources/starwars.sql | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/graphql-jpa-query-example-merge/src/main/resources/starwars.sql b/graphql-jpa-query-example-merge/src/main/resources/starwars.sql index 665e105cf..df6cf7606 100644 --- a/graphql-jpa-query-example-merge/src/main/resources/starwars.sql +++ b/graphql-jpa-query-example-merge/src/main/resources/starwars.sql @@ -4,10 +4,15 @@ insert into CodeList (id, type, code, description, sequence, active, parent_id) (0, 'org.crygier.graphql.model.starwars.Gender', 'Male', 'Male', 1, true, null), (1, 'org.crygier.graphql.model.starwars.Gender', 'Female', 'Female', 2, true, null); +-- Insert Droid Functions +insert into droid_function(id, function) values +( '1000', 'Protocol'), +( '1001', 'Astromech'); + -- Insert Droids -insert into Character (id, name, primaryFunction, dtype) values - ('2000', 'C-3PO', 'Protocol', 'Droid'), - ('2001', 'R2-D2', 'Astromech', 'Droid'); +insert into character (id, name, primary_function, dtype) values + ('2000', 'C-3PO', '1000', 'Droid'), + ('2001', 'R2-D2', '1001', 'Droid'); -- Insert Humans insert into character (id, name, homePlanet, favorite_droid_id, dtype, gender_code_id) values From 09fe970d1ef3e8d46894cdadcf20f1043c658bba Mon Sep 17 00:00:00 2001 From: molexx Date: Fri, 15 Mar 2019 01:00:04 +0000 Subject: [PATCH 09/14] account for droid_function in newly merged tests --- .../jpa/query/schema/StarwarsQueryExecutorTests.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/StarwarsQueryExecutorTests.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/StarwarsQueryExecutorTests.java index f744cfb3f..43efeffff 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/StarwarsQueryExecutorTests.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/StarwarsQueryExecutorTests.java @@ -661,7 +661,7 @@ public void queryWithWhereInsideOneToManyRelationsNotExisting() { " name" + " favoriteDroid {" + " name" + - " primaryFunction" + + " primaryFunction { function }" + " appearsIn" + " }" + " friends {" + @@ -698,7 +698,7 @@ public void queryWithWhereInsideCompositeRelationsAndCollectionFiltering() { " favoriteDroid {" + " id" + " name" + - " primaryFunction" + + " primaryFunction { function }" + " }" + " friends(where: {name: {LIKE: \"Leia\"}}) {" + " id" + @@ -709,7 +709,7 @@ public void queryWithWhereInsideCompositeRelationsAndCollectionFiltering() { "}"; String expected = "{Humans={select=[" - + "{id=1000, name=Luke Skywalker, favoriteDroid={id=2000, name=C-3PO, primaryFunction=Protocol}, friends=[{id=1003, name=Leia Organa}]}" + + "{id=1000, name=Luke Skywalker, favoriteDroid={id=2000, name=C-3PO, primaryFunction={function=Protocol}}, friends=[{id=1003, name=Leia Organa}]}" + "]}}"; //when: From d42d111e986d375643c515b98a1a5fc9f61740fc Mon Sep 17 00:00:00 2001 From: Igor Dianov Date: Sun, 14 Apr 2019 19:57:26 -0700 Subject: [PATCH 10/14] feat: implemented many to one relation criteria expression --- .../graphql/jpa/query/example/books/Book.java | 4 - .../example/starwars}/DroidFunction.java | 10 +- .../jpa/query/example/starwars/Droid.java | 14 +- .../query/example/starwars/DroidFunction.java | 10 +- .../schema/impl/GraphQLJpaSchemaBuilder.java | 49 +++++-- .../impl/QraphQLJpaBaseDataFetcher.java | 30 +++- .../query/schema/GraphQLExecutorTests.java | 43 +++++- .../schema/StarwarsQueryExecutorTests.java | 135 ++++++++++++++++-- 8 files changed, 250 insertions(+), 45 deletions(-) rename {graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/model => graphql-jpa-query-example-merge/src/main/java/com/introproventures/graphql/jpa/query/example/starwars}/DroidFunction.java (84%) diff --git a/graphql-jpa-query-example-merge/src/main/java/com/introproventures/graphql/jpa/query/example/books/Book.java b/graphql-jpa-query-example-merge/src/main/java/com/introproventures/graphql/jpa/query/example/books/Book.java index 07fce13ab..a81493125 100644 --- a/graphql-jpa-query-example-merge/src/main/java/com/introproventures/graphql/jpa/query/example/books/Book.java +++ b/graphql-jpa-query-example-merge/src/main/java/com/introproventures/graphql/jpa/query/example/books/Book.java @@ -23,8 +23,6 @@ import javax.persistence.Id; import javax.persistence.ManyToOne; -import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnoreFilter; -import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnoreOrder; import lombok.Data; @Data @@ -33,8 +31,6 @@ public class Book { @Id Long id; - @GraphQLIgnoreOrder - @GraphQLIgnoreFilter String title; @ManyToOne(fetch=FetchType.LAZY) diff --git a/graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/model/DroidFunction.java b/graphql-jpa-query-example-merge/src/main/java/com/introproventures/graphql/jpa/query/example/starwars/DroidFunction.java similarity index 84% rename from graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/model/DroidFunction.java rename to graphql-jpa-query-example-merge/src/main/java/com/introproventures/graphql/jpa/query/example/starwars/DroidFunction.java index dc14da997..88512879d 100644 --- a/graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/model/DroidFunction.java +++ b/graphql-jpa-query-example-merge/src/main/java/com/introproventures/graphql/jpa/query/example/starwars/DroidFunction.java @@ -1,14 +1,16 @@ package com.introproventures.graphql.jpa.query.example.starwars; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription; import lombok.Data; import lombok.EqualsAndHashCode; -import javax.persistence.Entity; -import javax.persistence.Id; - -@Entity(name = "droid_function") +@Entity(name = "droidFunction") +@Table(name = "droid_function") @GraphQLDescription("Represents the functions a droid can have") @Data @EqualsAndHashCode() diff --git a/graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/starwars/Droid.java b/graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/starwars/Droid.java index 113ac8521..cbe4bd871 100644 --- a/graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/starwars/Droid.java +++ b/graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/starwars/Droid.java @@ -17,9 +17,11 @@ package com.introproventures.graphql.jpa.query.example.starwars; import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription; - import lombok.Data; import lombok.EqualsAndHashCode; @@ -29,7 +31,13 @@ @EqualsAndHashCode(callSuper=true) public class Droid extends Character { - @GraphQLDescription("Documents the primary purpose this droid serves") - String primaryFunction; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "primary_function") + DroidFunction primaryFunction; + //description moved to getter to test it gets picked up + @GraphQLDescription("Documents the primary purpose this droid serves") + public DroidFunction getPrimaryFunction() { + return(primaryFunction); + } } diff --git a/graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/starwars/DroidFunction.java b/graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/starwars/DroidFunction.java index dc14da997..88512879d 100644 --- a/graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/starwars/DroidFunction.java +++ b/graphql-jpa-query-example/src/main/java/com/introproventures/graphql/jpa/query/example/starwars/DroidFunction.java @@ -1,14 +1,16 @@ package com.introproventures.graphql.jpa.query.example.starwars; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription; import lombok.Data; import lombok.EqualsAndHashCode; -import javax.persistence.Entity; -import javax.persistence.Id; - -@Entity(name = "droid_function") +@Entity(name = "droidFunction") +@Table(name = "droid_function") @GraphQLDescription("Represents the functions a droid can have") @Data @EqualsAndHashCode() diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java index cb58177cd..8fc6af2dd 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java @@ -62,6 +62,7 @@ import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLInputObjectField; import graphql.schema.GraphQLInputObjectType; +import graphql.schema.GraphQLInputObjectType.Builder; import graphql.schema.GraphQLInputType; import graphql.schema.GraphQLList; import graphql.schema.GraphQLObjectType; @@ -294,7 +295,7 @@ private GraphQLInputObjectType getWhereInputType(ManagedType managedType) { return inputObjectCache.computeIfAbsent(managedType, this::computeWhereInputType); } - private GraphQLInputObjectType computeWhereInputType(ManagedType managedType) { + private String resolveWhereInputTypeName(ManagedType managedType) { String typeName=""; if (managedType instanceof EmbeddableType){ typeName = managedType.getJavaType().getSimpleName()+"EmbeddableType"; @@ -302,9 +303,14 @@ private GraphQLInputObjectType computeWhereInputType(ManagedType managedType) typeName = ((EntityType)managedType).getName(); } - String type = namingStrategy.pluralize(typeName)+"RelationCriteriaExpression"; + return namingStrategy.pluralize(typeName)+"RelationCriteriaExpression"; - GraphQLInputObjectType whereInputObject = GraphQLInputObjectType.newInputObject() + } + + private GraphQLInputObjectType computeWhereInputType(ManagedType managedType) { + String type=resolveWhereInputTypeName(managedType); + + Builder whereInputObject = GraphQLInputObjectType.newInputObject() .name(type) .description("Where logical AND specification of the provided list of criteria expressions") .field(GraphQLInputObjectField.newInputObjectField() @@ -326,11 +332,31 @@ private GraphQLInputObjectType computeWhereInputType(ManagedType managedType) .map(this::getWhereInputField) .collect(Collectors.toList()) ) - .build(); + .fields(managedType.getAttributes().stream() + .filter(this::isToOne) + .filter(this::isNotIgnored) + .filter(this::isNotIgnoredFilter) + .map(this::getWhereInputRelationField) + .collect(Collectors.toList()) + ); - return whereInputObject; + return whereInputObject.build(); } + + private GraphQLInputObjectField getWhereInputRelationField(Attribute attribute) { + ManagedType foreignType = getForeignType(attribute); + + String type = resolveWhereInputTypeName(foreignType); + String description = getSchemaDescription(attribute.getJavaMember()); + + return GraphQLInputObjectField.newInputObjectField() + .name(attribute.getName()) + .description(description) + .type(new GraphQLTypeReference(type)) + .build(); + } + private GraphQLInputObjectField getWhereInputField(Attribute attribute) { GraphQLInputType type = getWhereAttributeType(attribute); @@ -375,8 +401,8 @@ private GraphQLInputType getWhereAttributeType(Attribute attribute) { .description("Equals criteria") .type(getAttributeInputType(attribute)) .build() - ) - .field(GraphQLInputObjectField.newInputObjectField() + ) + .field(GraphQLInputObjectField.newInputObjectField() .name(Criteria.NE.name()) .description("Not Equals criteria") .type(getAttributeInputType(attribute)) @@ -610,7 +636,7 @@ && isNotIgnoredOrder(attribute) ) { } // Get the fields that can be queried on (i.e. Simple Types, no Sub-Objects) - if (attribute instanceof SingularAttribute + if (attribute instanceof SingularAttribute && attribute.getPersistentAttributeType() != Attribute.PersistentAttributeType.BASIC) { ManagedType foreignType = getForeignType(attribute); @@ -622,7 +648,7 @@ else if (attribute instanceof PluralAttribute && (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_MANY || attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.MANY_TO_MANY)) { EntityType declaringType = (EntityType) ((PluralAttribute) attribute).getDeclaringType(); - EntityType elementType = (EntityType) ((PluralAttribute) attribute).getElementType(); + ManagedType elementType = getForeignType(attribute); arguments.add(getWhereArgument(elementType)); dataFetcher = new GraphQLJpaOneToManyDataFetcher(entityManager, declaringType, (PluralAttribute) attribute); @@ -638,7 +664,10 @@ else if (attribute instanceof PluralAttribute } protected ManagedType getForeignType(Attribute attribute) { - return (ManagedType) ((SingularAttribute) attribute).getType(); + if(SingularAttribute.class.isInstance(attribute)) + return (ManagedType) ((SingularAttribute) attribute).getType(); + else + return (EntityType) ((PluralAttribute) attribute).getElementType(); } @SuppressWarnings( { "rawtypes" } ) diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/QraphQLJpaBaseDataFetcher.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/QraphQLJpaBaseDataFetcher.java index 2abc318ec..826c24853 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/QraphQLJpaBaseDataFetcher.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/QraphQLJpaBaseDataFetcher.java @@ -38,6 +38,7 @@ import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Fetch; import javax.persistence.criteria.From; import javax.persistence.criteria.Join; import javax.persistence.criteria.JoinType; @@ -52,7 +53,6 @@ import com.introproventures.graphql.jpa.query.annotation.GraphQLDefaultOrderBy; import com.introproventures.graphql.jpa.query.schema.impl.PredicateFilter.Criteria; - import graphql.GraphQLException; import graphql.execution.ValuesResolver; import graphql.language.Argument; @@ -178,14 +178,14 @@ protected final List getFieldArguments(Field field, CriteriaQuery q if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.MANY_TO_ONE || attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_ONE ) { - reuseJoin(from, selectedField.getName(), false); + Join join = reuseJoin(from, selectedField.getName(), false); } } } else { - // We must add plural attributes with explicit join to avoid Hibernate error: + // We must add plural attributes with explicit fetch to avoid Hibernate error: // "query specified join fetching, but the owner of the fetched association was not present in the select list" - // TODO Let's try detect many-to-many relation and reuse outer join - reuseJoin(from, selectedField.getName(), false); + // TODO Let's try detect optional relation and apply join type + Join join = reuseJoin(from, selectedField.getName(), false); } } } @@ -396,7 +396,12 @@ private Predicate getFieldPredicate(String fieldName, CriteriaBuilder cb, From join = reuseJoin(path, fieldName, false); + +// join.getParent().fetch(fieldName); + + return getArgumentPredicate(cb, join, wherePredicateEnvironment(environment, fieldDefinition, arguments), new Argument(logical.name(), expressionValue)); } @@ -525,6 +530,19 @@ private Join reuseJoin(From path, String fieldName, boolean outer) { } return outer ? path.join(fieldName, JoinType.LEFT) : path.join(fieldName); } + + // trying to find already existing joins to reuse + private Fetch reuseFetches(From path, String fieldName, boolean outer) { + + for (Fetch join : path.getFetches()) { + if (join.getAttribute().getName().equals(fieldName)) { + if ((join.getJoinType() == JoinType.LEFT) == outer) { + return join; + } + } + } + return outer ? path.fetch(fieldName, JoinType.LEFT) : path.fetch(fieldName); + } @SuppressWarnings( { "unchecked", "rawtypes" } ) protected Object convertValue(DataFetchingEnvironment environment, Argument argument, Value value) { diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/GraphQLExecutorTests.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/GraphQLExecutorTests.java index 03d76f669..1919f3fd2 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/GraphQLExecutorTests.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/GraphQLExecutorTests.java @@ -20,9 +20,11 @@ import java.util.Arrays; import java.util.HashMap; -import java.util.Map; import java.util.List; +import java.util.Map; + import javax.persistence.EntityManager; + import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaExecutor; import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaSchemaBuilder; import graphql.ErrorType; @@ -570,6 +572,45 @@ public void queryForAuthorssWithWhereBooksGenreEquals() { assertThat(result.toString()).isEqualTo(expected); } + @Test + public void queryForAuthorssWithWhereBooksManyToOneRelationCriteria() { + //given + String query = "query { " + + " Authors(where: {" + + " books: {" + + " author: {" + + " name: {LIKE: \"Leo\"}" + + " }" + + " }" + + " }) {" + + " select {" + + " id" + + " name" + + " books {" + + " id" + + " title" + + " genre" + + " author {" + + " name" + + " }" + + " }" + + " }" + + " }" + + "}"; + + String expected = "{Authors={select=[{" + + "id=1, name=Leo Tolstoy, books=[{id=2, title=War and Peace, genre=NOVEL, author={name=Leo Tolstoy}}, " + + "{id=3, title=Anna Karenina, genre=NOVEL, author={name=Leo Tolstoy}}" + + "]}" + + "]}}"; + + //when + Object result = executor.execute(query).getData(); + + // then + assertThat(result.toString()).isEqualTo(expected); + } + @Test public void queryWithWhereInsideOneToManyRelationsImplicitAND() { diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/StarwarsQueryExecutorTests.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/StarwarsQueryExecutorTests.java index 43efeffff..bb899a275 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/StarwarsQueryExecutorTests.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/StarwarsQueryExecutorTests.java @@ -26,6 +26,9 @@ import javax.persistence.Query; import javax.transaction.Transactional; +import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaExecutor; +import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaSchemaBuilder; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -35,9 +38,6 @@ import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; -import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaExecutor; -import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaSchemaBuilder; - @RunWith(SpringRunner.class) @SpringBootTest @TestPropertySource({"classpath:hibernate.properties"}) @@ -753,7 +753,7 @@ public void queryWithWhereInsideOneToManyRelations() { @Test - public void queryFilterManyToOne() { + public void queryFilterManyToOneEmbdeddedCriteria() { //given: String query = "query { Droids { select { name primaryFunction(where: {function: {EQ:\"Astromech\"}}) { function }}}}"; @@ -770,10 +770,38 @@ public void queryFilterManyToOne() { //then: assertThat(result.toString()).isEqualTo(expected); } - + + @Test + public void queryFilterManyToOnRelationCriteria() { + //given: + String query = "query { " + + " Droids(where: {primaryFunction: { function: {EQ:\"Astromech\"}}}) { " + + " select {" + + " name " + + " primaryFunction {" + + " function" + + " }" + + " }" + + " }" + + "}"; + + String expected = "{Droids={" + + "select=[{" + + "name=R2-D2, " + + "primaryFunction={function=Astromech}" + + "}]" + + "}}"; + + //when: + Object result = executor.execute(query).getData(); + + //then: + assertThat(result.toString()).isEqualTo(expected); + } @Test - public void queryFilterNestedManyToOne() { + @Ignore // TODO + public void queryFilterNestedManyToOneToDo() { //given: String query = "query {" + " Humans {" + @@ -793,18 +821,48 @@ public void queryFilterNestedManyToOne() { String expected = "{Humans={" + "select=[" + - /*"{" + - "id=1000, " + - "name=Luke Skywalker, " + + "{" + + "id=1001, " + + "name=Darth Vader, " + "homePlanet=Tatooine, " + "favoriteDroid={" + - "name=C-3PO, " + + "name=R2-D2, " + "primaryFunction={" + - "function=Protocol" + + "function=Astromech" + "}" + "}" + - "}, " + - */ + "}" + + "]" + + "}}"; + + //when: + Object result = executor.execute(query).getData(); + + //then: + assertThat(result.toString()).isEqualTo(expected); + } + + @Test + public void queryFilterNestedManyToOneRelationCriteria() { + //given: + String query = "query {" + + " Humans(where: { favoriteDroid: { primaryFunction: { function: { EQ:\"Astromech\" } } } } ) {" + + " select {" + + " id" + + " name" + + " homePlanet" + + " favoriteDroid {" + + " name" + + " primaryFunction {" + + " function" + + " }" + + " }" + + " }" + + " }" + + "}"; + + String expected = "{Humans={" + + "select=[" + "{" + "id=1001, " + "name=Darth Vader, " + @@ -825,5 +883,56 @@ public void queryFilterNestedManyToOne() { //then: assertThat(result.toString()).isEqualTo(expected); } + + @Test + public void queryFilterNestedManyToManyRelationCriteria() { + //given: + String query = "query {" + + " Humans(where: {" + + " friends: { name: { LIKE: \"Leia\" } } " + + " favoriteDroid: { primaryFunction: { function: { EQ: \"Protocol\" } } }" + + " }) {" + + " select {" + + " id" + + " name" + + " homePlanet" + + " favoriteDroid {" + + " name" + + " primaryFunction {" + + " function" + + " }" + + " }" + + " friends {" + + " name" + + " }" + + " }" + + " } " + + "}"; + String expected = "{Humans={select=[{" + + "id=1000, " + + "name=Luke Skywalker, " + + "homePlanet=Tatooine, " + + "favoriteDroid={" + + "name=C-3PO, " + + "primaryFunction={" + + "function=Protocol" + + "}" + + "}, " + + "friends=[" + + "{name=C-3PO}, " + + "{name=Han Solo}, " + + "{name=Leia Organa}, " + + "{name=R2-D2}" + + "]}" + + "]}}"; + + //when: + Object result = executor.execute(query).getData(); + + //then: + assertThat(result.toString()).isEqualTo(expected); + } + + } From c21ae6338fbbdd46eeb58213c79bd16376af6ca8 Mon Sep 17 00:00:00 2001 From: Igor Dianov Date: Sun, 14 Apr 2019 20:07:42 -0700 Subject: [PATCH 11/14] chore: clean up code --- .../impl/QraphQLJpaBaseDataFetcher.java | 25 +++---------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/QraphQLJpaBaseDataFetcher.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/QraphQLJpaBaseDataFetcher.java index 826c24853..92f0f0091 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/QraphQLJpaBaseDataFetcher.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/QraphQLJpaBaseDataFetcher.java @@ -38,7 +38,6 @@ import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Fetch; import javax.persistence.criteria.From; import javax.persistence.criteria.Join; import javax.persistence.criteria.JoinType; @@ -178,14 +177,14 @@ protected final List getFieldArguments(Field field, CriteriaQuery q if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.MANY_TO_ONE || attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_ONE ) { - Join join = reuseJoin(from, selectedField.getName(), false); + reuseJoin(from, selectedField.getName(), false); } } } else { // We must add plural attributes with explicit fetch to avoid Hibernate error: // "query specified join fetching, but the owner of the fetched association was not present in the select list" // TODO Let's try detect optional relation and apply join type - Join join = reuseJoin(from, selectedField.getName(), false); + reuseJoin(from, selectedField.getName(), false); } } } @@ -396,12 +395,7 @@ private Predicate getFieldPredicate(String fieldName, CriteriaBuilder cb, From join = reuseJoin(path, fieldName, false); - -// join.getParent().fetch(fieldName); - - return getArgumentPredicate(cb, join, + return getArgumentPredicate(cb, reuseJoin(path, fieldName, false), wherePredicateEnvironment(environment, fieldDefinition, arguments), new Argument(logical.name(), expressionValue)); } @@ -531,19 +525,6 @@ private Join reuseJoin(From path, String fieldName, boolean outer) { return outer ? path.join(fieldName, JoinType.LEFT) : path.join(fieldName); } - // trying to find already existing joins to reuse - private Fetch reuseFetches(From path, String fieldName, boolean outer) { - - for (Fetch join : path.getFetches()) { - if (join.getAttribute().getName().equals(fieldName)) { - if ((join.getJoinType() == JoinType.LEFT) == outer) { - return join; - } - } - } - return outer ? path.fetch(fieldName, JoinType.LEFT) : path.fetch(fieldName); - } - @SuppressWarnings( { "unchecked", "rawtypes" } ) protected Object convertValue(DataFetchingEnvironment environment, Argument argument, Value value) { if (value instanceof NullValue) { From 8430f61f4f566da475ee1552af34ddab529aeefc Mon Sep 17 00:00:00 2001 From: Igor Dianov Date: Mon, 15 Apr 2019 13:18:44 -0700 Subject: [PATCH 12/14] feat: add nested one-to-many criteria expression support --- .../jpa/query/example/books/Author.java | 4 +- .../graphql/jpa/query/example/books/Book.java | 2 + .../schema/impl/GraphQLJpaSchemaBuilder.java | 18 ++- .../query/schema/GraphQLExecutorTests.java | 133 +++++++++++++++++- .../jpa/query/schema/model/book/Author.java | 5 +- .../jpa/query/schema/model/book/Book.java | 2 + 6 files changed, 149 insertions(+), 15 deletions(-) diff --git a/graphql-jpa-query-example-merge/src/main/java/com/introproventures/graphql/jpa/query/example/books/Author.java b/graphql-jpa-query-example-merge/src/main/java/com/introproventures/graphql/jpa/query/example/books/Author.java index 6bd10b220..12b8080f7 100644 --- a/graphql-jpa-query-example-merge/src/main/java/com/introproventures/graphql/jpa/query/example/books/Author.java +++ b/graphql-jpa-query-example-merge/src/main/java/com/introproventures/graphql/jpa/query/example/books/Author.java @@ -16,7 +16,7 @@ package com.introproventures.graphql.jpa.query.example.books; -import java.util.Collection; +import java.util.Set; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -34,5 +34,5 @@ public class Author { String name; @OneToMany(mappedBy="author", fetch=FetchType.LAZY) - Collection books; + Set books; } diff --git a/graphql-jpa-query-example-merge/src/main/java/com/introproventures/graphql/jpa/query/example/books/Book.java b/graphql-jpa-query-example-merge/src/main/java/com/introproventures/graphql/jpa/query/example/books/Book.java index a81493125..682cc054e 100644 --- a/graphql-jpa-query-example-merge/src/main/java/com/introproventures/graphql/jpa/query/example/books/Book.java +++ b/graphql-jpa-query-example-merge/src/main/java/com/introproventures/graphql/jpa/query/example/books/Book.java @@ -24,9 +24,11 @@ import javax.persistence.ManyToOne; import lombok.Data; +import lombok.EqualsAndHashCode; @Data @Entity +@EqualsAndHashCode(exclude="author") public class Book { @Id Long id; diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java index 8fc6af2dd..d831cd0a4 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java @@ -116,7 +116,7 @@ public class GraphQLJpaSchemaBuilder implements GraphQLSchemaBuilder { private String description = "GraphQL Schema for all entities in this JPA application"; private boolean isUseDistinctParameter = false; - private boolean isDefaultDistinct = false; + private boolean isDefaultDistinct = true; public GraphQLJpaSchemaBuilder(EntityManager entityManager) { this.entityManager = entityManager; @@ -264,19 +264,12 @@ private GraphQLArgument getWhereArgument(ManagedType managedType) { .collect(Collectors.toList()) ) .fields(managedType.getAttributes().stream() - .filter(this::isToOne) + .filter(Attribute::isAssociation) .filter(this::isNotIgnored) .filter(this::isNotIgnoredFilter) .map(this::getInputObjectField) .collect(Collectors.toList()) ) - .fields(managedType.getAttributes().stream() - .filter(this::isToMany) - .filter(this::isNotIgnored) - .filter(this::isNotIgnoredFilter) - .map(this::getInputObjectField) - .collect(Collectors.toList()) - ) .build(); whereArgument = GraphQLArgument.newArgument() @@ -333,7 +326,7 @@ private GraphQLInputObjectType computeWhereInputType(ManagedType managedType) .collect(Collectors.toList()) ) .fields(managedType.getAttributes().stream() - .filter(this::isToOne) + .filter(this::isValidAssociation) .filter(this::isNotIgnored) .filter(this::isNotIgnoredFilter) .map(this::getWhereInputRelationField) @@ -770,6 +763,11 @@ protected final boolean isValidInput(Attribute attribute) { attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.EMBEDDED; } + protected final boolean isValidAssociation(Attribute attribute) { + return isOneToMany(attribute) || isToOne(attribute); + } + + private String getSchemaDescription(Member member) { if (member instanceof AnnotatedElement) { diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/GraphQLExecutorTests.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/GraphQLExecutorTests.java index 1919f3fd2..d5016fba1 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/GraphQLExecutorTests.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/GraphQLExecutorTests.java @@ -562,7 +562,10 @@ public void queryForAuthorssWithWhereBooksGenreEquals() { "}"; String expected = "{Authors={select=[" - + "{id=1, name=Leo Tolstoy, books=[{id=2, title=War and Peace, genre=NOVEL}, {id=3, title=Anna Karenina, genre=NOVEL}]}" + + "{id=1, name=Leo Tolstoy, books=[" + + "{id=2, title=War and Peace, genre=NOVEL}, " + + "{id=3, title=Anna Karenina, genre=NOVEL}" + + "]}" + "]}}"; //when @@ -715,6 +718,134 @@ public void queryWithWhereInsideOneToManyRelationsWithExplictOR() { //then: assertThat(result.toString()).isEqualTo(expected); } + + @Test + public void queryWithWhereInsideOneToManyNestedRelationsWithManyToOneAndOR() { + //given: + String query = "query { " + + " Authors(where: {" + + " books: {" + + " author: {name: {LIKE:\"Leo\"}}" + + " OR: {" + + " title: {LIKE: \"War\"}" + + " title: {LIKE: \"Anna\"}" + + " }" + + " }" + + " }) {" + + " select {" + + " id" + + " name" + + " books {" + + " id" + + " title" + + " genre" + + " }" + + " }" + + " }" + + "}"; + + String expected = "{Authors={select=[" + + "{id=1, name=Leo Tolstoy, books=[" + + "{id=2, title=War and Peace, genre=NOVEL}, " + + "{id=3, title=Anna Karenina, genre=NOVEL}" + + "]}" + + "]}}"; + + //when: + Object result = executor.execute(query).getData(); + + //then: + assertThat(result.toString()).isEqualTo(expected); + } + + @Test + public void queryWithWhereInsideOneToManyNestedRelationsWithOneToManyDeepSelect() { + //given: + String query = "query { " + + " Authors(where: {" + + " books: {" + + " author: {name: {LIKE:\"Leo\"}}" + + " }" + + " }) {" + + " select {" + + " id" + + " name" + + " books {" + + " id" + + " title" + + " genre" + + " author {" + + " name" + + " }" + + " }" + + " }" + + " }" + + "}"; + + String expected = "{Authors={select=[{" + + "id=1, name=Leo Tolstoy, books=[" + + "{id=2, title=War and Peace, genre=NOVEL, author={name=Leo Tolstoy}}, " + + "{id=3, title=Anna Karenina, genre=NOVEL, author={name=Leo Tolstoy}}" + + "]}" + + "]}}"; + + //when: + Object result = executor.execute(query).getData(); + + //then: + assertThat(result.toString()).isEqualTo(expected); + } + + + @Test + public void queryWithWhereInsideManyToOneNestedRelationsWithOnToManyCollectionFilter() { + //given: + String query = "query { " + + " Books(where: {" + + " title:{LIKE: \"War\"}" + + " author: {" + + " name:{LIKE: \"Leo\"}" + + " books: {title: {LIKE: \"Anna\"}}" + + " }" + + " }) {" + + " select {" + + " id" + + " title" + + " genre" + + " author {" + + " id" + + " name" + + " books {" + + " id" + + " title" + + " genre" + + " }" + + " }" + + " }" + + " }" + + "}"; + + String expected = "{Books={select=[{" + + "id=2, " + + "title=War and Peace, " + + "genre=NOVEL, " + + "author={" + + "id=1, " + + "name=Leo Tolstoy, " + + "books=[" + + "{id=3, title=Anna Karenina, genre=NOVEL}" + + "]" + + "}" + + "}]}}"; + + //when: + Object result = executor.execute(query).getData(); + + //then: + assertThat(result.toString()).isEqualTo(expected); + } + + @Test diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/book/Author.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/book/Author.java index 6cc045910..406c81b6e 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/book/Author.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/book/Author.java @@ -16,7 +16,6 @@ package com.introproventures.graphql.jpa.query.schema.model.book; -import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -30,6 +29,7 @@ import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToMany; +import javax.persistence.OrderBy; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -48,7 +48,8 @@ public class Author { String name; @OneToMany(mappedBy="author", fetch=FetchType.LAZY) - Collection books; + @OrderBy("id ASC") + Set books; @ElementCollection(fetch=FetchType.LAZY) @CollectionTable(name = "author_phone_numbers", joinColumns = @JoinColumn(name = "author_id")) diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/book/Book.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/book/Book.java index 9b395c1d4..56aa31ec4 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/book/Book.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/book/Book.java @@ -28,9 +28,11 @@ import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnoreFilter; import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnoreOrder; import lombok.Data; +import lombok.EqualsAndHashCode; @Data @Entity +@EqualsAndHashCode(exclude="author") public class Book { @Id Long id; From a602804391ae58141dbad16bcb6e23623a39a185 Mon Sep 17 00:00:00 2001 From: Igor Dianov Date: Mon, 15 Apr 2019 13:19:41 -0700 Subject: [PATCH 13/14] fix: use default distinct configuration in one-to-many data fetcher --- .../jpa/query/schema/impl/GraphQLJpaOneToManyDataFetcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaOneToManyDataFetcher.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaOneToManyDataFetcher.java index 16d570b27..0d7bb3895 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaOneToManyDataFetcher.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaOneToManyDataFetcher.java @@ -136,7 +136,7 @@ protected TypedQuery getQuery(DataFetchingEnvironment environment, Field fiel // optionally add default ordering mayBeAddDefaultOrderBy(query, join, cb); - return entityManager.createQuery(query.distinct(true)); + return entityManager.createQuery(query.distinct(isDistinct)); } From bd9d6677bd28a529d443f1cf1e5e693a6bf78693 Mon Sep 17 00:00:00 2001 From: Igor Dianov Date: Mon, 15 Apr 2019 13:29:02 -0700 Subject: [PATCH 14/14] fix: polish test formatting --- .../graphql/jpa/query/schema/GraphQLExecutorTests.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/GraphQLExecutorTests.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/GraphQLExecutorTests.java index d5016fba1..d59c339db 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/GraphQLExecutorTests.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/GraphQLExecutorTests.java @@ -602,9 +602,10 @@ public void queryForAuthorssWithWhereBooksManyToOneRelationCriteria() { "}"; String expected = "{Authors={select=[{" - + "id=1, name=Leo Tolstoy, books=[{id=2, title=War and Peace, genre=NOVEL, author={name=Leo Tolstoy}}, " - + "{id=3, title=Anna Karenina, genre=NOVEL, author={name=Leo Tolstoy}}" - + "]}" + + "id=1, name=Leo Tolstoy, books=[" + + "{id=2, title=War and Peace, genre=NOVEL, author={name=Leo Tolstoy}}, " + + "{id=3, title=Anna Karenina, genre=NOVEL, author={name=Leo Tolstoy}}" + + "]}" + "]}}"; //when