SelectExpressionAnalyzer.java

1
/*
2
 * Copyright OpenSearch Contributors
3
 * SPDX-License-Identifier: Apache-2.0
4
 */
5
6
7
package org.opensearch.sql.analysis;
8
9
import com.google.common.collect.ImmutableList;
10
import java.util.Collections;
11
import java.util.List;
12
import java.util.Map;
13
import java.util.stream.Collectors;
14
import lombok.RequiredArgsConstructor;
15
import org.opensearch.sql.analysis.symbol.Namespace;
16
import org.opensearch.sql.ast.AbstractNodeVisitor;
17
import org.opensearch.sql.ast.expression.Alias;
18
import org.opensearch.sql.ast.expression.AllFields;
19
import org.opensearch.sql.ast.expression.Field;
20
import org.opensearch.sql.ast.expression.QualifiedName;
21
import org.opensearch.sql.ast.expression.UnresolvedExpression;
22
import org.opensearch.sql.data.type.ExprType;
23
import org.opensearch.sql.expression.DSL;
24
import org.opensearch.sql.expression.Expression;
25
import org.opensearch.sql.expression.NamedExpression;
26
import org.opensearch.sql.expression.ReferenceExpression;
27
28
/**
29
 * Analyze the select list in the {@link AnalysisContext} to construct the list of
30
 * {@link NamedExpression}.
31
 */
32
@RequiredArgsConstructor
33
public class SelectExpressionAnalyzer
34
    extends
35
    AbstractNodeVisitor<List<NamedExpression>, AnalysisContext> {
36
  private final ExpressionAnalyzer expressionAnalyzer;
37
38
  private ExpressionReferenceOptimizer optimizer;
39
40
  /**
41
   * Analyze Select fields.
42
   */
43
  public List<NamedExpression> analyze(List<UnresolvedExpression> selectList,
44
                                       AnalysisContext analysisContext,
45
                                       ExpressionReferenceOptimizer optimizer) {
46
    this.optimizer = optimizer;
47
    ImmutableList.Builder<NamedExpression> builder = new ImmutableList.Builder<>();
48
    for (UnresolvedExpression unresolvedExpression : selectList) {
49
      builder.addAll(unresolvedExpression.accept(this, analysisContext));
50
    }
51 1 1. analyze : replaced return value with Collections.emptyList for org/opensearch/sql/analysis/SelectExpressionAnalyzer::analyze → KILLED
    return builder.build();
52
  }
53
54
  @Override
55
  public List<NamedExpression> visitField(Field node, AnalysisContext context) {
56 1 1. visitField : replaced return value with Collections.emptyList for org/opensearch/sql/analysis/SelectExpressionAnalyzer::visitField → KILLED
    return Collections.singletonList(DSL.named(node.accept(expressionAnalyzer, context)));
57
  }
58
59
  @Override
60
  public List<NamedExpression> visitAlias(Alias node, AnalysisContext context) {
61
    Expression expr = referenceIfSymbolDefined(node, context);
62 1 1. visitAlias : replaced return value with Collections.emptyList for org/opensearch/sql/analysis/SelectExpressionAnalyzer::visitAlias → KILLED
    return Collections.singletonList(DSL.named(
63
        unqualifiedNameIfFieldOnly(node, context),
64
        expr,
65
        node.getAlias()));
66
  }
67
68
  /**
69
   * The Alias could be
70
   * 1. SELECT name, AVG(age) FROM s BY name ->
71
   * Project(Alias("name", expr), Alias("AVG(age)", aggExpr))
72
   * Agg(Alias("AVG(age)", aggExpr))
73
   * 2. SELECT length(name), AVG(age) FROM s BY length(name)
74
   * Project(Alias("name", expr), Alias("AVG(age)", aggExpr))
75
   * Agg(Alias("AVG(age)", aggExpr))
76
   * 3. SELECT length(name) as l, AVG(age) FROM s BY l
77
   * Project(Alias("name", expr, l), Alias("AVG(age)", aggExpr))
78
   * Agg(Alias("AVG(age)", aggExpr), Alias("length(name)", groupExpr))
79
   */
80
  private Expression referenceIfSymbolDefined(Alias expr,
81
                                              AnalysisContext context) {
82
    UnresolvedExpression delegatedExpr = expr.getDelegated();
83
84
    // Pass named expression because expression like window function loses full name
85
    // (OVER clause) and thus depends on name in alias to be replaced correctly
86 1 1. referenceIfSymbolDefined : replaced return value with null for org/opensearch/sql/analysis/SelectExpressionAnalyzer::referenceIfSymbolDefined → KILLED
    return optimizer.optimize(
87
        DSL.named(
88
            expr.getName(),
89
            delegatedExpr.accept(expressionAnalyzer, context),
90
            expr.getAlias()),
91
        context);
92
  }
93
94
  @Override
95
  public List<NamedExpression> visitAllFields(AllFields node,
96
                                              AnalysisContext context) {
97
    TypeEnvironment environment = context.peek();
98
    Map<String, ExprType> lookupAllFields = environment.lookupAllFields(Namespace.FIELD_NAME);
99 2 1. lambda$visitAllFields$0 : replaced return value with null for org/opensearch/sql/analysis/SelectExpressionAnalyzer::lambda$visitAllFields$0 → KILLED
2. visitAllFields : replaced return value with Collections.emptyList for org/opensearch/sql/analysis/SelectExpressionAnalyzer::visitAllFields → KILLED
    return lookupAllFields.entrySet().stream().map(entry -> DSL.named(entry.getKey(),
100
        new ReferenceExpression(entry.getKey(), entry.getValue()))).collect(Collectors.toList());
101
  }
102
103
  /**
104
   * Get unqualified name if select item is just a field. For example, suppose an index
105
   * named "accounts", return "age" for "SELECT accounts.age". But do nothing for expression
106
   * in "SELECT ABS(accounts.age)".
107
   * Note that an assumption is made implicitly that original name field in Alias must be
108
   * the same as the values in QualifiedName. This is true because AST builder does this.
109
   * Otherwise, what unqualified() returns will override Alias's name as NamedExpression's name
110
   * even though the QualifiedName doesn't have qualifier.
111
   */
112
  private String unqualifiedNameIfFieldOnly(Alias node, AnalysisContext context) {
113
    UnresolvedExpression selectItem = node.getDelegated();
114 1 1. unqualifiedNameIfFieldOnly : negated conditional → KILLED
    if (selectItem instanceof QualifiedName) {
115
      QualifierAnalyzer qualifierAnalyzer = new QualifierAnalyzer(context);
116 1 1. unqualifiedNameIfFieldOnly : replaced return value with "" for org/opensearch/sql/analysis/SelectExpressionAnalyzer::unqualifiedNameIfFieldOnly → KILLED
      return qualifierAnalyzer.unqualified((QualifiedName) selectItem);
117
    }
118 1 1. unqualifiedNameIfFieldOnly : replaced return value with "" for org/opensearch/sql/analysis/SelectExpressionAnalyzer::unqualifiedNameIfFieldOnly → KILLED
    return node.getName();
119
  }
120
121
}

