From 4ff3e11880bb4bc1929f167dab294c4ecdc78995 Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Tue, 2 Apr 2024 14:30:38 +0200 Subject: [PATCH] fix: Look for static labels to contain a label, not vice versa. (#2887) Fixes #2886. --- .../mapping/DefaultNeo4jPersistentEntity.java | 7 ++ .../core/mapping/NodeDescriptionStore.java | 8 +- .../integration/issues/gh2886/Apple.java | 25 ++++ .../integration/issues/gh2886/Fruit.java | 52 ++++++++ .../issues/gh2886/FruitRepository.java | 31 +++++ .../integration/issues/gh2886/Gh2886IT.java | 119 ++++++++++++++++++ .../issues/gh2886/MagicalFruit.java | 45 +++++++ .../integration/issues/gh2886/Orange.java | 25 ++++ 8 files changed, 308 insertions(+), 4 deletions(-) create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Apple.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Fruit.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/FruitRepository.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Gh2886IT.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/MagicalFruit.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Orange.java diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentEntity.java b/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentEntity.java index edd4a3032..d6e0efc7c 100644 --- a/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentEntity.java +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentEntity.java @@ -608,4 +608,11 @@ final class DefaultNeo4jPersistentEntity extends BasicPersistentEntity entityDescription, List labels) { + private NodeDescriptionAndLabels computeConcreteNodeDescription(NodeDescription entityDescription, @Nullable List labels) { boolean isConcreteClassThatFulfillsEverything = !Modifier.isAbstract(entityDescription.getUnderlyingClass().getModifiers()) && entityDescription.getStaticLabels().containsAll(labels); @@ -139,9 +139,9 @@ final class NodeDescriptionStore { int unmatchedLabelsCount = 0; List matchingLabels = new ArrayList<>(); - for (String staticLabel : staticLabels) { - if (labels.contains(staticLabel)) { - matchingLabels.add(staticLabel); + for (String label : labels) { + if (staticLabels.contains(label)) { + matchingLabels.add(label); } else { unmatchedLabelsCount++; } diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Apple.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Apple.java new file mode 100644 index 000000000..8734ef33a --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Apple.java @@ -0,0 +1,25 @@ +/* + * 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.gh2886; + +import org.springframework.data.neo4j.core.schema.Node; + +/** + * GH-2886 + */ +@Node(primaryLabel = "Apple") +public class Apple extends MagicalFruit { +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Fruit.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Fruit.java new file mode 100644 index 000000000..120b301c4 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Fruit.java @@ -0,0 +1,52 @@ +/* + * 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.gh2886; + +import java.util.HashSet; +import java.util.Set; + +import org.springframework.data.neo4j.core.schema.DynamicLabels; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; + +/** + * GH-2886 + */ +@Node(primaryLabel = "Fruit") +public abstract class Fruit { + + @Id + protected String id; + + @DynamicLabels + protected Set labels = new HashSet<>(); + + public String getId() { + return this.id; + } + + public Set getLabels() { + return this.labels; + } + + public void setId(String id) { + this.id = id; + } + + public void setLabels(Set labels) { + this.labels = labels; + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/FruitRepository.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/FruitRepository.java new file mode 100644 index 000000000..dff4f612a --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/FruitRepository.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.gh2886; + +import java.util.List; + +import org.springframework.data.neo4j.repository.Neo4jRepository; +import org.springframework.data.neo4j.repository.query.Query; +import org.springframework.stereotype.Repository; + +/** + * GH-2886 + */ +@Repository +public interface FruitRepository extends Neo4jRepository { + @Query("MATCH (f:Fruit) RETURN f") + List findAllFruits(); +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Gh2886IT.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Gh2886IT.java new file mode 100644 index 000000000..9a0b1354b --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Gh2886IT.java @@ -0,0 +1,119 @@ +/* + * 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.gh2886; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.neo4j.driver.Driver; +import org.neo4j.driver.Session; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.neo4j.core.DatabaseSelectionProvider; +import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager; +import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager; +import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; +import org.springframework.data.neo4j.test.BookmarkCapture; +import org.springframework.data.neo4j.test.Neo4jExtension; +import org.springframework.data.neo4j.test.Neo4jImperativeTestConfiguration; +import org.springframework.data.neo4j.test.Neo4jIntegrationTest; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +/** + * GH-2886 + */ +@Neo4jIntegrationTest +public class Gh2886IT { + + protected static Neo4jExtension.Neo4jConnectionSupport neo4jConnectionSupport; + + @BeforeAll + static void setup(@Autowired Driver driver, @Autowired BookmarkCapture bookmarkCapture) { + try (Session session = driver.session(bookmarkCapture.createSessionConfig())) { + session.run("MATCH (n) detach delete n").consume(); + bookmarkCapture.seedWith(session.lastBookmark()); + } + } + + @Test + void dynamicLabels(@Autowired FruitRepository repository) { + + Apple f1 = new Apple(); + f1.setVolume(1.0); + f1.setColor("Red"); + f1.setLabels(Collections.singleton("X")); + + Apple f2 = new Apple(); + f2.setColor("Blue"); + + Orange f3 = new Orange(); + f2.setVolume(3.0); + f3.setColor("Red"); + f3.setLabels(Collections.singleton("Y")); + + Orange f4 = new Orange(); + f4.setColor("Yellow"); + + repository.saveAll(Stream.of(f1, f2, f3, f4).collect(Collectors.toList())); + + List fruits = repository.findAllFruits(); + assertThat(fruits).allMatch(f -> f instanceof Apple || f instanceof Orange); + } + + @Configuration + @EnableTransactionManagement + @EnableNeo4jRepositories(considerNestedRepositories = true) + static class Config extends Neo4jImperativeTestConfiguration { + + @Bean + public BookmarkCapture bookmarkCapture() { + return new BookmarkCapture(); + } + + @Override + public PlatformTransactionManager transactionManager( + Driver driver, DatabaseSelectionProvider databaseNameProvider) { + + BookmarkCapture bookmarkCapture = bookmarkCapture(); + return new Neo4jTransactionManager(driver, databaseNameProvider, + Neo4jBookmarkManager.create(bookmarkCapture)); + } + + @Override + protected Collection getMappingBasePackages() { + return Collections.singleton(Apple.class.getPackage().getName()); + } + + @Bean + public Driver driver() { + return neo4jConnectionSupport.getDriver(); + } + + @Override + public boolean isCypher5Compatible() { + return neo4jConnectionSupport.isCypher5SyntaxCompatible(); + } + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/MagicalFruit.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/MagicalFruit.java new file mode 100644 index 000000000..3e462ea1c --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/MagicalFruit.java @@ -0,0 +1,45 @@ +/* + * 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.gh2886; + +import org.springframework.data.neo4j.core.schema.Node; + +/** + * GH-2886 + */ +@Node(primaryLabel = "MagicalFruit") +public class MagicalFruit extends Fruit { + + private double volume; + + private String color; + + public double getVolume() { + return this.volume; + } + + public String getColor() { + return this.color; + } + + public void setVolume(double volume) { + this.volume = volume; + } + + public void setColor(String color) { + this.color = color; + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Orange.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Orange.java new file mode 100644 index 000000000..1ba826583 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Orange.java @@ -0,0 +1,25 @@ +/* + * 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.gh2886; + +import org.springframework.data.neo4j.core.schema.Node; + +/** + * GH-2886 + */ +@Node(primaryLabel = "Orange") +public class Orange extends MagicalFruit { +}