Skip to content

Commit c0050a1

Browse files
DATAGRAPH-1286 - Support generated ids in derived finder methods.
This introduces NativeIdFilterFunction that is applied when a derived finder method hits a native id property.
1 parent 7494727 commit c0050a1

File tree

12 files changed

+234
-42
lines changed

12 files changed

+234
-42
lines changed

spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/Neo4jPersistentProperty.java

+7
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,13 @@ public boolean isIdProperty() {
143143
return propertyType.idProperty;
144144
}
145145

146+
/**
147+
* @return True if this property describes the internal ID property.
148+
*/
149+
public boolean isInternalIdProperty() {
150+
return propertyType == PropertyType.INTERNAL_ID_PROPERTY;
151+
}
152+
146153
PropertyType getPropertyType() {
147154
return propertyType;
148155
}

spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/FilterBuildersDefinition.java

+24-9
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@
1818
import java.util.Collections;
1919
import java.util.LinkedList;
2020
import java.util.List;
21+
import java.util.function.Predicate;
2122

2223
import org.neo4j.ogm.cypher.BooleanOperator;
2324
import org.neo4j.ogm.cypher.Filters;
25+
import org.springframework.data.mapping.PersistentPropertyPath;
26+
import org.springframework.data.neo4j.mapping.Neo4jMappingContext;
27+
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
2428
import org.springframework.data.neo4j.repository.query.filter.FilterBuilder;
2529
import org.springframework.data.repository.query.parser.Part;
2630

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

4145
private final List<FilterBuilder> filterBuilders;
46+
private final Predicate<Part> isInternalIdProperty;
4247

43-
static UnstartedBuild forType(Class<?> entityType) {
44-
return new UnstartedBuild(entityType);
48+
static UnstartedBuild forType(Neo4jMappingContext mappingContext, Class<?> entityType) {
49+
return new UnstartedBuild(mappingContext, entityType);
4550
}
4651

47-
private FilterBuildersDefinition(Class<?> entityType, Part basePart) {
52+
private FilterBuildersDefinition(Neo4jMappingContext mappingContext, Class<?> entityType, Part basePart) {
4853
this.entityType = entityType;
4954
this.basePart = basePart;
5055
this.filterBuilders = new LinkedList<>();
51-
52-
this.filterBuilders.add(FilterBuilder.forPartAndEntity(basePart, entityType, BooleanOperator.NONE));
56+
this.isInternalIdProperty = part -> {
57+
PersistentPropertyPath<Neo4jPersistentProperty> path = mappingContext
58+
.getPersistentPropertyPath(part.getProperty());
59+
Neo4jPersistentProperty possibleIdProperty = path.getRequiredLeafProperty();
60+
return possibleIdProperty.isInternalIdProperty();
61+
};
62+
this.filterBuilders.add(FilterBuilder.forPartAndEntity(basePart, entityType, BooleanOperator.NONE,
63+
isInternalIdProperty));
5364
}
5465

5566
TemplatedQuery buildTemplatedQuery() {
@@ -66,24 +77,28 @@ Part getBasePart() {
6677
}
6778

6879
FilterBuildersDefinition and(Part part) {
69-
this.filterBuilders.add(FilterBuilder.forPartAndEntity(part, entityType, BooleanOperator.AND));
80+
this.filterBuilders.add(FilterBuilder.forPartAndEntity(part, entityType, BooleanOperator.AND, isInternalIdProperty));
7081
return this;
7182
}
7283

7384
FilterBuildersDefinition or(Part part) {
74-
this.filterBuilders.add(FilterBuilder.forPartAndEntity(part, entityType, BooleanOperator.OR));
85+
this.filterBuilders.add(FilterBuilder.forPartAndEntity(part, entityType, BooleanOperator.OR, isInternalIdProperty));
7586
return this;
7687
}
7788

7889
static class UnstartedBuild {
90+
91+
private final Neo4jMappingContext mappingContext;
92+
7993
private final Class<?> entityType;
8094

81-
UnstartedBuild(Class<?> entityType) {
95+
UnstartedBuild(Neo4jMappingContext mappingContext, Class<?> entityType) {
96+
this.mappingContext = mappingContext;
8297
this.entityType = entityType;
8398
}
8499

85100
FilterBuildersDefinition startWith(Part firstPart) {
86-
return new FilterBuildersDefinition(entityType, firstPart);
101+
return new FilterBuildersDefinition(mappingContext, entityType, firstPart);
87102
}
88103
}
89104
}

spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/GraphQueryMethod.java

-13
Original file line numberDiff line numberDiff line change
@@ -118,19 +118,6 @@ private Integer getQueryDepthParamIndex(Method method) {
118118
}
119119
}
120120
}
121-
/*
122-
//Java 8 only
123-
Parameter[] parameters = method.getParameters();
124-
for (int i = 0; i < method.getParameterCount(); i++) {
125-
if (parameters[i].isAnnotationPresent(Depth.class)) {
126-
if (parameters[i].getType() == Integer.class || parameters[i].getType() == int.class) {
127-
return i;
128-
}
129-
else {
130-
throw new IllegalArgumentException("Depth parameter in " + method.getName() + " must be an integer");
131-
}
132-
}
133-
}*/
134121
return null;
135122
}
136123

spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/PartTreeNeo4jQuery.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.neo4j.ogm.session.Session;
2323
import org.slf4j.Logger;
2424
import org.slf4j.LoggerFactory;
25+
import org.springframework.data.neo4j.mapping.Neo4jMappingContext;
2526
import org.springframework.data.repository.query.RepositoryQuery;
2627
import org.springframework.data.repository.query.ResultProcessor;
2728
import org.springframework.data.repository.query.parser.PartTree;
@@ -49,11 +50,11 @@ public PartTreeNeo4jQuery(GraphQueryMethod graphQueryMethod, MetaData metaData,
4950
super(graphQueryMethod, metaData, session);
5051

5152
Class<?> domainType = graphQueryMethod.getEntityInformation().getJavaType();
52-
5353
this.graphQueryMethod = graphQueryMethod;
5454
this.tree = new PartTree(graphQueryMethod.getName(), domainType);
5555

56-
this.queryTemplate = new TemplatedQueryCreator(this.tree, domainType).createQuery();
56+
this.queryTemplate = new TemplatedQueryCreator(this.tree,
57+
(Neo4jMappingContext) this.graphQueryMethod.getMappingContext(), domainType).createQuery();
5758
}
5859

5960
@Override

spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/TemplatedQueryCreator.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Optional;
2020

2121
import org.springframework.data.domain.Sort;
22+
import org.springframework.data.neo4j.mapping.Neo4jMappingContext;
2223
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
2324
import org.springframework.data.repository.query.parser.Part;
2425
import org.springframework.data.repository.query.parser.PartTree;
@@ -33,17 +34,19 @@
3334
*/
3435
class TemplatedQueryCreator extends AbstractQueryCreator<TemplatedQuery, FilterBuildersDefinition> {
3536

37+
private final Neo4jMappingContext mappingContext;
3638
private final Class<?> entityType;
3739

38-
public TemplatedQueryCreator(PartTree tree, Class<?> entityType) {
40+
public TemplatedQueryCreator(PartTree tree, Neo4jMappingContext mappingContext, Class<?> entityType) {
3941
super(tree);
4042

43+
this.mappingContext = mappingContext;
4144
this.entityType = entityType;
4245
}
4346

4447
@Override
4548
protected FilterBuildersDefinition create(Part part, Iterator<Object> iterator) {
46-
return FilterBuildersDefinition.forType(entityType) //
49+
return FilterBuildersDefinition.forType(mappingContext, entityType) //
4750
.startWith(part);
4851
}
4952

spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/filter/FilterBuilder.java

+22-8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.List;
2121
import java.util.Objects;
2222
import java.util.Stack;
23+
import java.util.function.Predicate;
2324

2425
import org.neo4j.ogm.cypher.BooleanOperator;
2526
import org.neo4j.ogm.cypher.Filter;
@@ -41,27 +42,40 @@ public abstract class FilterBuilder {
4142
protected Part part;
4243
protected BooleanOperator booleanOperator;
4344
protected Class<?> entityType;
45+
protected Predicate<Part> isInternalIdProperty = part -> false;
4446

45-
public static FilterBuilder forPartAndEntity(Part part, Class<?> entityType, BooleanOperator booleanOperator) {
47+
public static FilterBuilder forPartAndEntity(Part part, Class<?> entityType, BooleanOperator booleanOperator,
48+
Predicate<Part> isInternalIdProperty) {
49+
FilterBuilder filterBuilder;
4650
switch (part.getType()) {
4751
case NEAR:
48-
return new DistanceComparisonBuilder(part, booleanOperator, entityType);
52+
filterBuilder = new DistanceComparisonBuilder(part, booleanOperator, entityType);
53+
break;
4954
case BETWEEN:
50-
return new BetweenComparisonBuilder(part, booleanOperator, entityType);
55+
filterBuilder = new BetweenComparisonBuilder(part, booleanOperator, entityType);
56+
break;
5157
case NOT_CONTAINING:
5258
case CONTAINING:
53-
return resolveMatchingContainsFilterBuilder(part, entityType, booleanOperator);
59+
filterBuilder = resolveMatchingContainsFilterBuilder(part, entityType, booleanOperator);
60+
break;
5461
case IS_NULL:
5562
case IS_NOT_NULL:
56-
return new IsNullFilterBuilder(part, booleanOperator, entityType);
63+
filterBuilder = new IsNullFilterBuilder(part, booleanOperator, entityType);
64+
break;
5765
case EXISTS:
58-
return new ExistsFilterBuilder(part, booleanOperator, entityType);
66+
filterBuilder = new ExistsFilterBuilder(part, booleanOperator, entityType);
67+
break;
5968
case TRUE:
6069
case FALSE:
61-
return new BooleanComparisonBuilder(part, booleanOperator, entityType);
70+
filterBuilder = new BooleanComparisonBuilder(part, booleanOperator, entityType);
71+
break;
6272
default:
63-
return new PropertyComparisonBuilder(part, booleanOperator, entityType);
73+
filterBuilder = new PropertyComparisonBuilder(part, booleanOperator, entityType);
74+
break;
6475
}
76+
77+
filterBuilder.isInternalIdProperty = isInternalIdProperty;
78+
return filterBuilder;
6579
}
6680

6781
FilterBuilder(Part part, BooleanOperator booleanOperator, Class<?> entityType) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2011-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.neo4j.repository.query.filter;
17+
18+
import static org.neo4j.ogm.cypher.ComparisonOperator.*;
19+
20+
import java.util.HashMap;
21+
import java.util.Map;
22+
23+
import org.neo4j.ogm.cypher.ComparisonOperator;
24+
import org.neo4j.ogm.cypher.Filter;
25+
import org.neo4j.ogm.cypher.function.FilterFunction;
26+
27+
/**
28+
* This is a specialised filter function taking care of filtering native id properties.
29+
*
30+
* @author Michael J. Simons
31+
* @soundtrack Freddie Mercury - Never Boring
32+
*/
33+
final class NativeIdFilterFunction implements FilterFunction<Object> {
34+
35+
// This function belongs somewhat more into OGM than SDN. The reason having it here is simple: The filter is build
36+
// explicitly and not via reflection and we don't want to have yet another shim managing separate possible versions
37+
// OGM like we already have with the embedded support, entity instantiator and some other things. ^ms
38+
39+
private final ComparisonOperator operator;
40+
private final Object value;
41+
private Filter filter;
42+
43+
NativeIdFilterFunction(ComparisonOperator operator, Object value) {
44+
this.operator = operator;
45+
this.value = value;
46+
}
47+
48+
@Override
49+
public Object getValue() {
50+
return this.value;
51+
}
52+
53+
@Override
54+
public Filter getFilter() {
55+
return filter;
56+
}
57+
58+
@Override
59+
public void setFilter(Filter filter) {
60+
this.filter = filter;
61+
}
62+
63+
@Override
64+
public String expression(String nodeIdentifier) {
65+
66+
switch (operator) {
67+
case EQUALS:
68+
case GREATER_THAN:
69+
case GREATER_THAN_EQUAL:
70+
case LESS_THAN:
71+
case LESS_THAN_EQUAL:
72+
case IN:
73+
return String.format("id(%s) %s $`%s` ", nodeIdentifier, operator.getValue(), filter.uniqueParameterName());
74+
default:
75+
throw new IllegalArgumentException("Unsupported comparision operator for an ID attribute.");
76+
}
77+
}
78+
79+
@Override
80+
public Map<String, Object> parameters() {
81+
Map<String, Object> map = new HashMap<>();
82+
if (operator.isOneOf(EQUALS, GREATER_THAN, GREATER_THAN_EQUAL, LESS_THAN, LESS_THAN_EQUAL, IN)) {
83+
map.put(filter.uniqueParameterName(), filter.getTransformedPropertyValue());
84+
}
85+
return map;
86+
}
87+
}

spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/filter/PropertyComparisonBuilder.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,14 @@ public List<Filter> build(Stack<Object> params) {
4545

4646
Object value = params.pop();
4747

48-
Filter filter = new Filter(nestedAttributes.isEmpty() ? propertyName() : nestedAttributes.getLeafPropertySegment(), convertToComparisonOperator(part.getType()), value);
48+
Filter filter;
49+
String propertyName = nestedAttributes.isEmpty() ? propertyName() : nestedAttributes.getLeafPropertySegment();
50+
if (isInternalIdProperty.test(part)) {
51+
filter = new Filter(new NativeIdFilterFunction(convertToComparisonOperator(part.getType()), value));
52+
filter.setPropertyName(propertyName);
53+
} else {
54+
filter = new Filter(propertyName, convertToComparisonOperator(part.getType()), value);
55+
}
4956
filter.setOwnerEntityType(entityType);
5057
filter.setBooleanOperator(booleanOperator);
5158
filter.setNegated(isNegated());

spring-data-neo4j/src/test/java/org/springframework/data/neo4j/domain/sample/NodeWithUUIDAsId.java

-6
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@
2828
*/
2929
public class NodeWithUUIDAsId {
3030

31-
private Long id;
32-
3331
@Id @GeneratedValue(strategy = UuidStrategy.class) @Convert(UuidStringConverter.class) private UUID myNiceId;
3432

3533
private String someProperty;
@@ -38,10 +36,6 @@ public NodeWithUUIDAsId(String someProperty) {
3836
this.someProperty = someProperty;
3937
}
4038

41-
public Long getId() {
42-
return id;
43-
}
44-
4539
public UUID getMyNiceId() {
4640
return myNiceId;
4741
}

spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/repo/UserRepository.java

+6
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,12 @@ public interface UserRepository extends PersonRepository<User, Long> {
138138

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

141+
Page<User> findAllByIdIn(Iterable<Long> id, Pageable pageable);
142+
143+
List<User> findAllByIdInAndNameLike(Iterable<Long> id, String name);
144+
145+
List<User> findAllByIdAndName(Long id, String name);
146+
141147
@Query("invalid")
142148
void invalidQuery();
143149

0 commit comments

Comments
 (0)