Skip to content

feat: add AND/OR logical criteria list in schema and query #134

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ protected Predicate getPredicate(CriteriaBuilder cb, Root<?> root, From<?,?> pat
return null;

if(isWhereArgument(argument))
return getWherePredicate(cb, root, path, argumentEnvironment(environment, argument.getName()), argument);
return getWherePredicate(cb, root, path, argumentEnvironment(environment, argument), argument);

return super.getPredicate(cb, root, path, environment, argument);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,13 +239,13 @@ private GraphQLArgument computeWhereArgument(ManagedType<?> managedType) {
.field(GraphQLInputObjectField.newInputObjectField()
.name(OR)
.description("Logical operation for expressions")
.type(new GraphQLTypeReference(type))
.type(new GraphQLList(new GraphQLTypeReference(type)))
.build()
)
.field(GraphQLInputObjectField.newInputObjectField()
.name(AND)
.description("Logical operation for expressions")
.type(new GraphQLTypeReference(type))
.type(new GraphQLList(new GraphQLTypeReference(type)))
.build()
)
.fields(managedType.getAttributes().stream()
Expand Down Expand Up @@ -310,13 +310,13 @@ private GraphQLInputObjectType computeWhereInputType(ManagedType<?> managedType)
.field(GraphQLInputObjectField.newInputObjectField()
.name(OR)
.description("Logical operation for expressions")
.type(new GraphQLTypeReference(type))
.type(new GraphQLList(new GraphQLTypeReference(type)))
.build()
)
.field(GraphQLInputObjectField.newInputObjectField()
.name(AND)
.description("Logical operation for expressions")
.type(new GraphQLTypeReference(type))
.type(new GraphQLList(new GraphQLTypeReference(type)))
.build()
)
.fields(managedType.getAttributes().stream()
Expand Down Expand Up @@ -381,13 +381,13 @@ private GraphQLInputType getWhereAttributeType(Attribute<?,?> attribute) {
.field(GraphQLInputObjectField.newInputObjectField()
.name(OR)
.description("Logical OR criteria expression")
.type(new GraphQLTypeReference(type))
.type(new GraphQLList(new GraphQLTypeReference(type)))
.build()
)
.field(GraphQLInputObjectField.newInputObjectField()
.name(AND)
.description("Logical AND criteria expression")
.type(new GraphQLTypeReference(type))
.type(new GraphQLList(new GraphQLTypeReference(type)))
.build()
)
.field(GraphQLInputObjectField.newInputObjectField()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static graphql.introspection.Introspection.TypeMetaFieldDef;
import static graphql.introspection.Introspection.TypeNameMetaFieldDef;

import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
Expand All @@ -30,6 +31,7 @@
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

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

whereValue.getObjectFields().stream()
.filter(it -> Logical.names().contains(it.getName()))
.map(it -> getArgumentPredicate(cb, path,
argumentEnvironment(environment, argument.getName()),
new Argument(it.getName(), it.getValue())))
.map(it -> {
Map<String, Object> arguments = getFieldArguments(environment, it, argument);

if(it.getValue() instanceof ArrayValue) {
return getArrayArgumentPredicate(cb, path,
argumentEnvironment(environment, arguments),
new Argument(it.getName(), it.getValue()));
}

return getArgumentPredicate(cb, path,
argumentEnvironment(environment, arguments),
new Argument(it.getName(), it.getValue()));
})
.forEach(predicates::add);

whereValue.getObjectFields().stream()
.filter(it -> !Logical.names().contains(it.getName()))
.map(it -> getFieldPredicate(it.getName(), cb, path, it,
argumentEnvironment(environment, argument.getName()),
new Argument(it.getName(), it.getValue())))
.filter(predicate -> predicate != null)
.forEach(predicates::add);
whereValue.getObjectFields()
.stream()
.filter(it -> !Logical.names().contains(it.getName()))
.map(it -> {
Map<String, Object> args = getFieldArguments(environment, it, argument);
Argument arg = new Argument(it.getName(), it.getValue());

if(isEntityType(environment)) {
Attribute<?,?> attribute = getAttribute(environment, arg);

if(attribute.isAssociation()) {
GraphQLFieldDefinition fieldDefinition = getFieldDef(environment.getGraphQLSchema(),
this.getObjectType(environment),
new Field(it.getName()));
boolean isOptional = false;

return getArgumentPredicate(cb, reuseJoin(path, it.getName(), isOptional),
wherePredicateEnvironment(environment, fieldDefinition, args),
arg);
}
}

return getFieldPredicate(it.getName(),
cb,
path,
it,
argumentEnvironment(environment, args),
arg);
})
.filter(predicate -> predicate != null)
.forEach(predicates::add);

return getCompoundPredicate(cb, predicates, logical);
}

protected Predicate getArrayArgumentPredicate(CriteriaBuilder cb,
From<?, ?> path,
DataFetchingEnvironment environment,
Argument argument) {
ArrayValue whereValue = getValue(argument);

if (whereValue.getValues().isEmpty())
return cb.disjunction();

Logical logical = extractLogical(argument);

List<Predicate> predicates = new ArrayList<>();

List<Map<String,Object>> arguments = environment.getArgument(logical.name());
List<ObjectValue> values = whereValue.getValues()
.stream()
.map(ObjectValue.class::cast).collect(Collectors.toList());

List<SimpleEntry<ObjectValue, Map<String, Object>>> tuples =
IntStream.range(0, values.size())
.mapToObj(i -> new SimpleEntry<ObjectValue, Map<String, Object>>(values.get(i),
arguments.get(i)))
.collect(Collectors.toList());

tuples.stream()
.flatMap(e -> e.getKey()
.getObjectFields()
.stream()
.filter(it -> Logical.names().contains(it.getName()))
.map(it -> {
Map<String, Object> args = e.getValue();
Argument arg = new Argument(it.getName(), it.getValue());

if(ArrayValue.class.isInstance(it.getValue())) {
return getArrayArgumentPredicate(cb,
path,
argumentEnvironment(environment, args),
arg);
}

return getArgumentPredicate(cb,
path,
argumentEnvironment(environment, args),
arg);

}))
.forEach(predicates::add);

tuples.stream()
.flatMap(e -> e.getKey()
.getObjectFields()
.stream()
.filter(it -> !Logical.names().contains(it.getName()))
.map(it -> {
Map<String, Object> args = e.getValue();
Argument arg = new Argument(it.getName(), it.getValue());

if(isEntityType(environment)) {
Attribute<?,?> attribute = getAttribute(environment, arg);

if(attribute.isAssociation()) {
GraphQLFieldDefinition fieldDefinition = getFieldDef(environment.getGraphQLSchema(),
this.getObjectType(environment),
new Field(it.getName()));
boolean isOptional = false;

return getArgumentPredicate(cb, reuseJoin(path, it.getName(), isOptional),
wherePredicateEnvironment(environment, fieldDefinition, args),
arg);
}
}

return getFieldPredicate(it.getName(),
cb,
path,
it,
argumentEnvironment(environment, args),
arg);
}))
.filter(predicate -> predicate != null)
.forEach(predicates::add);

return getCompoundPredicate(cb, predicates, logical);
}

private Map<String, Object> getFieldArguments(DataFetchingEnvironment environment, ObjectField field, Argument argument) {
Map<String, Object> arguments;

if (environment.getArgument(argument.getName()) instanceof Collection) {
Collection<Map<String,Object>> list = environment.getArgument(argument.getName());

arguments = list.stream()
.filter(args -> args.get(field.getName()) != null)
.findFirst()
.orElse(list.stream().findFirst().get());
} else {
arguments = environment.getArgument(argument.getName());
}

return arguments;
}

private Logical extractLogical(Argument argument) {
return Optional.of(argument.getName())
Expand All @@ -386,6 +524,38 @@ private Logical extractLogical(Argument argument) {
.orElse(Logical.AND);
}

private Predicate getArrayFieldPredicate(String fieldName,
CriteriaBuilder cb,
From<?, ?> path,
ObjectField objectField,
DataFetchingEnvironment environment,
Argument argument) {
ArrayValue value = ArrayValue.class.cast(objectField.getValue());

Logical logical = extractLogical(argument);

List<Predicate> predicates = new ArrayList<>();

value.getValues()
.stream()
.map(ObjectValue.class::cast)
.flatMap(it -> it.getObjectFields().stream())
.map(it -> {
Map<String, Object> args = getFieldArguments(environment, it, argument);
Argument arg = new Argument(it.getName(), it.getValue());

return getFieldPredicate(it.getName(),
cb,
path,
it,
argumentEnvironment(environment, args),
arg);
})
.forEach(predicates::add);

return getCompoundPredicate(cb, predicates, logical);
}

private Predicate getFieldPredicate(String fieldName, CriteriaBuilder cb, From<?,?> path, ObjectField objectField, DataFetchingEnvironment environment, Argument argument) {
ObjectValue expressionValue;

Expand All @@ -404,10 +574,20 @@ private Predicate getFieldPredicate(String fieldName, CriteriaBuilder cb, From<?
// Let's parse logical expressions, i.e. AND, OR
expressionValue.getObjectFields().stream()
.filter(it -> Logical.names().contains(it.getName()))
.map(it -> getFieldPredicate(fieldName, cb, path, it,
argumentEnvironment(environment, argument.getName()),
new Argument(it.getName(), it.getValue()))
)
.map(it -> {
Map<String, Object> args = getFieldArguments(environment, it, argument);
Argument arg = new Argument(it.getName(), it.getValue());

if(it.getValue() instanceof ArrayValue) {
return getArrayFieldPredicate(fieldName, cb, path, it,
argumentEnvironment(environment, args),
arg);
}

return getFieldPredicate(fieldName, cb, path, it,
argumentEnvironment(environment, args),
arg);
})
.forEach(predicates::add);

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

if(Logical.names().contains(argument.getName())) {
arguments.put(logical.name(), environment.getArgument(argument.getName()));
args.put(logical.name(), environment.getArgument(argument.getName()));
} else {
arguments.put(logical.name(), environment.getArgument(fieldName));
args.put(logical.name(), environment.getArgument(fieldName));

isOptional = isOptionalAttribute(getAttribute(environment, argument));
}

return getArgumentPredicate(cb, reuseJoin(path, fieldName, isOptional),
wherePredicateEnvironment(environment, fieldDefinition, arguments),
new Argument(logical.name(), expressionValue));
wherePredicateEnvironment(environment, fieldDefinition, args),
arg);
}

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

protected final DataFetchingEnvironment argumentEnvironment(DataFetchingEnvironment environment, String argumentName) {
protected final DataFetchingEnvironment argumentEnvironment(DataFetchingEnvironment environment, Map<String, Object> arguments) {
return DataFetchingEnvironmentBuilder.newDataFetchingEnvironment(environment)
.arguments(environment.getArgument(argumentName))
.arguments(arguments)
.build();
}

protected final DataFetchingEnvironment argumentEnvironment(DataFetchingEnvironment environment, Argument argument) {
Map<String, Object> arguments = environment.getArgument(argument.getName());

return DataFetchingEnvironmentBuilder.newDataFetchingEnvironment(environment)
.arguments(arguments)
.build();
}

Expand Down Expand Up @@ -687,6 +876,13 @@ private EntityType<?> getEntityType(GraphQLObjectType objectType) {
.findFirst()
.get();
}

private boolean isEntityType(DataFetchingEnvironment environment) {
GraphQLObjectType objectType = getObjectType(environment);
return entityManager.getMetamodel()
.getEntities().stream()
.anyMatch(it -> it.getName().equals(objectType.getName()));
}

/**
* Resolve GraphQL object type from Argument output type.
Expand Down
Loading