Mutations

51

1.1
Location : analyze
Killed by : org.opensearch.sql.analysis.SelectExpressionAnalyzerTest.[engine:junit-jupiter]/[class:org.opensearch.sql.analysis.SelectExpressionAnalyzerTest]/[method:named_expression_with_alias()]
replaced return value with Collections.emptyList for org/opensearch/sql/analysis/SelectExpressionAnalyzer::analyze → KILLED

56

1.1
Location : visitField
Killed by : org.opensearch.sql.analysis.SelectAnalyzeTest.[engine:junit-jupiter]/[class:org.opensearch.sql.analysis.SelectAnalyzeTest]/[method:select_and_project_all()]
replaced return value with Collections.emptyList for org/opensearch/sql/analysis/SelectExpressionAnalyzer::visitField → KILLED

62

1.1
Location : visitAlias
Killed by : org.opensearch.sql.analysis.SelectExpressionAnalyzerTest.[engine:junit-jupiter]/[class:org.opensearch.sql.analysis.SelectExpressionAnalyzerTest]/[method:named_expression_with_alias()]
replaced return value with Collections.emptyList for org/opensearch/sql/analysis/SelectExpressionAnalyzer::visitAlias → KILLED

86

1.1
Location : referenceIfSymbolDefined
Killed by : org.opensearch.sql.analysis.SelectExpressionAnalyzerTest.[engine:junit-jupiter]/[class:org.opensearch.sql.analysis.SelectExpressionAnalyzerTest]/[method:named_expression_with_alias()]
replaced return value with null for org/opensearch/sql/analysis/SelectExpressionAnalyzer::referenceIfSymbolDefined → KILLED

99

1.1
Location : lambda$visitAllFields$0
Killed by : org.opensearch.sql.analysis.SelectAnalyzeTest.[engine:junit-jupiter]/[class:org.opensearch.sql.analysis.SelectAnalyzeTest]/[method:select_and_project_all()]
replaced return value with null for org/opensearch/sql/analysis/SelectExpressionAnalyzer::lambda$visitAllFields$0 → KILLED

2.2
Location : visitAllFields
Killed by : org.opensearch.sql.analysis.SelectAnalyzeTest.[engine:junit-jupiter]/[class:org.opensearch.sql.analysis.SelectAnalyzeTest]/[method:select_and_project_all()]
replaced return value with Collections.emptyList for org/opensearch/sql/analysis/SelectExpressionAnalyzer::visitAllFields → KILLED

114

1.1
Location : unqualifiedNameIfFieldOnly
Killed by : org.opensearch.sql.analysis.SelectExpressionAnalyzerTest.[engine:junit-jupiter]/[class:org.opensearch.sql.analysis.SelectExpressionAnalyzerTest]/[method:field_name_with_qualifier_quoted()]
negated conditional → KILLED

116

1.1
Location : unqualifiedNameIfFieldOnly
Killed by : org.opensearch.sql.analysis.SelectExpressionAnalyzerTest.[engine:junit-jupiter]/[class:org.opensearch.sql.analysis.SelectExpressionAnalyzerTest]/[method:named_expression_with_alias()]
replaced return value with "" for org/opensearch/sql/analysis/SelectExpressionAnalyzer::unqualifiedNameIfFieldOnly → KILLED

118

1.1
Location : unqualifiedNameIfFieldOnly
Killed by : org.opensearch.sql.analysis.SelectExpressionAnalyzerTest.[engine:junit-jupiter]/[class:org.opensearch.sql.analysis.SelectExpressionAnalyzerTest]/[method:field_name_in_expression_with_qualifier()]
replaced return value with "" for org/opensearch/sql/analysis/SelectExpressionAnalyzer::unqualifiedNameIfFieldOnly → KILLED

Active mutators

Tests examined


Report generated by PIT 1.9.0