From 7a8b10be622a38fb53a9bd8e05a7913af254430c Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Wed, 10 Jul 2024 09:46:30 +0200 Subject: [PATCH] fix: Use direction as part of the node collection name for each relationship. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This avoids having duplicate keys in the map projection used to collect the nodes for each relationship in cases where a node has two relationships with the same name to the same label in different directions. Fixes #2918. Co-authored-by: Mathias Kühn --- .../core/mapping/RelationshipDescription.java | 2 +- .../neo4j/integration/issues/IssuesIT.java | 15 +++++++ .../issues/gh2918/ConditionNode.java | 40 +++++++++++++++++++ .../issues/gh2918/ConditionRepository.java | 24 +++++++++++ .../issues/gh2918/FailureNode.java | 31 ++++++++++++++ .../issues/gh2918/FailureRelationship.java | 33 +++++++++++++++ 6 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2918/ConditionNode.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2918/ConditionRepository.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2918/FailureNode.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2918/FailureRelationship.java diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/RelationshipDescription.java b/src/main/java/org/springframework/data/neo4j/core/mapping/RelationshipDescription.java index 5693e3bc2..0f9af2f8b 100644 --- a/src/main/java/org/springframework/data/neo4j/core/mapping/RelationshipDescription.java +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/RelationshipDescription.java @@ -117,7 +117,7 @@ default boolean isIncoming() { @NonNull default String generateRelatedNodesCollectionName(NodeDescription mostAbstractNodeDescription) { - return this.getSource().getMostAbstractParentLabel(mostAbstractNodeDescription) + "_" + this.getType() + "_" + this.getTarget().getPrimaryLabel(); + return this.getSource().getMostAbstractParentLabel(mostAbstractNodeDescription) + "_" + this.getType() + "_" + this.getTarget().getPrimaryLabel() + "_" + this.isOutgoing(); } /** diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/IssuesIT.java b/src/test/java/org/springframework/data/neo4j/integration/issues/IssuesIT.java index 6f782eb89..f5e3eaf3f 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/issues/IssuesIT.java +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/IssuesIT.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.as; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.tuple; import java.util.ArrayList; @@ -169,6 +170,8 @@ import org.springframework.data.neo4j.integration.issues.gh2906.FromRepository; import org.springframework.data.neo4j.integration.issues.gh2906.OutgoingBugRelationship; import org.springframework.data.neo4j.integration.issues.gh2906.ToRepository; +import org.springframework.data.neo4j.integration.issues.gh2918.ConditionNode; +import org.springframework.data.neo4j.integration.issues.gh2918.ConditionRepository; import org.springframework.data.neo4j.integration.issues.qbe.A; import org.springframework.data.neo4j.integration.issues.qbe.ARepository; import org.springframework.data.neo4j.integration.issues.qbe.B; @@ -1557,6 +1560,18 @@ private static void assertGH2906Graph(Driver driver, int cnt) { }); } + @Test + @Tag("GH-2918") + void loadCycleFreeWithInAndOutgoingRelationship(@Autowired ConditionRepository conditionRepository, @Autowired Driver driver) { + + var conditionSaved = conditionRepository.save(new ConditionNode()); + + // Condition has both an incoming and outgoing relationship typed CAUSES that will cause a duplicate key + // in the map projection for the relationships to load. The fix was to indicate the direction in the name + // used for projecting the relationship, too + assertThatNoException().isThrownBy(() -> conditionRepository.findById(conditionSaved.uuid)); + } + @Configuration @EnableTransactionManagement @EnableNeo4jRepositories(namedQueriesLocation = "more-custom-queries.properties") diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2918/ConditionNode.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2918/ConditionNode.java new file mode 100644 index 000000000..8cdc0232e --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2918/ConditionNode.java @@ -0,0 +1,40 @@ +/* + * Copyright 2011-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.integration.issues.gh2918; + +import java.util.Set; + +import org.springframework.data.neo4j.core.schema.GeneratedValue; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.schema.Relationship; +import org.springframework.data.neo4j.core.support.UUIDStringGenerator; + +/** + * @author Mathias Kühn + */ +@Node +public class ConditionNode { + @Id + @GeneratedValue(UUIDStringGenerator.class) + public String uuid; + + @Relationship(type = "CAUSES", direction = Relationship.Direction.INCOMING) + public Set upstreamFailures; + + @Relationship(type = "CAUSES", direction = Relationship.Direction.OUTGOING) + public Set downstreamFailures; +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2918/ConditionRepository.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2918/ConditionRepository.java new file mode 100644 index 000000000..82cf2c7d6 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2918/ConditionRepository.java @@ -0,0 +1,24 @@ +/* + * Copyright 2011-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.integration.issues.gh2918; + +import org.springframework.data.neo4j.repository.Neo4jRepository; + +/** + * @author Mathias Kühn + */ +public interface ConditionRepository extends Neo4jRepository { +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2918/FailureNode.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2918/FailureNode.java new file mode 100644 index 000000000..2827d8460 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2918/FailureNode.java @@ -0,0 +1,31 @@ +/* + * Copyright 2011-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.integration.issues.gh2918; + +import org.springframework.data.neo4j.core.schema.GeneratedValue; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.support.UUIDStringGenerator; + +/** + * @author Mathias Kühn + */ +@Node +public class FailureNode { + @Id + @GeneratedValue(UUIDStringGenerator.class) + public String uuid; +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2918/FailureRelationship.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2918/FailureRelationship.java new file mode 100644 index 000000000..ff86024d7 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2918/FailureRelationship.java @@ -0,0 +1,33 @@ +/* + * Copyright 2011-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.integration.issues.gh2918; + +import org.springframework.data.neo4j.core.schema.RelationshipId; +import org.springframework.data.neo4j.core.schema.RelationshipProperties; +import org.springframework.data.neo4j.core.schema.TargetNode; + +/** + * @author Mathias Kühn + */ +@RelationshipProperties +public abstract class FailureRelationship { + + @RelationshipId + public String id; + + @TargetNode + public FailureNode target; +}