JetBrains/xodus-dnq

The changes tracker sometimes misses a link deletion updates

sedovalx opened this issue · 1 comments

Here is the case:

class Group(entity: Entity) : XdEntity(entity) {
    companion object : XdNaturalEntityType<Group>() {
        fun new() = new {
            this.resource = Resource.new()
        }
    }

    var resource by xdLink1(Resource, onDelete = OnDeletePolicy.CASCADE, onTargetDelete = OnDeletePolicy.FAIL)

    override fun beforeFlush() {
        super.beforeFlush()

        if (hasChanges(Group::resource) && getOldValue(Group::resource) != null) {
            throw RuntimeException("Can't change resource")
        }
    }
}

class Service(entity: Entity) : XdEntity(entity) {
    companion object : XdNaturalEntityType<Service>() {
        val instance by xdSingleton()
    }

    val resources by xdChildren0_N(Resource::service)
}

class Resource(entity: Entity) : XdEntity(entity) {
    companion object : XdNaturalEntityType<Resource>() {
        fun new() = new {
            this.service = Service.instance
        }
    }

    var service: Service by xdParent(Service::resources)
}

class TestCase {
    @Test
    fun `should throw on changing resource`() {
        XdModel.registerNodes(Group, Resource, Service)
        val dir = Files.createTempDirectory("xodus")
        val store = StaticStoreContainer.init(dir.toFile(), "test")
        initMetaData(XdModel.hierarchy, store)

        val group1 = store.transactional { Group.new() }
        val group2 = store.transactional { Group.new() }

        assertThrows<RuntimeException> {
            store.transactional {
                group1.resource.delete()
                group1.resource = group2.resource
            }
        }
    }
}

The exceptions is never thrown in this case as there is no old value for the resource property. As for the changes tracker, here is what it contains on the moment of the beforeFlush execution:
image

The example above works as expected if I put the explicit resource deletion after the assignment:

group1.resource = group2.resource
group1.resource.delete()

Basically, the REMOVE change on the Group#resource link is not generated during the explicit deletion of the resource in case of the onTargetDelete = FAIL link constraint.