Skip to content

Commit 4e108a8

Browse files
committed
Early PoC custom cypher creator for repository methods.
Signed-off-by: Gerrit Meier <meistermeier@gmail.com>
1 parent 77c3499 commit 4e108a8

14 files changed

+163
-65
lines changed

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
<groupId>org.springframework.data</groupId>
2626
<artifactId>spring-data-neo4j</artifactId>
27-
<version>7.4.3-SNAPSHOT</version>
27+
<version>7.4.3-KREATOR-SNAPSHOT</version>
2828

2929
<name>Spring Data Neo4j</name>
3030
<description>Next generation Object-Graph-Mapping for Spring Data.</description>

src/main/java/org/springframework/data/neo4j/config/AbstractNeo4jConfig.java

+10
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import org.springframework.beans.factory.annotation.Autowired;
2222
import org.springframework.context.annotation.Bean;
2323
import org.springframework.context.annotation.Configuration;
24+
import org.springframework.core.Ordered;
25+
import org.springframework.core.annotation.Order;
2426
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
2527
import org.springframework.data.neo4j.core.Neo4jClient;
2628
import org.springframework.data.neo4j.core.Neo4jOperations;
@@ -30,6 +32,8 @@
3032
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
3133
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
3234
import org.springframework.data.neo4j.repository.config.Neo4jRepositoryConfigurationExtension;
35+
import org.springframework.data.neo4j.repository.query.CustomStatementKreator;
36+
import org.springframework.data.neo4j.repository.query.QueryFragments;
3337
import org.springframework.lang.Nullable;
3438
import org.springframework.transaction.PlatformTransactionManager;
3539

@@ -123,4 +127,10 @@ protected DatabaseSelectionProvider databaseSelectionProvider() {
123127

124128
return DatabaseSelectionProvider.getDefaultSelectionProvider();
125129
}
130+
131+
@Bean
132+
@Order(Ordered.LOWEST_PRECEDENCE)
133+
public CustomStatementKreator defaultStatementCreator() {
134+
return QueryFragments.KREATOR;
135+
}
126136
}

src/main/java/org/springframework/data/neo4j/config/AbstractReactiveNeo4jConfig.java

+10
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import org.springframework.beans.factory.annotation.Autowired;
2222
import org.springframework.context.annotation.Bean;
2323
import org.springframework.context.annotation.Configuration;
24+
import org.springframework.core.Ordered;
25+
import org.springframework.core.annotation.Order;
2426
import org.springframework.data.neo4j.core.ReactiveDatabaseSelectionProvider;
2527
import org.springframework.data.neo4j.core.ReactiveNeo4jClient;
2628
import org.springframework.data.neo4j.core.ReactiveNeo4jTemplate;
@@ -29,6 +31,8 @@
2931
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
3032
import org.springframework.data.neo4j.core.transaction.ReactiveNeo4jTransactionManager;
3133
import org.springframework.data.neo4j.repository.config.ReactiveNeo4jRepositoryConfigurationExtension;
34+
import org.springframework.data.neo4j.repository.query.CustomStatementKreator;
35+
import org.springframework.data.neo4j.repository.query.QueryFragments;
3236
import org.springframework.lang.Nullable;
3337
import org.springframework.transaction.PlatformTransactionManager;
3438
import org.springframework.transaction.ReactiveTransactionManager;
@@ -123,4 +127,10 @@ protected ReactiveDatabaseSelectionProvider reactiveDatabaseSelectionProvider()
123127

124128
return ReactiveDatabaseSelectionProvider.getDefaultSelectionProvider();
125129
}
130+
131+
@Bean
132+
@Order(Ordered.LOWEST_PRECEDENCE)
133+
public CustomStatementKreator defaultStatementCreator() {
134+
return QueryFragments.KREATOR;
135+
}
126136
}

src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java

