-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Rewrite EARLIEST/LATEST query operators to EARLIEST_BY/LATEST_BY #15095
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
7760674
9100e22
521d1ea
2af7ede
178bee6
b4154b3
8d36064
14a0d64
6e23061
354029e
b98ffcf
295d4a7
0d9cba9
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 | ||||
|---|---|---|---|---|---|---|
|
|
@@ -25,15 +25,23 @@ | |||||
| import org.apache.calcite.rex.RexBuilder; | ||||||
| import org.apache.calcite.rex.RexLiteral; | ||||||
| import org.apache.calcite.rex.RexNode; | ||||||
| import org.apache.calcite.runtime.CalciteContextException; | ||||||
| import org.apache.calcite.sql.SqlAggFunction; | ||||||
| import org.apache.calcite.sql.SqlCall; | ||||||
| import org.apache.calcite.sql.SqlFunctionCategory; | ||||||
| import org.apache.calcite.sql.SqlIdentifier; | ||||||
| import org.apache.calcite.sql.SqlKind; | ||||||
| import org.apache.calcite.sql.SqlNode; | ||||||
| import org.apache.calcite.sql.SqlOperatorBinding; | ||||||
| import org.apache.calcite.sql.parser.SqlParserPos; | ||||||
| import org.apache.calcite.sql.type.InferTypes; | ||||||
| import org.apache.calcite.sql.type.OperandTypes; | ||||||
| import org.apache.calcite.sql.type.SqlReturnTypeInference; | ||||||
| import org.apache.calcite.sql.type.SqlTypeName; | ||||||
| import org.apache.calcite.sql.type.SqlTypeUtil; | ||||||
| import org.apache.calcite.sql.util.SqlVisitor; | ||||||
| import org.apache.calcite.sql.validate.SqlValidator; | ||||||
| import org.apache.calcite.sql.validate.SqlValidatorException; | ||||||
| import org.apache.calcite.util.Optionality; | ||||||
| import org.apache.druid.error.DruidException; | ||||||
| import org.apache.druid.error.InvalidSqlInput; | ||||||
|
|
@@ -63,15 +71,22 @@ | |||||
| import org.apache.druid.sql.calcite.rel.VirtualColumnRegistry; | ||||||
|
|
||||||
| import javax.annotation.Nullable; | ||||||
| import java.util.ArrayList; | ||||||
| import java.util.Collections; | ||||||
| import java.util.List; | ||||||
| import java.util.stream.Collectors; | ||||||
|
|
||||||
| public class EarliestLatestAnySqlAggregator implements SqlAggregator | ||||||
| { | ||||||
| public static final SqlAggregator EARLIEST = new EarliestLatestAnySqlAggregator(AggregatorType.EARLIEST); | ||||||
| public static final SqlAggregator LATEST = new EarliestLatestAnySqlAggregator(AggregatorType.LATEST); | ||||||
| public static final SqlAggregator ANY_VALUE = new EarliestLatestAnySqlAggregator(AggregatorType.ANY_VALUE); | ||||||
| public static final SqlAggregator EARLIEST = new EarliestLatestAnySqlAggregator( | ||||||
| AggregatorType.EARLIEST, | ||||||
| EarliestLatestBySqlAggregator.EARLIEST_BY.calciteFunction() | ||||||
| ); | ||||||
| public static final SqlAggregator LATEST = new EarliestLatestAnySqlAggregator( | ||||||
| AggregatorType.LATEST, | ||||||
| EarliestLatestBySqlAggregator.LATEST_BY.calciteFunction() | ||||||
| ); | ||||||
| public static final SqlAggregator ANY_VALUE = new EarliestLatestAnySqlAggregator(AggregatorType.ANY_VALUE, null); | ||||||
|
|
||||||
| enum AggregatorType | ||||||
| { | ||||||
|
|
@@ -164,10 +179,10 @@ abstract AggregatorFactory createAggregatorFactory( | |||||
| private final AggregatorType aggregatorType; | ||||||
| private final SqlAggFunction function; | ||||||
|
|
||||||
| private EarliestLatestAnySqlAggregator(final AggregatorType aggregatorType) | ||||||
| private EarliestLatestAnySqlAggregator(final AggregatorType aggregatorType, final SqlAggFunction replacementAggFunc) | ||||||
| { | ||||||
| this.aggregatorType = aggregatorType; | ||||||
| this.function = new EarliestLatestSqlAggFunction(aggregatorType); | ||||||
| this.function = new EarliestLatestSqlAggFunction(aggregatorType, replacementAggFunc); | ||||||
| } | ||||||
|
|
||||||
| @Override | ||||||
|
|
@@ -313,12 +328,48 @@ public RelDataType inferReturnType(SqlOperatorBinding sqlOperatorBinding) | |||||
| } | ||||||
| } | ||||||
|
|
||||||
| private static class TimeColIdentifer extends SqlIdentifier | ||||||
| { | ||||||
|
|
||||||
| public TimeColIdentifer() | ||||||
| { | ||||||
| super("__time", SqlParserPos.ZERO); | ||||||
| } | ||||||
|
|
||||||
| @Override | ||||||
| public <R> R accept(SqlVisitor<R> visitor) | ||||||
| { | ||||||
|
|
||||||
| try { | ||||||
| return super.accept(visitor); | ||||||
| } | ||||||
| catch (CalciteContextException e) { | ||||||
| if (e.getCause() instanceof SqlValidatorException) { | ||||||
| throw DruidException.forPersona(DruidException.Persona.ADMIN) | ||||||
|
Contributor
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.
Suggested change
Contributor
Author
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. Chose
Contributor
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. This is the reason you are looking for: https://github.com/apache/druid/blob/master/sql/src/main/java/org/apache/druid/sql/calcite/planner/QueryHandler.java#L692 However, in this case, it seems like we do know the exact reason why the query failed - It's due to the absence of a time column. So perhaps change the wording to be more assertive. Also, we should change it to user, because of the reason highlighted in the link above.
Member
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'm not sure if the reason will always be absence of time column - what if there's a case of a join where
Contributor
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 don't know what will admin do with these planning errors even if they are hints. The hints are meant for the end-user. I don't want to hold this PR so would let it merge as it is. |
||||||
| .ofCategory(DruidException.Category.INVALID_INPUT) | ||||||
| .build( | ||||||
| e, | ||||||
| "Query could not be planned. A possible reason is [%s]", | ||||||
|
abhishekagarwal87 marked this conversation as resolved.
|
||||||
| "LATEST and EARLIEST aggregators implicitly depend on the __time column, but the " | ||||||
| + "table queried doesn't contain a __time column. Please use LATEST_BY or EARLIEST_BY " | ||||||
| + "and specify the column explicitly." | ||||||
| ); | ||||||
|
|
||||||
| } else { | ||||||
| throw e; | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| private static class EarliestLatestSqlAggFunction extends SqlAggFunction | ||||||
| { | ||||||
| private static final EarliestLatestReturnTypeInference EARLIEST_LATEST_ARG0_RETURN_TYPE_INFERENCE = | ||||||
| new EarliestLatestReturnTypeInference(0); | ||||||
|
|
||||||
| EarliestLatestSqlAggFunction(AggregatorType aggregatorType) | ||||||
| private final SqlAggFunction replacementAggFunc; | ||||||
|
|
||||||
| EarliestLatestSqlAggFunction(AggregatorType aggregatorType, SqlAggFunction replacementAggFunc) | ||||||
| { | ||||||
| super( | ||||||
| aggregatorType.name(), | ||||||
|
|
@@ -339,6 +390,43 @@ private static class EarliestLatestSqlAggFunction extends SqlAggFunction | |||||
| false, | ||||||
| Optionality.FORBIDDEN | ||||||
| ); | ||||||
| this.replacementAggFunc = replacementAggFunc; | ||||||
| } | ||||||
|
|
||||||
| @Override | ||||||
| public SqlNode rewriteCall( | ||||||
|
Member
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 wonder if after this patch constructors of classes like
Contributor
Author
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. Good point! I think it shouldn't. Maybe can take up those modifications in a separate PR. |
||||||
| SqlValidator validator, | ||||||
| SqlCall call | ||||||
| ) | ||||||
| { | ||||||
| // Rewrite EARLIEST/LATEST to EARLIEST_BY/LATEST_BY to make | ||||||
| // reference to __time column explicit so that Calcite tracks it | ||||||
|
|
||||||
| if (replacementAggFunc == null) { | ||||||
| return call; | ||||||
| } | ||||||
|
|
||||||
| List<SqlNode> operands = call.getOperandList(); | ||||||
|
|
||||||
| SqlParserPos pos = call.getParserPosition(); | ||||||
|
|
||||||
| if (operands.isEmpty() || operands.size() > 2) { | ||||||
| throw InvalidSqlInput.exception( | ||||||
| "Function [%s] expects 1 or 2 arguments but found [%s]", | ||||||
| getName(), | ||||||
| operands.size() | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| List<SqlNode> newOperands = new ArrayList<>(); | ||||||
| newOperands.add(operands.get(0)); | ||||||
| newOperands.add(new TimeColIdentifer()); | ||||||
|
|
||||||
| if (operands.size() == 2) { | ||||||
| newOperands.add(operands.get(1)); | ||||||
| } | ||||||
|
gargvishesh marked this conversation as resolved.
|
||||||
|
|
||||||
| return replacementAggFunc.createCall(pos, newOperands); | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
Uh oh!
There was an error while loading. Please reload this page.