Skip to content

Commit 764c439

Browse files
authored
feat: add AND/OR logical criteria list in schema and query (#134)
* feat: add criteria list schema support * feat: implement criteria list query support * feat: add criteria list unit tests
1 parent ac7bda6 commit 764c439

File tree

4 files changed

+633
-29
lines changed

4 files changed

+633
-29
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ protected Predicate getPredicate(CriteriaBuilder cb, Root<?> root, From<?,?> pat
173173
return null;
174174

175175
if(isWhereArgument(argument))
176-
return getWherePredicate(cb, root, path, argumentEnvironment(environment, argument.getName()), argument);
176+
return getWherePredicate(cb, root, path, argumentEnvironment(environment, argument), argument);
177177

178178
return super.getPredicate(cb, root, path, environment, argument);
179179
}

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -239,13 +239,13 @@ private GraphQLArgument computeWhereArgument(ManagedType<?> managedType) {
239239
.field(GraphQLInputObjectField.newInputObjectField()
240240
.name(OR)
241241
.description("Logical operation for expressions")
242-
.type(new GraphQLTypeReference(type))
242+
.type(new GraphQLList(new GraphQLTypeReference(type)))
243243
.build()
244244
)
245245
.field(GraphQLInputObjectField.newInputObjectField()
246246
.name(AND)
247247
.description("Logical operation for expressions")
248-
.type(new GraphQLTypeReference(type))
248+
.type(new GraphQLList(new GraphQLTypeReference(type)))
249249
.build()
250250
)
251251
.fields(managedType.getAttributes().stream()
@@ -310,13 +310,13 @@ private GraphQLInputObjectType computeWhereInputType(ManagedType<?> managedType)
310310
.field(GraphQLInputObjectField.newInputObjectField()
311311
.name(OR)
312312
.description("Logical operation for expressions")
313-
.type(new GraphQLTypeReference(type))
313+
.type(new GraphQLList(new GraphQLTypeReference(type)))
314314
.build()
315315
)
316316
.field(GraphQLInputObjectField.newInputObjectField()
317317
.name(AND)
318318
.description("Logical operation for expressions")
319-
.type(new GraphQLTypeReference(type))
319+
.type(new GraphQLList(new GraphQLTypeReference(type)))
320320
.build()
321321
)
322322
.fields(managedType.getAttributes().stream()
@@ -381,13 +381,13 @@ private GraphQLInputType getWhereAttributeType(Attribute<?,?> attribute) {
381381
.field(GraphQLInputObjectField.newInputObjectField()
382382
.name(OR)
383383
.description("Logical OR criteria expression")
384-
.type(new GraphQLTypeReference(type))
384+
.type(new GraphQLList(new GraphQLTypeReference(type)))
385385
.build()
386386
)
387387
.field(GraphQLInputObjectField.newInputObjectField()
388388
.name(AND)
389389
.description("Logical AND criteria expression")
390-
.type(new GraphQLTypeReference(type))
390+
.type(new GraphQLList(new GraphQLTypeReference(type)))
391391
.build()
392392
)
393393
.field(GraphQLInputObjectField.newInputObjectField()

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

Lines changed: 218 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static graphql.introspection.Introspection.TypeMetaFieldDef;
2020
import static graphql.introspection.Introspection.TypeNameMetaFieldDef;
2121

22+
import java.util.AbstractMap.SimpleEntry;
2223
import java.util.ArrayList;
2324
import java.util.Arrays;
2425
import java.util.Collection;
@@ -30,6 +31,7 @@
3031
import java.util.NoSuchElementException;
3132
import java.util.Optional;
3233
import java.util.stream.Collectors;
34+
import java.util.stream.IntStream;
3335
import java.util.stream.Stream;
3436

3537
import javax.persistence.EntityGraph;
@@ -363,21 +365,157 @@ protected Predicate getArgumentPredicate(CriteriaBuilder cb, From<?,?> path,
363365

364366
whereValue.getObjectFields().stream()
365367
.filter(it -> Logical.names().contains(it.getName()))
366-
.map(it -> getArgumentPredicate(cb, path,
367-
argumentEnvironment(environment, argument.getName()),
368-
new Argument(it.getName(), it.getValue())))
368+
.map(it -> {
369+
Map<String, Object> arguments = getFieldArguments(environment, it, argument);
370+
371+
if(it.getValue() instanceof ArrayValue) {
372+
return getArrayArgumentPredicate(cb, path,
373+
argumentEnvironment(environment, arguments),
374+
new Argument(it.getName(), it.getValue()));
375+
}
376+
377+
return getArgumentPredicate(cb, path,
378+
argumentEnvironment(environment, arguments),
379+
new Argument(it.getName(), it.getValue()));
380+
})
369381
.forEach(predicates::add);
370382

371-
whereValue.getObjectFields().stream()
372-
.filter(it -> !Logical.names().contains(it.getName()))
373-
.map(it -> getFieldPredicate(it.getName(), cb, path, it,
374-
argumentEnvironment(environment, argument.getName()),
375-
new Argument(it.getName(), it.getValue())))
376-
.filter(predicate -> predicate != null)
377-
.forEach(predicates::add);
383+
whereValue.getObjectFields()
384+
.stream()
385+
.filter(it -> !Logical.names().contains(it.getName()))
386+
.map(it -> {
387+
Map<String, Object> args = getFieldArguments(environment, it, argument);
388+
Argument arg = new Argument(it.getName(), it.getValue());
389+
390+
if(isEntityType(environment)) {
391+
Attribute<?,?> attribute = getAttribute(environment, arg);
392+
393+
if(attribute.isAssociation()) {
394+
GraphQLFieldDefinition fieldDefinition = getFieldDef(environment.getGraphQLSchema(),
395+
this.getObjectType(environment),
396+
new Field(it.getName()));
397+
boolean isOptional = false;
398+
399+
return getArgumentPredicate(cb, reuseJoin(path, it.getName(), isOptional),
400+
wherePredicateEnvironment(environment, fieldDefinition, args),
401+
arg);
402+
}
403+
}
404+
405+
return getFieldPredicate(it.getName(),
406+
cb,
407+
path,
408+
it,
409+
argumentEnvironment(environment, args),
410+
arg);
411+
})
412+
.filter(predicate -> predicate != null)
413+
.forEach(predicates::add);
378414

379415
return getCompoundPredicate(cb, predicates, logical);
380416
}
417+
418+
protected Predicate getArrayArgumentPredicate(CriteriaBuilder cb,
419+
From<?, ?> path,
420+
DataFetchingEnvironment environment,
421+
Argument argument) {
422+
ArrayValue whereValue = getValue(argument);
423+
424+
if (whereValue.getValues().isEmpty())
425+
return cb.disjunction();
426+
427+
Logical logical = extractLogical(argument);
428+
429+
List<Predicate> predicates = new ArrayList<>();
430+
431+
List<Map<String,Object>> arguments = environment.getArgument(logical.name());
432+
List<ObjectValue> values = whereValue.getValues()
433+
.stream()
434+
.map(ObjectValue.class::cast).collect(Collectors.toList());
435+
436+
List<SimpleEntry<ObjectValue, Map<String, Object>>> tuples =
437+
IntStream.range(0, values.size())
438+
.mapToObj(i -> new SimpleEntry<ObjectValue, Map<String, Object>>(values.get(i),
439+
arguments.get(i)))
440+
.collect(Collectors.toList());
441+
442+
tuples.stream()
443+
.flatMap(e -> e.getKey()
444+
.getObjectFields()
445+
.stream()
446+
.filter(it -> Logical.names().contains(it.getName()))
447+
.map(it -> {
448+
Map<String, Object> args = e.getValue();
449+
Argument arg = new Argument(it.getName(), it.getValue());
450+
451+
if(ArrayValue.class.isInstance(it.getValue())) {
452+
return getArrayArgumentPredicate(cb,
453+
path,
454+
argumentEnvironment(environment, args),
455+
arg);
456+
}
457+
458+
return getArgumentPredicate(cb,
459+
path,
460+
argumentEnvironment(environment, args),
461+
arg);
462+
463+
}))
464+
.forEach(predicates::add);
465+
466+
tuples.stream()
467+
.flatMap(e -> e.getKey()
468+
.getObjectFields()
469+
.stream()
470+
.filter(it -> !Logical.names().contains(it.getName()))
471+
.map(it -> {
472+
Map<String, Object> args = e.getValue();
473+
Argument arg = new Argument(it.getName(), it.getValue());
474+
475+
if(isEntityType(environment)) {
476+
Attribute<?,?> attribute = getAttribute(environment, arg);
477+
478+
if(attribute.isAssociation()) {
479+
GraphQLFieldDefinition fieldDefinition = getFieldDef(environment.getGraphQLSchema(),
480+
this.getObjectType(environment),
481+
new Field(it.getName()));
482+
boolean isOptional = false;
483+
484+
return getArgumentPredicate(cb, reuseJoin(path, it.getName(), isOptional),
485+
wherePredicateEnvironment(environment, fieldDefinition, args),
486+
arg);
487+
}
488+
}
489+
490+
return getFieldPredicate(it.getName(),
491+
cb,
492+
path,
493+
it,
494+
argumentEnvironment(environment, args),
495+
arg);
496+
}))
497+
.filter(predicate -> predicate != null)
498+
.forEach(predicates::add);
499+
500+
return getCompoundPredicate(cb, predicates, logical);
501+
}
502+
503+
private Map<String, Object> getFieldArguments(DataFetchingEnvironment environment, ObjectField field, Argument argument) {
504+
Map<String, Object> arguments;
505+
506+
if (environment.getArgument(argument.getName()) instanceof Collection) {
507+
Collection<Map<String,Object>> list = environment.getArgument(argument.getName());
508+
509+
arguments = list.stream()
510+
.filter(args -> args.get(field.getName()) != null)
511+
.findFirst()
512+
.orElse(list.stream().findFirst().get());
513+
} else {
514+
arguments = environment.getArgument(argument.getName());
515+
}
516+
517+
return arguments;
518+
}
381519

382520
private Logical extractLogical(Argument argument) {
383521
return Optional.of(argument.getName())
@@ -386,6 +524,38 @@ private Logical extractLogical(Argument argument) {
386524
.orElse(Logical.AND);
387525
}
388526

527+
private Predicate getArrayFieldPredicate(String fieldName,
528+
CriteriaBuilder cb,
529+
From<?, ?> path,
530+
ObjectField objectField,
531+
DataFetchingEnvironment environment,
532+
Argument argument) {
533+
ArrayValue value = ArrayValue.class.cast(objectField.getValue());
534+
535+
Logical logical = extractLogical(argument);
536+
537+
List<Predicate> predicates = new ArrayList<>();
538+
539+
value.getValues()
540+
.stream()
541+
.map(ObjectValue.class::cast)
542+
.flatMap(it -> it.getObjectFields().stream())
543+
.map(it -> {
544+
Map<String, Object> args = getFieldArguments(environment, it, argument);
545+
Argument arg = new Argument(it.getName(), it.getValue());
546+
547+
return getFieldPredicate(it.getName(),
548+
cb,
549+
path,
550+
it,
551+
argumentEnvironment(environment, args),
552+
arg);
553+
})
554+
.forEach(predicates::add);
555+
556+
return getCompoundPredicate(cb, predicates, logical);
557+
}
558+
389559
private Predicate getFieldPredicate(String fieldName, CriteriaBuilder cb, From<?,?> path, ObjectField objectField, DataFetchingEnvironment environment, Argument argument) {
390560
ObjectValue expressionValue;
391561

@@ -404,10 +574,20 @@ private Predicate getFieldPredicate(String fieldName, CriteriaBuilder cb, From<?
404574
// Let's parse logical expressions, i.e. AND, OR
405575
expressionValue.getObjectFields().stream()
406576
.filter(it -> Logical.names().contains(it.getName()))
407-
.map(it -> getFieldPredicate(fieldName, cb, path, it,
408-
argumentEnvironment(environment, argument.getName()),
409-
new Argument(it.getName(), it.getValue()))
410-
)
577+
.map(it -> {
578+
Map<String, Object> args = getFieldArguments(environment, it, argument);
579+
Argument arg = new Argument(it.getName(), it.getValue());
580+
581+
if(it.getValue() instanceof ArrayValue) {
582+
return getArrayFieldPredicate(fieldName, cb, path, it,
583+
argumentEnvironment(environment, args),
584+
arg);
585+
}
586+
587+
return getFieldPredicate(fieldName, cb, path, it,
588+
argumentEnvironment(environment, args),
589+
arg);
590+
})
411591
.forEach(predicates::add);
412592

413593
// Let's parse relation criteria expressions if present, i.e. books, author, etc.
@@ -418,20 +598,21 @@ private Predicate getFieldPredicate(String fieldName, CriteriaBuilder cb, From<?
418598
GraphQLFieldDefinition fieldDefinition = getFieldDef(environment.getGraphQLSchema(),
419599
this.getObjectType(environment),
420600
new Field(fieldName));
421-
Map<String, Object> arguments = new LinkedHashMap<>();
601+
Map<String, Object> args = new LinkedHashMap<>();
602+
Argument arg = new Argument(logical.name(), expressionValue);
422603
boolean isOptional = false;
423604

424605
if(Logical.names().contains(argument.getName())) {
425-
arguments.put(logical.name(), environment.getArgument(argument.getName()));
606+
args.put(logical.name(), environment.getArgument(argument.getName()));
426607
} else {
427-
arguments.put(logical.name(), environment.getArgument(fieldName));
608+
args.put(logical.name(), environment.getArgument(fieldName));
428609

429610
isOptional = isOptionalAttribute(getAttribute(environment, argument));
430611
}
431612

432613
return getArgumentPredicate(cb, reuseJoin(path, fieldName, isOptional),
433-
wherePredicateEnvironment(environment, fieldDefinition, arguments),
434-
new Argument(logical.name(), expressionValue));
614+
wherePredicateEnvironment(environment, fieldDefinition, args),
615+
arg);
435616
}
436617

437618
// Let's parse simple Criteria expressions, i.e. EQ, LIKE, etc.
@@ -441,7 +622,7 @@ private Predicate getFieldPredicate(String fieldName, CriteriaBuilder cb, From<?
441622
.stream()
442623
.filter(it -> Criteria.names().contains(it.getName()))
443624
.map(it -> getPredicateFilter(new ObjectField(fieldName, it.getValue()),
444-
argumentEnvironment(environment, argument.getName()),
625+
argumentEnvironment(environment, argument),
445626
new Argument(it.getName(), it.getValue())))
446627
.sorted()
447628
.map(it -> pb.getPredicate(path, path.get(it.getField()), it))
@@ -483,9 +664,17 @@ private PredicateFilter getPredicateFilter(ObjectField objectField, DataFetching
483664
return new PredicateFilter(objectField.getName(), filterValue, options );
484665
}
485666

486-
protected final DataFetchingEnvironment argumentEnvironment(DataFetchingEnvironment environment, String argumentName) {
667+
protected final DataFetchingEnvironment argumentEnvironment(DataFetchingEnvironment environment, Map<String, Object> arguments) {
487668
return DataFetchingEnvironmentBuilder.newDataFetchingEnvironment(environment)
488-
.arguments(environment.getArgument(argumentName))
669+
.arguments(arguments)
670+
.build();
671+
}
672+
673+
protected final DataFetchingEnvironment argumentEnvironment(DataFetchingEnvironment environment, Argument argument) {
674+
Map<String, Object> arguments = environment.getArgument(argument.getName());
675+
676+
return DataFetchingEnvironmentBuilder.newDataFetchingEnvironment(environment)
677+
.arguments(arguments)
489678
.build();
490679
}
491680

@@ -687,6 +876,13 @@ private EntityType<?> getEntityType(GraphQLObjectType objectType) {
687876
.findFirst()
688877
.get();
689878
}
879+
880+
private boolean isEntityType(DataFetchingEnvironment environment) {
881+
GraphQLObjectType objectType = getObjectType(environment);
882+
return entityManager.getMetamodel()
883+
.getEntities().stream()
884+
.anyMatch(it -> it.getName().equals(objectType.getName()));
885+
}
690886

691887
/**
692888
* Resolve GraphQL object type from Argument output type.

0 commit comments

Comments
 (0)