Skip to content

Commit 7fb990c

Browse files
GH-2262 - Fetch all properties when querying via a base interface.
When a statement is generated for a node description that describes an interface, we now always add the `__allProperties__` attribute to the map of properties pointing to `*`. We must do this because we cannot assume that every implementation of the interface actually has only the properties of the interface, but an arbitrary set of unknowns. The converter will now always look for this property if a requested property is not found on the surrounding map accessor. This fixes #2262.
1 parent c0b2e57 commit 7fb990c

File tree

5 files changed

+81
-6
lines changed

5 files changed

+81
-6
lines changed

src/main/java/org/springframework/data/neo4j/core/mapping/CypherGenerator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,7 @@ private List<Object> projectNodeProperties(NodeDescription<?> nodeDescription, S
559559
nodePropertiesProjection.add(graphProperty.getPropertyName());
560560
}
561561

562-
if (hasCompositeProperties) {
562+
if (hasCompositeProperties || nodeDescription.describesInterface()) {
563563
nodePropertiesProjection.add(Constants.NAME_OF_ALL_PROPERTIES);
564564
nodePropertiesProjection.add(node.project(Cypher.asterisk()));
565565
}

src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jEntityConverter.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535

3636
import org.neo4j.driver.Value;
3737
import org.neo4j.driver.Values;
38+
import org.neo4j.driver.internal.value.NullValue;
3839
import org.neo4j.driver.types.Entity;
3940
import org.neo4j.driver.types.MapAccessor;
4041
import org.neo4j.driver.types.Node;
@@ -559,7 +560,13 @@ private static Value extractValueOf(Neo4jPersistentProperty property, MapAccesso
559560
}
560561
} else {
561562
String graphPropertyName = property.getPropertyName();
562-
return propertyContainer.get(graphPropertyName);
563+
if (propertyContainer.containsKey(graphPropertyName)) {
564+
return propertyContainer.get(graphPropertyName);
565+
} else if (propertyContainer.containsKey(Constants.NAME_OF_ALL_PROPERTIES)) {
566+
return propertyContainer.get(Constants.NAME_OF_ALL_PROPERTIES).get(graphPropertyName);
567+
} else {
568+
return NullValue.NULL;
569+
}
563570
}
564571
}
565572

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
import org.springframework.data.neo4j.integration.shared.common.Cat;
4545
import org.springframework.data.neo4j.integration.shared.common.Dog;
4646
import org.springframework.data.neo4j.integration.shared.common.Inheritance;
47+
import org.springframework.data.neo4j.integration.shared.common.KotlinAnimationMovie;
48+
import org.springframework.data.neo4j.integration.shared.common.KotlinCinema;
4749
import org.springframework.data.neo4j.repository.Neo4jRepository;
4850
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
4951
import org.springframework.data.neo4j.repository.query.Query;
@@ -383,6 +385,26 @@ void shouldMatchPolymorphicInterfacesWhenFetchingAll(@Autowired ParentModelRepos
383385
assertThat(divisions).first().satisfies(twoDifferentInterfacesHaveBeenLoaded());
384386
}
385387

