Skip to content

Commit e4d0f46

Browse files
GH-2164 - Treat id properties correctly in derived finder methods.
This closes #2164.
1 parent 659b10c commit e4d0f46

File tree

6 files changed

+234
-36
lines changed

6 files changed

+234
-36
lines changed

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

+25-19
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,23 @@
1515
*/
1616
package org.springframework.data.neo4j.repository.query;
1717

18+
import static org.neo4j.cypherdsl.core.Functions.point;
19+
20+
import java.util.ArrayList;
21+
import java.util.Iterator;
22+
import java.util.LinkedList;
23+
import java.util.List;
24+
import java.util.Map;
25+
import java.util.Optional;
26+
import java.util.Queue;
27+
import java.util.concurrent.atomic.AtomicInteger;
28+
import java.util.function.BiFunction;
29+
import java.util.function.Function;
30+
import java.util.function.Supplier;
31+
import java.util.function.UnaryOperator;
32+
import java.util.stream.Collectors;
33+
import java.util.stream.Stream;
34+
1835
import org.neo4j.cypherdsl.core.Condition;
1936
import org.neo4j.cypherdsl.core.Conditions;
2037
import org.neo4j.cypherdsl.core.Cypher;
@@ -51,23 +68,6 @@
5168
import org.springframework.lang.NonNull;
5269
import org.springframework.lang.Nullable;
5370

