diff --git a/entity-service-impl/src/main/java/org/hypertrace/entity/data/service/EntityNormalizer.java b/entity-service-impl/src/main/java/org/hypertrace/entity/data/service/EntityNormalizer.java index 1b631c9d..b52d6b95 100644 --- a/entity-service-impl/src/main/java/org/hypertrace/entity/data/service/EntityNormalizer.java +++ b/entity-service-impl/src/main/java/org/hypertrace/entity/data/service/EntityNormalizer.java @@ -2,6 +2,8 @@ import static java.util.function.Predicate.not; +import io.grpc.Status; +import io.reactivex.rxjava3.core.Single; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -14,6 +16,7 @@ import org.hypertrace.entity.type.service.v1.AttributeType; public class EntityNormalizer { + private final EntityTypeClient entityTypeV2Client; private final EntityIdGenerator idGenerator; private final IdentifyingAttributeCache identifyingAttributeCache; @@ -31,8 +34,8 @@ public EntityNormalizer( * Normalizes the entity to a canonical, ready-to-upsert form * * @param receivedEntity - * @throws RuntimeException If entity can not be normalized * @return + * @throws RuntimeException If entity can not be normalized */ Entity normalize(String tenantId, Entity receivedEntity) { if (StringUtils.isEmpty(receivedEntity.getEntityType())) { @@ -100,7 +103,11 @@ private boolean isV2Type(String entityType) { return this.entityTypeV2Client .get(entityType) .map(unused -> true) - .onErrorReturnItem(false) + .onErrorResumeNext( + throwable -> + Status.NOT_FOUND.getCode().equals(Status.fromThrowable(throwable).getCode()) + ? Single.just(false) + : Single.error(throwable)) .blockingGet(); } diff --git a/entity-service-impl/src/main/java/org/hypertrace/entity/query/service/EntityQueryServiceImpl.java b/entity-service-impl/src/main/java/org/hypertrace/entity/query/service/EntityQueryServiceImpl.java index 295dd9df..371dbd3a 100644 --- a/entity-service-impl/src/main/java/org/hypertrace/entity/query/service/EntityQueryServiceImpl.java +++ b/entity-service-impl/src/main/java/org/hypertrace/entity/query/service/EntityQueryServiceImpl.java @@ -43,7 +43,6 @@ import org.hypertrace.core.documentstore.Document; import org.hypertrace.core.documentstore.JSONDocument; import org.hypertrace.core.documentstore.Key; -import org.hypertrace.core.documentstore.SingleValueKey; import org.hypertrace.core.documentstore.expression.impl.ConstantExpression; import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; import org.hypertrace.core.documentstore.expression.impl.LogicalExpression; @@ -155,7 +154,7 @@ public EntityQueryServiceImpl( entityChangeEventGenerator, new EntityAttributeChangeEvaluator(config, entityAttributeMapping), entityCounterMetricSender, - entityTypeChannel, + EntityTypeClient.builder(entityTypeChannel).build(), !config.hasPathOrNull(CHUNK_SIZE_CONFIG) ? DEFAULT_CHUNK_SIZE : config.getInt(CHUNK_SIZE_CONFIG), @@ -174,7 +173,7 @@ public EntityQueryServiceImpl( EntityChangeEventGenerator entityChangeEventGenerator, EntityAttributeChangeEvaluator entityAttributeChangeEvaluator, EntityCounterMetricSender entityCounterMetricSender, - Channel entityTypeChannel, + EntityTypeClient entityTypeClient, int chunkSize, int maxEntitiesToDelete, int maxStringLengthForUpdate) { @@ -186,7 +185,7 @@ public EntityQueryServiceImpl( entityAttributeChangeEvaluator, entityCounterMetricSender, new EntityFetcher(entitiesCollection, DOCUMENT_PARSER), - entityTypeChannel, + entityTypeClient, chunkSize, maxEntitiesToDelete, maxStringLengthForUpdate); @@ -200,7 +199,7 @@ public EntityQueryServiceImpl( EntityAttributeChangeEvaluator entityAttributeChangeEvaluator, EntityCounterMetricSender entityCounterMetricSender, EntityFetcher entityFetcher, - Channel entityTypeChannel, + EntityTypeClient entityTypeClient, int chunkSize, int maxEntitiesToDelete, int maxStringLengthForUpdate) { @@ -213,7 +212,6 @@ public EntityQueryServiceImpl( this.entityFetcher = entityFetcher; this.entityAttributeChangeEvaluator = entityAttributeChangeEvaluator; this.entityCounterMetricSender = entityCounterMetricSender; - EntityTypeClient entityTypeClient = EntityTypeClient.builder(entityTypeChannel).build(); IdentifyingAttributeCache identifyingAttributeCache = new IdentifyingAttributeCache(datastore); this.entityNormalizer = new EntityNormalizer(entityTypeClient, new EntityIdGenerator(), identifyingAttributeCache); @@ -457,14 +455,26 @@ public void bulkUpdateEntityArrayAttribute( responseObserver.onError(new ServiceException("Tenant id is missing in the request.")); return; } + + if (StringUtils.isBlank(request.getEntityType())) { + LOG.warn("Entity type is missing in bulk update entity array request"); + responseObserver.onError( + Status.INVALID_ARGUMENT + .withDescription("Entity type is missing in the request.") + .asException()); + return; + } + try { Set keys = request.getEntityIdsList().stream() - .map(entityId -> new SingleValueKey(tenantId, entityId)) + .map( + entityId -> + this.entityNormalizer.getEntityDocKey( + tenantId, request.getEntityType(), entityId)) .collect(Collectors.toCollection(LinkedHashSet::new)); String attributeId = request.getAttribute().getColumnName(); - String subDocPath = entityAttributeMapping .getDocStorePathByAttributeId(requestContext, attributeId) @@ -590,7 +600,8 @@ private void doBulkUpdate( continue; } entitiesUpdateMap.put( - new SingleValueKey(requestContext.getTenantId().orElseThrow(), entityId), + this.entityNormalizer.getEntityDocKey( + requestContext.getTenantId().orElseThrow(), entityType, entityId), transformedUpdateOperations); boolean shouldSendNotification = this.entityAttributeChangeEvaluator.shouldSendNotification( @@ -692,7 +703,7 @@ public void deleteEntities( return; } - if (existingEntities.size() == 0) { + if (existingEntities.isEmpty()) { LOG.debug("{}. No entities found to delete", request); responseObserver.onNext(DeleteEntitiesResponse.newBuilder().build()); responseObserver.onCompleted(); @@ -820,19 +831,17 @@ private BulkUpdateAllMatchingFilterResponse doBulkUpdate( queryConverter.convert(entityQueryRequest, requestContext); final List existingEntities = entityFetcher.query(updateFilterQuery); - final List keys = getKeysToUpdate(entityType, existingEntities); - final List updatedEntityResponses = buildUpdatedEntityResponse(keys); + final List updatedEntityResponses = + buildUpdatedEntityResponse(existingEntities); responseBuilder.addSummaries( UpdateSummary.newBuilder().addAllUpdatedEntities(updatedEntityResponses)); - - if (keys.isEmpty()) { + if (updatedEntityResponses.isEmpty()) { // Nothing to update LOG.debug("No entity found with filter {} for updating", update.getFilter()); continue; } final List updateOperations = update.getOperationsList(); - final List updates = convertUpdates(requestContext, updateOperations); final boolean shouldSendNotification = @@ -856,9 +865,9 @@ private BulkUpdateAllMatchingFilterResponse doBulkUpdate( return responseBuilder.build(); } - private List buildUpdatedEntityResponse(final List keys) { - return keys.stream() - .map(SingleValueKey::getValue) + private List buildUpdatedEntityResponse(final List entities) { + return entities.stream() + .map(Entity::getEntityId) .map(id -> UpdatedEntity.newBuilder().setId(id)) .map(UpdatedEntity.Builder::build) .collect(toUnmodifiableList()); @@ -871,8 +880,10 @@ private Converter getUpdateConverte new TypeLiteral>() {})); } - private List getKeysToUpdate( - final String entityType, final List existingEntities) { + private List getKeysToUpdate( + final RequestContext requestContext, + final String entityType, + final List existingEntities) { final Optional idAttribute = entityAttributeMapping.getIdentifierAttributeId(entityType); @@ -883,7 +894,10 @@ private List getKeysToUpdate( } return existingEntities.stream() - .map(entity -> new SingleValueKey(entity.getTenantId(), entity.getEntityId())) + .map( + entity -> + this.entityNormalizer.getEntityDocKey( + requestContext.getTenantId().orElseThrow(), entityType, entity.getEntityId())) .collect(toUnmodifiableList()); } diff --git a/entity-service-impl/src/main/java/org/hypertrace/entity/query/service/converter/UpdateConverter.java b/entity-service-impl/src/main/java/org/hypertrace/entity/query/service/converter/UpdateConverter.java index 6bdeac68..1f0282a0 100644 --- a/entity-service-impl/src/main/java/org/hypertrace/entity/query/service/converter/UpdateConverter.java +++ b/entity-service-impl/src/main/java/org/hypertrace/entity/query/service/converter/UpdateConverter.java @@ -8,6 +8,7 @@ import static org.hypertrace.core.attribute.service.v1.AttributeKind.TYPE_STRING; import static org.hypertrace.core.attribute.service.v1.AttributeKind.TYPE_STRING_ARRAY; import static org.hypertrace.core.attribute.service.v1.AttributeKind.TYPE_STRING_MAP; +import static org.hypertrace.core.attribute.service.v1.AttributeKind.TYPE_TIMESTAMP; import static org.hypertrace.core.documentstore.model.subdoc.UpdateOperator.ADD_TO_LIST_IF_ABSENT; import static org.hypertrace.core.documentstore.model.subdoc.UpdateOperator.REMOVE_ALL_FROM_LIST; import static org.hypertrace.core.documentstore.model.subdoc.UpdateOperator.SET; @@ -28,8 +29,11 @@ import static org.hypertrace.entity.query.service.v1.ValueType.STRING; import static org.hypertrace.entity.query.service.v1.ValueType.STRING_ARRAY; import static org.hypertrace.entity.query.service.v1.ValueType.STRING_MAP; +import static org.hypertrace.entity.query.service.v1.ValueType.TIMESTAMP; import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; import java.util.Map; import java.util.Set; import javax.inject.Inject; @@ -49,19 +53,23 @@ @AllArgsConstructor(onConstructor_ = {@Inject}) public class UpdateConverter implements Converter { + private static final Joiner DOT_JOINER = Joiner.on("."); - private static final Map VALUE_TYPE_TO_ATTRIBUTE_KIND_MAP = - Map.ofEntries( - entry(STRING, TYPE_STRING), - entry(LONG, TYPE_INT64), - entry(INT, TYPE_INT64), - entry(FLOAT, TYPE_DOUBLE), - entry(DOUBLE, TYPE_DOUBLE), - entry(BYTES, TYPE_BYTES), - entry(BOOL, TYPE_BOOL), - entry(STRING_ARRAY, TYPE_STRING_ARRAY), - entry(STRING_MAP, TYPE_STRING_MAP)); + private static final Multimap VALUE_TYPE_TO_ATTRIBUTE_KIND_MULTI_MAP = + new ImmutableMultimap.Builder() + .put(entry(STRING, TYPE_STRING)) + .put(entry(LONG, TYPE_INT64)) + .put(entry(INT, TYPE_INT64)) + .put(entry(FLOAT, TYPE_DOUBLE)) + .put(entry(DOUBLE, TYPE_DOUBLE)) + .put(entry(BYTES, TYPE_BYTES)) + .put(entry(BOOL, TYPE_BOOL)) + .put(entry(LONG, TYPE_TIMESTAMP)) + .put(entry(TIMESTAMP, TYPE_TIMESTAMP)) + .put(entry(STRING_ARRAY, TYPE_STRING_ARRAY)) + .put(entry(STRING_MAP, TYPE_STRING_MAP)) + .build(); private static final Map OPERATOR_MAP = Map.ofEntries( @@ -139,7 +147,7 @@ private void validateDataType( return; } - if (!attributeKind.equals(VALUE_TYPE_TO_ATTRIBUTE_KIND_MAP.get(valueType))) { + if (!VALUE_TYPE_TO_ATTRIBUTE_KIND_MULTI_MAP.get(valueType).contains(attributeKind)) { throw new ConversionException( String.format( "Mismatching value type (%s) for attribute of type %s", valueType, attributeKind)); diff --git a/entity-service-impl/src/test/java/org/hypertrace/entity/data/service/EntityNormalizerTest.java b/entity-service-impl/src/test/java/org/hypertrace/entity/data/service/EntityNormalizerTest.java index da37b059..a8699fad 100644 --- a/entity-service-impl/src/test/java/org/hypertrace/entity/data/service/EntityNormalizerTest.java +++ b/entity-service-impl/src/test/java/org/hypertrace/entity/data/service/EntityNormalizerTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.when; +import io.grpc.Status; import io.reactivex.rxjava3.core.Single; import java.util.List; import java.util.Map; @@ -26,6 +27,7 @@ @ExtendWith(MockitoExtension.class) class EntityNormalizerTest { + private static final String TENANT_ID = "tenant"; private static final String V1_ENTITY_TYPE = "v1-entity"; private static final String V2_ENTITY_TYPE = "v2-entity"; @@ -59,7 +61,7 @@ void throwsOnV1EntityTypeMissingIdAttr() { when(this.mockIdAttrCache.getIdentifyingAttributes(TENANT_ID, V1_ENTITY_TYPE)) .thenReturn(List.of(V1_ID_ATTR)); when(this.mockEntityTypeClient.get(V1_ENTITY_TYPE)) - .thenReturn(Single.error(new RuntimeException())); + .thenReturn(Single.error(Status.NOT_FOUND.asRuntimeException())); Entity inputEntity = Entity.newBuilder().setEntityType(V1_ENTITY_TYPE).build(); Exception exception = @@ -67,7 +69,8 @@ void throwsOnV1EntityTypeMissingIdAttr() { IllegalArgumentException.class, () -> this.normalizer.normalize(TENANT_ID, inputEntity)); assertEquals( - "Received and expected identifying attributes differ. Received: [] . Expected: [required-attr]", + "Received and expected identifying attributes differ. Received: [] . Expected: " + + "[required-attr]", exception.getMessage()); } @@ -80,7 +83,7 @@ void normalizesV1EntityTypeWithExtraIdAttr() { when(this.mockIdAttrCache.getIdentifyingAttributes(TENANT_ID, V1_ENTITY_TYPE)) .thenReturn(List.of(V1_ID_ATTR)); when(this.mockEntityTypeClient.get(V1_ENTITY_TYPE)) - .thenReturn(Single.error(new RuntimeException())); + .thenReturn(Single.error(Status.NOT_FOUND.asRuntimeException())); Entity inputEntity = Entity.newBuilder() .setEntityType(V1_ENTITY_TYPE) @@ -109,6 +112,17 @@ void throwsOnV2EntityMissingId() { assertEquals("Entity ID is empty", exception.getMessage()); } + @Test + void throwsIfEntityTypeClientIsDown() { + when(this.mockEntityTypeClient.get(V2_ENTITY_TYPE)) + .thenReturn(Single.error(new RuntimeException())); + Entity inputEntity = Entity.newBuilder().setEntityType(V2_ENTITY_TYPE).build(); + + Exception exception = + assertThrows( + RuntimeException.class, () -> this.normalizer.normalize(TENANT_ID, inputEntity)); + } + @Test void normalizesV1EntityWithAttrs() { Map valueMap = buildValueMap(Map.of(V1_ID_ATTR.getName(), "foo-value")); @@ -117,7 +131,7 @@ void normalizesV1EntityWithAttrs() { when(this.mockIdAttrCache.getIdentifyingAttributes(TENANT_ID, V1_ENTITY_TYPE)) .thenReturn(List.of(V1_ID_ATTR)); when(this.mockEntityTypeClient.get(V1_ENTITY_TYPE)) - .thenReturn(Single.error(new RuntimeException())); + .thenReturn(Single.error(Status.NOT_FOUND.asRuntimeException())); Entity inputEntity = Entity.newBuilder() .setEntityType(V1_ENTITY_TYPE) @@ -162,7 +176,7 @@ void returnsV2TypeKeyForV2Entity() { @Test void returnsSimpleKeyForV1Entity() { when(this.mockEntityTypeClient.get(V1_ENTITY_TYPE)) - .thenReturn(Single.error(new RuntimeException())); + .thenReturn(Single.error(Status.NOT_FOUND.asRuntimeException())); // Getting a key for a v1 entity when provided with direct id assertEquals( diff --git a/entity-service-impl/src/test/java/org/hypertrace/entity/query/service/EntityQueryServiceImplTest.java b/entity-service-impl/src/test/java/org/hypertrace/entity/query/service/EntityQueryServiceImplTest.java index 66b903d5..e014bae6 100644 --- a/entity-service-impl/src/test/java/org/hypertrace/entity/query/service/EntityQueryServiceImplTest.java +++ b/entity-service-impl/src/test/java/org/hypertrace/entity/query/service/EntityQueryServiceImplTest.java @@ -18,9 +18,10 @@ import static org.mockito.quality.Strictness.LENIENT; import com.google.protobuf.util.JsonFormat; -import io.grpc.Channel; import io.grpc.Context; +import io.grpc.Status; import io.grpc.stub.StreamObserver; +import io.reactivex.rxjava3.core.Single; import java.io.IOException; import java.util.Collections; import java.util.LinkedHashSet; @@ -84,7 +85,9 @@ import org.hypertrace.entity.query.service.v1.ValueType; import org.hypertrace.entity.service.change.event.api.EntityChangeEventGenerator; import org.hypertrace.entity.service.util.DocStoreJsonFormat; +import org.hypertrace.entity.type.service.rxclient.EntityTypeClient; import org.hypertrace.entity.v1.entitytype.EntityType; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -109,6 +112,7 @@ public class EntityQueryServiceImplTest { @Mock EntityChangeEventGenerator entityChangeEventGenerator; @Mock EntityFetcher entityFetcher; @Mock EntityAttributeChangeEvaluator entityAttributeChangeEvaluator; + @Mock EntityTypeClient entityTypeClient; private static final String API_ID = "API.id"; private static final String ATTRIBUTE_ID1 = "Entity.id"; @@ -119,6 +123,12 @@ public class EntityQueryServiceImplTest { private static final String ATTRIBUTE_ID3 = "Entity.labels"; private static final String EDS_COLUMN_NAME3 = "attributes.labels"; + @BeforeEach + void setup() { + when(this.entityTypeClient.get(TEST_ENTITY_TYPE)) + .thenReturn(Single.error(Status.NOT_FOUND.asRuntimeException())); + } + @Test public void testUpdate_noTenantId() throws Exception { StreamObserver mockResponseObserver = mock(StreamObserver.class); @@ -136,7 +146,7 @@ public void testUpdate_noTenantId() throws Exception { entityAttributeChangeEvaluator, new EntityCounterMetricSender(), entityFetcher, - mock(Channel.class), + entityTypeClient, 1, 1000, 5000); @@ -167,7 +177,7 @@ public void testUpdate_noEntityType() throws Exception { entityAttributeChangeEvaluator, new EntityCounterMetricSender(), entityFetcher, - mock(Channel.class), + entityTypeClient, 1, 1000, 5000); @@ -199,7 +209,7 @@ public void testUpdate_noEntityId() throws Exception { entityAttributeChangeEvaluator, new EntityCounterMetricSender(), entityFetcher, - mock(Channel.class), + entityTypeClient, 1, 1000, 5000); @@ -233,7 +243,7 @@ public void testUpdate_noOperation() throws Exception { entityAttributeChangeEvaluator, new EntityCounterMetricSender(), entityFetcher, - mock(Channel.class), + entityTypeClient, 1, 1000, 5000); @@ -296,7 +306,7 @@ public void testUpdate_success() throws Exception { entityAttributeChangeEvaluator, new EntityCounterMetricSender(), entityFetcher, - mock(Channel.class), + entityTypeClient, 1, 1000, 5000); @@ -334,7 +344,7 @@ public void testBulkUpdate_noTenantId() throws Exception { entityAttributeChangeEvaluator, new EntityCounterMetricSender(), entityFetcher, - mock(Channel.class), + entityTypeClient, 1, 1000, 5000); @@ -366,7 +376,7 @@ public void testBulkUpdate_noEntityType() throws Exception { entityAttributeChangeEvaluator, new EntityCounterMetricSender(), entityFetcher, - mock(Channel.class), + entityTypeClient, 1, 1000, 5000); @@ -398,7 +408,7 @@ public void testBulkUpdate_noEntities() throws Exception { entityAttributeChangeEvaluator, new EntityCounterMetricSender(), entityFetcher, - mock(Channel.class), + entityTypeClient, 1, 1000, 5000); @@ -439,7 +449,7 @@ public void testBulkUpdate_entitiesWithNoUpdateOperations() throws Exception { entityAttributeChangeEvaluator, new EntityCounterMetricSender(), entityFetcher, - mock(Channel.class), + entityTypeClient, 1, 1000, 5000); @@ -486,7 +496,7 @@ public void testBulkUpdate_success() throws Exception { entityAttributeChangeEvaluator, new EntityCounterMetricSender(), entityFetcher, - mock(Channel.class), + entityTypeClient, 1, 1000, 5000); @@ -527,7 +537,7 @@ void testBulkUpdateAllMatchingFilter_noTenantId() throws Exception { entityAttributeChangeEvaluator, new EntityCounterMetricSender(), entityFetcher, - mock(Channel.class), + entityTypeClient, 1, 1000, 5000); @@ -561,7 +571,7 @@ void testBulkUpdateAllMatchingFilter_noEntityType() throws Exception { entityAttributeChangeEvaluator, new EntityCounterMetricSender(), entityFetcher, - mock(Channel.class), + entityTypeClient, 1, 1000, 5000); @@ -599,7 +609,7 @@ void testBulkUpdateAllMatchingFilter_entitiesWithNoUpdateOperations() throws Exc entityAttributeChangeEvaluator, new EntityCounterMetricSender(), entityFetcher, - mock(Channel.class), + entityTypeClient, 1, 1000, 5000); @@ -699,7 +709,7 @@ void testBulkUpdateAllMatchingFilter_success() throws Exception { entityAttributeChangeEvaluator, new EntityCounterMetricSender(), entityFetcher, - mock(Channel.class), + entityTypeClient, 1, 1000, 5000); @@ -739,7 +749,7 @@ public void testExecute_noTenantId() throws Exception { entityAttributeChangeEvaluator, new EntityCounterMetricSender(), entityFetcher, - mock(Channel.class), + entityTypeClient, 1, 1000, 5000); @@ -816,7 +826,7 @@ public void testExecute_success() throws Exception { entityChangeEventGenerator, entityAttributeChangeEvaluator, new EntityCounterMetricSender(), - mock(Channel.class), + entityTypeClient, 1, 1000, 5000); @@ -908,7 +918,7 @@ public void testExecute_success_chunksize_2() throws Exception { entityChangeEventGenerator, entityAttributeChangeEvaluator, new EntityCounterMetricSender(), - mock(Channel.class), + entityTypeClient, 2, 1000, 5000); @@ -963,7 +973,7 @@ void testBulkUpdateEntityArrayAttribute() throws Exception { entityAttributeChangeEvaluator, new EntityCounterMetricSender(), entityFetcher, - mock(Channel.class), + entityTypeClient, 1, 1000, 5000); @@ -1037,7 +1047,7 @@ public void testDeleteEntities() throws IOException { entityAttributeChangeEvaluator, new EntityCounterMetricSender(), entityFetcher, - mock(Channel.class), + entityTypeClient, 100, 1000, 5000); @@ -1111,7 +1121,7 @@ public void testExecute_withAliases() throws Exception { entityAttributeChangeEvaluator, new EntityCounterMetricSender(), entityFetcher, - mock(Channel.class), + entityTypeClient, 100, 1000, 5000); @@ -1157,7 +1167,7 @@ public void test_buildTotalQuery() throws Exception { entityAttributeChangeEvaluator, new EntityCounterMetricSender(), entityFetcher, - mock(Channel.class), + entityTypeClient, 1, 1000, 5000); @@ -1209,7 +1219,7 @@ public void test_sendCorrectTotalResponse() throws Exception { entityAttributeChangeEvaluator, new EntityCounterMetricSender(), entityFetcher, - mock(Channel.class), + entityTypeClient, 1, 1000, 5000); diff --git a/entity-service-impl/src/test/java/org/hypertrace/entity/query/service/converter/UpdateConverterTest.java b/entity-service-impl/src/test/java/org/hypertrace/entity/query/service/converter/UpdateConverterTest.java index 1572747b..8fdbe958 100644 --- a/entity-service-impl/src/test/java/org/hypertrace/entity/query/service/converter/UpdateConverterTest.java +++ b/entity-service-impl/src/test/java/org/hypertrace/entity/query/service/converter/UpdateConverterTest.java @@ -8,6 +8,7 @@ import static org.hypertrace.core.attribute.service.v1.AttributeKind.TYPE_INT64; import static org.hypertrace.core.attribute.service.v1.AttributeKind.TYPE_STRING; import static org.hypertrace.core.attribute.service.v1.AttributeKind.TYPE_STRING_ARRAY; +import static org.hypertrace.core.attribute.service.v1.AttributeKind.TYPE_TIMESTAMP; import static org.hypertrace.entity.query.service.v1.AttributeUpdateOperation.AttributeUpdateOperator.ATTRIBUTE_UPDATE_OPERATION_UNSPECIFIED; import static org.hypertrace.entity.query.service.v1.AttributeUpdateOperation.AttributeUpdateOperator.ATTRIBUTE_UPDATE_OPERATOR_ADD_TO_LIST_IF_ABSENT; import static org.hypertrace.entity.query.service.v1.AttributeUpdateOperation.AttributeUpdateOperator.ATTRIBUTE_UPDATE_OPERATOR_REMOVE_FROM_LIST; @@ -74,6 +75,30 @@ void setUp() { new UpdateConverter(mockEntityAttributeMapping, new ValueHelper(new ValueOneOfAccessor())); } + @Test + void testConvert_updateTimestamp_withValueTypeLong() throws Exception { + final AttributeUpdateOperation operation = + AttributeUpdateOperation.newBuilder() + .setAttribute(ColumnIdentifier.newBuilder().setColumnName("columnName")) + .setOperator(ATTRIBUTE_UPDATE_OPERATOR_SET) + .setValue( + LiteralConstant.newBuilder() + .setValue(Value.newBuilder().setValueType(LONG).setLong(123))) + .build(); + final RequestContext requestContext = new RequestContext(); + final SubDocumentUpdate expectedResult = + SubDocumentUpdate.of( + "attributes.subDocPath", + SubDocumentValue.of(new JSONDocument("{\"value\":{\"long\":123}}"))); + when(mockEntityAttributeMapping.getDocStorePathByAttributeId(requestContext, "columnName")) + .thenReturn(Optional.of("attributes.subDocPath")); + when(mockEntityAttributeMapping.getAttributeKind(requestContext, "columnName")) + .thenReturn(Optional.of(TYPE_TIMESTAMP)); + final SubDocumentUpdate result = updateConverter.convert(operation, requestContext); + + assertEquals(expectedResult, result); + } + @Test void testConvert() throws Exception { final AttributeUpdateOperation operation = diff --git a/entity-type-service-rx-client/src/main/java/org/hypertrace/entity/type/service/rxclient/EntityTypeCachingClient.java b/entity-type-service-rx-client/src/main/java/org/hypertrace/entity/type/service/rxclient/EntityTypeCachingClient.java index 8126b339..c9ea7708 100644 --- a/entity-type-service-rx-client/src/main/java/org/hypertrace/entity/type/service/rxclient/EntityTypeCachingClient.java +++ b/entity-type-service-rx-client/src/main/java/org/hypertrace/entity/type/service/rxclient/EntityTypeCachingClient.java @@ -5,12 +5,13 @@ import com.google.common.cache.LoadingCache; import io.grpc.CallCredentials; import io.grpc.Channel; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.core.Single; import java.time.Duration; import java.util.Collections; import java.util.Map; -import java.util.NoSuchElementException; import java.util.Optional; import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; @@ -67,8 +68,9 @@ private Single> getOrInvalidate(TenantBasedCacheKey key) return this.cache.getUnchecked(key).doOnError(x -> this.cache.invalidate(key)); } - private NoSuchElementException buildErrorForMissingType(String name) { - return new NoSuchElementException( - String.format("No entity type available for name '%s'", name)); + private StatusRuntimeException buildErrorForMissingType(String name) { + return Status.NOT_FOUND + .withDescription("No entity type found with name: " + name) + .asRuntimeException(); } } diff --git a/entity-type-service-rx-client/src/test/java/org/hypertrace/entity/type/service/rxclient/EntityTypeCachingClientTest.java b/entity-type-service-rx-client/src/test/java/org/hypertrace/entity/type/service/rxclient/EntityTypeCachingClientTest.java index 7b985e07..bd058903 100644 --- a/entity-type-service-rx-client/src/test/java/org/hypertrace/entity/type/service/rxclient/EntityTypeCachingClientTest.java +++ b/entity-type-service-rx-client/src/test/java/org/hypertrace/entity/type/service/rxclient/EntityTypeCachingClientTest.java @@ -23,7 +23,6 @@ import io.reactivex.rxjava3.core.Single; import java.io.IOException; import java.util.List; -import java.util.NoSuchElementException; import java.util.Optional; import org.hypertrace.core.grpcutils.context.RequestContext; import org.hypertrace.entity.type.service.v2.EntityType; @@ -119,7 +118,7 @@ void cachesConsecutiveGetAllCallsInSameContext() throws Exception { @Test void throwsErrorIfNoKeyMatch() { assertThrows( - NoSuchElementException.class, + StatusRuntimeException.class, () -> this.grpcTestContext.run(() -> this.typeClient.get("third").blockingGet())); }