Skip to content

Added support for handling complex attribute expression. #107

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 79 commits into from
Dec 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
eea9a4f
added unit test for contains key op
Nov 25, 2021
0866e4f
added unit tests
Nov 25, 2021
d3250d5
wip
Nov 26, 2021
ffb0863
nit
Nov 26, 2021
81c05b4
Merge branch 'main' into feat_support_for_complex_attr
sarthak77 Nov 26, 2021
b80b43e
added unit test
Nov 26, 2021
eb6b5d2
added supported op in filter
Nov 26, 2021
5bf2f93
refactored
Nov 26, 2021
e88259f
added support for grouping and ordering
Nov 29, 2021
f5a8c21
nit
Nov 29, 2021
782e220
nit
Nov 29, 2021
2a73447
updated unit tests
Nov 29, 2021
9205f2a
updated group by and order by
Nov 29, 2021
0f42b46
nit
Nov 29, 2021
8c41693
nit
Nov 29, 2021
a13b6fb
added check for map attribute
Nov 29, 2021
d7c7188
resolved PR comments
Nov 29, 2021
337136e
resolved PR comments
Nov 30, 2021
1ac51d7
nit
Nov 30, 2021
f9688f4
added unit test
Nov 30, 2021
8497b31
added support for map attr selection
Dec 1, 2021
b01b297
bug fix
Dec 2, 2021
353b354
refactored
Dec 2, 2021
d6b8f96
refactored
Dec 2, 2021
2abafa8
refactored
Dec 2, 2021
4f8504d
nit
Dec 2, 2021
bf1799d
rearranged functions
Dec 2, 2021
1cbc848
refactored
Dec 2, 2021
b18a7b9
added unit test
Dec 2, 2021
d400a10
resolved pr comments
Dec 3, 2021
b966b11
refactored
Dec 3, 2021
0beb1bc
resolved comments
Dec 3, 2021
f38ebbb
refactored
Dec 3, 2021
e262a32
refactored
Dec 3, 2021
5c5d76f
reordered
Dec 3, 2021
b09f816
resolved comments
Dec 6, 2021
dd468d1
added code for pre processing request
Dec 6, 2021
b393cac
resolved comments
Dec 7, 2021
4d26a92
refactored
Dec 7, 2021
ded94c8
added unit tests
Dec 7, 2021
5e281c0
added unit tests
Dec 7, 2021
ed3dfc2
updated tests
Dec 7, 2021
2188e4a
removed duplicate code
Dec 7, 2021
0c03085
refactored tests
Dec 7, 2021
c80415c
resolved PR comments
Dec 7, 2021
567141d
wip
Dec 8, 2021
974c10e
modified pre processing
Dec 8, 2021
b77f8b0
resolved comments
Dec 8, 2021
c22f5a2
resolved comments
Dec 8, 2021
7aef6cf
added comment
Dec 8, 2021
229e08e
renamed methods
Dec 9, 2021
2dffa95
resolved comments
Dec 9, 2021
ccdd7c2
Merge remote-tracking branch 'origin/main' into feat_support_for_comp…
Dec 9, 2021
b1645da
refactored
Dec 9, 2021
2e0654f
refactored
Dec 9, 2021
857fe8d
resolved comments
Dec 9, 2021
66c042d
added comments
Dec 9, 2021
31598a2
resolved comments
Dec 9, 2021
696bacc
nit
Dec 9, 2021
35f2155
nit
Dec 9, 2021
24bfed6
refactored
Dec 9, 2021
1532c87
refactored
Dec 10, 2021
2495b14
spotless
Dec 10, 2021
2b0eca8
merged from main
Dec 10, 2021
91edddf
removed duplicate methods
Dec 10, 2021
778d7e9
nit
Dec 10, 2021
e373d6b
added validation
Dec 13, 2021
ecf588b
Merge branch 'main' into feat_support_for_complex_attr
sarthak77 Dec 13, 2021
7a2df87
resolved conflicts
Dec 13, 2021
38a95fa
resolved comments
Dec 13, 2021
245fc5a
resolved comments
Dec 13, 2021
fab4f95
nit
Dec 13, 2021
457762e
refactored
Dec 14, 2021
668ae4c
removed unnecessary conditions
Dec 14, 2021
ae2e871
added unit tests
Dec 14, 2021
a24645b
added unit tests
Dec 14, 2021
6720331
added unit tests
Dec 14, 2021
5154731
added integ test
Dec 14, 2021
e973e48
resolved comments
Dec 14, 2021
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
@@ -1,11 +1,16 @@
package org.hypertrace.core.query.service;