54-
import java.util.ArrayList;
55-
import java.util.Iterator;
56-
import java.util.LinkedList;
57-
import java.util.List;
58-
import java.util.Map;
59-
import java.util.Optional;
60-
import java.util.Queue;
61-
import java.util.concurrent.atomic.AtomicInteger;
62-
import java.util.function.BiFunction;
63-
import java.util.function.Function;
64-
import java.util.function.Supplier;
65-
import java.util.function.UnaryOperator;
66-
import java.util.stream.Collectors;
67-
import java.util.stream.Stream;
68-
69-
import static org.neo4j.cypherdsl.core.Functions.point;
70-
7171
/**
7272
* A Cypher-DSL based implementation of the {@link AbstractQueryCreator} that eventually creates Cypher queries as
7373
* strings to be used by a Neo4j client or driver as statement template.
@@ -534,7 +534,9 @@ private Expression toCypherProperty(PersistentPropertyPath<Neo4jPersistentProper
534534
Expression expression;
535535

536536
if (owner.equals(this.nodeDescription) && path.getLength() == 1) {
537-
expression = Cypher.property(Constants.NAME_OF_ROOT_NODE, leafProperty.getPropertyName());
537+
expression = leafProperty.isInternalIdProperty() ?
538+
Cypher.call("id").withArgs(Constants.NAME_OF_ROOT_NODE).asFunction() :
539+
Cypher.property(Constants.NAME_OF_ROOT_NODE, leafProperty.getPropertyName());
538540
} else {
539541
PropertyPathWrapper propertyPathWrapper = propertyPathWrappers.stream()
540542
.filter(rp -> rp.getPropertyPath().equals(path)).findFirst().get();
@@ -545,7 +547,11 @@ private Expression toCypherProperty(PersistentPropertyPath<Neo4jPersistentProper
545547
} else {
546548
cypherElementName = propertyPathWrapper.getNodeName();
547549
}
548-
expression = Cypher.property(cypherElementName, leafProperty.getPropertyName());
550+
if (leafProperty.isInternalIdProperty()) {
551+
expression = Cypher.call("id").withArgs(Cypher.name(cypherElementName)).asFunction();
552+
} else {
553+
expression = Cypher.property(cypherElementName, leafProperty.getPropertyName());
554+
}
549555
}
550556

551557
if (addToLower) {

src/test/java/org/springframework/data/neo4j/integration/imperative/ProjectionIT.java

+74-17
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@
2020
import java.util.AbstractMap;
2121
import java.util.Collection;
2222
import java.util.Map;
23+
import java.util.Optional;
2324

2425
import org.junit.jupiter.api.BeforeEach;
2526
import org.junit.jupiter.api.Test;
2627
import org.neo4j.driver.Driver;
28+
import org.neo4j.driver.Record;
2729
import org.neo4j.driver.Session;
2830
import org.neo4j.driver.Transaction;
2931
import org.neo4j.driver.Values;
@@ -40,6 +42,7 @@
4042
import org.springframework.data.neo4j.integration.shared.common.NamesOnlyDto;
4143
import org.springframework.data.neo4j.integration.shared.common.Person;
4244
import org.springframework.data.neo4j.integration.shared.common.PersonSummary;
45+
import org.springframework.data.neo4j.integration.shared.common.ProjectionTestRoot;
4346
import org.springframework.data.neo4j.repository.Neo4jRepository;
4447
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
4548
import org.springframework.data.neo4j.test.Neo4jExtension;
@@ -61,6 +64,8 @@ class ProjectionIT {
6164
private static Neo4jExtension.Neo4jConnectionSupport neo4jConnectionSupport;
6265

6366
private final Driver driver;
67+
private Long projectionTestRootId;
68+
private Long projectionTestLevel1Id;
6469

6570
@Autowired
6671
ProjectionIT(Driver driver) {
@@ -69,24 +74,39 @@ class ProjectionIT {
6974

7075
@BeforeEach
7176
void setup() {
72-
Session session = driver.session();
73-
Transaction transaction = session.beginTransaction();
74-
75-
transaction.run("MATCH (n) detach delete n");
76-
77-
for (Map.Entry<String, String> person : new Map.Entry[] {
78-
new AbstractMap.SimpleEntry(FIRST_NAME, LAST_NAME),
79-
new AbstractMap.SimpleEntry(FIRST_NAME2, LAST_NAME),
80-
}) {
81-
transaction.run(" MERGE (address:Address{city: $city})"
82-
+ "CREATE (:Person{firstName: $firstName, lastName: $lastName})"
83-
+ "-[:LIVES_AT]-> (address)",
84-
Values.parameters("firstName", person.getKey(), "lastName", person.getValue(), "city", CITY));
85-
}
8677

87-
transaction.commit();
88-
transaction.close();
89-
session.close();
78+
try (Session session = driver.session();
79+
Transaction transaction = session.beginTransaction();) {
80+
81+
transaction.run("MATCH (n) detach delete n");
82+
83+
for (Map.Entry<String, String> person : new Map.Entry[] {
84+
new AbstractMap.SimpleEntry(FIRST_NAME, LAST_NAME),
85+
new AbstractMap.SimpleEntry(FIRST_NAME2, LAST_NAME),
86+
}) {
87+
transaction.run(" MERGE (address:Address{city: $city})"
88+
+ "CREATE (:Person{firstName: $firstName, lastName: $lastName})"
89+
+ "-[:LIVES_AT]-> (address)",
90+
Values.parameters("firstName", person.getKey(), "lastName", person.getValue(), "city", CITY));
91+
}
92+
93+
Record result = transaction.run("create (r:ProjectionTestRoot {name: 'root'}) \n"
94+
+ "create (l11:ProjectionTestLevel1 {name: 'level11'})\n"
95+
+ "create (l12:ProjectionTestLevel1 {name: 'level12'})\n"
96+
+ "create (l21:ProjectionTestLevel2 {name: 'level21'})\n"
97+
+ "create (l22:ProjectionTestLevel2 {name: 'level22'})\n"
98+
+ "create (l23:ProjectionTestLevel2 {name: 'level23'})\n"
99+
+ "create (r) - [:LEVEL_1] -> (l11)\n"
100+
+ "create (r) - [:LEVEL_1] -> (l12)\n"
101+
+ "create (l11) - [:LEVEL_2] -> (l21)\n"
102+
+ "create (l11) - [:LEVEL_2] -> (l22)\n"
103+
+ "create (l12) - [:LEVEL_2] -> (l23)\n"
104+
+ "return id(r), id(l11)").single();
105+
106+
projectionTestRootId = result.get(0).asLong();
107+
projectionTestLevel1Id = result.get(1).asLong();
108+
transaction.commit();
109+
}
90110
}
91111

92112
@Test
@@ -188,6 +208,29 @@ void projectionsShouldBeSliceable(@Autowired ProjectionPersonRepository reposito
188208
assertThat(people).extracting(NamesOnly::getFullName).containsExactly(FIRST_NAME + " " + LAST_NAME);
189209
}
190210

211+
@Test // GH-2164
212+
void findByIdWithProjectionShouldWork(@Autowired TreestructureRepository repository) {
213+
214+
Optional<SimpleProjection> optionalProjection = repository
215+
.findById(projectionTestRootId, SimpleProjection.class);
216+
assertThat(optionalProjection).map(SimpleProjection::getName).hasValue("root");
217+
}
218+
219+
@Test // GH-2164
220+
void findByIdInDerivedFinderMethodInRelatedObjectShouldWork(@Autowired TreestructureRepository repository) {
221+
222+
Optional<ProjectionTestRoot> optionalProjection = repository.findOneByLevel1Id(projectionTestLevel1Id);
223+
assertThat(optionalProjection).map(ProjectionTestRoot::getName).hasValue("root");
224+
}
225+
226+
@Test // GH-2164
227+
void findByIdInDerivedFinderMethodInRelatedObjectWithProjectionShouldWork(
228+
@Autowired TreestructureRepository repository) {
229+
230+
Optional<SimpleProjection> optionalProjection = repository.findOneByLevel1Id(projectionTestLevel1Id, SimpleProjection.class);
231+
assertThat(optionalProjection).map(SimpleProjection::getName).hasValue("root");
232+
}
233+
191234
interface ProjectionPersonRepository extends Neo4jRepository<Person, Long> {
192235

193236
Collection<NamesOnly> findByLastName(String lastName);
@@ -203,6 +246,20 @@ interface ProjectionPersonRepository extends Neo4jRepository<Person, Long> {
203246
<T> Collection<T> findByLastNameAndFirstName(String lastName, String firstName, Class<T> projectionClass);
204247
}
205248

249+
interface TreestructureRepository extends Neo4jRepository<ProjectionTestRoot, Long> {
250+
251+
<T> Optional<T> findById(Long id, Class<T> typeOfProjection);
252+
253+
Optional<ProjectionTestRoot> findOneByLevel1Id(Long idOfLevel1);
254+
255+
<T> Optional<T> findOneByLevel1Id(Long idOfLevel1, Class<T> typeOfProjection);
256+
}
257+
258+
interface SimpleProjection {
259+
260+
String getName();
261+
}
262+
206263
@Configuration
207264
@EnableNeo4jRepositories(considerNestedRepositories = true)
208265
@EnableTransactionManagement
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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.integration.shared.common;
17+
18+
import org.springframework.data.neo4j.core.schema.GeneratedValue;
19+
import org.springframework.data.neo4j.core.schema.Id;
20+
21+
/**
22+
* @author Michael J. Simons
23+
*/
24+
public abstract class ProjectionTestBase {
25+
26+
@Id @GeneratedValue
27+
private Long id;
28+
29+
private String name;
30+
31+
public Long getId() {
32+
return id;
33+
}
34+
35+
public String getName() {
36+
return name;
37+
}
38+
39+
public void setName(String name) {
40+
this.name = name;
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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.integration.shared.common;
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
21+
import org.springframework.data.neo4j.core.schema.Node;
22+
23+
/**
24+
* @author Michael J. Simons
25+
*/
26+
@Node
27+
public class ProjectionTestLevel1 extends ProjectionTestBase {
28+
29+
private List<ProjectionTestLevel2> level2 = new ArrayList<>();
30+
31+
public List<ProjectionTestLevel2> getLevel2() {
32+
return level2;
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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.integration.shared.common;
17+
18+
import org.springframework.data.neo4j.core.schema.Node;
19+
20+
/**
21+
* @author Michael J. Simons
22+
*/
23+
@Node
24+
public class ProjectionTestLevel2 extends ProjectionTestBase {
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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.integration.shared.common;
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
21+
import org.springframework.data.neo4j.core.schema.Node;
22+
23+
/**
24+
* @author Michael J. Simons
25+
*/
26+
@Node
27+
public class ProjectionTestRoot extends ProjectionTestBase {
28+
29+
private List<ProjectionTestLevel1> level1 = new ArrayList<>();
30+
31+
public List<ProjectionTestLevel1> getLevel1() {
32+
return level1;
33+
}
34+
}

0 commit comments

Comments
 (0)