diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/entity/model/Attribute.kt b/search-service/src/main/kotlin/com/egm/stellio/search/entity/model/Attribute.kt index cb0a4950a..c8c28bb82 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/entity/model/Attribute.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/entity/model/Attribute.kt @@ -1,9 +1,11 @@ package com.egm.stellio.search.entity.model import com.egm.stellio.shared.model.ExpandedTerm +import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_LANGUAGE import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_LANGUAGEMAP_TERM import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_OBJECT import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_TYPE_TERM +import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_VALUE import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_VALUE_TERM import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_GEOPROPERTY_TYPE import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_GEOPROPERTY_VALUES @@ -19,6 +21,7 @@ import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_RELATIONSHIP_OBJECTS import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_RELATIONSHIP_TYPE import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_VOCABPROPERTY_TYPE import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_VOCABPROPERTY_VALUES +import com.egm.stellio.shared.util.JsonUtils.serializeObject import io.r2dbc.postgresql.codec.Json import org.springframework.data.annotation.Id import java.net.URI @@ -83,8 +86,8 @@ data class Attribute( VocabProperty -> NGSILD_VOCABPROPERTY_VALUES } - fun toNullCompactedRepresentation(): Map { - return when (this) { + fun toNullCompactedRepresentation(): Map = + when (this) { Property, GeoProperty, JsonProperty, VocabProperty -> mapOf( JSONLD_TYPE_TERM to this.name, @@ -101,6 +104,12 @@ data class Attribute( JSONLD_LANGUAGEMAP_TERM to mapOf(NGSILD_NONE_TERM to NGSILD_NULL) ) } - } + + fun toNullValue(): String = + when (this) { + Property, GeoProperty, JsonProperty, VocabProperty, Relationship -> NGSILD_NULL + LanguageProperty -> + serializeObject(listOf(mapOf(JSONLD_VALUE to NGSILD_NULL, JSONLD_LANGUAGE to NGSILD_NONE_TERM))) + } } } diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/entity/service/EntityAttributeService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/entity/service/EntityAttributeService.kt index 4f0fd56e8..99a751103 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/entity/service/EntityAttributeService.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/entity/service/EntityAttributeService.kt @@ -329,7 +329,7 @@ class EntityAttributeService( val attributesToDeleteWithPayload = attributesToDelete .map { Triple( - it.id, + it, deletedAt, JsonLdUtils.expandAttribute( it.attributeName, @@ -348,7 +348,7 @@ class EntityAttributeService( WHERE temporal_entity_attribute.id = new.uuid """.trimIndent() ) - .bind("values", attributesToDeleteWithPayload.map { arrayOf(it.first, it.second, it.third.toJson()) }) + .bind("values", attributesToDeleteWithPayload.map { arrayOf(it.first.id, it.second, it.third.toJson()) }) .allToMappedList { Triple( toUuid(it["id"]), @@ -357,9 +357,10 @@ class EntityAttributeService( ) } - attributesToDeleteWithPayload.forEach { (uuid, deletedAt, expandedAttributePayload) -> + attributesToDeleteWithPayload.forEach { (attribute, deletedAt, expandedAttributePayload) -> attributeInstanceService.addDeletedAttributeInstance( - attributeUuid = uuid, + attributeUuid = attribute.id, + value = attribute.attributeType.toNullValue(), deletedAt = deletedAt, attributeValues = expandedAttributePayload ).bind() diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/temporal/service/AttributeInstanceService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/temporal/service/AttributeInstanceService.kt index dfdef99f6..d718bd9ee 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/temporal/service/AttributeInstanceService.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/temporal/service/AttributeInstanceService.kt @@ -14,6 +14,7 @@ import com.egm.stellio.search.common.util.toJsonString import com.egm.stellio.search.common.util.toUuid import com.egm.stellio.search.common.util.toZonedDateTime import com.egm.stellio.search.entity.model.Attribute +import com.egm.stellio.search.entity.model.Attribute.AttributeValueType import com.egm.stellio.search.entity.model.AttributeMetadata import com.egm.stellio.search.entity.util.toAttributeMetadata import com.egm.stellio.search.temporal.model.AggregatedAttributeInstanceResult @@ -36,7 +37,6 @@ import com.egm.stellio.shared.model.OperationNotSupportedException import com.egm.stellio.shared.model.ResourceNotFoundException import com.egm.stellio.shared.model.toNgsiLdAttribute import com.egm.stellio.shared.util.INCONSISTENT_VALUES_IN_AGGREGATION_MESSAGE -import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_NULL import com.egm.stellio.shared.util.attributeOrInstanceNotFoundMessage import com.egm.stellio.shared.util.getSubFromSecurityContext import com.egm.stellio.shared.util.ngsiLdDateTime @@ -143,13 +143,14 @@ class AttributeInstanceService( @Transactional suspend fun addDeletedAttributeInstance( attributeUuid: UUID, + value: String, deletedAt: ZonedDateTime, attributeValues: Map> ): Either { val attributeInstance = AttributeInstance( attributeUuid = attributeUuid, timeAndProperty = deletedAt to DELETED_AT, - value = Triple(NGSILD_NULL, null, null), + value = Triple(value, null, null), payload = attributeValues, sub = getSubFromSecurityContext().getOrNull() ) @@ -267,9 +268,11 @@ class AttributeInstanceService( } else "SELECT temporal_entity_attribute, min(time) as start, max(time) as end, $allAggregates " } else { - val valueColumn = when (attributes[0].attributeValueType) { - Attribute.AttributeValueType.NUMBER -> "measured_value as value" - Attribute.AttributeValueType.GEOMETRY -> "public.ST_AsText(geo_value) as value" + val valueColumn = when { + // for deletedAt, the NGSI-LD Null representation is always stored as string in value column + temporalQuery.timeproperty == DELETED_AT -> "value" + attributes[0].attributeValueType == AttributeValueType.NUMBER -> "measured_value as value" + attributes[0].attributeValueType == AttributeValueType.GEOMETRY -> "public.ST_AsText(geo_value) as value" else -> "value" } val subColumn = diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/entity/service/EntityAttributeServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/entity/service/EntityAttributeServiceTests.kt index e87dd1faa..d56a430d7 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/entity/service/EntityAttributeServiceTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/entity/service/EntityAttributeServiceTests.kt @@ -18,6 +18,7 @@ import com.egm.stellio.shared.util.BEEHIVE_TYPE import com.egm.stellio.shared.util.INCOMING_PROPERTY import com.egm.stellio.shared.util.JsonLdUtils import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_DEFAULT_VOCAB +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_NULL import com.egm.stellio.shared.util.JsonLdUtils.expandAttribute import com.egm.stellio.shared.util.JsonUtils.serializeObject import com.egm.stellio.shared.util.NGSILD_TEST_CORE_CONTEXTS @@ -261,7 +262,9 @@ class EntityAttributeServiceTests : WithTimescaleContainer, WithKafkaContainer { val rawEntity = loadSampleData() coEvery { attributeInstanceService.create(any()) } returns Unit.right() - coEvery { attributeInstanceService.addDeletedAttributeInstance(any(), any(), any()) } returns Unit.right() + coEvery { + attributeInstanceService.addDeletedAttributeInstance(any(), any(), any(), any()) + } returns Unit.right() entityAttributeService.createAttributes(rawEntity, APIC_COMPOUND_CONTEXTS) .shouldSucceed() @@ -479,7 +482,9 @@ class EntityAttributeServiceTests : WithTimescaleContainer, WithKafkaContainer { val rawEntity = loadSampleData() coEvery { attributeInstanceService.create(any()) } returns Unit.right() - coEvery { attributeInstanceService.addDeletedAttributeInstance(any(), any(), any()) } returns Unit.right() + coEvery { + attributeInstanceService.addDeletedAttributeInstance(any(), any(), any(), any()) + } returns Unit.right() entityAttributeService.createAttributes(rawEntity, APIC_COMPOUND_CONTEXTS).shouldSucceed() @@ -503,6 +508,7 @@ class EntityAttributeServiceTests : WithTimescaleContainer, WithKafkaContainer { coVerify(exactly = 1) { attributeInstanceService.addDeletedAttributeInstance( any(), + NGSILD_NULL, createdAt, expandAttribute( INCOMING_PROPERTY, @@ -523,7 +529,9 @@ class EntityAttributeServiceTests : WithTimescaleContainer, WithKafkaContainer { val rawEntity = loadSampleData() coEvery { attributeInstanceService.create(any()) } returns Unit.right() - coEvery { attributeInstanceService.addDeletedAttributeInstance(any(), any(), any()) } returns Unit.right() + coEvery { + attributeInstanceService.addDeletedAttributeInstance(any(), any(), any(), any()) + } returns Unit.right() entityAttributeService.createAttributes(rawEntity, APIC_COMPOUND_CONTEXTS) .shouldSucceed() @@ -632,7 +640,9 @@ class EntityAttributeServiceTests : WithTimescaleContainer, WithKafkaContainer { val rawEntity = loadSampleData("beehive_two_temporal_properties.jsonld") coEvery { attributeInstanceService.create(any()) } returns Unit.right() - coEvery { attributeInstanceService.addDeletedAttributeInstance(any(), any(), any()) } returns Unit.right() + coEvery { + attributeInstanceService.addDeletedAttributeInstance(any(), any(), any(), any()) + } returns Unit.right() entityAttributeService.createAttributes(rawEntity, APIC_COMPOUND_CONTEXTS) @@ -648,6 +658,7 @@ class EntityAttributeServiceTests : WithTimescaleContainer, WithKafkaContainer { coVerify { attributeInstanceService.addDeletedAttributeInstance( any(), + NGSILD_NULL, deletedAt, expandAttribute( INCOMING_PROPERTY, @@ -673,7 +684,9 @@ class EntityAttributeServiceTests : WithTimescaleContainer, WithKafkaContainer { val rawEntity = loadSampleData("beehive_multi_instance_property.jsonld") coEvery { attributeInstanceService.create(any()) } returns Unit.right() - coEvery { attributeInstanceService.addDeletedAttributeInstance(any(), any(), any()) } returns Unit.right() + coEvery { + attributeInstanceService.addDeletedAttributeInstance(any(), any(), any(), any()) + } returns Unit.right() entityAttributeService.createAttributes(rawEntity, APIC_COMPOUND_CONTEXTS) @@ -686,7 +699,7 @@ class EntityAttributeServiceTests : WithTimescaleContainer, WithKafkaContainer { ).shouldSucceed() coVerify(exactly = 2) { - attributeInstanceService.addDeletedAttributeInstance(any(), any(), any()) + attributeInstanceService.addDeletedAttributeInstance(any(), any(), any(), any()) } entityAttributeService.getForEntityAndAttribute(beehiveTestCId, INCOMING_PROPERTY) @@ -698,7 +711,9 @@ class EntityAttributeServiceTests : WithTimescaleContainer, WithKafkaContainer { val rawEntity = loadSampleData("beehive.jsonld") coEvery { attributeInstanceService.create(any()) } returns Unit.right() - coEvery { attributeInstanceService.addDeletedAttributeInstance(any(), any(), any()) } returns Unit.right() + coEvery { + attributeInstanceService.addDeletedAttributeInstance(any(), any(), any(), any()) + } returns Unit.right() entityAttributeService.createAttributes(rawEntity, APIC_COMPOUND_CONTEXTS) @@ -708,7 +723,7 @@ class EntityAttributeServiceTests : WithTimescaleContainer, WithKafkaContainer { ).shouldSucceed() coVerify(exactly = 4) { - attributeInstanceService.addDeletedAttributeInstance(any(), any(), any()) + attributeInstanceService.addDeletedAttributeInstance(any(), any(), any(), any()) } entityAttributeService.getForEntityAndAttribute(beehiveTestCId, INCOMING_PROPERTY) diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/temporal/service/AttributeInstanceServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/temporal/service/AttributeInstanceServiceTests.kt index dbe5d4f33..3c991e043 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/temporal/service/AttributeInstanceServiceTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/temporal/service/AttributeInstanceServiceTests.kt @@ -761,6 +761,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer attributeInstanceService.addDeletedAttributeInstance( incomingAttribute.id, + NGSILD_NULL, deletedAt, attributeValues )