388+
@Test // GH-2262
389+
void shouldMatchPolymorphicKotlinInterfacesWhenFetchingAll(@Autowired CinemaRepository repository) {
390+
391+
try (Session session = driver.session(bookmarkCapture.createSessionConfig()); Transaction transaction = session.beginTransaction()) {
392+
transaction.run("CREATE (:KotlinMovie:KotlinAnimationMovie {id: 'movie001', name: 'movie-001', studio: 'Pixar'})<-[:Plays]-(c:KotlinCinema {id:'cine-01', name: 'GrandRex'}) RETURN id(c) AS id")
393+
.single().get(0).asLong();
394+
transaction.commit();
395+
bookmarkCapture.seedWith(session.lastBookmark());
396+
}
397+
398+
List<KotlinCinema> divisions = repository.findAll();
399+
assertThat(divisions).hasSize(1);
400+
assertThat(divisions).first().satisfies(c -> {
401+
assertThat(c.getPlays()).hasSize(1);
402+
assertThat(c.getPlays()).first().isInstanceOf(KotlinAnimationMovie.class)
403+
.extracting(m -> ((KotlinAnimationMovie) m).getStudio())
404+
.isEqualTo("Pixar");
405+
});
406+
}
407+
386408
private Consumer<Inheritance.ParentModel2> twoDifferentInterfacesHaveBeenLoaded() {
387409
return d -> {
388410
assertThat(d.getIsRelatedTo()).hasSize(2);
@@ -441,6 +463,8 @@ interface DivisionRepository extends Neo4jRepository<Inheritance.Division, Long>
441463

442464
interface ParentModelRepository extends Neo4jRepository<Inheritance.ParentModel2, Long> {}
443465

466+
interface CinemaRepository extends Neo4jRepository<KotlinCinema, String> {}
467+
444468
@Configuration
445469
@EnableNeo4jRepositories(considerNestedRepositories = true)
446470
@EnableTransactionManagement

src/test/kotlin/org/springframework/data/neo4j/integration/imperative/KotlinInheritanceIT.kt

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,9 @@ import org.springframework.data.neo4j.core.Neo4jTemplate
2929
import org.springframework.data.neo4j.core.findById
3030
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager
3131
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager
32-
import org.springframework.data.neo4j.integration.shared.common.ConcreteDataNodeWithAbstractKotlinBase
33-
import org.springframework.data.neo4j.integration.shared.common.ConcreteDataNodeWithOpenKotlinBase
34-
import org.springframework.data.neo4j.integration.shared.common.ConcreteNodeWithAbstractKotlinBase
35-
import org.springframework.data.neo4j.integration.shared.common.ConcreteNodeWithOpenKotlinBase
32+
import org.springframework.data.neo4j.integration.shared.common.*
33+
import org.springframework.data.neo4j.repository.Neo4jRepository
34+
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories
3635
import org.springframework.data.neo4j.test.BookmarkCapture
3736
import org.springframework.data.neo4j.test.Neo4jExtension
3837
import org.springframework.data.neo4j.test.Neo4jIntegrationTest
@@ -192,8 +191,32 @@ class KotlinInheritanceIT @Autowired constructor(
192191
assertThat(cnt).isEqualTo(1L)
193192
}
194193

194+
@Test // GH-2262
195+
fun shouldMatchPolymorphicInterfacesWhenFetchingAll(@Autowired cinemaRepository: KotlinCinemaRepository) {
196+
197+
driver.session(bookmarkCapture.createSessionConfig()).use { session ->
198+
session.writeTransaction { tx ->
199+
tx.run("CREATE (:KotlinMovie:KotlinAnimationMovie {id: 'movie001', name: 'movie-001', studio: 'Pixar'})<-[:Plays]-(c:KotlinCinema {id:'cine-01', name: 'GrandRex'}) RETURN id(c) AS id")
200+
.single()["id"].asLong()
201+
}
202+
bookmarkCapture.seedWith(session.lastBookmark())
203+
}
204+
205+
val cinemas = cinemaRepository.findAll()
206+
assertThat(cinemas).hasSize(1);
207+
assertThat(cinemas).first().satisfies { c ->
208+
assertThat(c.plays).hasSize(1);
209+
assertThat(c.plays).first().isInstanceOf(KotlinAnimationMovie::class.java)
210+
.extracting { m -> (m as KotlinAnimationMovie).studio }
211+
.isEqualTo("Pixar")
212+
}
213+
}
214+
215+
interface KotlinCinemaRepository: Neo4jRepository<KotlinCinema, String>
216+
195217
@Configuration
196218
@EnableTransactionManagement
219+
@EnableNeo4jRepositories(considerNestedRepositories = true)
197220
open class MyConfig : AbstractNeo4jConfig() {
198221
@Bean
199222
override fun driver(): Driver {
@@ -205,6 +228,10 @@ class KotlinInheritanceIT @Autowired constructor(
205228
return BookmarkCapture()
206229
}
207230

231+
override fun getMappingBasePackages(): Collection<String?>? {
232+
return setOf(Inheritance::class.java.getPackage().name)
233+
}
234+
208235
@Bean
209236
override fun transactionManager(driver: Driver, databaseNameProvider: DatabaseSelectionProvider): PlatformTransactionManager {
210237
val bookmarkCapture = bookmarkCapture()

src/test/kotlin/org/springframework/data/neo4j/integration/shared/common/KotlinInheritance.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import org.springframework.data.annotation.Transient
1919
import org.springframework.data.neo4j.core.schema.GeneratedValue
2020
import org.springframework.data.neo4j.core.schema.Id
2121
import org.springframework.data.neo4j.core.schema.Node
22+
import org.springframework.data.neo4j.core.schema.Relationship
2223

2324
/**
2425
* @author Michael J. Simons
@@ -71,3 +72,19 @@ class ConcreteNodeWithOpenKotlinBase(name: String, val anotherProperty: String)
7172
@Node
7273
data class ConcreteDataNodeWithOpenKotlinBase(override @Transient val name: String, val anotherProperty: String) : OpenKotlinBase(name) {
7374
}
75+
76+
77+
@Node
78+
interface KotlinMovie {
79+
val id: String
80+
val name: String
81+
}
82+
83+
84+
@Node
85+
class KotlinCinema(@Id val id: String, val name: String,
86+
@Relationship("Plays", direction = Relationship.Direction.OUTGOING) val plays: List<KotlinMovie>
87+
)
88+
89+
@Node
90+
class KotlinAnimationMovie(@Id override val id: String, override val name: String, val studio: String?) : KotlinMovie

0 commit comments

Comments
 (0)