|
31 | 31 | import java.util.List; |
32 | 32 | import java.util.Objects; |
33 | 33 | import java.util.Optional; |
| 34 | +import java.util.Set; |
34 | 35 | import java.util.stream.Collectors; |
35 | 36 | import java.util.stream.Stream; |
36 | 37 | import org.apache.commons.lang3.tuple.ImmutablePair; |
@@ -284,13 +285,6 @@ public LogicalPlan visitFilter(Filter node, AnalysisContext context) { |
284 | 285 | return new LogicalFilter(child, optimized); |
285 | 286 | } |
286 | 287 |
|
287 | | - /** |
288 | | - * Ensure NESTED function is not used in GROUP BY, and HAVING clauses. Fallback to legacy engine. |
289 | | - * Can remove when support is added for NESTED function in WHERE, GROUP BY, ORDER BY, and HAVING |
290 | | - * clauses. |
291 | | - * |
292 | | - * @param condition : Filter condition |
293 | | - */ |
294 | 288 | private void verifySupportsCondition(Expression condition) { |
295 | 289 | if (condition instanceof FunctionExpression) { |
296 | 290 | if (((FunctionExpression) condition) |
@@ -386,53 +380,106 @@ public LogicalPlan visitRareTopN(RareTopN node, AnalysisContext context) { |
386 | 380 | public LogicalPlan visitProject(Project node, AnalysisContext context) { |
387 | 381 | LogicalPlan child = node.getChild().get(0).accept(this, context); |
388 | 382 |
|
389 | | - if (node.hasArgument()) { |
390 | | - Argument argument = node.getArgExprList().get(0); |
391 | | - Boolean exclude = (Boolean) argument.getValue().getValue(); |
392 | | - if (exclude) { |
393 | | - TypeEnvironment curEnv = context.peek(); |
394 | | - List<ReferenceExpression> referenceExpressions = |
395 | | - node.getProjectList().stream() |
396 | | - .map(expr -> (ReferenceExpression) expressionAnalyzer.analyze(expr, context)) |
397 | | - .collect(Collectors.toList()); |
398 | | - referenceExpressions.forEach(ref -> curEnv.remove(ref)); |
399 | | - return new LogicalRemove(child, ImmutableSet.copyOf(referenceExpressions)); |
400 | | - } |
401 | | - } |
402 | | - |
403 | | - // For each unresolved window function, analyze it by "insert" a window and sort operator |
404 | | - // between project and its child. |
405 | | - for (UnresolvedExpression expr : node.getProjectList()) { |
406 | | - WindowExpressionAnalyzer windowAnalyzer = |
407 | | - new WindowExpressionAnalyzer(expressionAnalyzer, child); |
408 | | - child = windowAnalyzer.analyze(expr, context); |
| 383 | + if (isExcludeMode(node)) { |
| 384 | + return buildLogicalRemove(node, child, context); |
409 | 385 | } |
410 | 386 |
|
411 | | - for (UnresolvedExpression expr : node.getProjectList()) { |
412 | | - HighlightAnalyzer highlightAnalyzer = new HighlightAnalyzer(expressionAnalyzer, child); |
413 | | - child = highlightAnalyzer.analyze(expr, context); |
414 | | - } |
| 387 | + child = processWindowExpressions(node.getProjectList(), child, context); |
| 388 | + child = processHighlightExpressions(node.getProjectList(), child, context); |
415 | 389 |
|
416 | 390 | List<NamedExpression> namedExpressions = |
417 | | - selectExpressionAnalyzer.analyze( |
418 | | - node.getProjectList(), |
419 | | - context, |
420 | | - new ExpressionReferenceOptimizer(expressionAnalyzer.getRepository(), child)); |
421 | | - |
422 | | - for (UnresolvedExpression expr : node.getProjectList()) { |
423 | | - NestedAnalyzer nestedAnalyzer = |
424 | | - new NestedAnalyzer(namedExpressions, expressionAnalyzer, child); |
425 | | - child = nestedAnalyzer.analyze(expr, context); |
426 | | - } |
| 391 | + resolveFieldExpressions(node.getProjectList(), child, context); |
| 392 | + |
| 393 | + child = processNestedAnalysis(node.getProjectList(), namedExpressions, child, context); |
427 | 394 |
|
428 | | - // new context |
429 | 395 | context.push(); |
430 | 396 | TypeEnvironment newEnv = context.peek(); |
431 | 397 | namedExpressions.forEach( |
432 | 398 | expr -> |
433 | 399 | newEnv.define(new Symbol(Namespace.FIELD_NAME, expr.getNameOrAlias()), expr.type())); |
434 | | - List<NamedExpression> namedParseExpressions = context.getNamedParseExpressions(); |
435 | | - return new LogicalProject(child, namedExpressions, namedParseExpressions); |
| 400 | + |
| 401 | + return new LogicalProject(child, namedExpressions, context.getNamedParseExpressions()); |
| 402 | + } |
| 403 | + |
| 404 | + private boolean isExcludeMode(Project node) { |
| 405 | + if (!node.hasArgument()) { |
| 406 | + return false; |
| 407 | + } |
| 408 | + try { |
| 409 | + Argument argument = node.getArgExprList().get(0); |
| 410 | + Object value = argument.getValue().getValue(); |
| 411 | + return Boolean.TRUE.equals(value); |
| 412 | + } catch (IndexOutOfBoundsException | NullPointerException e) { |
| 413 | + return false; |
| 414 | + } |
| 415 | + } |
| 416 | + |
| 417 | + private LogicalRemove buildLogicalRemove( |
| 418 | + Project node, LogicalPlan child, AnalysisContext context) { |
| 419 | + TypeEnvironment curEnv = context.peek(); |
| 420 | + List<ReferenceExpression> referenceExpressions = |
| 421 | + collectExclusionFields(node.getProjectList(), context); |
| 422 | + |
| 423 | + Set<String> allFields = curEnv.lookupAllFields(Namespace.FIELD_NAME).keySet(); |
| 424 | + Set<String> fieldsToExclude = |
| 425 | + referenceExpressions.stream().map(ReferenceExpression::getAttr).collect(Collectors.toSet()); |
| 426 | + |
| 427 | + if (allFields.equals(fieldsToExclude)) { |
| 428 | + throw new IllegalArgumentException( |
| 429 | + "Invalid field exclusion: operation would exclude all fields from the result set"); |
| 430 | + } |
| 431 | + |
| 432 | + referenceExpressions.forEach(curEnv::remove); |
| 433 | + return new LogicalRemove(child, ImmutableSet.copyOf(referenceExpressions)); |
| 434 | + } |
| 435 | + |
| 436 | + private LogicalPlan processWindowExpressions( |
| 437 | + List<UnresolvedExpression> projectList, LogicalPlan child, AnalysisContext context) { |
| 438 | + for (UnresolvedExpression expr : projectList) { |
| 439 | + child = new WindowExpressionAnalyzer(expressionAnalyzer, child).analyze(expr, context); |
| 440 | + } |
| 441 | + return child; |
| 442 | + } |
| 443 | + |
| 444 | + private LogicalPlan processHighlightExpressions( |
| 445 | + List<UnresolvedExpression> projectList, LogicalPlan child, AnalysisContext context) { |
| 446 | + for (UnresolvedExpression expr : projectList) { |
| 447 | + child = new HighlightAnalyzer(expressionAnalyzer, child).analyze(expr, context); |
| 448 | + } |
| 449 | + return child; |
| 450 | + } |
| 451 | + |
| 452 | + private List<NamedExpression> resolveFieldExpressions( |
| 453 | + List<UnresolvedExpression> projectList, LogicalPlan child, AnalysisContext context) { |
| 454 | + return selectExpressionAnalyzer.analyze( |
| 455 | + projectList, |
| 456 | + context, |
| 457 | + new ExpressionReferenceOptimizer(expressionAnalyzer.getRepository(), child)); |
| 458 | + } |
| 459 | + |
| 460 | + private LogicalPlan processNestedAnalysis( |
| 461 | + List<UnresolvedExpression> projectList, |
| 462 | + List<NamedExpression> namedExpressions, |
| 463 | + LogicalPlan child, |
| 464 | + AnalysisContext context) { |
| 465 | + for (UnresolvedExpression expr : projectList) { |
| 466 | + child = |
| 467 | + new NestedAnalyzer(namedExpressions, expressionAnalyzer, child).analyze(expr, context); |
| 468 | + } |
| 469 | + return child; |
| 470 | + } |
| 471 | + |
| 472 | + private List<ReferenceExpression> collectExclusionFields( |
| 473 | + List<UnresolvedExpression> projectList, AnalysisContext context) { |
| 474 | + List<NamedExpression> namedExpressions = |
| 475 | + projectList.stream() |
| 476 | + .map(expr -> expressionAnalyzer.analyze(expr, context)) |
| 477 | + .map(DSL::named) |
| 478 | + .collect(Collectors.toList()); |
| 479 | + |
| 480 | + return namedExpressions.stream() |
| 481 | + .map(field -> (ReferenceExpression) field.getDelegated()) |
| 482 | + .collect(Collectors.toList()); |
436 | 483 | } |
437 | 484 |
|
438 | 485 | /** Build {@link LogicalEval}. */ |
@@ -746,10 +793,6 @@ private LogicalSort buildSort( |
746 | 793 | return new LogicalSort(child, count, sortList); |
747 | 794 | } |
748 | 795 |
|
749 | | - /** |
750 | | - * The first argument is always "asc", others are optional. Given nullFirst argument, use its |
751 | | - * value. Otherwise just use DEFAULT_ASC/DESC. |
752 | | - */ |
753 | 796 | private SortOption analyzeSortOption(List<Argument> fieldArgs) { |
754 | 797 | Boolean asc = (Boolean) fieldArgs.get(0).getValue().getValue(); |
755 | 798 | Optional<Argument> nullFirst = |
|
0 commit comments