diff --git a/config-service-impl/src/main/java/org/hypertrace/config/service/ConfigServiceGrpcImpl.java b/config-service-impl/src/main/java/org/hypertrace/config/service/ConfigServiceGrpcImpl.java index d93c39df..7f1aded5 100644 --- a/config-service-impl/src/main/java/org/hypertrace/config/service/ConfigServiceGrpcImpl.java +++ b/config-service-impl/src/main/java/org/hypertrace/config/service/ConfigServiceGrpcImpl.java @@ -53,8 +53,7 @@ public void upsertConfig( try { ConfigResourceContext configResourceContext = getConfigResourceContext(request); UpsertedConfig upsertedConfig = - configStore.writeConfig( - configResourceContext, getUserId(), request.getConfig(), getUserEmail()); + configStore.writeConfig(configResourceContext, getUserId(), request, getUserEmail()); UpsertConfigResponse.Builder builder = UpsertConfigResponse.newBuilder(); builder.setConfig(request.getConfig()); builder.setCreationTimestamp(upsertedConfig.getCreationTimestamp()); diff --git a/config-service-impl/src/main/java/org/hypertrace/config/service/store/ConfigStore.java b/config-service-impl/src/main/java/org/hypertrace/config/service/store/ConfigStore.java index c5811fbe..68293ca4 100644 --- a/config-service-impl/src/main/java/org/hypertrace/config/service/store/ConfigStore.java +++ b/config-service-impl/src/main/java/org/hypertrace/config/service/store/ConfigStore.java @@ -10,6 +10,7 @@ import org.hypertrace.config.service.ConfigResourceContext; import org.hypertrace.config.service.v1.ContextSpecificConfig; import org.hypertrace.config.service.v1.UpsertAllConfigsResponse.UpsertedConfig; +import org.hypertrace.config.service.v1.UpsertConfigRequest; /** * Abstraction for the backend which stores and serves the configuration data for multiple @@ -25,7 +26,10 @@ public interface ConfigStore { * @return the config written to the store */ UpsertedConfig writeConfig( - ConfigResourceContext configResourceContext, String userId, Value config, String userEmail) + ConfigResourceContext configResourceContext, + String userId, + UpsertConfigRequest request, + String userEmail) throws IOException; /** diff --git a/config-service-impl/src/main/java/org/hypertrace/config/service/store/DocumentConfigStore.java b/config-service-impl/src/main/java/org/hypertrace/config/service/store/DocumentConfigStore.java index 8d96e8c1..0fd9e749 100644 --- a/config-service-impl/src/main/java/org/hypertrace/config/service/store/DocumentConfigStore.java +++ b/config-service-impl/src/main/java/org/hypertrace/config/service/store/DocumentConfigStore.java @@ -6,6 +6,7 @@ import static org.hypertrace.config.service.store.ConfigDocument.RESOURCE_NAMESPACE_FIELD_NAME; import static org.hypertrace.config.service.store.ConfigDocument.TENANT_ID_FIELD_NAME; import static org.hypertrace.config.service.store.ConfigDocument.VERSION_FIELD_NAME; +import static org.hypertrace.core.documentstore.Filter.Op.OR; import com.google.common.collect.Maps; import com.google.protobuf.Value; @@ -29,6 +30,7 @@ import org.hypertrace.config.service.ConfigServiceUtils; import org.hypertrace.config.service.v1.ContextSpecificConfig; import org.hypertrace.config.service.v1.UpsertAllConfigsResponse.UpsertedConfig; +import org.hypertrace.config.service.v1.UpsertConfigRequest; import org.hypertrace.core.documentstore.CloseableIterator; import org.hypertrace.core.documentstore.Collection; import org.hypertrace.core.documentstore.Datastore; @@ -37,6 +39,7 @@ import org.hypertrace.core.documentstore.Key; import org.hypertrace.core.documentstore.OrderBy; import org.hypertrace.core.documentstore.Query; +import org.hypertrace.core.documentstore.UpdateResult; /** Document store which stores and serves the configurations. */ @Slf4j @@ -46,34 +49,54 @@ public class DocumentConfigStore implements ConfigStore { private final Datastore datastore; private final Collection collection; + private final FilterBuilder filterBuilder; public DocumentConfigStore(Clock clock, Datastore datastore) { this.clock = clock; this.datastore = datastore; this.collection = this.datastore.getCollection(CONFIGURATIONS_COLLECTION); + this.filterBuilder = new FilterBuilder(); } @Override public UpsertedConfig writeConfig( ConfigResourceContext configResourceContext, String lastUpdatedUserId, - Value latestConfig, + UpsertConfigRequest request, String lastUpdatedUserEmail) throws IOException { Optional previousConfigDoc = getLatestVersionConfigDoc(configResourceContext); Optional optionalPreviousConfig = previousConfigDoc.flatMap(this::convertToContextSpecificConfig); + // reject create config with condition + if (optionalPreviousConfig.isEmpty() && request.hasUpsertCondition()) { + throw Status.FAILED_PRECONDITION + .withDescription("No upsert condition required for creating config") + .asRuntimeException(); + } + Key latestDocKey = new ConfigDocumentKey(configResourceContext); ConfigDocument latestConfigDocument = buildConfigDocument( configResourceContext, - latestConfig, + request.getConfig(), lastUpdatedUserId, previousConfigDoc, lastUpdatedUserEmail); - collection.upsert(latestDocKey, latestConfigDocument); + if (request.hasUpsertCondition()) { + Filter filter = this.filterBuilder.buildDocStoreFilter(request.getUpsertCondition()); + UpdateResult updateResult = collection.update(latestDocKey, latestConfigDocument, filter); + if (updateResult.getUpdatedCount() <= 0) { + throw Status.FAILED_PRECONDITION + .withDescription("Update failed because upsert condition did not match given record") + .asRuntimeException(); + } + } else { + collection.createOrReplace(latestDocKey, latestConfigDocument); + } + return optionalPreviousConfig .map(previousConfig -> this.buildUpsertResult(latestConfigDocument, previousConfig)) .orElseGet(() -> this.buildUpsertResult(latestConfigDocument)); @@ -262,7 +285,7 @@ private Filter buildConfigResourceContextsFilter( .map(this::getConfigResourceFieldContextFilter) .collect(Collectors.toUnmodifiableList()); Filter configResourceFieldContextFilter = new Filter(); - configResourceFieldContextFilter.setOp(Filter.Op.OR); + configResourceFieldContextFilter.setOp(OR); configResourceFieldContextFilter.setChildFilters(childFilters.toArray(Filter[]::new)); Filter tenantIdFilter = Filter.eq( diff --git a/config-service-impl/src/main/java/org/hypertrace/config/service/store/FilterBuilder.java b/config-service-impl/src/main/java/org/hypertrace/config/service/store/FilterBuilder.java new file mode 100644 index 00000000..51ed2807 --- /dev/null +++ b/config-service-impl/src/main/java/org/hypertrace/config/service/store/FilterBuilder.java @@ -0,0 +1,109 @@ +package org.hypertrace.config.service.store; + +import static org.hypertrace.config.service.store.ConfigDocument.CONFIG_FIELD_NAME; + +import io.grpc.Status; +import org.hypertrace.config.service.v1.LogicalFilter; +import org.hypertrace.config.service.v1.RelationalFilter; +import org.hypertrace.core.documentstore.Filter; + +class FilterBuilder { + + public Filter buildDocStoreFilter(org.hypertrace.config.service.v1.Filter filter) { + switch (filter.getTypeCase()) { + case LOGICAL_FILTER: + return evaluateCompositeExpression(filter.getLogicalFilter()); + case RELATIONAL_FILTER: + return evaluateLeafExpression(filter.getRelationalFilter()); + case TYPE_NOT_SET: + default: + throw Status.INVALID_ARGUMENT.withDescription("Filter type unset").asRuntimeException(); + } + } + + private Filter evaluateCompositeExpression(LogicalFilter logicalFilter) { + switch (logicalFilter.getOperator()) { + case LOGICAL_OPERATOR_OR: + { + Filter[] childFilters = + logicalFilter.getOperandsList().stream() + .map(this::buildDocStoreFilter) + .toArray(Filter[]::new); + Filter filter = new Filter(); + filter.setOp(Filter.Op.OR); + filter.setChildFilters(childFilters); + return filter; + } + case LOGICAL_OPERATOR_AND: + { + Filter[] childFilters = + logicalFilter.getOperandsList().stream() + .map(this::buildDocStoreFilter) + .toArray(Filter[]::new); + Filter filter = new Filter(); + filter.setOp(Filter.Op.AND); + filter.setChildFilters(childFilters); + return filter; + } + case LOGICAL_OPERATOR_UNSPECIFIED: + default: + throw Status.INVALID_ARGUMENT + .withDescription("Unknown logical operator while building filter") + .asRuntimeException(); + } + } + + private Filter evaluateLeafExpression(RelationalFilter relationalFilter) { + switch (relationalFilter.getOperator()) { + case RELATIONAL_OPERATOR_EQ: + return new Filter( + Filter.Op.EQ, + buildConfigFieldPath(relationalFilter.getConfigJsonPath()), + relationalFilter.getValue()); + case RELATIONAL_OPERATOR_NEQ: + return new Filter( + Filter.Op.NEQ, + buildConfigFieldPath(relationalFilter.getConfigJsonPath()), + relationalFilter.getValue()); + case RELATIONAL_OPERATOR_IN: + return new Filter( + Filter.Op.IN, + buildConfigFieldPath(relationalFilter.getConfigJsonPath()), + relationalFilter.getValue()); + case RELATIONAL_OPERATOR_NOT_IN: + return new Filter( + Filter.Op.NOT_IN, + buildConfigFieldPath(relationalFilter.getConfigJsonPath()), + relationalFilter.getValue()); + case RELATIONAL_OPERATOR_LT: + return new Filter( + Filter.Op.LT, + buildConfigFieldPath(relationalFilter.getConfigJsonPath()), + relationalFilter.getValue()); + case RELATIONAL_OPERATOR_GT: + return new Filter( + Filter.Op.GT, + buildConfigFieldPath(relationalFilter.getConfigJsonPath()), + relationalFilter.getValue()); + case RELATIONAL_OPERATOR_LTE: + return new Filter( + Filter.Op.LTE, + buildConfigFieldPath(relationalFilter.getConfigJsonPath()), + relationalFilter.getValue()); + case RELATIONAL_OPERATOR_GTE: + return new Filter( + Filter.Op.GTE, + buildConfigFieldPath(relationalFilter.getConfigJsonPath()), + relationalFilter.getValue()); + case UNRECOGNIZED: + default: + throw Status.INVALID_ARGUMENT + .withDescription("Unknown relational operator while building filter") + .asRuntimeException(); + } + } + + private String buildConfigFieldPath(String configJsonPath) { + return String.format("%s.%s", CONFIG_FIELD_NAME, configJsonPath); + } +} diff --git a/config-service-impl/src/test/java/org/hypertrace/config/service/ConfigServiceGrpcImplTest.java b/config-service-impl/src/test/java/org/hypertrace/config/service/ConfigServiceGrpcImplTest.java index cb8aea37..d224e3b3 100644 --- a/config-service-impl/src/test/java/org/hypertrace/config/service/ConfigServiceGrpcImplTest.java +++ b/config-service-impl/src/test/java/org/hypertrace/config/service/ConfigServiceGrpcImplTest.java @@ -65,12 +65,15 @@ class ConfigServiceGrpcImplTest { void upsertConfig() throws IOException { ConfigStore configStore = mock(ConfigStore.class); when(configStore.writeConfig( - any(ConfigResourceContext.class), anyString(), any(Value.class), anyString())) + any(ConfigResourceContext.class), + anyString(), + any(UpsertConfigRequest.class), + anyString())) .thenAnswer( invocation -> { - Value config = invocation.getArgument(2, Value.class); + UpsertConfigRequest request = invocation.getArgument(2, UpsertConfigRequest.class); return UpsertedConfig.newBuilder() - .setConfig(config) + .setConfig(request.getConfig()) .setCreationTimestamp(123) .setUpdateTimestamp(456) .build(); @@ -300,7 +303,11 @@ void deletingNonExistingConfigShouldThrowError() throws IOException { assertEquals(Status.NOT_FOUND, ((StatusException) throwable).getStatus()); verify(configStore, never()) - .writeConfig(any(ConfigResourceContext.class), anyString(), any(Value.class), anyString()); + .writeConfig( + any(ConfigResourceContext.class), + anyString(), + any(UpsertConfigRequest.class), + anyString()); verify(responseObserver, never()).onNext(any(DeleteConfigResponse.class)); verify(responseObserver, never()).onCompleted(); } diff --git a/config-service-impl/src/test/java/org/hypertrace/config/service/store/DocumentConfigStoreTest.java b/config-service-impl/src/test/java/org/hypertrace/config/service/store/DocumentConfigStoreTest.java index d4f26dbf..9fe2c406 100644 --- a/config-service-impl/src/test/java/org/hypertrace/config/service/store/DocumentConfigStoreTest.java +++ b/config-service-impl/src/test/java/org/hypertrace/config/service/store/DocumentConfigStoreTest.java @@ -9,6 +9,7 @@ import static org.hypertrace.config.service.TestUtils.getConfigResourceContext; import static org.hypertrace.config.service.store.DocumentConfigStore.CONFIGURATIONS_COLLECTION; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -17,6 +18,8 @@ import com.google.common.collect.ImmutableMap; import com.google.protobuf.Value; +import com.google.protobuf.util.Values; +import io.grpc.StatusRuntimeException; import java.io.IOException; import java.time.Clock; import java.util.Collections; @@ -28,13 +31,20 @@ import org.hypertrace.config.service.ConfigResource; import org.hypertrace.config.service.ConfigResourceContext; import org.hypertrace.config.service.v1.ContextSpecificConfig; +import org.hypertrace.config.service.v1.Filter; +import org.hypertrace.config.service.v1.LogicalFilter; +import org.hypertrace.config.service.v1.LogicalOperator; +import org.hypertrace.config.service.v1.RelationalFilter; +import org.hypertrace.config.service.v1.RelationalOperator; import org.hypertrace.config.service.v1.UpsertAllConfigsResponse.UpsertedConfig; +import org.hypertrace.config.service.v1.UpsertConfigRequest; import org.hypertrace.core.documentstore.CloseableIterator; import org.hypertrace.core.documentstore.Collection; import org.hypertrace.core.documentstore.Datastore; import org.hypertrace.core.documentstore.Document; import org.hypertrace.core.documentstore.Key; import org.hypertrace.core.documentstore.Query; +import org.hypertrace.core.documentstore.UpdateResult; import org.hypertrace.core.documentstore.metric.DocStoreMetricProvider; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -54,16 +64,49 @@ class DocumentConfigStoreTest { private static Collection collection; private DocumentConfigStore configStore; private Clock mockClock; + private FilterBuilder filterBuilder; @BeforeEach() void beforeEach() { collection = mock(Collection.class); this.mockClock = mock(Clock.class); + this.filterBuilder = mock(FilterBuilder.class); this.configStore = new DocumentConfigStore(mockClock, new MockDatastore()); } @Test - void writeConfig() throws IOException { + void WriteConfigForCreate() throws IOException { + CloseableIteratorImpl iterator = new CloseableIteratorImpl(List.of()); + when(collection.search(any(Query.class))).thenReturn(iterator); + UpsertConfigRequest request = mock(UpsertConfigRequest.class); + when(request.getConfig()).thenReturn(config1); + when(request.hasUpsertCondition()).thenReturn(false); + when(mockClock.millis()).thenReturn(TIMESTAMP1); + UpsertedConfig upsertedConfig = + configStore.writeConfig(configResourceContext, USER_ID, request, USER_EMAIL); + assertEquals(config1, upsertedConfig.getConfig()); + assertEquals(TIMESTAMP1, upsertedConfig.getCreationTimestamp()); + + ArgumentCaptor keyCaptor = ArgumentCaptor.forClass(Key.class); + ArgumentCaptor documentCaptor = ArgumentCaptor.forClass(Document.class); + verify(collection, times(1)).createOrReplace(keyCaptor.capture(), documentCaptor.capture()); + + Key key = keyCaptor.getValue(); + Document document = documentCaptor.getValue(); + assertEquals(new ConfigDocumentKey(configResourceContext), key); + assertEquals( + getConfigDocument(configResourceContext.getContext(), 1, config1, TIMESTAMP1, TIMESTAMP1), + document); + + // throw exception when passed an upsert condition + when(request.hasUpsertCondition()).thenReturn(true); + assertThrows( + StatusRuntimeException.class, + () -> configStore.writeConfig(configResourceContext, USER_ID, request, USER_EMAIL)); + } + + @Test + void WriteConfigForUpdateWithoutUpsertCondition() throws IOException { CloseableIteratorImpl iterator = new CloseableIteratorImpl( Collections.singletonList( @@ -74,15 +117,85 @@ void writeConfig() throws IOException { TIMESTAMP1, TIMESTAMP2))); when(collection.search(any(Query.class))).thenReturn(iterator); - + UpsertConfigRequest request = mock(UpsertConfigRequest.class); + when(request.getConfig()).thenReturn(config1); + when(request.hasUpsertCondition()).thenReturn(false); + when(mockClock.millis()).thenReturn(TIMESTAMP2); UpsertedConfig upsertedConfig = - configStore.writeConfig(configResourceContext, USER_ID, config1, USER_EMAIL); + configStore.writeConfig(configResourceContext, USER_ID, request, USER_EMAIL); assertEquals(config1, upsertedConfig.getConfig()); assertEquals(TIMESTAMP1, upsertedConfig.getCreationTimestamp()); + assertEquals(TIMESTAMP2, upsertedConfig.getUpdateTimestamp()); + + ArgumentCaptor keyCaptor = ArgumentCaptor.forClass(Key.class); + ArgumentCaptor documentCaptor = ArgumentCaptor.forClass(Document.class); + verify(collection, times(1)).createOrReplace(keyCaptor.capture(), documentCaptor.capture()); + + Key key = keyCaptor.getValue(); + Document document = documentCaptor.getValue(); + long newVersion = CONFIG_VERSION + 1; + assertEquals(new ConfigDocumentKey(configResourceContext), key); + assertEquals( + getConfigDocument( + configResourceContext.getContext(), newVersion, config1, TIMESTAMP1, TIMESTAMP2), + document); + } + + @Test + void WriteConfigForUpdateWithUpsertCondition() throws IOException { + CloseableIteratorImpl iterator = + new CloseableIteratorImpl( + Collections.singletonList( + getConfigDocument( + configResourceContext.getContext(), + CONFIG_VERSION, + config1, + TIMESTAMP1, + TIMESTAMP2))); + when(collection.search(any(Query.class))).thenReturn(iterator); + UpsertConfigRequest request = mock(UpsertConfigRequest.class); + UpdateResult updateResult = mock(UpdateResult.class); + when(request.hasUpsertCondition()).thenReturn(true); + when(request.getConfig()).thenReturn(config2); + when(collection.update(any(Key.class), any(), any())).thenReturn(updateResult); + when(updateResult.getUpdatedCount()).thenReturn(1L); + + Filter upsertCondition = + Filter.newBuilder() + .setLogicalFilter( + LogicalFilter.newBuilder() + .setOperator(LogicalOperator.LOGICAL_OPERATOR_AND) + .addOperands( + Filter.newBuilder() + .setRelationalFilter( + RelationalFilter.newBuilder() + .setConfigJsonPath("k1") + .setOperator(RelationalOperator.RELATIONAL_OPERATOR_EQ) + .setValue(Values.of(10)))) + .addOperands( + Filter.newBuilder() + .setRelationalFilter( + RelationalFilter.newBuilder() + .setConfigJsonPath("k2") + .setOperator(RelationalOperator.RELATIONAL_OPERATOR_EQ) + .setValue(Values.of("v2"))))) + .build(); + + org.hypertrace.core.documentstore.Filter docFilter = + new org.hypertrace.core.documentstore.Filter(); + when(request.getUpsertCondition()).thenReturn(upsertCondition); + when(filterBuilder.buildDocStoreFilter(upsertCondition)).thenReturn(docFilter); + UpsertedConfig upsertedConfig = + configStore.writeConfig(configResourceContext, USER_ID, request, USER_EMAIL); + assertEquals(config2, upsertedConfig.getConfig()); + assertEquals(TIMESTAMP1, upsertedConfig.getCreationTimestamp()); ArgumentCaptor keyCaptor = ArgumentCaptor.forClass(Key.class); ArgumentCaptor documentCaptor = ArgumentCaptor.forClass(Document.class); - verify(collection, times(1)).upsert(keyCaptor.capture(), documentCaptor.capture()); + ArgumentCaptor filterCaptor = + ArgumentCaptor.forClass(org.hypertrace.core.documentstore.Filter.class); + verify(collection, times(1)) + .update(keyCaptor.capture(), documentCaptor.capture(), filterCaptor.capture()); Key key = keyCaptor.getValue(); Document document = documentCaptor.getValue(); @@ -92,10 +205,16 @@ void writeConfig() throws IOException { getConfigDocument( configResourceContext.getContext(), newVersion, - config1, + config2, TIMESTAMP1, ((ConfigDocument) document).getUpdateTimestamp()), document); + + // failed upsert condition + when(updateResult.getUpdatedCount()).thenReturn(0L); + assertThrows( + StatusRuntimeException.class, + () -> configStore.writeConfig(configResourceContext, USER_ID, request, USER_EMAIL)); } @Test diff --git a/config-service-impl/src/test/java/org/hypertrace/config/service/store/FilterBuilderTest.java b/config-service-impl/src/test/java/org/hypertrace/config/service/store/FilterBuilderTest.java new file mode 100644 index 00000000..76e36281 --- /dev/null +++ b/config-service-impl/src/test/java/org/hypertrace/config/service/store/FilterBuilderTest.java @@ -0,0 +1,144 @@ +package org.hypertrace.config.service.store; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.google.protobuf.ListValue; +import com.google.protobuf.Value; +import com.google.protobuf.util.Values; +import java.util.List; +import org.hypertrace.config.service.v1.Filter; +import org.hypertrace.config.service.v1.LogicalFilter; +import org.hypertrace.config.service.v1.LogicalOperator; +import org.hypertrace.config.service.v1.RelationalFilter; +import org.hypertrace.config.service.v1.RelationalOperator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class FilterBuilderTest { + + private FilterBuilder filterBuilder; + + @BeforeEach + void init() { + this.filterBuilder = new FilterBuilder(); + } + + @Test + void buildDocStoreFilter1() { + Filter filter = + Filter.newBuilder() + .setRelationalFilter( + RelationalFilter.newBuilder() + .setConfigJsonPath("key1") + .setOperator(RelationalOperator.RELATIONAL_OPERATOR_EQ) + .setValue(Value.newBuilder().setStringValue("value1"))) + .build(); + + org.hypertrace.core.documentstore.Filter docFilter = + org.hypertrace.core.documentstore.Filter.eq("config.key1", Values.of("value1")); + assertEquals(docFilter.toString(), filterBuilder.buildDocStoreFilter(filter).toString()); + + Filter filter2 = + Filter.newBuilder() + .setRelationalFilter( + RelationalFilter.newBuilder() + .setConfigJsonPath("key2") + .setOperator(RelationalOperator.RELATIONAL_OPERATOR_EQ) + .setValue(Value.newBuilder().setNumberValue(300))) + .build(); + + org.hypertrace.core.documentstore.Filter docFilter2 = + org.hypertrace.core.documentstore.Filter.eq("config.key2", Values.of(300)); + assertEquals(docFilter2.toString(), filterBuilder.buildDocStoreFilter(filter2).toString()); + } + + @Test + void buildDocStoreFilter2() { + Filter filter = + Filter.newBuilder() + .setLogicalFilter( + LogicalFilter.newBuilder() + .setOperator(LogicalOperator.LOGICAL_OPERATOR_AND) + .addOperands( + Filter.newBuilder() + .setRelationalFilter( + RelationalFilter.newBuilder() + .setConfigJsonPath("key3") + .setOperator(RelationalOperator.RELATIONAL_OPERATOR_EQ) + .setValue(Values.of(true)))) + .addOperands( + Filter.newBuilder() + .setRelationalFilter( + RelationalFilter.newBuilder() + .setConfigJsonPath("key4") + .setOperator(RelationalOperator.RELATIONAL_OPERATOR_LTE) + .setValue(Values.of(100))))) + .build(); + + org.hypertrace.core.documentstore.Filter docFilter = + org.hypertrace.core.documentstore.Filter.eq("config.key3", Values.of(true)) + .and( + new org.hypertrace.core.documentstore.Filter( + org.hypertrace.core.documentstore.Filter.Op.LTE, + "config.key4", + Values.of(100))); + assertEquals(docFilter.toString(), filterBuilder.buildDocStoreFilter(filter).toString()); + } + + @Test + void buildDocStoreFilter3() { + Filter filter = + Filter.newBuilder() + .setLogicalFilter( + LogicalFilter.newBuilder() + .setOperator(LogicalOperator.LOGICAL_OPERATOR_OR) + .addOperands( + Filter.newBuilder() + .setLogicalFilter( + LogicalFilter.newBuilder() + .setOperator(LogicalOperator.LOGICAL_OPERATOR_AND) + .addOperands( + Filter.newBuilder() + .setRelationalFilter( + RelationalFilter.newBuilder() + .setConfigJsonPath("key5") + .setOperator( + RelationalOperator.RELATIONAL_OPERATOR_IN) + .setValue( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addValues( + Values.of("listValue1")) + .addValues( + Values.of("listValue2")))))) + .addOperands( + Filter.newBuilder() + .setRelationalFilter( + RelationalFilter.newBuilder() + .setConfigJsonPath("key6") + .setOperator( + RelationalOperator.RELATIONAL_OPERATOR_GTE) + .setValue(Values.of(100)))))) + .addOperands( + Filter.newBuilder() + .setRelationalFilter( + RelationalFilter.newBuilder() + .setConfigJsonPath("key7") + .setOperator(RelationalOperator.RELATIONAL_OPERATOR_EQ) + .setValue(Values.of("value7"))))) + .build(); + + org.hypertrace.core.documentstore.Filter docFilter = + new org.hypertrace.core.documentstore.Filter( + org.hypertrace.core.documentstore.Filter.Op.IN, + "config.key5", + Values.of(List.of(Values.of("listValue1"), Values.of("listValue2")))) + .and( + new org.hypertrace.core.documentstore.Filter( + org.hypertrace.core.documentstore.Filter.Op.GTE, "config.key6", Values.of(100))) + .or(org.hypertrace.core.documentstore.Filter.eq("config.key7", Values.of("value7"))); + + assertEquals(docFilter.toString(), filterBuilder.buildDocStoreFilter(filter).toString()); + } +}