Skip to content

Commit acc2f6a

Browse files
committed
GH-2137 - Replace path queries with cascading matches.
This mitigates the data that is generated in the database's memory upfront returning. Otherwise this could lead to un-responsive applications.
1 parent cfb2a87 commit acc2f6a

23 files changed

+1155
-500
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright 2011-2021 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.core;
17+
18+
import org.neo4j.cypherdsl.core.Cypher;
19+
import org.neo4j.cypherdsl.core.Functions;
20+
import org.neo4j.cypherdsl.core.Node;
21+
import org.neo4j.cypherdsl.core.Relationship;
22+
import org.neo4j.cypherdsl.core.Statement;
23+
import org.springframework.data.neo4j.core.mapping.Constants;
24+
25+
import java.util.Collection;
26+
import java.util.Collections;
27+
import java.util.HashMap;
28+
import java.util.HashSet;
29+
import java.util.Map;
30+
31+
final class GenericQueryAndParameters {
32+
33+
private final static String ROOT_NODE_IDS = "rootNodeIds";
34+
private final static String RELATIONSHIP_IDS = "relationshipIds";
35+
private final static String RELATED_NODE_IDS = "relatedNodeIds";
36+
37+
final static Statement STATEMENT = createStatement();
38+
final static GenericQueryAndParameters EMPTY =
39+
new GenericQueryAndParameters(Collections.emptySet(), Collections.emptySet(), Collections.emptySet());
40+
41+
private final Map<String, Collection<Long>> parameters = new HashMap<>(3);
42+
43+
GenericQueryAndParameters(Collection<Long> rootNodeIds, Collection<Long> relationshipsIds, Collection<Long> relatedNodeIds) {
44+
parameters.put(ROOT_NODE_IDS, rootNodeIds);
45+
parameters.put(RELATIONSHIP_IDS, relationshipsIds);
46+
parameters.put(RELATED_NODE_IDS, relatedNodeIds);
47+
}
48+
49+
GenericQueryAndParameters() {
50+
this(new HashSet<>(), new HashSet<>(), new HashSet<>());
51+
}
52+
53+
void with(Collection<Long> rootNodeIds, Collection<Long> relationshipsIds, Collection<Long> relatedNodeIds) {
54+
parameters.put(ROOT_NODE_IDS, rootNodeIds);
55+
parameters.put(RELATIONSHIP_IDS, relationshipsIds);
56+
parameters.put(RELATED_NODE_IDS, relatedNodeIds);
57+
}
58+
59+
Map<String, Object> getParameters() {
60+
return Collections.unmodifiableMap(parameters);
61+
}
62+
63+
boolean isEmpty() {
64+
return parameters.get(ROOT_NODE_IDS).isEmpty();
65+
}
66+
67+
private static Statement createStatement() {
68+
Node rootNodes = Cypher.anyNode(ROOT_NODE_IDS);
69+
Node relatedNodes = Cypher.anyNode(RELATED_NODE_IDS);
70+
Relationship relationships = Cypher.anyNode().relationshipBetween(Cypher.anyNode()).named(RELATIONSHIP_IDS);
71+
72+
return Cypher.match(rootNodes)
73+
.where(Functions.id(rootNodes).in(Cypher.parameter(ROOT_NODE_IDS)))
74+
.optionalMatch(relationships)
75+
.where(Functions.id(relationships).in(Cypher.parameter(RELATIONSHIP_IDS)))
76+
.optionalMatch(relatedNodes)
77+
.where(Functions.id(relatedNodes).in(Cypher.parameter(RELATED_NODE_IDS)))
78+
.returning(
79+
rootNodes.as(Constants.NAME_OF_SYNTHESIZED_ROOT_NODE),
80+
Functions.collectDistinct(relationships).as(Constants.NAME_OF_SYNTHESIZED_RELATIONS),
81+
Functions.collectDistinct(relatedNodes).as(Constants.NAME_OF_SYNTHESIZED_RELATED_NODES)
82+
).build();
83+
}
84+
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.springframework.dao.IncorrectResultSizeDataAccessException;
2525
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentProperty;
2626
import org.springframework.data.neo4j.repository.NoResultException;
27+
import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters;
2728

2829
/**
2930
* Specifies operations one can perform on a database, based on an <em>Domain Type</em>.
@@ -225,6 +226,17 @@ public interface Neo4jOperations {
225226
*/
226227
<T> ExecutableQuery<T> toExecutableQuery(PreparedQuery<T> preparedQuery);
227228

229+
/**
230+
* Create an executable query based on query fragment.
231+
*
232+
* @param domainType domain class the executable query should return
233+
* @param queryFragmentsAndParameters fragments and parameters to construct the query from
234+
* @param <T> The type of the objects returned by this query.
235+
* @return An executable query
236+
*/
237+
<T> ExecutableQuery<T> toExecutableQuery(Class<T> domainType,
238+
QueryFragmentsAndParameters queryFragmentsAndParameters);
239+
228240
/**
229241
* An interface for controlling query execution.
230242
*

0 commit comments

Comments
 (0)