import static org.hypertrace.core.query.service.api.Expression.ValueCase.ATTRIBUTE_EXPRESSION;
import static org.hypertrace.core.query.service.api.Expression.ValueCase.COLUMNIDENTIFIER;

import java.util.List;
import org.hypertrace.core.query.service.api.AttributeExpression;
import org.hypertrace.core.query.service.api.ColumnIdentifier;
import org.hypertrace.core.query.service.api.Expression;
import org.hypertrace.core.query.service.api.Expression.ValueCase;
import org.hypertrace.core.query.service.api.Filter;
import org.hypertrace.core.query.service.api.LiteralConstant;
import org.hypertrace.core.query.service.api.Operator;
import org.hypertrace.core.query.service.api.Value;
import org.hypertrace.core.query.service.api.ValueType;

Expand Down Expand Up @@ -72,29 +77,65 @@ public static Expression createNullNumberLiteralExpression() {
.build();
}

public static boolean isDateTimeFunction(Expression expression) {
return expression.getValueCase() == ValueCase.FUNCTION
&& expression.getFunction().getFunctionName().equals("dateTimeConvert");
public static Filter createContainsKeyFilter(AttributeExpression complexAttributeExpression) {
return createContainsKeyFilter(
complexAttributeExpression.getAttributeId(), complexAttributeExpression.getSubpath());
}

public static boolean isComplexAttribute(Expression expression) {
return expression.getValueCase().equals(ATTRIBUTE_EXPRESSION)
&& expression.getAttributeExpression().hasSubpath();
public static Filter createContainsKeyFilter(String column, String value) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: can be inline above function or can be private as it (createContainsKeyFilter(String column, String value) is only used in Tests now. But, can be taken as follow up.

return Filter.newBuilder()
.setOperator(Operator.CONTAINS_KEY)
.setLhs(createColumnExpression(column))
Copy link
Contributor

@aaron-steinfeld aaron-steinfeld Dec 8, 2021

Choose a reason for hiding this comment

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

Our new code should use attribute expressions since we're trying to deprecate column expressions (see other comment - I think this change would break without that fix).

Copy link
Member Author

Choose a reason for hiding this comment

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

Support for CONTAINS_KEY and CONTAINS_KEYVALUE for attributeExpression will be added as a follow up PR.

Copy link
Contributor

@aaron-steinfeld aaron-steinfeld Dec 9, 2021

Choose a reason for hiding this comment

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

CONTAINS_KEYVALUE support we never need (deprecated), CONTAINS_KEY should be trivial (I thought it was basically that one suggestion I made in another thread - will double check). If you want to do it in a separate PR that's fine, but we can't use attribute expressions until that support is added since the upstream won't be mixing column identifiers and attribute expressions - they'll all cut over together.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added this as a follow up PR task.

.setRhs(createStringArrayLiteralValueExpression(List.of(value)))
.build();
}

public static boolean isSimpleColumnExpression(Expression expression) {
return expression.getValueCase() == ValueCase.COLUMNIDENTIFIER
|| (expression.getValueCase() == ATTRIBUTE_EXPRESSION && !isComplexAttribute(expression));
public static Expression createStringArrayLiteralValueExpression(List<String> values) {
return Expression.newBuilder()
.setLiteral(
LiteralConstant.newBuilder()
.setValue(
Value.newBuilder()
.addAllStringArray(values)
.setValueType(ValueType.STRING_ARRAY)))
.build();
}

public static String getLogicalColumnNameForSimpleColumnExpression(Expression expression) {
if (!isSimpleColumnExpression(expression)) {
throw new RuntimeException("Expecting expression of type COLUMN or ATTRIBUTE");
// attribute expression with sub path is always a map attribute
public static boolean isAttributeExpressionWithSubpath(Expression expression) {
return expression.getValueCase() == ATTRIBUTE_EXPRESSION
&& expression.getAttributeExpression().hasSubpath();
}

public static boolean isSimpleAttributeExpression(Expression expression) {
switch (expression.getValueCase()) {
case COLUMNIDENTIFIER:
return true;
case ATTRIBUTE_EXPRESSION:
return !expression.getAttributeExpression().hasSubpath();
default:
return false;
}
if (expression.getValueCase() == ValueCase.COLUMNIDENTIFIER) {
return expression.getColumnIdentifier().getColumnName();
} else {
return expression.getAttributeExpression().getAttributeId();
}

public static boolean isDateTimeFunction(Expression expression) {
return expression.getValueCase() == ValueCase.FUNCTION
&& expression.getFunction().getFunctionName().equals("dateTimeConvert");
}

public static String getLogicalColumnName(Expression expression) {
switch (expression.getValueCase()) {
case COLUMNIDENTIFIER:
return expression.getColumnIdentifier().getColumnName();
case ATTRIBUTE_EXPRESSION:
return expression.getAttributeExpression().getAttributeId();
default:
throw new IllegalArgumentException(
"Supports "
+ ATTRIBUTE_EXPRESSION
+ " and "
+ COLUMNIDENTIFIER
+ " expression type only");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.hypertrace.core.query.service.api.Filter;
import org.hypertrace.core.query.service.api.LiteralConstant;
import org.hypertrace.core.query.service.api.Operator;
import org.hypertrace.core.query.service.api.OrderByExpression;
import org.hypertrace.core.query.service.api.QueryRequest;
import org.hypertrace.core.query.service.api.Row;
import org.hypertrace.core.query.service.api.Row.Builder;
Expand Down Expand Up @@ -621,5 +622,46 @@ private void validateQueryRequest(ExecutionContext executionContext, QueryReques
noGroupBy && noAggregations,
"If distinct selections are requested, there should be no groupBys or aggregations.");
}

// Validate attribute expressions

validateAttributeExpressionFilter(request.getFilter());

for (Expression expression : executionContext.getAllSelections()) {
if (isInvalidExpression(expression)) {
throw new IllegalArgumentException("Invalid Query");
Copy link
Contributor

Choose a reason for hiding this comment

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

In a followup, would be good to return why it's invalid (and return this as a proper GRPC INVALID_ARGUMENT exception).

}
}

for (Expression expression : request.getGroupByList()) {
if (isInvalidExpression(expression)) {
throw new IllegalArgumentException("Invalid Query");
}
}

for (OrderByExpression orderByExpression : request.getOrderByList()) {
if (isInvalidExpression(orderByExpression.getExpression())) {
throw new IllegalArgumentException("Invalid Query");
}
}
}

private void validateAttributeExpressionFilter(Filter filter) {
if (filter.getChildFilterCount() > 0) {
for (Filter childFilter : filter.getChildFilterList()) {
validateAttributeExpressionFilter(childFilter);
}
} else {
if (isInvalidExpression(filter.getLhs())) {
throw new IllegalArgumentException("Invalid Query");
}
}
}

private boolean isInvalidExpression(Expression expression) {
return expression.getValueCase() == ValueCase.ATTRIBUTE_EXPRESSION
&& expression.getAttributeExpression().hasSubpath()
&& viewDefinition.getColumnType(expression.getAttributeExpression().getAttributeId())
!= ValueType.STRING_MAP;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package org.hypertrace.core.query.service.pinot;

import static org.hypertrace.core.query.service.QueryRequestUtil.isComplexAttribute;
import static org.hypertrace.core.query.service.api.Expression.ValueCase.ATTRIBUTE_EXPRESSION;
import static org.hypertrace.core.query.service.QueryRequestUtil.getLogicalColumnName;
import static org.hypertrace.core.query.service.QueryRequestUtil.isAttributeExpressionWithSubpath;
import static org.hypertrace.core.query.service.QueryRequestUtil.isSimpleAttributeExpression;
import static org.hypertrace.core.query.service.api.Expression.ValueCase.COLUMNIDENTIFIER;
import static org.hypertrace.core.query.service.api.Expression.ValueCase.LITERAL;

Expand Down Expand Up @@ -66,7 +67,7 @@ Entry<String, Params> toSQL(
// how it is created.
for (Expression expr : allSelections) {
pqlBuilder.append(delim);
pqlBuilder.append(convertExpression2String(expr, paramsBuilder, executionContext));
pqlBuilder.append(convertExpressionToString(expr, paramsBuilder, executionContext));
delim = ", ";
}

Expand All @@ -79,7 +80,7 @@ Entry<String, Params> toSQL(
if (request.hasFilter()) {
pqlBuilder.append(" AND ");
String filterClause =
convertFilter2String(request.getFilter(), paramsBuilder, executionContext);
convertFilterToString(request.getFilter(), paramsBuilder, executionContext);
pqlBuilder.append(filterClause);
}

Expand All @@ -89,7 +90,7 @@ Entry<String, Params> toSQL(
for (Expression groupByExpression : request.getGroupByList()) {
pqlBuilder.append(delim);
pqlBuilder.append(
convertExpression2String(groupByExpression, paramsBuilder, executionContext));
convertExpressionToString(groupByExpression, paramsBuilder, executionContext));
delim = ", ";
}
}
Expand All @@ -98,10 +99,9 @@ Entry<String, Params> toSQL(
delim = "";
for (OrderByExpression orderByExpression : request.getOrderByList()) {
pqlBuilder.append(delim);
String orderBy =
convertExpression2String(
orderByExpression.getExpression(), paramsBuilder, executionContext);
pqlBuilder.append(orderBy);
pqlBuilder.append(
convertExpressionToString(
orderByExpression.getExpression(), paramsBuilder, executionContext));
if (SortOrder.DESC.equals(orderByExpression.getOrder())) {
pqlBuilder.append(" desc ");
}
Expand All @@ -126,16 +126,16 @@ Entry<String, Params> toSQL(
return new SimpleEntry<>(pqlBuilder.toString(), paramsBuilder.build());
}

private String convertFilter2String(
private String convertFilterToString(
Filter filter, Builder paramsBuilder, ExecutionContext executionContext) {
StringBuilder builder = new StringBuilder();
String operator = convertOperator2String(filter.getOperator());
String operator = convertOperatorToString(filter.getOperator());
if (filter.getChildFilterCount() > 0) {
String delim = "";
builder.append("( ");
for (Filter childFilter : filter.getChildFilterList()) {
builder.append(delim);
builder.append(convertFilter2String(childFilter, paramsBuilder, executionContext));
builder.append(convertFilterToString(childFilter, paramsBuilder, executionContext));
builder.append(" ");
delim = operator + " ";
}
Expand All @@ -149,9 +149,9 @@ private String convertFilter2String(
builder.append(operator);
builder.append("(");
builder.append(
convertExpression2String(filter.getLhs(), paramsBuilder, executionContext));
convertExpressionToString(filter.getLhs(), paramsBuilder, executionContext));
builder.append(",");
builder.append(convertExpression2String(rhs, paramsBuilder, executionContext));
builder.append(convertExpressionToString(rhs, paramsBuilder, executionContext));
builder.append(")");
break;
case CONTAINS_KEY:
Expand Down Expand Up @@ -185,11 +185,11 @@ private String convertFilter2String(
default:
rhs = handleValueConversionForLiteralExpression(filter.getLhs(), filter.getRhs());
builder.append(
convertExpression2String(filter.getLhs(), paramsBuilder, executionContext));
convertExpressionToString(filter.getLhs(), paramsBuilder, executionContext));
builder.append(" ");
builder.append(operator);
builder.append(" ");
builder.append(convertExpression2String(rhs, paramsBuilder, executionContext));
builder.append(convertExpressionToString(rhs, paramsBuilder, executionContext));
}
}
return builder.toString();
Expand All @@ -203,9 +203,7 @@ private String convertFilter2String(
* @return newly created literal {@link Expression} of rhs if converted else the same one.
*/
private Expression handleValueConversionForLiteralExpression(Expression lhs, Expression rhs) {
if (!((lhs.getValueCase().equals(COLUMNIDENTIFIER)
|| (lhs.getValueCase().equals(ATTRIBUTE_EXPRESSION) && !isComplexAttribute(lhs)))
&& rhs.getValueCase().equals(LITERAL))) {
if (!(isSimpleAttributeExpression(lhs) && rhs.getValueCase().equals(LITERAL))) {
return rhs;
}

Expand All @@ -226,7 +224,7 @@ private Expression handleValueConversionForLiteralExpression(Expression lhs, Exp
}
}

private String convertOperator2String(Operator operator) {
private String convertOperatorToString(Operator operator) {
switch (operator) {
case AND:
return "AND";
Expand Down Expand Up @@ -263,7 +261,7 @@ private String convertOperator2String(Operator operator) {
}
}

private String convertExpression2String(
private String convertExpressionToString(
Expression expression, Builder paramsBuilder, ExecutionContext executionContext) {
switch (expression.getValueCase()) {
case COLUMNIDENTIFIER:
Expand All @@ -272,8 +270,21 @@ private String convertExpression2String(
viewDefinition.getPhysicalColumnNames(getLogicalColumnName(expression));
return joiner.join(columnNames);
case ATTRIBUTE_EXPRESSION:
if (isComplexAttribute(expression)) {
/** TODO:Handle complex attribute */
if (isAttributeExpressionWithSubpath(expression)) {
String keyCol = convertExpressionToMapKeyColumn(expression);
String valCol = convertExpressionToMapValueColumn(expression);
String pathExpression = expression.getAttributeExpression().getSubpath();
LiteralConstant pathExpressionLiteral =
LiteralConstant.newBuilder()
.setValue(Value.newBuilder().setString(pathExpression).build())
.build();

return String.format(
"%s(%s,%s,%s)",
MAP_VALUE,
keyCol,
convertLiteralToString(pathExpressionLiteral, paramsBuilder),
valCol);
} else {
// this takes care of the Map Type where it's split into 2 columns
columnNames = viewDefinition.getPhysicalColumnNames(getLogicalColumnName(expression));
Expand All @@ -286,54 +297,30 @@ private String convertExpression2String(
executionContext,
expression.getFunction(),
argExpression ->
convertExpression2String(argExpression, paramsBuilder, executionContext));
convertExpressionToString(argExpression, paramsBuilder, executionContext));
case ORDERBY:
OrderByExpression orderBy = expression.getOrderBy();
return convertExpression2String(orderBy.getExpression(), paramsBuilder, executionContext);
return convertExpressionToString(orderBy.getExpression(), paramsBuilder, executionContext);
case VALUE_NOT_SET:
break;
}
return "";
}

private String convertExpressionToMapKeyColumn(Expression expression) {
if ((expression.getValueCase() == COLUMNIDENTIFIER)
|| (expression.getValueCase() == ATTRIBUTE_EXPRESSION && !isComplexAttribute(expression))) {
String col = viewDefinition.getKeyColumnNameForMap(getLogicalColumnName(expression));
if (col != null && col.length() > 0) {
return col;
}
String col = viewDefinition.getKeyColumnNameForMap(getLogicalColumnName(expression));
if (col != null && col.length() > 0) {
return col;
}
throw new IllegalArgumentException(
"operator CONTAINS_KEY/KEYVALUE supports multi value column only");
throw new IllegalArgumentException("operator supports multi value column only");
Copy link
Contributor

Choose a reason for hiding this comment

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

Follow up is fine but we should fixup this error message since the type check is gone. Now, this error is reached if no key column exists for the provided attribute - that could be an attribute that's mistyped, but it also may just not be a column in that view.

Copy link
Member Author

Choose a reason for hiding this comment

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

So what should be the error here now ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Unable to lookup key column for attribute: {}, or something like that

}

private String convertExpressionToMapValueColumn(Expression expression) {
if ((expression.getValueCase() == COLUMNIDENTIFIER)
|| (expression.getValueCase() == ATTRIBUTE_EXPRESSION && !isComplexAttribute(expression))) {
String col = viewDefinition.getValueColumnNameForMap(getLogicalColumnName(expression));
if (col != null && col.length() > 0) {
return col;
}
}
throw new IllegalArgumentException(
"operator CONTAINS_KEY/KEYVALUE supports multi value column only");
}

private String getLogicalColumnName(Expression expression) {
switch (expression.getValueCase()) {
case COLUMNIDENTIFIER:
return expression.getColumnIdentifier().getColumnName();
case ATTRIBUTE_EXPRESSION:
return expression.getAttributeExpression().getAttributeId();
default:
throw new IllegalArgumentException(
"Supports "
+ ATTRIBUTE_EXPRESSION
+ " and "
+ COLUMNIDENTIFIER
+ " expression type only");
String col = viewDefinition.getValueColumnNameForMap(getLogicalColumnName(expression));
if (col != null && col.length() > 0) {
return col;
}
throw new IllegalArgumentException("operator supports multi value column only");
}

private LiteralConstant[] convertExpressionToMapLiterals(Expression expression) {
Expand Down
Loading