Skip to content

Commit f0dcac2

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 c212db0 commit f0dcac2

File tree

10 files changed

+265
-3
lines changed

10 files changed

+265
-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
@@ -337,7 +337,7 @@ public <T, R> R saveAs(T instance, Class<R> resultType) {
337337
return null;
338338
}
339339

340-
if (resultType.isInstance(instance)) {
340+
if (resultType.equals(instance.getClass())) {
341341
return resultType.cast(save(instance));
342342
}
343343

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

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

323-
if (resultType.isInstance(instance)) {
323+
if (resultType.equals(instance.getClass())) {
324324
return save(instance).map(resultType::cast);
325325
}
326326

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
@@ -149,6 +149,18 @@ static QueryFragmentsAndParameters forExample(Neo4jMappingContext mappingContext
149149
return QueryFragmentsAndParameters.forExample(mappingContext, example, pageable, null, includeField);
150150
}
151151

152+
/**
153+
* Utility method for creating a query fragment including parameters for a given condition.
154+
*
155+
* @param entityMetaData The metadata of a given and known entity
156+
* @param condition A Cypher-DSL condition
157+
* @return Fully populated fragments and parameter
158+
*/
159+
@API(status = API.Status.EXPERIMENTAL, since = "6.1.7")
160+
public static QueryFragmentsAndParameters forCondition(Neo4jPersistentEntity<?> entityMetaData, Condition condition) {
161+
return forCondition(entityMetaData, condition, null, null);
162+
}
163+
152164
static QueryFragmentsAndParameters forCondition(Neo4jPersistentEntity<?> entityMetaData,
153165
Condition condition,
154166
@Nullable Pageable pageable,

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,19 @@
4949
import org.springframework.data.neo4j.config.AbstractNeo4jConfig;
5050
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
5151
import org.springframework.data.neo4j.core.Neo4jTemplate;
52+
import org.springframework.data.neo4j.core.mapping.Constants;
53+
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
54+
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
5255
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
5356
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
57+
import org.springframework.data.neo4j.integration.issues.gh2415.BaseNodeEntity;
58+
import org.springframework.data.neo4j.integration.issues.gh2415.NodeEntity;
59+
import org.springframework.data.neo4j.integration.issues.gh2415.NodeWithDefinedCredentials;
5460
import org.springframework.data.neo4j.integration.shared.common.Person;
5561
import org.springframework.data.neo4j.integration.shared.common.PersonWithAllConstructor;
5662
import org.springframework.data.neo4j.integration.shared.common.PersonWithAssignedId;
5763
import org.springframework.data.neo4j.integration.shared.common.ThingWithGeneratedId;
64+
import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters;
5865
import org.springframework.data.neo4j.test.BookmarkCapture;
5966
import org.springframework.data.neo4j.test.Neo4jExtension.Neo4jConnectionSupport;
6067
import org.springframework.data.neo4j.test.Neo4jIntegrationTest;
@@ -111,6 +118,14 @@ void setupData() {
111118
transaction.run("CREATE (p:Person{firstName: 'Bela', lastName: 'B.'})");
112119
transaction.run("CREATE (p:PersonWithAssignedId{id: 'x', firstName: 'John', lastName: 'Doe'})");
113120

121+
transaction.run(
122+
"CREATE (root:NodeEntity:BaseNodeEntity{nodeId: 'root'}) " +
123+
"CREATE (company:NodeEntity:BaseNodeEntity{nodeId: 'comp'}) " +
124+
"CREATE (cred:Credential{id: 'uuid-1', name: 'Creds'}) " +
125+
"CREATE (company)-[:CHILD_OF]->(root) " +
126+
"CREATE (root)-[:HAS_CREDENTIAL]->(cred) " +
127+
"CREATE (company)-[:WITH_CREDENTIAL]->(cred)");
128+
114129
transaction.commit();
115130
bookmarkCapture.seedWith(session.lastBookmark());
116131
}
@@ -799,6 +814,21 @@ void statementWithParamsShouldWork() {
799814
assertThat(people).extracting(Person::getLastName).containsExactly("Schnitzel");
800815
}
801816

817+
@Test // GH-2415
818+
void saveWithProjectionImplementedByEntity(@Autowired Neo4jMappingContext mappingContext) {
819+
820+
Neo4jPersistentEntity<?> metaData = mappingContext.getPersistentEntity(BaseNodeEntity.class);
821+
NodeEntity nodeEntity = neo4jTemplate
822+
.find(BaseNodeEntity.class)
823+
.as(NodeEntity.class)
824+
.matching(QueryFragmentsAndParameters.forCondition(metaData, Constants.NAME_OF_TYPED_ROOT_NODE.apply(metaData).property("nodeId").isEqualTo(Cypher.literalOf("root"))))
825+
.one().get();
826+
neo4jTemplate.saveAs(nodeEntity, NodeWithDefinedCredentials.class);
827+
828+
nodeEntity = neo4jTemplate.findById(nodeEntity.getNodeId(), NodeEntity.class).get();
829+
assertThat(nodeEntity.getChildren()).hasSize(1);
830+
}
831+
802832
@Configuration
803833
@EnableTransactionManagement
804834
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: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,19 @@
5151
import org.springframework.data.neo4j.config.AbstractReactiveNeo4jConfig;
5252
import org.springframework.data.neo4j.core.ReactiveDatabaseSelectionProvider;
5353
import org.springframework.data.neo4j.core.ReactiveNeo4jTemplate;
54+
import org.springframework.data.neo4j.core.mapping.Constants;
55+
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
56+
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
5457
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
5558
import org.springframework.data.neo4j.core.transaction.ReactiveNeo4jTransactionManager;
59+
import org.springframework.data.neo4j.integration.issues.gh2415.BaseNodeEntity;
60+
import org.springframework.data.neo4j.integration.issues.gh2415.NodeEntity;
61+
import org.springframework.data.neo4j.integration.issues.gh2415.NodeWithDefinedCredentials;
5662
import org.springframework.data.neo4j.integration.shared.common.Person;
5763
import org.springframework.data.neo4j.integration.shared.common.PersonWithAllConstructor;
5864
import org.springframework.data.neo4j.integration.shared.common.PersonWithAssignedId;
5965
import org.springframework.data.neo4j.integration.shared.common.ThingWithGeneratedId;
66+
import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters;
6067
import org.springframework.data.neo4j.test.BookmarkCapture;
6168
import org.springframework.data.neo4j.test.Neo4jExtension;
6269
import org.springframework.data.neo4j.test.Neo4jExtension.Neo4jConnectionSupport;
@@ -116,6 +123,14 @@ void setupData(@Autowired BookmarkCapture bookmarkCapture) {
116123
transaction.run("CREATE (p:Person{firstName: 'Bela', lastName: 'B.'})");
117124
transaction.run("CREATE (p:PersonWithAssignedId{id: 'x', firstName: 'John', lastName: 'Doe'})");
118125

126+
transaction.run(
127+
"CREATE (root:NodeEntity:BaseNodeEntity{nodeId: 'root'}) " +
128+
"CREATE (company:NodeEntity:BaseNodeEntity{nodeId: 'comp'}) " +
129+
"CREATE (cred:Credential{id: 'uuid-1', name: 'Creds'}) " +
130+
"CREATE (company)-[:CHILD_OF]->(root) " +
131+
"CREATE (root)-[:HAS_CREDENTIAL]->(cred) " +
132+
"CREATE (company)-[:WITH_CREDENTIAL]->(cred)");
133+
119134
transaction.commit();
120135

121136
bookmarkCapture.seedWith(session.lastBookmark());
@@ -803,6 +818,22 @@ void shouldSaveAsWithAssignedIdProjected() {
803818
.verifyComplete();
804819
}
805820

821+
@Test // GH-2415
822+
void saveWithProjectionImplementedByEntity(@Autowired Neo4jMappingContext mappingContext) {
823+
824+
Neo4jPersistentEntity<?> metaData = mappingContext.getPersistentEntity(BaseNodeEntity.class);
825+
neo4jTemplate
826+
.find(BaseNodeEntity.class)
827+
.as(NodeEntity.class)
828+
.matching(QueryFragmentsAndParameters.forCondition(metaData, Constants.NAME_OF_TYPED_ROOT_NODE.apply(metaData).property("nodeId").isEqualTo(Cypher.literalOf("root"))))
829+
.one()
830+
.flatMap(nodeEntity -> neo4jTemplate.saveAs(nodeEntity, NodeWithDefinedCredentials.class))
831+
.flatMap(nodeEntity -> neo4jTemplate.findById(nodeEntity.getNodeId(), NodeEntity.class))
832+
.as(StepVerifier::create)
833+
.consumeNextWith(nodeEntity -> assertThat(nodeEntity.getChildren()).hasSize(1))
834+
.verifyComplete();
835+
}
836+
806837
@Configuration
807838
@EnableTransactionManagement
808839
static class Config extends AbstractReactiveNeo4jConfig {

0 commit comments

Comments
 (0)