Skip to content

Commit bf4ab3d

Browse files
MLSTRMChristopher Wall
and
Christopher Wall
authored
Add logical NOT support (#492)
* Add logical NOT support * Correct formatting on changes * Improve test coverage --------- Co-authored-by: Christopher Wall <[email protected]>
1 parent 3fb0f2d commit bf4ab3d

File tree

4 files changed

+306
-3
lines changed

4 files changed

+306
-3
lines changed

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

+17-3
Original file line numberDiff line numberDiff line change
@@ -1603,12 +1603,26 @@ private Predicate getCompoundPredicate(CriteriaBuilder cb, List<Predicate> predi
16031603
if (predicates.isEmpty()) return cb.disjunction();
16041604

16051605
if (predicates.size() == 1) {
1606+
if (logical == Logical.NOT) {
1607+
return cb.not(predicates.get(0));
1608+
}
16061609
return predicates.get(0);
16071610
}
16081611

1609-
return (logical == Logical.OR)
1610-
? cb.or(predicates.toArray(new Predicate[0]))
1611-
: cb.and(predicates.toArray(new Predicate[0]));
1612+
switch (logical) {
1613+
case OR:
1614+
return cb.or(predicates.toArray(new Predicate[0]));
1615+
case AND:
1616+
case EXISTS:
1617+
case NOT_EXISTS:
1618+
return cb.and(predicates.toArray(new Predicate[0]));
1619+
case NOT:
1620+
throw new RuntimeException("NOT expression cannot be applied to multiple predicates at once");
1621+
default:
1622+
throw new RuntimeException(
1623+
"Unable to resolve applicable compound predicate for logical operand " + logical
1624+
);
1625+
}
16121626
}
16131627

16141628
private PredicateFilter getPredicateFilter(

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

+32
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,14 @@ private GraphQLArgument computeWhereArgument(ManagedType<?> managedType) {
722722
.type(new GraphQLList(new GraphQLTypeReference(type)))
723723
.build()
724724
)
725+
.field(
726+
GraphQLInputObjectField
727+
.newInputObjectField()
728+
.name(Logical.NOT.name())
729+
.description("Logical operation for expression")
730+
.type(new GraphQLTypeReference(type))
731+
.build()
732+
)
725733
.field(
726734
GraphQLInputObjectField
727735
.newInputObjectField()
@@ -815,6 +823,14 @@ private GraphQLInputObjectType computeSubqueryInputType(ManagedType<?> managedTy
815823
.type(new GraphQLList(new GraphQLTypeReference(type)))
816824
.build()
817825
)
826+
.field(
827+
GraphQLInputObjectField
828+
.newInputObjectField()
829+
.name(Logical.NOT.name())
830+
.description("Logical operation for expression")
831+
.type(new GraphQLTypeReference(type))
832+
.build()
833+
)
818834
.field(
819835
GraphQLInputObjectField
820836
.newInputObjectField()
@@ -896,6 +912,14 @@ private GraphQLInputObjectType computeWhereInputType(ManagedType<?> managedType)
896912
.type(new GraphQLList(new GraphQLTypeReference(type)))
897913
.build()
898914
)
915+
.field(
916+
GraphQLInputObjectField
917+
.newInputObjectField()
918+
.name(Logical.NOT.name())
919+
.description("Logical operation for expression")
920+
.type(new GraphQLTypeReference(type))
921+
.build()
922+
)
899923
.field(
900924
GraphQLInputObjectField
901925
.newInputObjectField()
@@ -1008,6 +1032,14 @@ private GraphQLInputType getWhereAttributeType(Attribute<?, ?> attribute) {
10081032
.type(new GraphQLList(new GraphQLTypeReference(type)))
10091033
.build()
10101034
)
1035+
.field(
1036+
GraphQLInputObjectField
1037+
.newInputObjectField()
1038+
.name(Logical.NOT.name())
1039+
.description("Logical NOT criteria expression")
1040+
.type(new GraphQLTypeReference(type))
1041+
.build()
1042+
)
10111043
.field(
10121044
GraphQLInputObjectField
10131045
.newInputObjectField()

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

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
enum Logical {
2424
AND,
2525
OR,
26+
NOT,
2627
EXISTS,
2728
NOT_EXISTS;
2829

schema/src/test/java/com/introproventures/graphql/jpa/query/support/GraphQLExecutorTestsSupport.java

+256
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,24 @@ public void queryForEnumNe() {
393393
assertThat(result.toString()).isEqualTo(expected);
394394
}
395395

396+
@Test
397+
public void queryForEnumNotEq() {
398+
//given
399+
String query = "{ Books(where: {genre: {NOT: {EQ:PLAY}}}) { select { id title, genre } }}";
400+
401+
String expected =
402+
"{Books={select=[" +
403+
"{id=2, title=War and Peace, genre=NOVEL}, " +
404+
"{id=3, title=Anna Karenina, genre=NOVEL}" +
405+
"]}}";
406+
407+
//when
408+
Object result = executor.execute(query).getData();
409+
410+
// then
411+
assertThat(result.toString()).isEqualTo(expected);
412+
}
413+
396414
@Test
397415
public void queryForEnumNin() {
398416
//given
@@ -411,6 +429,24 @@ public void queryForEnumNin() {
411429
assertThat(result.toString()).isEqualTo(expected);
412430
}
413431

432+
@Test
433+
public void queryForEnumNotIn() {
434+
//given
435+
String query = "{ Books(where: {genre: {NOT: {IN: PLAY}}}) { select { id title, genre } }}";
436+
437+
String expected =
438+
"{Books={select=[" +
439+
"{id=2, title=War and Peace, genre=NOVEL}, " +
440+
"{id=3, title=Anna Karenina, genre=NOVEL}" +
441+
"]}}";
442+
443+
//when
444+
Object result = executor.execute(query).getData();
445+
446+
// then
447+
assertThat(result.toString()).isEqualTo(expected);
448+
}
449+
414450
@Test
415451
public void queryForParentWithEnum() {
416452
//given
@@ -467,6 +503,165 @@ public void queryAuthorBooksWithExplictOptional() {
467503
assertThat(result.toString()).isEqualTo(expected);
468504
}
469505

506+
@Test
507+
public void queryAuthorBooksWithExplictOptionalNotLike() {
508+
//given
509+
String query =
510+
"query { " +
511+
"Authors(" +
512+
" where: {" +
513+
" books: {" +
514+
" title: {NOT: {LIKE: \"Th\"}}" +
515+
" }" +
516+
" }" +
517+
" ) {" +
518+
" select {" +
519+
" id" +
520+
" name" +
521+
" books(optional: true) {" +
522+
" id" +
523+
" title(orderBy: ASC)" +
524+
" genre" +
525+
" }" +
526+
" }" +
527+
" }" +
528+
"}";
529+
530+
String expected =
531+
"{Authors={select=[" +
532+
"{id=1, name=Leo Tolstoy, books=[" +
533+
"{id=3, title=Anna Karenina, genre=NOVEL}, " +
534+
"{id=2, title=War and Peace, genre=NOVEL}]}" +
535+
"]}}";
536+
537+
//when
538+
Object result = executor.execute(query).getData();
539+
540+
// then
541+
assertThat(result.toString()).isEqualTo(expected);
542+
}
543+
544+
@Test
545+
public void queryAuthorBooksWithExplictOptionalNotLikeConjunction() {
546+
//given
547+
String query =
548+
"query { " +
549+
"Authors(" +
550+
" where: {" +
551+
" books: {" +
552+
" AND: [{title: {NOT: {LIKE: \"War\"}}} {title: {NOT: {LIKE: \"Anna\"}}}]" +
553+
" }" +
554+
" }" +
555+
" ) {" +
556+
" select {" +
557+
" id" +
558+
" name" +
559+
" books(optional: true) {" +
560+
" id" +
561+
" title(orderBy: ASC)" +
562+
" genre" +
563+
" }" +
564+
" }" +
565+
" }" +
566+
"}";
567+
568+
String expected =
569+
"{Authors={select=[" +
570+
"{id=4, name=Anton Chekhov, books=[" +
571+
"{id=5, title=The Cherry Orchard, genre=PLAY}, " +
572+
"{id=6, title=The Seagull, genre=PLAY}, " +
573+
"{id=7, title=Three Sisters, genre=PLAY}" +
574+
"]}" +
575+
"]}}";
576+
577+
//when
578+
Object result = executor.execute(query).getData();
579+
580+
// then
581+
assertThat(result.toString()).isEqualTo(expected);
582+
}
583+
584+
@Test
585+
public void queryAuthorBooksWithNotOutsideOfLikeDisjunction() {
586+
//should be the same result as queryAuthorBooksWithExplictOptionalNotLikeConjunction above
587+
//due to logical inversion AND(Not Like A, Not Like B) => NOT(OR(Like a, Like b))
588+
//given
589+
String query =
590+
"query { " +
591+
"Authors(" +
592+
" where: {" +
593+
" books: {" +
594+
" NOT: {OR: [{title: {LIKE: \"War\"}} {title: {LIKE: \"Anna\"}}]}" +
595+
" }" +
596+
" }" +
597+
" ) {" +
598+
" select {" +
599+
" id" +
600+
" name" +
601+
" books(optional: true) {" +
602+
" id" +
603+
" title(orderBy: ASC)" +
604+
" genre" +
605+
" }" +
606+
" }" +
607+
" }" +
608+
"}";
609+
610+
String expected =
611+
"{Authors={select=[" +
612+
"{id=4, name=Anton Chekhov, books=[" +
613+
"{id=5, title=The Cherry Orchard, genre=PLAY}, " +
614+
"{id=6, title=The Seagull, genre=PLAY}, " +
615+
"{id=7, title=Three Sisters, genre=PLAY}" +
616+
"]}" +
617+
"]}}";
618+
619+
//when
620+
Object result = executor.execute(query).getData();
621+
622+
// then
623+
assertThat(result.toString()).isEqualTo(expected);
624+
}
625+
626+
@Test
627+
public void queryAuthorBooksWithNotOutsideOfConjunction() {
628+
//given
629+
String query =
630+
"query { " +
631+
"Authors(" +
632+
" where: {" +
633+
" books: {" +
634+
" NOT: {AND: [{id: {GE: 4}} {genre: {EQ: PLAY}}]}" +
635+
" }" +
636+
" }" +
637+
" ) {" +
638+
" select {" +
639+
" id" +
640+
" name" +
641+
" books(optional: true) {" +
642+
" id" +
643+
" title(orderBy: ASC)" +
644+
" genre" +
645+
" }" +
646+
" }" +
647+
" }" +
648+
"}";
649+
650+
String expected =
651+
"{Authors={select=[" +
652+
"{id=1, name=Leo Tolstoy, books=[" +
653+
"{id=3, title=Anna Karenina, genre=NOVEL}, " +
654+
"{id=2, title=War and Peace, genre=NOVEL}" +
655+
"]}" +
656+
"]}}";
657+
658+
//when
659+
Object result = executor.execute(query).getData();
660+
661+
// then
662+
assertThat(result.toString()).isEqualTo(expected);
663+
}
664+
470665
@Test
471666
public void queryAuthorBooksWithExplictOptionalEXISTS() {
472667
//given
@@ -506,6 +701,51 @@ public void queryAuthorBooksWithExplictOptionalEXISTS() {
506701
assertThat(result.toString()).isEqualTo(expected);
507702
}
508703

704+
@Test
705+
public void queryAuthorBooksWithExplictOptionalNotEXISTS() {
706+
//given
707+
String query =
708+
"query { " +
709+
"Authors(" +
710+
" where: {" +
711+
" NOT: {" +
712+
" EXISTS: {" +
713+
" books: {" +
714+
" title: {LIKE: \"War\"}" +
715+
" }" +
716+
" }" +
717+
" }" +
718+
" }" +
719+
" ) {" +
720+
" select {" +
721+
" id" +
722+
" name" +
723+
" books(optional: true) {" +
724+
" id" +
725+
" title(orderBy: ASC)" +
726+
" genre" +
727+
" }" +
728+
" }" +
729+
" }" +
730+
"}";
731+
732+
String expected =
733+
"{Authors={select=[" +
734+
"{id=4, name=Anton Chekhov, books=[" +
735+
"{id=5, title=The Cherry Orchard, genre=PLAY}," +
736+
" {id=6, title=The Seagull, genre=PLAY}," +
737+
" {id=7, title=Three Sisters, genre=PLAY}" +
738+
"]}, " +
739+
"{id=8, name=Igor Dianov, books=[]}" +
740+
"]}}";
741+
742+
//when
743+
Object result = executor.execute(query).getData();
744+
745+
// then
746+
assertThat(result.toString()).isEqualTo(expected);
747+
}
748+
509749
@Test
510750
public void queryAuthorBooksWithCollectionOrderBy() {
511751
//given
@@ -2055,6 +2295,22 @@ public void ignoreOrder() {
20552295
.contains(ErrorType.ValidationError, Arrays.asList("Books", "select", "description"));
20562296
}
20572297

2298+
@Test
2299+
public void logicalNotOnlySupportsSingleOperand() {
2300+
//given
2301+
String query = "{ Books(where: {NOT: [{id: {EQ: 1}} {id: {EQ: 2}}]}){ select { id description } }}";
2302+
2303+
List<GraphQLError> result = executor.execute(query).getErrors();
2304+
2305+
//then
2306+
assertThat(result).hasSize(1);
2307+
assertThat(result.get(0))
2308+
.isExactlyInstanceOf(ValidationError.class)
2309+
.extracting(ValidationError.class::cast)
2310+
.extracting("errorType", "queryPath")
2311+
.contains(ErrorType.ValidationError, Arrays.asList("Books"));
2312+
}
2313+
20582314
@Test
20592315
public void titleOrder() {
20602316
//given

0 commit comments

Comments
 (0)