-
Notifications
You must be signed in to change notification settings - Fork 10
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
fix: handle group_by identifier more generically #127
Changes from all commits
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 |
---|---|---|
@@ -0,0 +1,14 @@ | ||
[ | ||
{ | ||
"item":"Soap", | ||
"qty_count":2 | ||
}, | ||
{ | ||
"item":"Comb", | ||
"qty_count":2 | ||
}, | ||
{ | ||
"item":"Shampoo", | ||
"qty_count":2 | ||
} | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,16 @@ | ||
package org.hypertrace.core.documentstore.postgres.query.v1.transformer; | ||
|
||
import java.util.List; | ||
import java.util.Set; | ||
import java.util.function.Predicate; | ||
import java.util.stream.Collectors; | ||
import org.hypertrace.core.documentstore.expression.impl.AggregateExpression; | ||
import org.hypertrace.core.documentstore.expression.impl.ConstantExpression; | ||
import org.hypertrace.core.documentstore.expression.impl.FunctionExpression; | ||
import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; | ||
import org.hypertrace.core.documentstore.expression.type.GroupTypeExpression; | ||
import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; | ||
import org.hypertrace.core.documentstore.parser.GroupTypeExpressionVisitor; | ||
import org.hypertrace.core.documentstore.parser.SelectTypeExpressionVisitor; | ||
import org.hypertrace.core.documentstore.query.Query; | ||
import org.hypertrace.core.documentstore.query.SelectionSpec; | ||
|
@@ -29,42 +34,110 @@ | |
* | ||
* This is the similar behavior supported in our other document store implementation (e.g Mongo) | ||
* */ | ||
public class PostgresSelectionQueryTransformer | ||
implements QueryTransformer, SelectTypeExpressionVisitor { | ||
public class PostgresSelectionQueryTransformer implements QueryTransformer { | ||
|
||
@Override | ||
public Query transform(Query query) { | ||
// no-op if group by clause exits | ||
if (!query.getAggregations().isEmpty()) return query; | ||
LocalSelectTypeAggregationExpressionSelector aggregationExpressionSelector = | ||
new LocalSelectTypeAggregationExpressionSelector(); | ||
Boolean noAggregation = | ||
query.getSelections().stream() | ||
.filter( | ||
selectionSpec -> | ||
selectionSpec.getExpression().accept(aggregationExpressionSelector)) | ||
.findAny() | ||
.isEmpty(); | ||
if (noAggregation) return query; | ||
|
||
// get all identifier of group by clause | ||
LocalGroupByIdentifierExpressionSelector groupByIdentifierExpressionSelector = | ||
new LocalGroupByIdentifierExpressionSelector(); | ||
Set<GroupTypeExpression> groupByIdentifierExpressions = | ||
query.getAggregations().stream() | ||
.filter(exp -> exp.accept(groupByIdentifierExpressionSelector)) | ||
.collect(Collectors.toUnmodifiableSet()); | ||
|
||
// check for all selections, remove non-aggregated selections. | ||
// keep only matching identifier expression with group by along with rest. | ||
LocalSelectTypeIdentifierExpressionSelector identifierExpressionSelector = | ||
new LocalSelectTypeIdentifierExpressionSelector(); | ||
List<SelectionSpec> finalSelectionSpecs = | ||
query.getSelections().stream() | ||
.filter(selectionSpec -> selectionSpec.getExpression().accept(this)) | ||
.filter( | ||
getSelectionSpecPredicate( | ||
groupByIdentifierExpressions, identifierExpressionSelector)) | ||
.collect(Collectors.toUnmodifiableList()); | ||
|
||
return finalSelectionSpecs.size() > 0 | ||
return finalSelectionSpecs.size() != query.getSelections().size() | ||
? new TransformedQueryBuilder(query).setSelections(finalSelectionSpecs).build() | ||
: query; | ||
} | ||
|
||
@Override | ||
public Boolean visit(AggregateExpression expression) { | ||
return true; | ||
private Predicate<SelectionSpec> getSelectionSpecPredicate( | ||
Set<GroupTypeExpression> groupByIdentifierExpressions, | ||
LocalSelectTypeIdentifierExpressionSelector identifierExpressionSelector) { | ||
return selectionSpec -> { | ||
SelectTypeExpression expression = selectionSpec.getExpression(); | ||
return (!(boolean) expression.accept(identifierExpressionSelector)) | ||
|| groupByIdentifierExpressions.contains(expression); | ||
}; | ||
} | ||
|
||
@Override | ||
public Boolean visit(ConstantExpression expression) { | ||
return false; | ||
private static class LocalSelectTypeAggregationExpressionSelector | ||
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. If this is just returning the instance type, do need a separate visitor hierarchy? Could we just do using instance of? 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. I think, this is a more readable pattern. |
||
implements SelectTypeExpressionVisitor { | ||
@Override | ||
public Boolean visit(AggregateExpression expression) { | ||
return true; | ||
} | ||
|
||
@Override | ||
public Boolean visit(ConstantExpression expression) { | ||
return false; | ||
} | ||
|
||
@Override | ||
public Boolean visit(FunctionExpression expression) { | ||
return false; | ||
} | ||
|
||
@Override | ||
public Boolean visit(IdentifierExpression expression) { | ||
return false; | ||
} | ||
} | ||
|
||
@Override | ||
public Boolean visit(FunctionExpression expression) { | ||
return false; | ||
private static class LocalSelectTypeIdentifierExpressionSelector | ||
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. Same here |
||
implements SelectTypeExpressionVisitor { | ||
@Override | ||
public Boolean visit(AggregateExpression expression) { | ||
return false; | ||
} | ||
|
||
@Override | ||
public Boolean visit(ConstantExpression expression) { | ||
return false; | ||
} | ||
|
||
@Override | ||
public Boolean visit(FunctionExpression expression) { | ||
return false; | ||
} | ||
|
||
@Override | ||
public Boolean visit(IdentifierExpression expression) { | ||
return true; | ||
} | ||
} | ||
|
||
@Override | ||
public Boolean visit(IdentifierExpression expression) { | ||
return false; | ||
private static class LocalGroupByIdentifierExpressionSelector | ||
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. Same |
||
implements GroupTypeExpressionVisitor { | ||
@Override | ||
public Boolean visit(FunctionExpression expression) { | ||
return false; | ||
} | ||
|
||
@Override | ||
public Boolean visit(IdentifierExpression expression) { | ||
return true; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -917,4 +917,35 @@ void testQueryWithFunctionalLhsInRelationalFilter() { | |
assertEquals(1, params.getObjectParams().size()); | ||
assertEquals(50, params.getObjectParams().get(1)); | ||
} | ||
|
||
@Test | ||
void testQueryQ1DistinctCountAggregationWithMatchingSelectionAndGroupBy() { | ||
org.hypertrace.core.documentstore.query.Query query = | ||
org.hypertrace.core.documentstore.query.Query.builder() | ||
.addSelection( | ||
AggregateExpression.of(DISTINCT_COUNT, IdentifierExpression.of("quantity")), | ||
"qty_count") | ||
.addSelection(IdentifierExpression.of("item")) | ||
.addSelection(IdentifierExpression.of("price")) | ||
.setFilter( | ||
RelationalExpression.of( | ||
IdentifierExpression.of("price"), LTE, ConstantExpression.of(10))) | ||
.addAggregation(IdentifierExpression.of("item")) | ||
.build(); | ||
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. Add price aggregation? |
||
|
||
PostgresQueryParser postgresQueryParser = | ||
new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); | ||
String sql = postgresQueryParser.parse(); | ||
|
||
assertEquals( | ||
"SELECT COUNT(DISTINCT document->>'quantity' ) AS \"qty_count\", " | ||
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. It confuses to see where the 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. Discussed offline. |
||
+ "document->'item' AS item " | ||
+ "FROM testCollection WHERE CAST (document->>'price' AS NUMERIC) <= ? " | ||
+ "GROUP BY document->'item'", | ||
sql); | ||
|
||
Params params = postgresQueryParser.getParamsBuilder().build(); | ||
assertEquals(1, params.getObjectParams().size()); | ||
assertEquals(10, params.getObjectParams().get(1)); | ||
} | ||
} |
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.
Add group by on
price
as well, because it's a part of the selection?