neo4j / sdn-rx

Nextgen Spring Data module for Neo4j supporting (not only) reactive data access and immutable support
https://neo4j.github.io/sdn-rx
Apache License 2.0
65 stars 23 forks source link

ReactiveNeo4jRepository/ReactiveNeo4jTemplate - saveAll method implementation #272

Closed sergeily closed 4 years ago

sergeily commented 4 years ago

Description Method org.neo4j.springframework.data.core.ReactiveNeo4jTemplate#saveAll does not save @DynamicLabels

Stack:

Java 11
Spring boot version: 2.3.0.RELEASE
SDN/RX version: 1.1.1
Neo4j version: 4.0 and 4.1 

How to reproduce? Our simplified steps to reproduce

  1. Create entity

    @Node("Column")
    public class DaeColumnNode {
    @Id
    private String daeId;
    
    @DynamicLabels
    private Set<String> labels = new HashSet<>();
    
    public String getDaeId() {
        return daeId;
    }
    public DaeColumnNode setDaeId(String daeId) {
        this.daeId = daeId;
        return this;
    }
    
    public Set<String> getLabels() {
        return labels;
    }
    public DaeColumnNode setLabels(Set<String> labels) {
        this.labels = labels;
        return this;
    }
    }
  2. Create and run test

@Testcontainers @ReactiveDataNeo4jTest class ReactiveColumnNodeTest {

@Container
private static Neo4jContainer<?> neo4jContainer = new Neo4jContainer<>("neo4j:4.1");

@DynamicPropertySource
static void neo4jProperties(DynamicPropertyRegistry registry) {
    registry.add("org.neo4j.driver.uri", neo4jContainer::getBoltUrl);
    registry.add("org.neo4j.driver.authentication.username", () -> "neo4j");
    registry.add("org.neo4j.driver.authentication.password", neo4jContainer::getAdminPassword);
}

@Test
void shouldSaveAndReadEntities(@Autowired ReactiveNeo4jTemplate neo4jTemplate) {
    DaeColumnNode column = new DaeColumnNode();
    column.setDaeId("123-abc");
    column.setLabels(new HashSet<>(){{
        add("Column");
        add("Model");
        add("Data");
    }});

    StepVerifier.create(neo4jTemplate.save(column))
        .expectNextCount(1L)
        .verifyComplete();

    StepVerifier.create(neo4jTemplate.findAll(DaeColumnNode.class))
        .consumeNextWith(response -> {
            assertThat(column.getDaeId().equals(response.getDaeId()));
            assertThat(response.getLabels().contains("Model"));
            assertThat(response.getLabels().contains("Data"));
        })
        .verifyComplete();
}

@Test
void shouldSaveAllAndReadAllEntities(@Autowired ReactiveNeo4jTemplate neo4jTemplate) {
    DaeColumnNode column = new DaeColumnNode();
    column.setDaeId("987-zyx");
    column.setLabels(new HashSet<>(){{
        add("Column");
        add("Model");
        add("Data");
    }});

    StepVerifier.create(neo4jTemplate.saveAll(Collections.singletonList(column)))
        .expectNextCount(1L)
        .verifyComplete();

    StepVerifier.create(neo4jTemplate.findAll(DaeColumnNode.class))
        .consumeNextWith(response -> {
            assertThat(column.getDaeId().equals(response.getDaeId()));
            assertThat(response.getLabels().contains("Model"));
            assertThat(response.getLabels().contains("Data"));
        })
        .verifyComplete();
}

}


Test "shouldSaveAndReadEntities" _(save method is used)_ will pass 
Test "shouldSaveAllAndReadAllEntities" _(saveAll method is used)_ will fail beause response.getLabels() is empty collection

**How to reproduce? (with controller)**
**Our steps to reproduce**

1. Create entity
```java
@Node("Column")
public class DaeColumnNode {
    @Id
    private String daeId;

    @DynamicLabels
    private Set<String> labels = new HashSet<>();

    public String getDaeId() {
        return daeId;
    }
    public DaeColumnNode setDaeId(String daeId) {
        this.daeId = daeId;
        return this;
    }

    public Set<String> getLabels() {
        return labels;
    }
    public DaeColumnNode setLabels(Set<String> labels) {
        this.labels = labels;
        return this;
    }
}
  1. Create repository
    
    public interface DaeColumnRepository extends ReactiveNeo4jRepository<DaeColumnNode, String> {

}


3. Create controller
```java
@RestController
@RequestMapping("/column")
public class DaeColumnController {

    private final DaeColumnRepository columnRepository;

    public DaeColumnController(DaeColumnRepository columnRepository) {
        this.columnRepository = columnRepository;
    }

    @PostMapping("/save")
    public Mono<DaeColumnNode> save(@RequestBody DaeColumnNode node){
        return columnRepository.save(node);
    }

    @PostMapping("/save/all")
    public Flux<DaeColumnNode> saveAll(@RequestBody List<DaeColumnNode> nodes){
        return columnRepository.saveAll(nodes);
    }
}
  1. Run
    
    curl --location --request POST 'localhost:8080/column/save' \
    --header 'Content-Type: application/json' \
    --data-raw '{
    "daeId": "123-abc",
    "labels": [
        "Column",
        "Test",
        "Model"
    ]
    }'

and

curl --location --request POST 'localhost:8080/column/save/all' \ --header 'Content-Type: application/json' \ --data-raw '[ { "daeId": "987-zyx", "labels": [ "Column", "Test", "Model" ] } ]'


5. Run cypher query

MATCH (n:Column) RETURN n, labels(n) LIMIT 25

that returns

╒═══════════════════╤═════════════════════════╕ │"n" │"labels(n)" │ ╞═══════════════════╪═════════════════════════╡ │{"daeId":"123-abc"}│["Column","Test","Model"]│ ├───────────────────┼─────────────────────────┤ │{"daeId":"987-zyx"}│["Column"] │ └───────────────────┴─────────────────────────┘

meistermeier commented 4 years ago

Thanks for reporting this. It seems to be a bug on our side. We will take care about this. Since we are in a migration process to the original Spring Data Neo4j and have to archive this repository, the issue also go migrated to: https://jira.spring.io/browse/DATAGRAPH-1339 Please watch or comment on the issue in Jira for further communication.