+5-9
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@
9191
import org.springframework.data.neo4j.core.schema.TargetNode;
9292
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
9393
import org.springframework.data.neo4j.repository.NoResultException;
94-
import org.springframework.data.neo4j.repository.query.CustomStatementCreator;
94+
import org.springframework.data.neo4j.repository.query.CustomStatementKreator;
9595
import org.springframework.data.neo4j.repository.query.QueryFragments;
9696
import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters;
9797
import org.springframework.data.projection.ProjectionFactory;
@@ -149,7 +149,7 @@ public boolean isReadOnly() {
149149
private TransactionTemplate transactionTemplate;
150150

151151
private TransactionTemplate transactionTemplateReadOnly;
152-
private CustomStatementCreator customStatementCreator;
152+
private CustomStatementKreator customStatementKreator;
153153

154154
public Neo4jTemplate(Neo4jClient neo4jClient) {
155155
this(neo4jClient, new Neo4jMappingContext());
@@ -1158,8 +1158,8 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
11581158
this.elementIdOrIdFunction = SpringDataCypherDsl.elementIdOrIdFunction.apply(cypherDslConfiguration.getDialect());
11591159
this.cypherGenerator.setElementIdOrIdFunction(elementIdOrIdFunction);
11601160

1161-
beanFactory.getBeanProvider(CustomStatementCreator.class)
1162-
.ifAvailable(customStatementCreatorDings -> this.customStatementCreator = customStatementCreatorDings);
1161+
beanFactory.getBeanProvider(CustomStatementKreator.class)
1162+
.ifAvailable(customStatementCreatorDings -> this.customStatementKreator = customStatementCreatorDings);
11631163
if (this.transactionTemplate != null && this.transactionTemplateReadOnly != null) {
11641164
return;
11651165
}
@@ -1324,11 +1324,7 @@ private Optional<Neo4jClient.RecordFetchSpec<T>> createFetchSpec() {
13241324
}
13251325
statement = nodesAndRelationshipsById.toStatement(entityMetaData);
13261326
} else {
1327-
if (customStatementCreator != null) {
1328-
statement = queryFragments.toStatement(customStatementCreator);
1329-
} else {
1330-
statement = queryFragments.toStatement();
1331-
}
1327+
statement = queryFragments.toStatement();
13321328
}
13331329
cypherQuery = renderer.render(statement);
13341330
finalParameters = TemplateSupport.mergeParameters(statement, finalParameters);

src/main/java/org/springframework/data/neo4j/repository/query/CustomStatementCreator.java renamed to src/main/java/org/springframework/data/neo4j/repository/query/CustomStatementKreator.java

+15-2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,19 @@
3030
/**
3131
* @author Gerrit Meier
3232
*/
33-
public interface CustomStatementCreator {
34-
Statement createStatement(List<PatternElement> matchOn, Condition condition, Predicate<PropertyFilter.RelaxedPropertyPath> includeField, Neo4jPersistentEntity<?> returnTuple, Collection<Expression> returnExpressions, Collection<SortItem> orderBy, Long skip, Number limit);
33+
public interface CustomStatementKreator {
34+
Statement createStatement(
35+
Neo4jPersistentEntity<?> neo4jPersistentEntity,
36+
List<PatternElement> matchOn,
37+
Condition condition,
38+
Predicate<PropertyFilter.RelaxedPropertyPath> includeField,
39+
boolean isDistinctReturn,
40+
Collection<Expression> returnExpressions,
41+
Collection<SortItem> orderBy,
42+
Long skip,
43+
Number limit,
44+
Expression deleteExpression
45+
);
46+
47+
boolean supports(String repositoryName, String methodName);
3548
}

src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryCreator.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,12 @@ final class CypherQueryCreator extends AbstractQueryCreator<QueryFragmentsAndPar
118118
* Can be used to modify the limit of a paged or sliced query.
119119
*/
120120
private final UnaryOperator<Integer> limitModifier;
121+
private final CustomStatementKreator kreator;
121122

122123
CypherQueryCreator(Neo4jMappingContext mappingContext, QueryMethod queryMethod, Class<?> domainType, Neo4jQueryType queryType, PartTree tree,
123-
Neo4jParameterAccessor actualParameters, Collection<PropertyFilter.ProjectedPath> includedProperties,
124-
BiFunction<Object, Neo4jPersistentPropertyConverter<?>, Object> parameterConversion,
125-
UnaryOperator<Integer> limitModifier) {
124+
Neo4jParameterAccessor actualParameters, Collection<PropertyFilter.ProjectedPath> includedProperties,
125+
BiFunction<Object, Neo4jPersistentPropertyConverter<?>, Object> parameterConversion,
126+
UnaryOperator<Integer> limitModifier, CustomStatementKreator kreator) {
126127

127128
super(tree, actualParameters);
128129
this.mappingContext = mappingContext;
@@ -141,6 +142,7 @@ final class CypherQueryCreator extends AbstractQueryCreator<QueryFragmentsAndPar
141142
this.pagingParameter = actualParameters.getPageable();
142143
this.scrollPosition = actualParameters.getScrollPosition();
143144
this.limitModifier = limitModifier;
145+
this.kreator = kreator;
144146

145147
AtomicInteger symbolicNameIndex = new AtomicInteger();
146148

@@ -190,7 +192,7 @@ protected QueryFragmentsAndParameters complete(@Nullable Condition condition, So
190192

191193
@NonNull
192194
private QueryFragments createQueryFragments(@Nullable Condition condition, Sort sort) {
193-
QueryFragments queryFragments = new QueryFragments();
195+
QueryFragments queryFragments = new QueryFragments(kreator);
194196

195197
// all the ways we could query for
196198
Node startNode = Cypher.node(nodeDescription.getPrimaryLabel(), nodeDescription.getAdditionalLabels())

src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQueryLookupStrategy.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.neo4j.repository.query;
1717

1818
import java.lang.reflect.Method;
19+
import java.util.List;
1920

2021
import org.apiguardian.api.API;
2122
import org.neo4j.cypherdsl.core.renderer.Configuration;
@@ -43,13 +44,15 @@ public final class Neo4jQueryLookupStrategy implements QueryLookupStrategy {
4344
private final Neo4jOperations neo4jOperations;
4445
private final ValueExpressionDelegate delegate;
4546
private final Configuration configuration;
47+
private final List<CustomStatementKreator> kreatorBeans;
4648

4749
public Neo4jQueryLookupStrategy(Neo4jOperations neo4jOperations, Neo4jMappingContext mappingContext,
48-
ValueExpressionDelegate delegate, Configuration configuration) {
50+
ValueExpressionDelegate delegate, Configuration configuration, List<CustomStatementKreator> kreatorBeans) {
4951
this.neo4jOperations = neo4jOperations;
5052
this.mappingContext = mappingContext;
5153
this.delegate = delegate;
5254
this.configuration = configuration;
55+
this.kreatorBeans = kreatorBeans;
5356
}
5457

5558
/* (non-Javadoc)
@@ -71,7 +74,7 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata,
7174
} else if (queryMethod.isCypherBasedProjection()) {
7275
return CypherdslBasedQuery.create(neo4jOperations, mappingContext, queryMethod, factory, Renderer.getRenderer(configuration)::render);
7376
} else {
74-
return PartTreeNeo4jQuery.create(neo4jOperations, mappingContext, queryMethod, factory);
77+
return PartTreeNeo4jQuery.create(neo4jOperations, mappingContext, queryMethod, factory, kreatorBeans);
7578
}
7679
}
7780
}

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

+13-4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.neo4j.repository.query;
1717

1818
import java.util.Collection;
19+
import java.util.List;
1920
import java.util.Optional;
2021
import java.util.function.BiFunction;
2122
import java.util.function.Supplier;
@@ -43,18 +44,20 @@
4344
final class PartTreeNeo4jQuery extends AbstractNeo4jQuery {
4445

4546
private final PartTree tree;
47+
private final List<CustomStatementKreator> kreatorBeans;
4648

4749
public static RepositoryQuery create(Neo4jOperations neo4jOperations, Neo4jMappingContext mappingContext,
48-
Neo4jQueryMethod queryMethod, ProjectionFactory factory) {
50+
Neo4jQueryMethod queryMethod, ProjectionFactory factory, List<CustomStatementKreator> kreatorBeans) {
4951
return new PartTreeNeo4jQuery(neo4jOperations, mappingContext, queryMethod,
50-
new PartTree(queryMethod.getName(), getDomainType(queryMethod)), factory);
52+
new PartTree(queryMethod.getName(), getDomainType(queryMethod)), factory, kreatorBeans);
5153
}
5254

5355
private PartTreeNeo4jQuery(Neo4jOperations neo4jOperations, Neo4jMappingContext mappingContext,
54-
Neo4jQueryMethod queryMethod, PartTree tree, ProjectionFactory factory) {
56+
Neo4jQueryMethod queryMethod, PartTree tree, ProjectionFactory factory, List<CustomStatementKreator> kreatorBeans) {
5557
super(neo4jOperations, mappingContext, queryMethod, Neo4jQueryType.fromPartTree(tree), factory);
5658

5759
this.tree = tree;
60+
this.kreatorBeans = kreatorBeans;
5861
// Validate parts. Sort properties will be validated by Spring Data already.
5962
PartValidator validator = new PartValidator(mappingContext, queryMethod);
6063
this.tree.flatMap(OrPart::stream).forEach(validator::validatePart);
@@ -65,9 +68,15 @@ protected <T extends Object> PreparedQuery<T> prepareQuery(Class<T> returnedType
6568
Neo4jParameterAccessor parameterAccessor, @Nullable Neo4jQueryType queryType,
6669
@Nullable Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunction, UnaryOperator<Integer> limitModifier) {
6770

71+
CustomStatementKreator kreatorBean = kreatorBeans
72+
.stream()
73+
.filter(bean -> bean.supports(queryMethod.getRepositoryName(), queryMethod.getNamedQueryName()))
74+
.findFirst()
75+
.orElse(QueryFragments.KREATOR);
76+
6877
CypherQueryCreator queryCreator = new CypherQueryCreator(mappingContext, queryMethod, getDomainType(queryMethod),
6978
Optional.ofNullable(queryType).orElseGet(() -> Neo4jQueryType.fromPartTree(tree)), tree, parameterAccessor,
70-
includedProperties, this::convertParameter, limitModifier);
79+
includedProperties, this::convertParameter, limitModifier, kreatorBean);
7180

7281
QueryFragmentsAndParameters queryAndParameters = queryCreator.createQuery();
7382
return PreparedQuery.queryFor(returnedType).withQueryFragmentsAndParameters(queryAndParameters)

src/main/java/org/springframework/data/neo4j/repository/query/QueryFragments.java

+65-32
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.springframework.data.neo4j.core.mapping.NodeDescription;
3939
import org.springframework.data.neo4j.core.schema.Property;
4040
import org.springframework.lang.Nullable;
41+
import org.springframework.util.Assert;
4142

4243
/**
4344
* Collects the parts of a Cypher query to be handed over to the Cypher generator.
@@ -47,6 +48,48 @@
4748
*/
4849
@API(status = API.Status.INTERNAL, since = "6.0.4")
4950
public final class QueryFragments {
51+
// todo put me into something referencable for the bean creation
52+
public static final CustomStatementKreator KREATOR = new CustomStatementKreator() {
53+
@Override
54+
public Statement createStatement(Neo4jPersistentEntity<?> neo4jPersistentEntity, List<PatternElement> matchOn, Condition condition, Predicate<PropertyFilter.RelaxedPropertyPath> includeField, boolean isDistinctReturn, Collection<Expression> returnExpressions, Collection<SortItem> orderBy, Long skip, Number limit, Expression deleteExpression) {
55+
56+
StatementBuilder.OngoingReadingWithoutWhere match = null;
57+
58+
for (PatternElement patternElement : matchOn) {
59+
if (match == null) {
60+
match = Cypher.match(matchOn.get(0));
61+
} else {
62+
match = match.match(patternElement);
63+
}
64+
}
65+
66+
StatementBuilder.OngoingReadingWithWhere matchWithWhere = match.where(condition);
67+
68+
if (deleteExpression != null) {
69+
matchWithWhere = (StatementBuilder.OngoingReadingWithWhere) matchWithWhere.detachDelete(deleteExpression);
70+
}
71+
72+
StatementBuilder.OngoingReadingAndReturn returnPart = isDistinctReturn
73+
? matchWithWhere.returningDistinct(returnExpressions)
74+
: matchWithWhere.returning(returnExpressions);
75+
76+
Statement statement = returnPart
77+
.orderBy(orderBy)
78+
.skip(skip)
79+
.limit(limit).build();
80+
81+
statement.setRenderConstantsAsParameters(false);
82+
return statement;
83+
84+
}
85+
86+
@Override
87+
public boolean supports(String repositoryName, String methodName) {
88+
return true;
89+
}
90+
};
91+
92+
private final CustomStatementKreator kreator;
5093
private List<PatternElement> matchOn = new ArrayList<>();
5194
private Condition condition;
5295
private Collection<Expression> returnExpressions = new ArrayList<>();
@@ -56,12 +99,21 @@ public final class QueryFragments {
5699
private ReturnTuple returnTuple;
57100
private boolean scalarValueReturn = false;
58101
private Expression deleteExpression;
102+
59103
/**
60104
* This flag becomes {@literal true} for backward scrolling keyset pagination. Any {@code AbstractNeo4jQuery} will in turn reverse the result list.
61105
*/
62106
private boolean requiresReverseSort = false;
63107
private Predicate<PropertyFilter.RelaxedPropertyPath> projectingPropertyFilter;
64108

109+
public QueryFragments() {
110+
this.kreator = KREATOR;
111+
}
112+
113+
public QueryFragments(CustomStatementKreator kreator) {
114+
this.kreator = kreator;
115+
}
116+
65117
public void addMatchOn(PatternElement match) {
66118
this.matchOn.add(match);
67119
}
@@ -130,38 +182,19 @@ public void setRequiresReverseSort(boolean requiresReverseSort) {
130182
}
131183

132184
public Statement toStatement() {
133-
134-
StatementBuilder.OngoingReadingWithoutWhere match = null;
135-
136-
for (PatternElement patternElement : matchOn) {
137-
if (match == null) {
138-
match = Cypher.match(matchOn.get(0));
139-
} else {
140-
match = match.match(patternElement);
141-
}
142-
}
143-
144-
StatementBuilder.OngoingReadingWithWhere matchWithWhere = match.where(condition);
145-
146-
if (deleteExpression != null) {
147-
matchWithWhere = (StatementBuilder.OngoingReadingWithWhere) matchWithWhere.detachDelete(deleteExpression);
148-
}
149-
150-
StatementBuilder.OngoingReadingAndReturn returnPart = isDistinctReturn()
151-
? matchWithWhere.returningDistinct(getReturnExpressions())
152-
: matchWithWhere.returning(getReturnExpressions());
153-
154-
Statement statement = returnPart
155-
.orderBy(getOrderBy())
156-
.skip(skip)
157-
.limit(limit).build();
158-
159-
statement.setRenderConstantsAsParameters(false);
160-
return statement;
161-
}
162-
163-
public Statement toStatement(CustomStatementCreator customStatementCreator) {
164-
return customStatementCreator.createStatement(matchOn, condition, this::includeField, (Neo4jPersistentEntity<?>) returnTuple.nodeDescription, getReturnExpressions(), getOrderBy(), skip, limit);
185+
Assert.notNull(kreator, "Missing kreator bean. This _should_ not happen.");
186+
return kreator.createStatement(this.returnTuple != null
187+
? (Neo4jPersistentEntity<?>) returnTuple.nodeDescription
188+
: null,
189+
matchOn,
190+
condition,
191+
this::includeField,
192+
isDistinctReturn(),
193+
getReturnExpressions(),
194+
getOrderBy(),
195+
skip,
196+
limit,
197+
deleteExpression);
165198
}
166199

167200
private Collection<Expression> getReturnExpressions() {

0 commit comments

Comments
 (0)