-
Notifications
You must be signed in to change notification settings - Fork 8
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
Changes from all commits
eea9a4f
0866e4f
d3250d5
ffb0863
81c05b4
b80b43e
eb6b5d2
5bf2f93
e88259f
f5a8c21
782e220
2a73447
9205f2a
0f42b46
8c41693
a13b6fb
d7c7188
337136e
1ac51d7
f9688f4
8497b31
b01b297
353b354
d6b8f96
2abafa8
4f8504d
bf1799d
1cbc848
b18a7b9
d400a10
b966b11
0beb1bc
f38ebbb
e262a32
5c5d76f
b09f816
dd468d1
b393cac
4d26a92
ded94c8
5e281c0
ed3dfc2
2188e4a
0c03085
c80415c
567141d
974c10e
b77f8b0
c22f5a2
7aef6cf
229e08e
2dffa95
ccdd7c2
b1645da
2e0654f
857fe8d
66c042d
31598a2
696bacc
35f2155
24bfed6
1532c87
2495b14
2b0eca8
91edddf
778d7e9
e373d6b
ecf588b
7a2df87
38a95fa
245fc5a
fab4f95
457762e
668ae4c
ae2e871
a24645b
6720331
5154731
e973e48
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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; | ||
|
||
|
@@ -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) { | ||
return Filter.newBuilder() | ||
.setOperator(Operator.CONTAINS_KEY) | ||
.setLhs(createColumnExpression(column)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Support for There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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))) | ||
sarthak77 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.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(); | ||
aaron-steinfeld marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 |
---|---|---|
|
@@ -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; | ||
|
@@ -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"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
} | ||
} | ||
|
||
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; | ||
|
||
|
@@ -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 = ", "; | ||
} | ||
|
||
|
@@ -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); | ||
} | ||
|
||
|
@@ -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 = ", "; | ||
} | ||
} | ||
|
@@ -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 "); | ||
} | ||
|
@@ -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 + " "; | ||
} | ||
|
@@ -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: | ||
|
@@ -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(); | ||
|
@@ -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; | ||
} | ||
|
||
|
@@ -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"; | ||
|
@@ -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: | ||
|
@@ -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)); | ||
|
@@ -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"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So what should be the error here now ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
|
||
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) { | ||
|
There was a problem hiding this comment.
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.