Skip to content

@DynamicLabel with non-abstract class inheritance #2886

@LGDOI

Description

@LGDOI

SDN 6.3.18 returns incorrect instance type when a dynamic label is set on a node.

I have an example repo but here is a summary of the problem.

Entities

Given the following entity types

@Getter
@Setter
@NoArgsConstructor
@Node(primaryLabel = "Fruit")
public abstract class Fruit {

  @Id
  protected String id;

  @JsonIgnore
  @DynamicLabels
  protected Set<String> labels = Set.of();
}

Subclass of Fruit


@Getter
@Setter
@NoArgsConstructor
@Node(primaryLabel = "MagicalFruit")
public class MagicalFruit extends Fruit {

  @Serial
  private static final long serialVersionUID = -8776591636750117301L;

  @Property(name = "volume")
  private double volume;

  @Property(name = "color")
  private String color;

  @Override
  public int hashCode() {
    return new HashCodeBuilder().append(id).hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    return obj == this || (obj instanceof MagicalFruit other && Objects.equals(id, other.id));
  }
}

Sub classes of MagicalFruit

@Getter
@Setter
@NoArgsConstructor
@Node(primaryLabel = "Apple")
public class Apple extends MagicalFruit {

  @Override
  public boolean equals(Object obj) {
    return obj == this || (obj instanceof Apple other && Objects.equals(id, other.id));
  }
}

and

@Getter
@Setter
@NoArgsConstructor
@Node(primaryLabel = "Orange")
public class Orange extends MagicalFruit {

  @Override
  public boolean equals(Object obj) {
    return obj == this || (obj instanceof Orange other && Objects.equals(id, other.id));
  }
}

Repository

@Repository
public interface FruitRepository extends Neo4jRepository<Fruit, String> {
  @Query("MATCH (f:Fruit) RETURN f")
  List<Fruit> findAllFruits();
}

Test

@DataNeo4jTest
class FruitRepositoryTest(
  val fruitRepository: FruitRepository,
) {

  @Test
  fun `debug dynamic label and deserialization`() {
    val applesWithDynamicLabel = List(2) {
      Apple().apply {
        volume = it.toDouble()
        color = "Red"
        labels = setOf("Apple_$it")
      }
    }
    val applesWithoutDynamicLabel = List(2) {
      Apple().apply {
        volume = it.toDouble()
        color = "Blue"
      }
    }
    val orangesWithDynamicLabel = List(2) {
      Orange().apply {
        volume = it.toDouble()
        color = "Red"
        labels = setOf("Orange_$it")
      }
    }
    val orangesWithoutDynamicLabel = List(2) {
      Orange().apply {
        volume = it.toDouble()
        color = "Yellow"
      }
    }
    fruitRepository.saveAll(
      applesWithDynamicLabel + applesWithoutDynamicLabel + orangesWithDynamicLabel + orangesWithoutDynamicLabel)

    val fruits = fruitRepository.findAllFruits()
    assertThat(fruits.filterIsInstance<Apple>()).hasSize(4)
    assertThat(fruits.filterIsInstance<Orange>()).hasSize(4)
  }
}

The above test fails because fruits contains instances of MagicalFruit when dynamic labels are set.

Other Findings

When I change MagicalFruit class to an abstract class, the same test pass.

#2529 is very similar bug but that example has abstract on Feline class.

Is this required to have only one concrete class as a leaf node of the inheritance hierarchy for dynamic label to work?

Metadata

Metadata

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions