Skip to content

Commit 641e419

Browse files
aaron-steinfeldSarthak Singhalsarthak77kotharironak
authored
fix: support for attribute expressions in selections (#129)
* added sup for attr expr in execution context * added sup for attr expr in projection transformation * added sup for attr expr in pinotbasedrequesthandler * refactored * added unit test for execution context * added unit test in pinot based rqst handler * changed function signature * resolved comments * nit * fixed unit tests * added template for new tests * added integ test with attr expr for existing queries * added functions for integ test * added new integ tests * nit * added contains key filter for selection * fix: support for attribute expressioons in selections * test: update tests * style: fix nit * Apply suggestions from code review Co-authored-by: sarthak <sarthak02singhal@gmail.com> * added unit test for contains_key_value op * chore: update dependencies * chore: fix integration tests Co-authored-by: Sarthak Singhal <sarthaksinghal@Sarthaks-MacBook-Pro.local> Co-authored-by: sarthak <sarthak02singhal@gmail.com> Co-authored-by: kotharironak <53209990+kotharironak@users.noreply.github.com>
1 parent 976986d commit 641e419

21 files changed

+849
-604
lines changed

query-service-api/build.gradle.kts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ protobuf {
2323
// the identifier, which can be referred to in the "plugins"
2424
// container of the "generateProtoTasks" closure.
2525
id("grpc_java") {
26-
artifact = "io.grpc:protoc-gen-grpc-java:1.42.0"
26+
artifact = "io.grpc:protoc-gen-grpc-java:1.43.1"
2727
}
2828

2929
if (generateLocalGoGrpcFiles) {
@@ -66,8 +66,9 @@ tasks.test {
6666
}
6767

6868
dependencies {
69-
api("io.grpc:grpc-protobuf:1.42.0")
70-
api("io.grpc:grpc-stub:1.42.0")
69+
api(platform("io.grpc:grpc-bom:1.43.1"))
70+
api("io.grpc:grpc-protobuf")
71+
api("io.grpc:grpc-stub")
7172
api("javax.annotation:javax.annotation-api:1.3.2")
7273

7374
testImplementation("org.junit.jupiter:junit-jupiter:5.7.1")

query-service-client/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ plugins {
77

88
dependencies {
99
api(project(":query-service-api"))
10-
implementation("org.hypertrace.core.grpcutils:grpc-client-utils:0.6.2")
10+
implementation("org.hypertrace.core.grpcutils:grpc-client-utils:0.7.0")
1111

1212
// Logging
13-
implementation("org.slf4j:slf4j-api:1.7.30")
13+
implementation("org.slf4j:slf4j-api:1.7.32")
1414
// Config
1515
implementation("com.typesafe:config:1.4.1")
1616
}

query-service-impl/build.gradle.kts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@ tasks.test {
1010

1111
dependencies {
1212
constraints {
13-
implementation("com.fasterxml.jackson.core:jackson-databind:2.12.2") {
14-
because("Multiple vulnerabilities")
15-
}
1613
implementation("io.netty:netty:3.10.6.Final") {
1714
because("https://snyk.io/vuln/SNYK-JAVA-IONETTY-30430")
1815
}
@@ -28,21 +25,21 @@ dependencies {
2825
}
2926
api(project(":query-service-api"))
3027
api("com.typesafe:config:1.4.1")
31-
implementation("org.hypertrace.core.grpcutils:grpc-context-utils:0.6.2")
32-
implementation("org.hypertrace.core.grpcutils:grpc-client-utils:0.6.2")
33-
implementation("org.hypertrace.core.grpcutils:grpc-server-rx-utils:0.6.2")
28+
implementation("org.hypertrace.core.grpcutils:grpc-context-utils:0.7.0")
29+
implementation("org.hypertrace.core.grpcutils:grpc-client-utils:0.7.0")
30+
implementation("org.hypertrace.core.grpcutils:grpc-server-rx-utils:0.7.0")
3431
implementation("org.hypertrace.core.attribute.service:attribute-service-api:0.12.3")
3532
implementation("org.hypertrace.core.attribute.service:attribute-projection-registry:0.12.3")
3633
implementation("org.hypertrace.core.attribute.service:caching-attribute-service-client:0.12.3")
37-
implementation("org.hypertrace.core.serviceframework:service-framework-spi:0.1.28")
34+
implementation("org.hypertrace.core.serviceframework:service-framework-spi:0.1.33")
3835
implementation("com.google.inject:guice:5.0.1")
3936
implementation("org.apache.pinot:pinot-java-client:0.6.0") {
4037
// We want to use log4j2 impl so exclude the log4j binding of slf4j
4138
exclude("org.slf4j", "slf4j-log4j12")
4239
}
43-
implementation("org.slf4j:slf4j-api:1.7.30")
40+
implementation("org.slf4j:slf4j-api:1.7.32")
4441
implementation("commons-codec:commons-codec:1.15")
45-
implementation("org.hypertrace.core.serviceframework:platform-metrics:0.1.28")
42+
implementation("org.hypertrace.core.serviceframework:platform-metrics:0.1.33")
4643
implementation("com.google.protobuf:protobuf-java-util:3.15.6")
4744
implementation("com.google.guava:guava:30.1.1-jre")
4845
implementation("io.reactivex.rxjava3:rxjava:3.0.11")
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package org.hypertrace.core.query.service;
2+
3+
import static io.reactivex.rxjava3.core.Single.zip;
4+
5+
import io.reactivex.rxjava3.core.Observable;
6+
import io.reactivex.rxjava3.core.Single;
7+
import java.util.List;
8+
import org.hypertrace.core.query.service.api.AttributeExpression;
9+
import org.hypertrace.core.query.service.api.ColumnIdentifier;
10+
import org.hypertrace.core.query.service.api.Expression;
11+
import org.hypertrace.core.query.service.api.Filter;
12+
import org.hypertrace.core.query.service.api.Function;
13+
import org.hypertrace.core.query.service.api.LiteralConstant;
14+
import org.hypertrace.core.query.service.api.OrderByExpression;
15+
import org.hypertrace.core.query.service.api.QueryRequest;
16+
import org.slf4j.Logger;
17+
18+
public abstract class AbstractQueryTransformation implements QueryTransformation {
19+
20+
protected abstract Logger getLogger();
21+
22+
@Override
23+
public Single<QueryRequest> transform(
24+
QueryRequest queryRequest, QueryTransformationContext transformationContext) {
25+
return zip(
26+
this.transformExpressionList(queryRequest.getSelectionList()),
27+
this.transformExpressionList(queryRequest.getAggregationList()),
28+
this.transformFilter(queryRequest.getFilter()),
29+
this.transformExpressionList(queryRequest.getGroupByList()),
30+
this.transformOrderByList(queryRequest.getOrderByList()),
31+
(selections, aggregations, filter, groupBys, orderBys) ->
32+
this.rebuildRequest(
33+
queryRequest, selections, aggregations, filter, groupBys, orderBys))
34+
.doOnSuccess(transformed -> this.debugLogIfRequestTransformed(queryRequest, transformed));
35+
}
36+
37+
protected QueryRequest rebuildRequest(
38+
QueryRequest original,
39+
List<Expression> selections,
40+
List<Expression> aggregations,
41+
Filter filter,
42+
List<Expression> groupBys,
43+
List<OrderByExpression> orderBys) {
44+
45+
QueryRequest.Builder builder = original.toBuilder();
46+
47+
if (Filter.getDefaultInstance().equals(filter)) {
48+
builder.clearFilter();
49+
} else {
50+
builder.setFilter(filter);
51+
}
52+
53+
return builder
54+
.clearSelection()
55+
.addAllSelection(selections)
56+
.clearAggregation()
57+
.addAllAggregation(aggregations)
58+
.clearGroupBy()
59+
.addAllGroupBy(groupBys)
60+
.clearOrderBy()
61+
.addAllOrderBy(orderBys)
62+
.build();
63+
}
64+
65+
protected Single<Expression> transformExpression(Expression expression) {
66+
switch (expression.getValueCase()) {
67+
case COLUMNIDENTIFIER:
68+
return this.transformColumnIdentifier(expression.getColumnIdentifier());
69+
case ATTRIBUTE_EXPRESSION:
70+
return this.transformAttributeExpression(expression.getAttributeExpression());
71+
case FUNCTION:
72+
return this.transformFunction(expression.getFunction());
73+
case ORDERBY:
74+
return this.transformOrderBy(expression.getOrderBy())
75+
.map(expression.toBuilder()::setOrderBy)
76+
.map(Expression.Builder::build);
77+
case LITERAL:
78+
return this.transformLiteral(expression.getLiteral());
79+
case VALUE_NOT_SET:
80+
default:
81+
return Single.just(expression);
82+
}
83+
}
84+
85+
protected Single<Expression> transformColumnIdentifier(ColumnIdentifier columnIdentifier) {
86+
return Single.just(Expression.newBuilder().setColumnIdentifier(columnIdentifier).build());
87+
}
88+
89+
protected Single<Expression> transformAttributeExpression(
90+
AttributeExpression attributeExpression) {
91+
return Single.just(Expression.newBuilder().setAttributeExpression(attributeExpression).build());
92+
}
93+
94+
protected Single<Expression> transformLiteral(LiteralConstant literalConstant) {
95+
return Single.just(Expression.newBuilder().setLiteral(literalConstant).build());
96+
}
97+
98+
protected Single<Expression> transformFunction(Function function) {
99+
return this.transformExpressionList(function.getArgumentsList())
100+
.map(expressions -> function.toBuilder().clearArguments().addAllArguments(expressions))
101+
.map(Function.Builder::build)
102+
.map(Expression.newBuilder()::setFunction)
103+
.map(Expression.Builder::build);
104+
}
105+
106+
protected Single<OrderByExpression> transformOrderBy(OrderByExpression orderBy) {
107+
return this.transformExpression(orderBy.getExpression())
108+
.map(orderBy.toBuilder()::setExpression)
109+
.map(OrderByExpression.Builder::build);
110+
}
111+
112+
protected Single<Filter> transformFilter(Filter filter) {
113+
if (filter.equals(Filter.getDefaultInstance())) {
114+
return Single.just(filter);
115+
}
116+
117+
Single<Expression> lhsSingle = this.transformExpression(filter.getLhs());
118+
Single<Expression> rhsSingle = this.transformExpression(filter.getRhs());
119+
Single<List<Filter>> childFilterListSingle =
120+
Observable.fromIterable(filter.getChildFilterList())
121+
.concatMapSingle(this::transformFilter)
122+
.toList();
123+
return zip(
124+
lhsSingle,
125+
rhsSingle,
126+
childFilterListSingle,
127+
(lhs, rhs, childFilterList) ->
128+
this.rebuildFilterOmittingDefaults(filter, lhs, rhs, childFilterList));
129+
}
130+
131+
private Single<List<Expression>> transformExpressionList(List<Expression> expressionList) {
132+
return Observable.fromIterable(expressionList)
133+
.concatMapSingle(this::transformExpression)
134+
.toList();
135+
}
136+
137+
private Single<List<OrderByExpression>> transformOrderByList(
138+
List<OrderByExpression> orderByList) {
139+
return Observable.fromIterable(orderByList).concatMapSingle(this::transformOrderBy).toList();
140+
}
141+
142+
/**
143+
* This doesn't change any functional behavior, but omits fields that aren't needed, shrinking the
144+
* object and keeping it equivalent to the source object for equality checks.
145+
*/
146+
private Filter rebuildFilterOmittingDefaults(
147+
Filter original, Expression lhs, Expression rhs, List<Filter> childFilters) {
148+
Filter.Builder builder = original.toBuilder();
149+
150+
if (Expression.getDefaultInstance().equals(lhs)) {
151+
builder.clearLhs();
152+
} else {
153+
builder.setLhs(lhs);
154+
}
155+
156+
if (Expression.getDefaultInstance().equals(rhs)) {
157+
builder.clearRhs();
158+
} else {
159+
builder.setRhs(rhs);
160+
}
161+
162+
return builder.clearChildFilter().addAllChildFilter(childFilters).build();
163+
}
164+
165+
private void debugLogIfRequestTransformed(QueryRequest original, QueryRequest transformed) {
166+
if (!original.equals(transformed)) {
167+
getLogger()
168+
.debug(
169+
"Request transformation occurred. Original request: {} Transformed Request: {}",
170+
original,
171+
transformed);
172+
}
173+
}
174+
}

query-service-impl/src/main/java/org/hypertrace/core/query/service/QueryRequestUtil.java

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
package org.hypertrace.core.query.service;
22

33
import static org.hypertrace.core.query.service.api.Expression.ValueCase.ATTRIBUTE_EXPRESSION;
4-
import static org.hypertrace.core.query.service.api.Expression.ValueCase.COLUMNIDENTIFIER;
5-
import static org.hypertrace.core.query.service.api.Expression.ValueCase.FUNCTION;
64

75
import java.util.Optional;
86
import org.hypertrace.core.query.service.api.AttributeExpression;
9-
import org.hypertrace.core.query.service.api.ColumnIdentifier;
107
import org.hypertrace.core.query.service.api.Expression;
118
import org.hypertrace.core.query.service.api.Expression.ValueCase;
129
import org.hypertrace.core.query.service.api.Filter;
@@ -21,12 +18,6 @@
2118
*/
2219
public class QueryRequestUtil {
2320

24-
public static Expression createColumnExpression(String columnName) {
25-
return Expression.newBuilder()
26-
.setColumnIdentifier(ColumnIdentifier.newBuilder().setColumnName(columnName))
27-
.build();
28-
}
29-
3021
public static Expression createStringLiteralExpression(String value) {
3122
return Expression.newBuilder()
3223
.setLiteral(
@@ -145,10 +136,7 @@ public static Optional<String> getAlias(Expression expression) {
145136
? getLogicalColumnName(expression).get()
146137
: expression.getColumnIdentifier().getAlias());
147138
case ATTRIBUTE_EXPRESSION:
148-
return Optional.of(
149-
expression.getAttributeExpression().getAlias().isBlank()
150-
? getLogicalColumnName(expression).get()
151-
: expression.getAttributeExpression().getAlias());
139+
return Optional.of(getAlias(expression.getAttributeExpression()));
152140
case FUNCTION:
153141
// todo: handle recursive functions max(rollup(time,50)
154142
// workaround is to use alias for now
@@ -160,4 +148,10 @@ public static Optional<String> getAlias(Expression expression) {
160148
return Optional.empty();
161149
}
162150
}
151+
152+
public static String getAlias(AttributeExpression attributeExpression) {
153+
return attributeExpression.getAlias().isBlank()
154+
? attributeExpression.getAttributeId()
155+
: attributeExpression.getAlias();
156+
}
163157
}

query-service-impl/src/main/java/org/hypertrace/core/query/service/QueryServiceModule.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import javax.inject.Singleton;
77
import org.hypertrace.core.attribute.service.cachingclient.CachingAttributeClient;
88
import org.hypertrace.core.query.service.api.QueryServiceGrpc.QueryServiceImplBase;
9+
import org.hypertrace.core.query.service.attribubteexpression.AttributeExpressionModule;
910
import org.hypertrace.core.query.service.pinot.PinotModule;
1011
import org.hypertrace.core.query.service.projection.ProjectionModule;
1112
import org.hypertrace.core.query.service.prometheus.PrometheusModule;
@@ -33,5 +34,6 @@ protected void configure() {
3334
install(new PinotModule());
3435
install(new ProjectionModule());
3536
install(new PrometheusModule());
37+
install(new AttributeExpressionModule());
3638
}
3739
}

query-service-impl/src/main/java/org/hypertrace/core/query/service/QueryTransformation.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,20 @@
22

33
import io.reactivex.rxjava3.core.Single;
44
import org.hypertrace.core.query.service.api.QueryRequest;
5+
import org.jetbrains.annotations.NotNull;
56

6-
public interface QueryTransformation {
7+
public interface QueryTransformation extends Comparable<QueryTransformation> {
78
Single<QueryRequest> transform(
89
QueryRequest queryRequest, QueryTransformationContext transformationContext);
910

11+
default int getPriority() {
12+
return 10;
13+
}
14+
15+
@Override
16+
default int compareTo(@NotNull QueryTransformation other) {
17+
return Integer.compare(this.getPriority(), other.getPriority());
18+
}
19+
1020
interface QueryTransformationContext {}
1121
}

query-service-impl/src/main/java/org/hypertrace/core/query/service/QueryTransformationPipeline.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Single<QueryRequest> transform(QueryRequest originalRequest, String tenantId) {
2424
QueryTransformationContext transformationContext =
2525
new DefaultQueryTransformationContext(tenantId);
2626
return Observable.fromIterable(transformations)
27+
.sorted()
2728
.reduce(
2829
Single.just(originalRequest),
2930
(requestSingle, transformation) ->
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.hypertrace.core.query.service.attribubteexpression;
2+
3+
import com.google.inject.AbstractModule;
4+
import com.google.inject.multibindings.Multibinder;
5+
import org.hypertrace.core.query.service.QueryTransformation;
6+
7+
public class AttributeExpressionModule extends AbstractModule {
8+
9+
@Override
10+
protected void configure() {
11+
Multibinder<QueryTransformation> transformationMultibinder =
12+
Multibinder.newSetBinder(binder(), QueryTransformation.class);
13+
transformationMultibinder
14+
.addBinding()
15+
.to(AttributeExpressionSubpathExistsFilteringTransformation.class);
16+
transformationMultibinder.addBinding().to(AttributeExpressionNormalizationTransformation.class);
17+
}
18+
}

0 commit comments

Comments
 (0)