Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
import java.util.List;
import java.util.Optional;
import org.hypertrace.core.query.service.api.AttributeExpression;
import org.hypertrace.core.query.service.api.ColumnIdentifier;
import org.hypertrace.core.query.service.api.Expression;
Expand Down Expand Up @@ -110,15 +111,23 @@ protected Single<OrderByExpression> transformOrderBy(OrderByExpression orderBy)
}

protected Single<Filter> transformFilter(Filter filter) {
return transformFilterInternal(filter)
.map(filterOptional -> filterOptional.orElseGet(Filter::getDefaultInstance));
}

private Single<Optional<Filter>> transformFilterInternal(Filter filter) {
if (filter.equals(Filter.getDefaultInstance())) {
return Single.just(filter);
return Single.just(Optional.empty());
}

Single<Expression> lhsSingle = this.transformExpression(filter.getLhs());
Single<Expression> rhsSingle = this.transformExpression(filter.getRhs());
// remove defaults from child filters
Single<List<Filter>> childFilterListSingle =
Observable.fromIterable(filter.getChildFilterList())
.concatMapSingle(this::transformFilter)
.concatMapSingle(this::transformFilterInternal)
.filter(Optional::isPresent)
.map(Optional::get)
.toList();
return zip(
lhsSingle,
Expand All @@ -143,7 +152,7 @@ private Single<List<OrderByExpression>> transformOrderByList(
* This doesn't change any functional behavior, but omits fields that aren't needed, shrinking the
* object and keeping it equivalent to the source object for equality checks.
*/
private Filter rebuildFilterOmittingDefaults(
private Optional<Filter> rebuildFilterOmittingDefaults(
Filter original, Expression lhs, Expression rhs, List<Filter> childFilters) {
Filter.Builder builder = original.toBuilder();

Expand All @@ -159,7 +168,7 @@ private Filter rebuildFilterOmittingDefaults(
builder.setRhs(rhs);
}

return builder.clearChildFilter().addAllChildFilter(childFilters).build();
return Optional.of(builder.clearChildFilter().addAllChildFilter(childFilters).build());
}

private void debugLogIfRequestTransformed(QueryRequest original, QueryRequest transformed) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.hypertrace.core.query.service;

import com.google.protobuf.GeneratedMessageV3;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.util.JsonFormat;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.stream.Collectors;
import org.hypertrace.core.grpcutils.context.RequestContext;

public abstract class AbstractServiceTest<TQueryServiceRequestType extends GeneratedMessageV3> {

private static final String QUERY_SERVICE_TEST_REQUESTS_DIR = "test-requests";

protected TQueryServiceRequestType readQueryServiceRequest(String fileName) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is Tstands for? What about just having QueryServiceRequestType Is it how it was named in proto?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String resourceFileName = createResourceFileName(QUERY_SERVICE_TEST_REQUESTS_DIR, fileName);
String requestJsonStr = readResourceFileAsString(resourceFileName);

GeneratedMessageV3.Builder requestBuilder = getQueryServiceRequestBuilder();
try {
JsonFormat.parser().merge(requestJsonStr, requestBuilder);
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}

return (TQueryServiceRequestType) requestBuilder.build();
}

private static Reader readResourceFile(String fileName) {
InputStream in = RequestContext.class.getClassLoader().getResourceAsStream(fileName);
return new BufferedReader(new InputStreamReader(in));
}

private String readResourceFileAsString(String fileName) {
return ((BufferedReader) readResourceFile(fileName)).lines().collect(Collectors.joining());
}

private String createResourceFileName(String filesBaseDir, String fileName) {
return String.format("%s/%s/%s.json", filesBaseDir, getTestSuiteName(), fileName);
}

protected abstract String getTestSuiteName();

protected abstract GeneratedMessageV3.Builder getQueryServiceRequestBuilder();
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public void testQueryServiceImplConfigParser() {
assertEquals("query-service", appConfig.getString("service.name"));
assertEquals(8091, appConfig.getInt("service.admin.port"));
assertEquals(8090, appConfig.getInt("service.port"));
assertEquals(10, queryServiceConfig.getQueryRequestHandlersConfigs().size());
assertEquals(11, queryServiceConfig.getQueryRequestHandlersConfigs().size());
assertEquals(3, queryServiceConfig.getRequestHandlerClientConfigs().size());

RequestHandlerConfig handler0 = queryServiceConfig.getQueryRequestHandlersConfigs().get(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import static org.hypertrace.core.query.service.QueryRequestUtil.createContainsKeyFilter;
import static org.hypertrace.core.query.service.QueryRequestUtil.createStringLiteralValueExpression;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.List;
Expand Down Expand Up @@ -93,6 +94,55 @@ void transQueryWithComplexAttributeExpression_MultipleFilter() {
.contains(createContainsKeyFilter(spanTags2.getAttributeExpression())));
}

@Test
void transQueryWithEmptyFilter() {
Filter emptyFilter = Filter.getDefaultInstance();
QueryRequest originalRequest = QueryRequest.newBuilder().setFilter(emptyFilter).build();

QueryRequest expectedTransform =
this.transformation.transform(originalRequest, mockTransformationContext).blockingGet();
assertFalse(expectedTransform.hasFilter());
}

@Test
void transQueryWithComplexAttributeExpression_EmptyFilter() {
Expression spanTags1 = createComplexAttributeExpression("Span.tags", "FLAGS").build();
Expression spanTags2 = createComplexAttributeExpression("Span.tags", "span.kind").build();

Filter childFilter1 =
Filter.newBuilder()
.setLhs(spanTags1)
.setOperator(Operator.EQ)
.setRhs(createStringLiteralValueExpression("0"))
.build();
Filter childFilter2 =
Filter.newBuilder()
.setLhs(spanTags2)
.setOperator(Operator.EQ)
.setRhs(createStringLiteralValueExpression("server"))
.build();
Filter emptyFilter = Filter.getDefaultInstance();

Filter.Builder filter =
createCompositeFilter(Operator.AND, childFilter1, childFilter2, emptyFilter);
QueryRequest originalRequest = QueryRequest.newBuilder().setFilter(filter).build();

QueryRequest expectedTransform =
this.transformation.transform(originalRequest, mockTransformationContext).blockingGet();
List<Filter> childFilterList = expectedTransform.getFilter().getChildFilterList();

assertTrue(
childFilterList
.get(0)
.getChildFilterList()
.contains(createContainsKeyFilter(spanTags1.getAttributeExpression())));
assertTrue(
childFilterList
.get(1)
.getChildFilterList()
.contains(createContainsKeyFilter(spanTags2.getAttributeExpression())));
}

@Test
void transQueryWithComplexAttributeExpression_HierarchicalFilter() {
Expression spanTags1 = createComplexAttributeExpression("Span.tags", "FLAGS").build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.protobuf.GeneratedMessageV3;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import io.reactivex.rxjava3.core.Observable;
Expand All @@ -24,10 +25,13 @@
import java.util.stream.Stream;
import org.apache.pinot.client.ResultSet;
import org.apache.pinot.client.ResultSetGroup;
import org.hypertrace.core.query.service.AbstractQueryTransformation;
import org.hypertrace.core.query.service.AbstractServiceTest;
import org.hypertrace.core.query.service.ExecutionContext;
import org.hypertrace.core.query.service.QueryCost;
import org.hypertrace.core.query.service.QueryRequestBuilderUtils;
import org.hypertrace.core.query.service.QueryRequestUtil;
import org.hypertrace.core.query.service.QueryTransformation.QueryTransformationContext;
import org.hypertrace.core.query.service.api.Expression;
import org.hypertrace.core.query.service.api.Filter;
import org.hypertrace.core.query.service.api.LiteralConstant;
Expand All @@ -43,8 +47,11 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PinotBasedRequestHandlerTest {
public class PinotBasedRequestHandlerTest extends AbstractServiceTest<QueryRequest> {
private static final Logger LOGGER = LoggerFactory.getLogger(PinotBasedRequestHandlerTest.class);
// Test subject
private PinotBasedRequestHandler pinotBasedRequestHandler;
private final ObjectMapper objectMapper = new ObjectMapper();
Expand Down Expand Up @@ -1734,6 +1741,78 @@ public boolean isResultTableResultSetType(ResultSet resultSet) {
}
}

@Test
public void testEmptyChildFilterCase() throws IOException {
for (Config config : serviceConfig.getConfigList("queryRequestHandlersConfig")) {
if (!isPinotConfig(config)) {
continue;
}

if (!config.getString("name").equals("domain-service-handler")) {
continue;
}

// Mock the PinotClient
PinotClient pinotClient = mock(PinotClient.class);
PinotClientFactory factory = mock(PinotClientFactory.class);
when(factory.getPinotClient(any())).thenReturn(pinotClient);

String[][] resultTable = new String[][] {{"5"}};
List<String> columnNames = List.of("domain_id");
ResultSet resultSet = mockResultSet(1, 1, columnNames, resultTable);
ResultSetGroup resultSetGroup = mockResultSetGroup(List.of(resultSet));

PinotBasedRequestHandler handler =
new PinotBasedRequestHandler(
config.getString("name"),
config.getConfig("requestHandlerInfo"),
new ResultSetTypePredicateProvider() {
@Override
public boolean isSelectionResultSetType(ResultSet resultSet) {
return true;
}

@Override
public boolean isResultTableResultSetType(ResultSet resultSet) {
return false;
}
},
factory);

QueryRequest request = readQueryServiceRequest("request-with-empty-filter");
AbstractQueryTransformation transformation =
new AbstractQueryTransformation() {
@Override
protected Logger getLogger() {
return LOGGER;
}
};
QueryTransformationContext mockTransformationContext = mock(QueryTransformationContext.class);
QueryRequest transformedRequest =
transformation.transform(request, mockTransformationContext).blockingGet();
ExecutionContext context = new ExecutionContext("__default", transformedRequest);
QueryCost cost = handler.canHandle(transformedRequest, context);
Assertions.assertTrue(cost.getCost() > 0.0d && cost.getCost() < 1d);

// empty child filter should be ignored
String expectedQuery =
"Select DISTINCTCOUNTHLL(domain_id) FROM domainEventSpanView WHERE customer_id = ?"
+ " AND ( domain_id != ? AND ( start_time_millis >= ? AND start_time_millis < ? )"
+ " AND ( ( environment IN (?) ) ) ) limit 1";
Params params =
Params.newBuilder()
.addStringParam("__default")
.addStringParam("null")
.addLongParam(1658818870181L)
.addLongParam(1658905270181L)
.addStringParam("ui-data-validation")
.build();
when(pinotClient.executeQuery(expectedQuery, params)).thenReturn(resultSetGroup);

verifyResponseRows(handler.handleRequest(transformedRequest, context), resultTable);
}
}

@ParameterizedTest
@MethodSource("provideHandlerValue")
public void testGetTimeFilterColumn(int index, String expectedValue) {
Expand Down Expand Up @@ -1816,4 +1895,14 @@ private String stringify(Object obj) throws JsonProcessingException {
private boolean isPinotConfig(Config config) {
return config.getString("type").equals("pinot");
}

@Override
protected String getTestSuiteName() {
return "query";
}

@Override
protected GeneratedMessageV3.Builder getQueryServiceRequestBuilder() {
return QueryRequest.newBuilder();
}
}
21 changes: 20 additions & 1 deletion query-service-impl/src/test/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -382,5 +382,24 @@ service.config = {
}
}
}
{
name = domain-service-handler
type = pinot
clientConfig = broker
requestHandlerInfo = {
tenantColumnName: customer_id
startTimeAttributeName = "EVENT.startTime"
distinctCountAggFunction = DISTINCTCOUNTHLL
viewDefinition = {
viewName = domainEventSpanView
fieldMap = {
"EVENT.startTime": "start_time_millis",
"DOMAIN.id": "domain_id",
"DOMAIN.name": "domain_name",
"EVENT.environment": "environment"
}
}
}
}
]
}
}
Loading