Skip to content

Commit

Permalink
DATAGRAPH-1286 - Support generated ids in derived finder methods.
Browse files Browse the repository at this point in the history
This introduces NativeIdFilterFunction that is applied when a derived finder method hits a native id property.
  • Loading branch information
michael-simons committed Jan 20, 2020
1 parent 7494727 commit c0050a1
Show file tree
Hide file tree
Showing 12 changed files with 234 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,13 @@ public boolean isIdProperty() {
return propertyType.idProperty;
}

/**
* @return True if this property describes the internal ID property.
*/
public boolean isInternalIdProperty() {
return propertyType == PropertyType.INTERNAL_ID_PROPERTY;
}

PropertyType getPropertyType() {
return propertyType;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Predicate;

import org.neo4j.ogm.cypher.BooleanOperator;
import org.neo4j.ogm.cypher.Filters;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.neo4j.mapping.Neo4jMappingContext;
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
import org.springframework.data.neo4j.repository.query.filter.FilterBuilder;
import org.springframework.data.repository.query.parser.Part;

Expand All @@ -39,17 +43,24 @@ class FilterBuildersDefinition {
private final Part basePart;

private final List<FilterBuilder> filterBuilders;
private final Predicate<Part> isInternalIdProperty;

static UnstartedBuild forType(Class<?> entityType) {
return new UnstartedBuild(entityType);
static UnstartedBuild forType(Neo4jMappingContext mappingContext, Class<?> entityType) {
return new UnstartedBuild(mappingContext, entityType);
}

private FilterBuildersDefinition(Class<?> entityType, Part basePart) {
private FilterBuildersDefinition(Neo4jMappingContext mappingContext, Class<?> entityType, Part basePart) {
this.entityType = entityType;
this.basePart = basePart;
this.filterBuilders = new LinkedList<>();

this.filterBuilders.add(FilterBuilder.forPartAndEntity(basePart, entityType, BooleanOperator.NONE));
this.isInternalIdProperty = part -> {
PersistentPropertyPath<Neo4jPersistentProperty> path = mappingContext
.getPersistentPropertyPath(part.getProperty());
Neo4jPersistentProperty possibleIdProperty = path.getRequiredLeafProperty();
return possibleIdProperty.isInternalIdProperty();
};
this.filterBuilders.add(FilterBuilder.forPartAndEntity(basePart, entityType, BooleanOperator.NONE,
isInternalIdProperty));
}

TemplatedQuery buildTemplatedQuery() {
Expand All @@ -66,24 +77,28 @@ Part getBasePart() {
}

FilterBuildersDefinition and(Part part) {
this.filterBuilders.add(FilterBuilder.forPartAndEntity(part, entityType, BooleanOperator.AND));
this.filterBuilders.add(FilterBuilder.forPartAndEntity(part, entityType, BooleanOperator.AND, isInternalIdProperty));
return this;
}

FilterBuildersDefinition or(Part part) {
this.filterBuilders.add(FilterBuilder.forPartAndEntity(part, entityType, BooleanOperator.OR));
this.filterBuilders.add(FilterBuilder.forPartAndEntity(part, entityType, BooleanOperator.OR, isInternalIdProperty));
return this;
}

static class UnstartedBuild {

private final Neo4jMappingContext mappingContext;

private final Class<?> entityType;

UnstartedBuild(Class<?> entityType) {
UnstartedBuild(Neo4jMappingContext mappingContext, Class<?> entityType) {
this.mappingContext = mappingContext;
this.entityType = entityType;
}

FilterBuildersDefinition startWith(Part firstPart) {
return new FilterBuildersDefinition(entityType, firstPart);
return new FilterBuildersDefinition(mappingContext, entityType, firstPart);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,19 +118,6 @@ private Integer getQueryDepthParamIndex(Method method) {
}
}
}
/*
//Java 8 only
Parameter[] parameters = method.getParameters();
for (int i = 0; i < method.getParameterCount(); i++) {
if (parameters[i].isAnnotationPresent(Depth.class)) {
if (parameters[i].getType() == Integer.class || parameters[i].getType() == int.class) {
return i;
}
else {
throw new IllegalArgumentException("Depth parameter in " + method.getName() + " must be an integer");
}
}
}*/
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.neo4j.ogm.session.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.neo4j.mapping.Neo4jMappingContext;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.parser.PartTree;
Expand Down Expand Up @@ -49,11 +50,11 @@ public PartTreeNeo4jQuery(GraphQueryMethod graphQueryMethod, MetaData metaData,
super(graphQueryMethod, metaData, session);

Class<?> domainType = graphQueryMethod.getEntityInformation().getJavaType();

this.graphQueryMethod = graphQueryMethod;
this.tree = new PartTree(graphQueryMethod.getName(), domainType);

this.queryTemplate = new TemplatedQueryCreator(this.tree, domainType).createQuery();
this.queryTemplate = new TemplatedQueryCreator(this.tree,
(Neo4jMappingContext) this.graphQueryMethod.getMappingContext(), domainType).createQuery();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Optional;

import org.springframework.data.domain.Sort;
import org.springframework.data.neo4j.mapping.Neo4jMappingContext;
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.data.repository.query.parser.PartTree;
Expand All @@ -33,17 +34,19 @@
*/
class TemplatedQueryCreator extends AbstractQueryCreator<TemplatedQuery, FilterBuildersDefinition> {

private final Neo4jMappingContext mappingContext;
private final Class<?> entityType;

public TemplatedQueryCreator(PartTree tree, Class<?> entityType) {
public TemplatedQueryCreator(PartTree tree, Neo4jMappingContext mappingContext, Class<?> entityType) {
super(tree);

this.mappingContext = mappingContext;
this.entityType = entityType;
}

@Override
protected FilterBuildersDefinition create(Part part, Iterator<Object> iterator) {
return FilterBuildersDefinition.forType(entityType) //
return FilterBuildersDefinition.forType(mappingContext, entityType) //
.startWith(part);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Stack;
import java.util.function.Predicate;

import org.neo4j.ogm.cypher.BooleanOperator;
import org.neo4j.ogm.cypher.Filter;
Expand All @@ -41,27 +42,40 @@ public abstract class FilterBuilder {
protected Part part;
protected BooleanOperator booleanOperator;
protected Class<?> entityType;
protected Predicate<Part> isInternalIdProperty = part -> false;

public static FilterBuilder forPartAndEntity(Part part, Class<?> entityType, BooleanOperator booleanOperator) {
public static FilterBuilder forPartAndEntity(Part part, Class<?> entityType, BooleanOperator booleanOperator,
Predicate<Part> isInternalIdProperty) {
FilterBuilder filterBuilder;
switch (part.getType()) {
case NEAR:
return new DistanceComparisonBuilder(part, booleanOperator, entityType);
filterBuilder = new DistanceComparisonBuilder(part, booleanOperator, entityType);
break;
case BETWEEN:
return new BetweenComparisonBuilder(part, booleanOperator, entityType);
filterBuilder = new BetweenComparisonBuilder(part, booleanOperator, entityType);
break;
case NOT_CONTAINING:
case CONTAINING:
return resolveMatchingContainsFilterBuilder(part, entityType, booleanOperator);
filterBuilder = resolveMatchingContainsFilterBuilder(part, entityType, booleanOperator);
break;
case IS_NULL:
case IS_NOT_NULL:
return new IsNullFilterBuilder(part, booleanOperator, entityType);
filterBuilder = new IsNullFilterBuilder(part, booleanOperator, entityType);
break;
case EXISTS:
return new ExistsFilterBuilder(part, booleanOperator, entityType);
filterBuilder = new ExistsFilterBuilder(part, booleanOperator, entityType);
break;
case TRUE:
case FALSE:
return new BooleanComparisonBuilder(part, booleanOperator, entityType);
filterBuilder = new BooleanComparisonBuilder(part, booleanOperator, entityType);
break;
default:
return new PropertyComparisonBuilder(part, booleanOperator, entityType);
filterBuilder = new PropertyComparisonBuilder(part, booleanOperator, entityType);
break;
}

filterBuilder.isInternalIdProperty = isInternalIdProperty;
return filterBuilder;
}

FilterBuilder(Part part, BooleanOperator booleanOperator, Class<?> entityType) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright 2011-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.repository.query.filter;

import static org.neo4j.ogm.cypher.ComparisonOperator.*;

import java.util.HashMap;
import java.util.Map;

import org.neo4j.ogm.cypher.ComparisonOperator;
import org.neo4j.ogm.cypher.Filter;
import org.neo4j.ogm.cypher.function.FilterFunction;

/**
* This is a specialised filter function taking care of filtering native id properties.
*
* @author Michael J. Simons
* @soundtrack Freddie Mercury - Never Boring
*/
final class NativeIdFilterFunction implements FilterFunction<Object> {

// This function belongs somewhat more into OGM than SDN. The reason having it here is simple: The filter is build
// explicitly and not via reflection and we don't want to have yet another shim managing separate possible versions
// OGM like we already have with the embedded support, entity instantiator and some other things. ^ms

private final ComparisonOperator operator;
private final Object value;
private Filter filter;

NativeIdFilterFunction(ComparisonOperator operator, Object value) {
this.operator = operator;
this.value = value;
}

@Override
public Object getValue() {
return this.value;
}

@Override
public Filter getFilter() {
return filter;
}

@Override
public void setFilter(Filter filter) {
this.filter = filter;
}

@Override
public String expression(String nodeIdentifier) {

switch (operator) {
case EQUALS:
case GREATER_THAN:
case GREATER_THAN_EQUAL:
case LESS_THAN:
case LESS_THAN_EQUAL:
case IN:
return String.format("id(%s) %s $`%s` ", nodeIdentifier, operator.getValue(), filter.uniqueParameterName());
default:
throw new IllegalArgumentException("Unsupported comparision operator for an ID attribute.");
}
}

@Override
public Map<String, Object> parameters() {
Map<String, Object> map = new HashMap<>();
if (operator.isOneOf(EQUALS, GREATER_THAN, GREATER_THAN_EQUAL, LESS_THAN, LESS_THAN_EQUAL, IN)) {
map.put(filter.uniqueParameterName(), filter.getTransformedPropertyValue());
}
return map;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,14 @@ public List<Filter> build(Stack<Object> params) {

Object value = params.pop();

Filter filter = new Filter(nestedAttributes.isEmpty() ? propertyName() : nestedAttributes.getLeafPropertySegment(), convertToComparisonOperator(part.getType()), value);
Filter filter;
String propertyName = nestedAttributes.isEmpty() ? propertyName() : nestedAttributes.getLeafPropertySegment();
if (isInternalIdProperty.test(part)) {
filter = new Filter(new NativeIdFilterFunction(convertToComparisonOperator(part.getType()), value));
filter.setPropertyName(propertyName);
} else {
filter = new Filter(propertyName, convertToComparisonOperator(part.getType()), value);
}
filter.setOwnerEntityType(entityType);
filter.setBooleanOperator(booleanOperator);
filter.setNegated(isNegated());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@
*/
public class NodeWithUUIDAsId {

private Long id;

@Id @GeneratedValue(strategy = UuidStrategy.class) @Convert(UuidStringConverter.class) private UUID myNiceId;

private String someProperty;
Expand All @@ -38,10 +36,6 @@ public NodeWithUUIDAsId(String someProperty) {
this.someProperty = someProperty;
}

public Long getId() {
return id;
}

public UUID getMyNiceId() {
return myNiceId;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ public interface UserRepository extends PersonRepository<User, Long> {

Slice<User> findByNameAndRatingsStars(String name, int stars, Pageable pageable);

Page<User> findAllByIdIn(Iterable<Long> id, Pageable pageable);

List<User> findAllByIdInAndNameLike(Iterable<Long> id, String name);

List<User> findAllByIdAndName(Long id, String name);

@Query("invalid")
void invalidQuery();

Expand Down
Loading

0 comments on commit c0050a1

Please sign in to comment.