From ce3dc986e422ce4e7f3a6521265225c45cbf9a1a Mon Sep 17 00:00:00 2001 From: Gerrit Meier Date: Thu, 1 Jun 2023 15:26:26 +0200 Subject: [PATCH] GH-2727 - Fix multiple levels of projecting properties mapping. Closes #2727 --- .../neo4j/core/mapping/CypherGenerator.java | 21 ++----- .../neo4j/integration/issues/IssuesIT.java | 60 +++++++++++++++++++ .../issues/gh2727/FirstLevelEntity.java | 48 +++++++++++++++ .../gh2727/FirstLevelEntityRepository.java | 26 ++++++++ .../issues/gh2727/FirstLevelProjection.java | 38 ++++++++++++ .../issues/gh2727/OrderedRelation.java | 50 ++++++++++++++++ .../issues/gh2727/SecondLevelEntity.java | 48 +++++++++++++++ .../gh2727/SecondLevelEntityRelationship.java | 22 +++++++ .../issues/gh2727/SecondLevelProjection.java | 40 +++++++++++++ .../issues/gh2727/ThirdLevelEntity.java | 42 +++++++++++++ .../gh2727/ThirdLevelEntityRelationship.java | 22 +++++++ .../issues/gh2727/ThirdLevelProjection.java | 25 ++++++++ 12 files changed, 426 insertions(+), 16 deletions(-) create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/FirstLevelEntity.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/FirstLevelEntityRepository.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/FirstLevelProjection.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/OrderedRelation.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/SecondLevelEntity.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/SecondLevelEntityRelationship.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/SecondLevelProjection.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/ThirdLevelEntity.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/ThirdLevelEntityRelationship.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/ThirdLevelProjection.java diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/CypherGenerator.java b/src/main/java/org/springframework/data/neo4j/core/mapping/CypherGenerator.java index adef4c77f..dc6519000 100644 --- a/src/main/java/org/springframework/data/neo4j/core/mapping/CypherGenerator.java +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/CypherGenerator.java @@ -744,21 +744,7 @@ public enum CypherGenerator { if (property.isDynamicLabels() || property.isComposite()) { continue; } - PropertyFilter.RelaxedPropertyPath from; - - if (relationshipDescription == null) { - from = parentPath.append(property.getFieldName()); - - } else if (relationshipDescription.hasRelationshipProperties()) { - @SuppressWarnings("ConstantConditions") // All prerequisites have been checked by the persistence context at this point. - String relationshipPropertyTargetNodeFieldName = - ((Neo4jPersistentEntity) relationshipDescription.getRelationshipPropertiesEntity()) - .getPersistentProperty(TargetNode.class).getFieldName(); - - from = parentPath.append(relationshipPropertyTargetNodeFieldName + "." + property.getFieldName()); - } else { - from = parentPath.append(property.getFieldName()); - } + PropertyFilter.RelaxedPropertyPath from = parentPath.append(property.getFieldName()); if (!includeField.test(from)) { continue; @@ -828,7 +814,10 @@ public enum CypherGenerator { Neo4jPersistentEntity endNodeDescription = (Neo4jPersistentEntity) relationshipDescription.getTarget(); processedRelationships.add(relationshipDescription); - PropertyFilter.RelaxedPropertyPath newParentPath = parentPath.append(relationshipDescription.getFieldName()); + PropertyFilter.RelaxedPropertyPath newParentPath = relationshipDescription.hasRelationshipProperties() + ? parentPath.append(relationshipDescription.getFieldName()).append(((Neo4jPersistentEntity) relationshipDescription.getRelationshipPropertiesEntity()) + .getPersistentProperty(TargetNode.class).getFieldName()) + : parentPath.append(relationshipDescription.getFieldName()); if (relationshipDescription.isDynamic()) { Relationship relationship = relationshipDescription.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 e2aa604b4..acf11b869 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 @@ -145,6 +145,13 @@ import org.springframework.data.neo4j.integration.issues.gh2639.Sales; 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; +import org.springframework.data.neo4j.integration.issues.gh2727.FirstLevelEntity; +import org.springframework.data.neo4j.integration.issues.gh2727.FirstLevelEntityRepository; +import org.springframework.data.neo4j.integration.issues.gh2727.FirstLevelProjection; +import org.springframework.data.neo4j.integration.issues.gh2727.SecondLevelEntity; +import org.springframework.data.neo4j.integration.issues.gh2727.SecondLevelEntityRelationship; +import org.springframework.data.neo4j.integration.issues.gh2727.ThirdLevelEntity; +import org.springframework.data.neo4j.integration.issues.gh2727.ThirdLevelEntityRelationship; import org.springframework.data.neo4j.integration.misc.ConcreteImplementationTwo; import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters; @@ -1030,6 +1037,59 @@ class IssuesIT extends TestBase { } + @Test + @Tag("GH-2727") + void mapsProjectionChainWithRelationshipProperties(@Autowired FirstLevelEntityRepository firstLevelEntityRepository) { + + String secondLevelValue = "someSecondLevelValue"; + String thirdLevelValue = "someThirdLevelValue"; + + final List secondLevelEntityRelationships = new ArrayList<>(); + for (int i = 0; i < 2; i++) { + final List thirdLevelEntityRelationships = new ArrayList<>(); + for (int j = 0; j < 3; j++) { + final ThirdLevelEntity thirdLevelEntity = new ThirdLevelEntity(); + thirdLevelEntity.setSomeValue(thirdLevelValue); + final ThirdLevelEntityRelationship thirdLevelRelationship = new ThirdLevelEntityRelationship(); + thirdLevelRelationship.setTarget(thirdLevelEntity); + thirdLevelRelationship.setOrder(j + 1); + thirdLevelEntityRelationships.add(thirdLevelRelationship); + } + + final SecondLevelEntity secondLevelEntity = SecondLevelEntity.builder() + .thirdLevelEntityRelationshipProperties(thirdLevelEntityRelationships) + .someValue(secondLevelValue) + .build(); + + final SecondLevelEntityRelationship secondLevelRelationship = new SecondLevelEntityRelationship(); + secondLevelRelationship.setTarget(secondLevelEntity); + secondLevelRelationship.setOrder(i + 1); + secondLevelEntityRelationships.add(secondLevelRelationship); + } + + final FirstLevelEntity firstLevelEntity = FirstLevelEntity.builder() + .secondLevelEntityRelationshipProperties(secondLevelEntityRelationships) + .name("Test") + .build(); + + firstLevelEntityRepository.save(firstLevelEntity); + + FirstLevelProjection firstLevelProjection = firstLevelEntityRepository.findOneById(firstLevelEntity.getId()); + assertThat(firstLevelProjection).isNotNull(); + assertThat(firstLevelProjection.getSecondLevelEntityRelationshipProperties()).hasSize(2) + .allSatisfy(secondLevelRelationship -> { + assertThat(secondLevelRelationship.getTarget().getSomeValue().equals(secondLevelValue)); + assertThat(secondLevelRelationship.getOrder()).isGreaterThan(0); + assertThat(secondLevelRelationship.getTarget().getThirdLevelEntityRelationshipProperties()) + .isNotEmpty() + .allSatisfy(thirdLevel -> { + assertThat(thirdLevel.getOrder()).isGreaterThan(0); + assertThat(thirdLevel.getTarget()).isNotNull(); + assertThat(thirdLevel.getTarget().getSomeValue()).isEqualTo(thirdLevelValue); + }); + }); + } + @Configuration @EnableTransactionManagement @EnableNeo4jRepositories(namedQueriesLocation = "more-custom-queries.properties") diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/FirstLevelEntity.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/FirstLevelEntity.java new file mode 100644 index 000000000..5eb16ada5 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/FirstLevelEntity.java @@ -0,0 +1,48 @@ +/* + * Copyright 2011-2023 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.gh2727; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; +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 java.util.List; + +/** + * @author Gerrit Meier + */ +@SuperBuilder +@NoArgsConstructor +@Getter +@Setter +@Node("FirstLevel") +@EqualsAndHashCode(of = {"id"}) +public class FirstLevelEntity { + @Id + @GeneratedValue + private Long id; + + private String name; + + @Relationship("HasSecondLevel") + private List secondLevelEntityRelationshipProperties; +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/FirstLevelEntityRepository.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/FirstLevelEntityRepository.java new file mode 100644 index 000000000..51faa951a --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/FirstLevelEntityRepository.java @@ -0,0 +1,26 @@ +/* + * Copyright 2011-2023 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.gh2727; + +import org.springframework.data.neo4j.repository.Neo4jRepository; + +/** + * @author Gerrit Meier + */ +public interface FirstLevelEntityRepository extends Neo4jRepository { + + FirstLevelProjection findOneById(Long id); +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/FirstLevelProjection.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/FirstLevelProjection.java new file mode 100644 index 000000000..14d436626 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/FirstLevelProjection.java @@ -0,0 +1,38 @@ +/* + * Copyright 2011-2023 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.gh2727; + +import java.util.Set; + +/** + * @author Gerrit Meier + */ +public interface FirstLevelProjection { + Long getId(); + + Set getSecondLevelEntityRelationshipProperties(); + + /** + * + */ + interface SecondLevelRelationshipProjection { + Long getId(); + + SecondLevelProjection getTarget(); + + Integer getOrder(); + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/OrderedRelation.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/OrderedRelation.java new file mode 100644 index 000000000..52e84650c --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/OrderedRelation.java @@ -0,0 +1,50 @@ +/* + * Copyright 2011-2023 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.gh2727; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.neo4j.core.schema.GeneratedValue; +import org.springframework.data.neo4j.core.schema.Property; +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 Gerrit Meier + * @param relationship properties type + */ +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +@RelationshipProperties +public class OrderedRelation implements Comparable> { + @RelationshipId + @GeneratedValue + private Long id; + @TargetNode + private T target; + @Property + private Integer order; + + @Override + public int compareTo(final OrderedRelation o) { + return order - o.order; + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/SecondLevelEntity.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/SecondLevelEntity.java new file mode 100644 index 000000000..2cde6f9d6 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/SecondLevelEntity.java @@ -0,0 +1,48 @@ +/* + * Copyright 2011-2023 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.gh2727; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; +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 java.util.List; + +/** + * @author Gerrit Meier + */ +@SuperBuilder +@NoArgsConstructor +@Getter +@Setter +@Node("SecondLevel") +@EqualsAndHashCode(of = {"id"}) +public class SecondLevelEntity { + @Id + @GeneratedValue + private Long id; + + private String someValue; + + @Relationship("HasThirdLevel") + private List thirdLevelEntityRelationshipProperties; +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/SecondLevelEntityRelationship.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/SecondLevelEntityRelationship.java new file mode 100644 index 000000000..9b2679abb --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/SecondLevelEntityRelationship.java @@ -0,0 +1,22 @@ +/* + * Copyright 2011-2023 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.gh2727; + +/** + * @author Gerrit Meier + */ +public class SecondLevelEntityRelationship extends OrderedRelation { +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/SecondLevelProjection.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/SecondLevelProjection.java new file mode 100644 index 000000000..9898a8adf --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/SecondLevelProjection.java @@ -0,0 +1,40 @@ +/* + * Copyright 2011-2023 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.gh2727; + +import java.util.Set; + +/** + * @author Gerrit Meier + */ +public interface SecondLevelProjection { + Long getId(); + + String getSomeValue(); + + Set getThirdLevelEntityRelationshipProperties(); + + /** + * + */ + interface ThirdLevelRelationshipProjection { + Long getId(); + + ThirdLevelProjection getTarget(); + + Integer getOrder(); + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/ThirdLevelEntity.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/ThirdLevelEntity.java new file mode 100644 index 000000000..fcfe6b978 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/ThirdLevelEntity.java @@ -0,0 +1,42 @@ +/* + * Copyright 2011-2023 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.gh2727; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; +import org.springframework.data.neo4j.core.schema.GeneratedValue; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; + +/** + * @author Gerrit Meier + */ +@SuperBuilder +@NoArgsConstructor +@Getter +@Setter +@Node("ThirdLevel") +@EqualsAndHashCode(of = {"id"}) +public class ThirdLevelEntity { + @Id + @GeneratedValue + private Long id; + + private String someValue; +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/ThirdLevelEntityRelationship.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/ThirdLevelEntityRelationship.java new file mode 100644 index 000000000..ce0fcb7c3 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/ThirdLevelEntityRelationship.java @@ -0,0 +1,22 @@ +/* + * Copyright 2011-2023 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.gh2727; + +/** + * @author Gerrit Meier + */ +public class ThirdLevelEntityRelationship extends OrderedRelation { +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/ThirdLevelProjection.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/ThirdLevelProjection.java new file mode 100644 index 000000000..0b12f2c82 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2727/ThirdLevelProjection.java @@ -0,0 +1,25 @@ +/* + * Copyright 2011-2023 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.gh2727; + +/** + * @author Gerrit Meier + */ +public interface ThirdLevelProjection { + Long getId(); + + String getSomeValue(); +}