|
51 | 51 | import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Row; |
52 | 52 | import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.SearchedCaseExpression; |
53 | 53 | import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.SimpleCaseExpression; |
| 54 | +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.SortItem; |
54 | 55 | import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.SubqueryExpression; |
55 | 56 | import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Trim; |
56 | 57 | import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.WhenClause; |
| 58 | +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Window; |
| 59 | +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.WindowFrame; |
| 60 | +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.WindowSpecification; |
57 | 61 | import org.apache.iotdb.db.i18n.DataNodeQueryMessages; |
58 | 62 | import org.apache.iotdb.db.queryengine.plan.relational.planner.ScopeAware; |
59 | 63 | import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.AstVisitor; |
|
73 | 77 | import static java.lang.String.format; |
74 | 78 | import static java.util.Objects.requireNonNull; |
75 | 79 | import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.ExpressionTreeUtils.extractAggregateFunctions; |
| 80 | +import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.ExpressionTreeUtils.extractWindowExpressions; |
76 | 81 | import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.ExpressionTreeUtils.isAggregationFunction; |
77 | 82 | import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.ScopeReferenceExtractor.getReferencesToScope; |
78 | 83 | import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.ScopeReferenceExtractor.hasReferencesToScope; |
79 | 84 | import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.ScopeReferenceExtractor.isFieldFromScope; |
80 | 85 | import static org.apache.iotdb.db.queryengine.plan.relational.planner.ScopeAware.scopeAwareKey; |
| 86 | +import static org.apache.iotdb.db.queryengine.plan.relational.utils.NodeUtils.getSortItemsFromOrderBy; |
81 | 87 |
|
82 | 88 | /** Checks whether an expression is constant with respect to the group */ |
83 | 89 | class AggregationAnalyzer { |
@@ -288,19 +294,85 @@ public Boolean visitTrim(Trim node, Void context) { |
288 | 294 | @Override |
289 | 295 | public Boolean visitFunctionCall(FunctionCall node, Void context) { |
290 | 296 | if (isAggregationFunction(node.getName().toString())) { |
291 | | - List<FunctionCall> aggregateFunctions = extractAggregateFunctions(node.getArguments()); |
| 297 | + if (node.getWindow().isEmpty()) { |
| 298 | + List<FunctionCall> aggregateFunctions = extractAggregateFunctions(node.getArguments()); |
| 299 | + List<Expression> windowExpressions = extractWindowExpressions(node.getArguments()); |
| 300 | + |
| 301 | + if (!aggregateFunctions.isEmpty()) { |
| 302 | + throw new SemanticException( |
| 303 | + String.format( |
| 304 | + "Cannot nest aggregations inside aggregation '%s': %s", |
| 305 | + node.getName(), aggregateFunctions)); |
| 306 | + } |
| 307 | + |
| 308 | + if (!windowExpressions.isEmpty()) { |
| 309 | + throw new SemanticException( |
| 310 | + String.format( |
| 311 | + "Cannot nest window functions inside aggregation '%s': %s", |
| 312 | + node.getName(), windowExpressions)); |
| 313 | + } |
| 314 | + |
| 315 | + return true; |
| 316 | + } |
| 317 | + } else { |
| 318 | + // Reject FILTER for non-aggregation functions when FunctionCall supports FILTER. |
| 319 | + // Reject ORDER BY for non-aggregation functions when FunctionCall supports function-level |
| 320 | + // ORDER BY. |
| 321 | + } |
| 322 | + |
| 323 | + if (node.getWindow().isPresent()) { |
| 324 | + Window window = node.getWindow().get(); |
| 325 | + if (window instanceof WindowSpecification windowSpecification |
| 326 | + && !process(windowSpecification, context)) { |
| 327 | + return false; |
| 328 | + } |
| 329 | + } |
| 330 | + |
| 331 | + return node.getArguments().stream().allMatch(expression -> process(expression, context)); |
| 332 | + } |
292 | 333 |
|
293 | | - if (!aggregateFunctions.isEmpty()) { |
| 334 | + @Override |
| 335 | + public Boolean visitWindowSpecification(WindowSpecification node, Void context) { |
| 336 | + for (Expression expression : node.getPartitionBy()) { |
| 337 | + if (!process(expression, context)) { |
294 | 338 | throw new SemanticException( |
295 | 339 | String.format( |
296 | | - "Cannot nest aggregations inside aggregation '%s': %s", |
297 | | - node.getName(), aggregateFunctions)); |
| 340 | + "PARTITION BY expression '%s' must be an aggregate expression or appear in GROUP BY clause", |
| 341 | + expression)); |
298 | 342 | } |
| 343 | + } |
299 | 344 |
|
300 | | - return true; |
| 345 | + for (SortItem sortItem : getSortItemsFromOrderBy(node.getOrderBy())) { |
| 346 | + Expression expression = sortItem.getSortKey(); |
| 347 | + if (!process(expression, context)) { |
| 348 | + throw new SemanticException( |
| 349 | + String.format( |
| 350 | + "ORDER BY expression '%s' must be an aggregate expression or appear in GROUP BY clause", |
| 351 | + expression)); |
| 352 | + } |
301 | 353 | } |
302 | 354 |
|
303 | | - return node.getArguments().stream().allMatch(expression -> process(expression, context)); |
| 355 | + if (node.getFrame().isPresent()) { |
| 356 | + process(node.getFrame().get(), context); |
| 357 | + } |
| 358 | + |
| 359 | + return true; |
| 360 | + } |
| 361 | + |
| 362 | + @Override |
| 363 | + public Boolean visitWindowFrame(WindowFrame node, Void context) { |
| 364 | + if (node.getStart().getValue().isPresent() |
| 365 | + && !process(node.getStart().getValue().get(), context)) { |
| 366 | + throw new SemanticException( |
| 367 | + "Window frame start must be an aggregate expression or appear in GROUP BY clause"); |
| 368 | + } |
| 369 | + if (node.getEnd().isPresent() |
| 370 | + && node.getEnd().get().getValue().isPresent() |
| 371 | + && !process(node.getEnd().get().getValue().get(), context)) { |
| 372 | + throw new SemanticException( |
| 373 | + "Window frame end must be an aggregate expression or appear in GROUP BY clause"); |
| 374 | + } |
| 375 | + return true; |
304 | 376 | } |
305 | 377 |
|
306 | 378 | @Override |
|
0 commit comments