Skip to content

Commit 8c262f9

Browse files
Check for equality of type before shortcutting projection.
This fixes #2415 by checking for exact equality of the type before shortcutting an unnessary projection.
1 parent 90cc8d4 commit 8c262f9

File tree

10 files changed

+261
-3
lines changed

10 files changed

+261
-3
lines changed

etc/jqassistant/api.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ and annotations.
1313
----
1414
MATCH (c:Java)-[:ANNOTATED_BY]->(a)-[:OF_TYPE]->(t:Type {fqn: 'org.apiguardian.api.API'}),
1515
(p)-[:DECLARES]->(c)
16-
WHERE c:Member AND NOT c:Constructor
16+
WHERE c:Member AND NOT (c:Constructor OR c:Method)
1717
RETURN p.fqn, c.name
1818
----
1919

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ public <T, R> R saveAs(T instance, Class<R> resultType) {
330330
return null;
331331
}
332332

333-
if (resultType.isInstance(instance)) {
333+
if (resultType.equals(instance.getClass())) {
334334
return (R) save(instance);
335335
}
336336

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ public <T, R> Mono<R> saveAs(T instance, Class<R> resultType) {
314314
return null;
315315
}
316316

317-
if (resultType.isInstance(instance)) {
317+
if (resultType.equals(instance.getClass())) {
318318
return (Mono<R>) save(instance);
319319
}
320320

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,18 @@ static QueryFragmentsAndParameters forExample(Neo4jMappingContext mappingContext
135135
return QueryFragmentsAndParameters.forExample(mappingContext, example, pageable, null);
136136
}
137137

138+
/**
139+
* Utility method for creating a query fragment including parameters for a given condition.
140+
*
141+
* @param entityMetaData The metadata of a given and known entity
142+
* @param condition A Cypher-DSL condition
143+
* @return Fully populated fragments and parameter
144+
*/
145+
@API(status = API.Status.EXPERIMENTAL, since = "6.1.7")
146+
public static QueryFragmentsAndParameters forCondition(Neo4jPersistentEntity<?> entityMetaData, Condition condition) {
147+
return forCondition(entityMetaData, condition, null, null);
148+
}
149+
138150
static QueryFragmentsAndParameters forCondition(Neo4jPersistentEntity<?> entityMetaData,
139151
Condition condition,
140152
@Nullable Pageable pageable,

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@
5151
import org.springframework.data.neo4j.core.Neo4jTemplate;
5252
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
5353
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
54+
import org.springframework.data.neo4j.integration.issues.gh2415.BaseNodeEntity;
55+
import org.springframework.data.neo4j.integration.issues.gh2415.NodeEntity;
56+
import org.springframework.data.neo4j.integration.issues.gh2415.NodeWithDefinedCredentials;
5457
import org.springframework.data.neo4j.integration.shared.common.Person;
5558
import org.springframework.data.neo4j.integration.shared.common.PersonWithAllConstructor;
5659
import org.springframework.data.neo4j.integration.shared.common.PersonWithAssignedId;
@@ -111,6 +114,14 @@ void setupData() {
111114
transaction.run("CREATE (p:Person{firstName: 'Bela', lastName: 'B.'})");
112115
transaction.run("CREATE (p:PersonWithAssignedId{id: 'x', firstName: 'John', lastName: 'Doe'})");
113116

117+
transaction.run(
118+
"CREATE (root:NodeEntity:BaseNodeEntity{nodeId: 'root'}) " +
119+
"CREATE (company:NodeEntity:BaseNodeEntity{nodeId: 'comp'}) " +
120+
"CREATE (cred:Credential{id: 'uuid-1', name: 'Creds'}) " +
121+
"CREATE (company)-[:CHILD_OF]->(root) " +
122+
"CREATE (root)-[:HAS_CREDENTIAL]->(cred) " +
123+
"CREATE (company)-[:WITH_CREDENTIAL]->(cred)");
124+
114125
transaction.commit();
115126
bookmarkCapture.seedWith(session.lastBookmark());
116127
}
@@ -799,6 +810,23 @@ void statementWithParamsShouldWork() {
799810
assertThat(people).extracting(Person::getLastName).containsExactly("Schnitzel");
800811
}
801812

813+
@Test // GH-2415
814+
void saveWithProjectionImplementedByEntity() {
815+
816+
NodeEntity nodeEntity = neo4jTemplate
817+
.find(BaseNodeEntity.class)
818+
.as(NodeEntity.class)
819+
.matching(
820+
"MATCH p=(n:BaseNodeEntity)-[r]-(t) WHERE n.nodeId = $nodeId WITH n, collect([x in relationships(p) |x]) AS r, collect(t) AS o RETURN n, r, o",
821+
Collections.singletonMap("nodeId", "root")
822+
)
823+
.one().get();
824+
neo4jTemplate.saveAs(nodeEntity, NodeWithDefinedCredentials.class);
825+
826+
nodeEntity = neo4jTemplate.findById(nodeEntity.getNodeId(), NodeEntity.class).get();
827+
assertThat(nodeEntity.getChildren()).hasSize(1);
828+
}
829+
802830
@Configuration
803831
@EnableTransactionManagement
804832
static class Config extends AbstractNeo4jConfig {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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.issues.gh2415;
17+
18+
import lombok.AccessLevel;
19+
import lombok.Data;
20+
import lombok.EqualsAndHashCode;
21+
import lombok.NoArgsConstructor;
22+
import lombok.Setter;
23+
import lombok.experimental.NonFinal;
24+
import lombok.experimental.SuperBuilder;
25+
26+
import org.springframework.data.neo4j.core.schema.GeneratedValue;
27+
import org.springframework.data.neo4j.core.schema.Id;
28+
import org.springframework.data.neo4j.core.schema.Node;
29+
import org.springframework.data.neo4j.core.support.UUIDStringGenerator;
30+
31+
/**
32+
* @author Andreas Berger
33+
*/
34+
@Node
35+
@NonFinal
36+
@Data
37+
@Setter(AccessLevel.PRIVATE)
38+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
39+
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
40+
@SuperBuilder(toBuilder = true)
41+
public class BaseNodeEntity {
42+
43+
@Id
44+
@GeneratedValue(UUIDStringGenerator.class)
45+
@EqualsAndHashCode.Include
46+
private String nodeId;
47+
48+
private String name;
49+
50+
@Override
51+
public String toString() {
52+
return getClass().getSimpleName() + " - " + getName() + " (" + getNodeId() + ")";
53+
}
54+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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.issues.gh2415;
17+
18+
import lombok.AllArgsConstructor;
19+
import lombok.Builder;
20+
import lombok.EqualsAndHashCode;
21+
import lombok.Value;
22+
import lombok.With;
23+
24+
import org.springframework.data.annotation.Immutable;
25+
import org.springframework.data.neo4j.core.schema.GeneratedValue;
26+
import org.springframework.data.neo4j.core.schema.Id;
27+
import org.springframework.data.neo4j.core.schema.Node;
28+
import org.springframework.data.neo4j.core.support.UUIDStringGenerator;
29+
30+
import com.fasterxml.jackson.annotation.JsonIgnore;
31+
32+
/**
33+
* @author Andreas Berger
34+
*/
35+
@Node
36+
@Value
37+
@With
38+
@AllArgsConstructor
39+
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
40+
@Immutable
41+
@Builder(toBuilder = true)
42+
public class Credential {
43+
44+
@JsonIgnore
45+
@Id
46+
@GeneratedValue(UUIDStringGenerator.class)
47+
@EqualsAndHashCode.Include
48+
String id;
49+
50+
String name;
51+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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.issues.gh2415;
17+
18+
import lombok.AccessLevel;
19+
import lombok.Data;
20+
import lombok.EqualsAndHashCode;
21+
import lombok.NoArgsConstructor;
22+
import lombok.Setter;
23+
import lombok.experimental.SuperBuilder;
24+
25+
import java.util.Set;
26+
27+
import org.springframework.data.neo4j.core.schema.Node;
28+
import org.springframework.data.neo4j.core.schema.Relationship;
29+
30+
import com.fasterxml.jackson.annotation.JsonIgnore;
31+
32+
/**
33+
* @author Andreas Berger
34+
*/
35+
@Node
36+
@Data
37+
@Setter(AccessLevel.PRIVATE)
38+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
39+
@EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true)
40+
@SuperBuilder(toBuilder = true)
41+
public class NodeEntity extends BaseNodeEntity implements NodeWithDefinedCredentials {
42+
43+
@JsonIgnore
44+
@Relationship(type = "CHILD_OF", direction = Relationship.Direction.INCOMING)
45+
private Set<BaseNodeEntity> children;
46+
47+
@Relationship(type = "HAS_CREDENTIAL")
48+
private Set<Credential> definedCredentials;
49+
50+
@Override
51+
public String toString() {
52+
return super.toString();
53+
}
54+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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.issues.gh2415;
17+
18+
import java.util.Set;
19+
20+
/**
21+
* @author Andreas Berger
22+
*/
23+
public interface NodeWithDefinedCredentials {
24+
25+
String getNodeId();
26+
27+
String getName();
28+
29+
Set<Credential> getDefinedCredentials();
30+
}

src/test/java/org/springframework/data/neo4j/integration/reactive/ReactiveNeo4jTemplateIT.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@
5353
import org.springframework.data.neo4j.core.ReactiveNeo4jTemplate;
5454
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
5555
import org.springframework.data.neo4j.core.transaction.ReactiveNeo4jTransactionManager;
56+
import org.springframework.data.neo4j.integration.issues.gh2415.BaseNodeEntity;
57+
import org.springframework.data.neo4j.integration.issues.gh2415.NodeEntity;
58+
import org.springframework.data.neo4j.integration.issues.gh2415.NodeWithDefinedCredentials;
5659
import org.springframework.data.neo4j.integration.shared.common.Person;
5760
import org.springframework.data.neo4j.integration.shared.common.PersonWithAllConstructor;
5861
import org.springframework.data.neo4j.integration.shared.common.PersonWithAssignedId;
@@ -116,6 +119,14 @@ void setupData(@Autowired BookmarkCapture bookmarkCapture) {
116119
transaction.run("CREATE (p:Person{firstName: 'Bela', lastName: 'B.'})");
117120
transaction.run("CREATE (p:PersonWithAssignedId{id: 'x', firstName: 'John', lastName: 'Doe'})");
118121

122+
transaction.run(
123+
"CREATE (root:NodeEntity:BaseNodeEntity{nodeId: 'root'}) " +
124+
"CREATE (company:NodeEntity:BaseNodeEntity{nodeId: 'comp'}) " +
125+
"CREATE (cred:Credential{id: 'uuid-1', name: 'Creds'}) " +
126+
"CREATE (company)-[:CHILD_OF]->(root) " +
127+
"CREATE (root)-[:HAS_CREDENTIAL]->(cred) " +
128+
"CREATE (company)-[:WITH_CREDENTIAL]->(cred)");
129+
119130
transaction.commit();
120131

121132
bookmarkCapture.seedWith(session.lastBookmark());
@@ -803,6 +814,24 @@ void shouldSaveAsWithAssignedIdProjected() {
803814
.verifyComplete();
804815
}
805816

817+
@Test // GH-2415
818+
void saveWithProjectionImplementedByEntity() {
819+
820+
neo4jTemplate
821+
.find(BaseNodeEntity.class)
822+
.as(NodeEntity.class)
823+
.matching(
824+
"MATCH p=(n:BaseNodeEntity)-[r]-(t) WHERE n.nodeId = $nodeId WITH n, collect([x in relationships(p) |x]) AS r, collect(t) AS o RETURN n, r, o",
825+
Collections.singletonMap("nodeId", "root")
826+
)
827+
.one()
828+
.flatMap(nodeEntity -> neo4jTemplate.saveAs(nodeEntity, NodeWithDefinedCredentials.class))
829+
.flatMap(nodeEntity -> neo4jTemplate.findById(nodeEntity.getNodeId(), NodeEntity.class))
830+
.as(StepVerifier::create)
831+
.consumeNextWith(nodeEntity -> assertThat(nodeEntity.getChildren()).hasSize(1))
832+
.verifyComplete();
833+
}
834+
806835
@Configuration
807836
@EnableTransactionManagement
808837
static class Config extends AbstractReactiveNeo4jConfig {

0 commit comments

Comments
 (0)