Skip to content

fix: add support for nested where criteria expressions #148

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 1 commit into from
Jun 9, 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 @@ -46,7 +46,6 @@
*/
class GraphQLJpaOneToManyDataFetcher extends GraphQLJpaQueryDataFetcher {

protected static final String OPTIONAL = "optional";
private final PluralAttribute<Object,Object,Object> attribute;

public GraphQLJpaOneToManyDataFetcher(EntityManager entityManager,
Expand Down Expand Up @@ -141,11 +140,8 @@ protected TypedQuery<?> getQuery(DataFetchingEnvironment environment, Field fiel

query.select(join.alias(attribute.getName()));

List<Predicate> predicates = getFieldArguments(field, query, cb, join, environment).stream()
.filter(it -> !OPTIONAL.equals(it.getName()))
.map(it -> getPredicate(cb, from, join, environment, it))
.filter(it -> it != null)
.collect(Collectors.toList());
List<Predicate> predicates = getFieldPredicates(field, query, cb, from, join, environment);

query.where(
predicates.toArray(new Predicate[predicates.size()])
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -661,12 +661,14 @@ else if (attribute instanceof PluralAttribute

// make it configurable via builder api
arguments.add(optionalArgument(toManyDefaultOptional));

dataFetcher = new GraphQLJpaOneToManyDataFetcher(entityManager,
baseEntity,
toManyDefaultOptional,
isDefaultDistinct,
(PluralAttribute) attribute);

if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.MANY_TO_MANY) {
dataFetcher = new GraphQLJpaOneToManyDataFetcher(entityManager,
baseEntity,
toManyDefaultOptional,
isDefaultDistinct,
(PluralAttribute) attribute);
}
}

return GraphQLFieldDefinition.newFieldDefinition()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,11 @@
*/
class QraphQLJpaBaseDataFetcher implements DataFetcher<Object> {

private static final String OPTIONAL = "optional";
private static final String WHERE = "where";

protected static final String OPTIONAL = "optional";

protected static final List<String> ARGUMENTS = Arrays.asList(OPTIONAL);

// "__typename" is part of the graphql introspection spec and has to be ignored
private static final String TYPENAME = "__typename";
Expand Down Expand Up @@ -130,11 +134,7 @@ protected TypedQuery<?> getQuery(DataFetchingEnvironment environment, Field fiel
from.alias(from.getModel().getName());

// Build predicates from query arguments
List<Predicate> predicates = getFieldArguments(field, query, cb, from, environment)
.stream()
.map(it -> getPredicate(cb, from, from, environment, it))
.filter(it -> it != null)
.collect(Collectors.toList());
List<Predicate> predicates = getFieldPredicates(field, query, cb,from, from, environment);

// Use AND clause to filter results
if(!predicates.isEmpty())
Expand All @@ -146,9 +146,10 @@ protected TypedQuery<?> getQuery(DataFetchingEnvironment environment, Field fiel
return entityManager.createQuery(query.distinct(isDistinct));
}

protected final List<Argument> getFieldArguments(Field field, CriteriaQuery<?> query, CriteriaBuilder cb, From<?,?> from, DataFetchingEnvironment environment) {
protected final List<Predicate> getFieldPredicates(Field field, CriteriaQuery<?> query, CriteriaBuilder cb, Root<?> root, From<?,?> from, DataFetchingEnvironment environment) {

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

// Loop through all of the fields being requested
field.getSelectionSet().getSelections().forEach(selection -> {
Expand All @@ -161,6 +162,8 @@ protected final List<Argument> getFieldArguments(Field field, CriteriaQuery<?> q
Path<?> fieldPath = from.get(selectedField.getName());
From<?,?> fetch = null;
Optional<Argument> optionalArgument = getArgument(selectedField, OPTIONAL);
Optional<Argument> whereArgument = getArgument(selectedField, WHERE);
Boolean isOptional = null;

// Build predicate arguments for singular attributes only
if(fieldPath.getModel() instanceof SingularAttribute) {
Expand All @@ -176,46 +179,50 @@ protected final List<Argument> getFieldArguments(Field field, CriteriaQuery<?> q
query.orderBy(cb.asc(fieldPath));
}

// Process where arguments clauses.
arguments.addAll(selectedField.getArguments()
.stream()
.filter(it -> !isOrderByArgument(it) && !isOptionalArgument(it))
.map(it -> new Argument(selectedField.getName() + "." + it.getName(), it.getValue()))
.collect(Collectors.toList()));

// Check if it's an object and the foreign side is One. Then we can eagerly join causing an inner join instead of 2 queries
if (fieldPath.getModel() instanceof SingularAttribute) {
SingularAttribute<?,?> attribute = (SingularAttribute<?,?>) fieldPath.getModel();
if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.MANY_TO_ONE
|| attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_ONE
) {
// Let's do fugly conversion
Boolean isOptional = optionalArgument.map(it -> getArgumentValue(environment, it, Boolean.class))
.orElse(attribute.isOptional());

// Let's apply left outer join to retrieve optional associations
fetch = reuseFetch(from, selectedField.getName(), isOptional);
}
SingularAttribute<?,?> attribute = (SingularAttribute<?,?>) fieldPath.getModel();
if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.MANY_TO_ONE
|| attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_ONE
) {
// Let's do fugly conversion
isOptional = optionalArgument.map(it -> getArgumentValue(environment, it, Boolean.class))
.orElse(attribute.isOptional());

// Let's apply left outer join to retrieve optional associations
fetch = reuseFetch(from, selectedField.getName(), isOptional);
} else if(attribute.getPersistentAttributeType() == PersistentAttributeType.EMBEDDED) {
// Process where arguments clauses.
arguments.addAll(selectedField.getArguments()
.stream()
.filter(it -> !isOrderByArgument(it) && !isOptionalArgument(it))
.map(it -> new Argument(selectedField.getName() + "." + it.getName(),
it.getValue()))
.collect(Collectors.toList()));

}
} else {
// We must add plural attributes with explicit join
// We must add plural attributes with explicit join fetch
// Let's do fugly conversion
// the many end is a collection, and it is always optional by default (empty collection)
Boolean isOptional = optionalArgument.map(it -> getArgumentValue(environment, it, Boolean.class))
isOptional = optionalArgument.map(it -> getArgumentValue(environment, it, Boolean.class))
.orElse(toManyDefaultOptional);

// Let's apply join to retrieve associated collection
fetch = reuseFetch(from, selectedField.getName(), isOptional);

// TODO add fetch argument parameter
// Let's fetch element collections to avoid filtering their values used where search criteria
from.fetch(selectedField.getName(),
isOptional ? JoinType.LEFT : JoinType.INNER);
GraphQLObjectType objectType = getObjectType(environment);
EntityType<?> entityType = getEntityType(objectType);

PluralAttribute<?, ?, ?> attribute = (PluralAttribute<?, ?, ?>) entityType.getAttribute(selectedField.getName());

if(PersistentAttributeType.ELEMENT_COLLECTION == attribute.getPersistentAttributeType()) {
from.fetch(selectedField.getName());
}
}

// Let's build join fetch graph to avoid Hibernate error:
// "query specified join fetching, but the owner of the fetched association was not present in the select list"
if(fetch != null && selectedField.getSelectionSet() != null) {
if(selectedField.getSelectionSet() != null && fetch != null) {
GraphQLFieldDefinition fieldDefinition = getFieldDef(environment.getGraphQLSchema(),
this.getObjectType(environment),
selectedField);
Expand All @@ -224,16 +231,21 @@ protected final List<Argument> getFieldArguments(Field field, CriteriaQuery<?> q
DataFetchingEnvironment fieldEnvironment = wherePredicateEnvironment(environment,
fieldDefinition,
args);
// TODO nested where criteria expressions
getFieldArguments(selectedField, query, cb, fetch, fieldEnvironment);
predicates.addAll(getFieldPredicates(selectedField, query, cb, root, fetch, fieldEnvironment));
}
}
}
});

arguments.addAll(field.getArguments());

return arguments;
arguments.stream()
.filter(it -> !isOrderByArgument(it) && !isOptionalArgument(it))
.map(it -> getPredicate(cb, root, from, environment, it))
.filter(it -> it != null)
.forEach(predicates::add);

return predicates;
}

/**
Expand Down Expand Up @@ -331,7 +343,7 @@ protected Predicate getPredicate(CriteriaBuilder cb, Root<?> from, From<?,?> pat
String fieldName = argument.getName().split("\\.")[0];

From<?,?> join = getCompoundJoin(path, argument.getName(), true);
Argument where = new Argument("where", argument.getValue());
Argument where = new Argument(WHERE, argument.getValue());
Map<String, Object> variables = environment.getExecutionContext().getVariables();

GraphQLFieldDefinition fieldDef = getFieldDef(
Expand All @@ -342,7 +354,7 @@ protected Predicate getPredicate(CriteriaBuilder cb, Root<?> from, From<?,?> pat

Map<String, Object> arguments = (Map<String, Object>) new ValuesResolver()
.getArgumentValues(fieldDef.getArguments(), Collections.singletonList(where), variables)
.get("where");
.get(WHERE);

return getWherePredicate(cb, from, join, wherePredicateEnvironment(environment, fieldDef, arguments), where);
}
Expand Down Expand Up @@ -417,7 +429,7 @@ protected Predicate getArgumentPredicate(CriteriaBuilder cb, From<?,?> from,
new Field(it.getName()));
boolean isOptional = false;

return getArgumentPredicate(cb, reuseFetch(from, it.getName(), isOptional),
return getArgumentPredicate(cb, reuseJoin(from, it.getName(), isOptional),
wherePredicateEnvironment(environment, fieldDefinition, args),
arg);
}
Expand Down Expand Up @@ -502,7 +514,7 @@ protected Predicate getArgumentsPredicate(CriteriaBuilder cb,
new Field(it.getName()));
boolean isOptional = false;

return getArgumentPredicate(cb, reuseFetch(path, it.getName(), isOptional),
return getArgumentPredicate(cb, reuseJoin(path, it.getName(), isOptional),
wherePredicateEnvironment(environment, fieldDefinition, args),
arg);
}
Expand Down Expand Up @@ -631,7 +643,7 @@ private Predicate getLogicalPredicate(String fieldName, CriteriaBuilder cb, From
isOptional = isOptionalAttribute(getAttribute(environment, argument));
}

return getArgumentPredicate(cb, reuseFetch(path, fieldName, isOptional),
return getArgumentPredicate(cb, reuseJoin(path, fieldName, isOptional),
wherePredicateEnvironment(environment, fieldDefinition, args),
arg);
}
Expand Down Expand Up @@ -714,7 +726,7 @@ protected final DataFetchingEnvironment wherePredicateEnvironment(DataFetchingEn
private From<?,?> getCompoundJoin(From<?,?> rootPath, String fieldName, boolean outer) {
String[] compoundField = fieldName.split("\\.");

Join<?,?> join;
From<?,?> join;

if (compoundField.length == 1) {
return rootPath;
Expand All @@ -740,7 +752,7 @@ private From<?,?> getCompoundJoin(From<?,?> rootPath, String fieldName, boolean
private Path<?> getCompoundJoinedPath(From<?,?> rootPath, String fieldName, boolean outer) {
String[] compoundField = fieldName.split("\\.");

Join<?,?> join;
From<?,?> join;

if (compoundField.length == 1) {
return rootPath.get(compoundField[0]);
Expand All @@ -760,7 +772,7 @@ private Path<?> getCompoundJoinedPath(From<?,?> rootPath, String fieldName, bool
}

// trying to find already existing joins to reuse
private Join<?,?> reuseJoin(From<?, ?> from, String fieldName, boolean outer) {
private From<?,?> reuseJoin(From<?, ?> from, String fieldName, boolean outer) {

for (Join<?,?> join : from.getJoins()) {
if (join.getAttribute().getName().equals(fieldName)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,7 @@ public void queryEnumArrayVariableBindingInEmbeddedRelation() {
+ "{id=1, name=Leo Tolstoy, books=["
+ "{id=2, title=War and Peace, genre=NOVEL}, "
+ "{id=3, title=Anna Karenina, genre=NOVEL}"
+ "]}, "
+ "{id=4, name=Anton Chekhov, books=[]}, "
+ "{id=8, name=Igor Dianov, books=[]}"
+ "]}"
+ "]}}";

//when
Expand Down
Loading