ExpressionReferenceOptimizer.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 java.util.HashMap;
10
import java.util.List;
11
import java.util.Map;
12
import java.util.stream.Collectors;
13
import org.opensearch.sql.expression.Expression;
14
import org.opensearch.sql.expression.ExpressionNodeVisitor;
15
import org.opensearch.sql.expression.FunctionExpression;
16
import org.opensearch.sql.expression.NamedExpression;
17
import org.opensearch.sql.expression.ReferenceExpression;
18
import org.opensearch.sql.expression.aggregation.Aggregator;
19
import org.opensearch.sql.expression.conditional.cases.CaseClause;
20
import org.opensearch.sql.expression.conditional.cases.WhenClause;
21
import org.opensearch.sql.expression.function.BuiltinFunctionRepository;
22
import org.opensearch.sql.planner.logical.LogicalAggregation;
23
import org.opensearch.sql.planner.logical.LogicalPlan;
24
import org.opensearch.sql.planner.logical.LogicalPlanNodeVisitor;
25
import org.opensearch.sql.planner.logical.LogicalWindow;
26
27
/**
28
 * The optimizer used to replace the expression referred in the SelectClause
29
 * e.g. The query SELECT abs(name), sum(age)-avg(age) FROM test GROUP BY abs(name).
30
 * will be translated the AST
31
 * Project[abs(age), sub(sum(age), avg(age))
32
 *  Agg(agg=[sum(age), avg(age)], group=[abs(age)]]
33
 *   Relation
34
 * The sum(age) and avg(age) in the Project could be replace by the analyzed reference, the
35
 * LogicalPlan should be
36
 * LogicalProject[Ref("abs(age)"), sub(Ref("sum(age)"), Ref("avg(age)"))
37
 *  LogicalAgg(agg=[sum(age), avg(age)], group=[abs(age)]]
38
 *   LogicalRelation
39
 */
40
public class ExpressionReferenceOptimizer
41
    extends ExpressionNodeVisitor<Expression, AnalysisContext> {
42
  private final BuiltinFunctionRepository repository;
43
44
  /**
45
   * The map of expression and it's reference.
46
   * For example, The NamedAggregator should produce the map of Aggregator to Ref(name)
47
   */
48
  private final Map<Expression, Expression> expressionMap = new HashMap<>();
49
50
  public ExpressionReferenceOptimizer(
51
      BuiltinFunctionRepository repository, LogicalPlan logicalPlan) {
52
    this.repository = repository;
53
    logicalPlan.accept(new ExpressionMapBuilder(), null);
54
  }
55
56
  public Expression optimize(Expression analyzed, AnalysisContext context) {
57 1 1. optimize : replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer::optimize → KILLED
    return analyzed.accept(this, context);
58
  }
59
60
  @Override
61
  public Expression visitNode(Expression node, AnalysisContext context) {
62 1 1. visitNode : replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer::visitNode → KILLED
    return node;
63
  }
64
65
  @Override
66
  public Expression visitFunction(FunctionExpression node, AnalysisContext context) {
67 1 1. visitFunction : negated conditional → KILLED
    if (expressionMap.containsKey(node)) {
68 1 1. visitFunction : replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer::visitFunction → KILLED
      return expressionMap.get(node);
69
    } else {
70
      final List<Expression> args =
71 1 1. lambda$visitFunction$0 : replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer::lambda$visitFunction$0 → KILLED
          node.getArguments().stream().map(expr -> expr.accept(this, context))
72
              .collect(Collectors.toList());
73 1 1. visitFunction : replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer::visitFunction → KILLED
      return (Expression) repository.compile(node.getFunctionName(), args);
74
    }
75
  }
76
77
  @Override
78
  public Expression visitAggregator(Aggregator<?> node, AnalysisContext context) {
79 1 1. visitAggregator : replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer::visitAggregator → KILLED
    return expressionMap.getOrDefault(node, node);
80
  }
81
82
  @Override
83
  public Expression visitNamed(NamedExpression node, AnalysisContext context) {
84 1 1. visitNamed : negated conditional → KILLED
    if (expressionMap.containsKey(node)) {
85 1 1. visitNamed : replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer::visitNamed → KILLED
      return expressionMap.get(node);
86
    }
87 1 1. visitNamed : replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer::visitNamed → KILLED
    return node.getDelegated().accept(this, context);
88
  }
89
90
  /**
91
   * Implement this because Case/When is not registered in function repository.
92
   */
93
  @Override
94
  public Expression visitCase(CaseClause node, AnalysisContext context) {
95 1 1. visitCase : negated conditional → KILLED
    if (expressionMap.containsKey(node)) {
96 1 1. visitCase : replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer::visitCase → KILLED
      return expressionMap.get(node);
97
    }
98
99
    List<WhenClause> whenClauses = node.getWhenClauses()
100
                                       .stream()
101 1 1. lambda$visitCase$1 : replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer::lambda$visitCase$1 → KILLED
                                       .map(expr -> (WhenClause) expr.accept(this, context))
102
                                       .collect(Collectors.toList());
103
    Expression defaultResult = null;
104 1 1. visitCase : negated conditional → KILLED
    if (node.getDefaultResult() != null) {
105
      defaultResult = node.getDefaultResult().accept(this, context);
106
    }
107 1 1. visitCase : replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer::visitCase → KILLED
    return new CaseClause(whenClauses, defaultResult);
108
  }
109
110
  @Override
111
  public Expression visitWhen(WhenClause node, AnalysisContext context) {
112 1 1. visitWhen : replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer::visitWhen → KILLED
    return new WhenClause(
113
        node.getCondition().accept(this, context),
114
        node.getResult().accept(this, context));
115
  }
116
117
118
  /**
119
   * Expression Map Builder.
120
   */
121
  class ExpressionMapBuilder extends LogicalPlanNodeVisitor<Void, Void> {
122
123
    @Override
124
    public Void visitNode(LogicalPlan plan, Void context) {
125 1 1. visitNode : removed call to java/util/List::forEach → KILLED
      plan.getChild().forEach(child -> child.accept(this, context));
126
      return null;
127
    }
128
129
    @Override
130
    public Void visitAggregation(LogicalAggregation plan, Void context) {
131
      // Create the mapping for all the aggregator.
132 1 1. visitAggregation : removed call to java/util/List::forEach → KILLED
      plan.getAggregatorList().forEach(namedAggregator -> expressionMap
133
          .put(namedAggregator.getDelegated(),
134
              new ReferenceExpression(namedAggregator.getName(), namedAggregator.type())));
135
      // Create the mapping for all the group by.
136 1 1. visitAggregation : removed call to java/util/List::forEach → KILLED
      plan.getGroupByList().forEach(groupBy -> expressionMap
137
          .put(groupBy.getDelegated(),
138
              new ReferenceExpression(groupBy.getNameOrAlias(), groupBy.type())));
139
      return null;
140
    }
141
142
    @Override
143
    public Void visitWindow(LogicalWindow plan, Void context) {
144
      Expression windowFunc = plan.getWindowFunction();
145
      expressionMap.put(windowFunc,
146
          new ReferenceExpression(((NamedExpression) windowFunc).getName(), windowFunc.type()));
147 1 1. visitWindow : replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer$ExpressionMapBuilder::visitWindow → SURVIVED
      return visitNode(plan, context);
148
    }
149
  }
150
}

Mutations

57

1.1
Location : optimize
Killed by : org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest.[engine:junit-jupiter]/[class:org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest]/[method:window_expression_should_be_replaced()]
replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer::optimize → KILLED

62

1.1
Location : visitNode
Killed by : org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest.[engine:junit-jupiter]/[class:org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest]/[method:expression_without_aggregation_should_not_be_replaced()]
replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer::visitNode → KILLED

67

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

68

1.1
Location : visitFunction
Killed by : org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest.[engine:junit-jupiter]/[class:org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest]/[method:group_expression_should_be_replaced()]
replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer::visitFunction → KILLED

71

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

73

1.1
Location : visitFunction
Killed by : org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest.[engine:junit-jupiter]/[class:org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest]/[method:expression_without_aggregation_should_not_be_replaced()]
replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer::visitFunction → KILLED

79

1.1
Location : visitAggregator
Killed by : org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest.[engine:junit-jupiter]/[class:org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest]/[method:aggregation_expression_should_be_replaced()]
replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer::visitAggregator → KILLED

84

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

85

1.1
Location : visitNamed
Killed by : org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest.[engine:junit-jupiter]/[class:org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest]/[method:window_expression_should_be_replaced()]
replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer::visitNamed → KILLED

87

1.1
Location : visitNamed
Killed by : org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest.[engine:junit-jupiter]/[class:org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest]/[method:case_clause_should_be_replaced()]
replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer::visitNamed → KILLED

95

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

96

1.1
Location : visitCase
Killed by : org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest.[engine:junit-jupiter]/[class:org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest]/[method:case_clause_should_be_replaced()]
replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer::visitCase → KILLED

101

1.1
Location : lambda$visitCase$1
Killed by : org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest.[engine:junit-jupiter]/[class:org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest]/[method:aggregation_in_case_when_clause_should_be_replaced()]
replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer::lambda$visitCase$1 → KILLED

104

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

107

1.1
Location : visitCase
Killed by : org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest.[engine:junit-jupiter]/[class:org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest]/[method:aggregation_in_case_when_clause_should_be_replaced()]
replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer::visitCase → KILLED

112

1.1
Location : visitWhen
Killed by : org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest.[engine:junit-jupiter]/[class:org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest]/[method:aggregation_in_case_when_clause_should_be_replaced()]
replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer::visitWhen → KILLED

125

1.1
Location : visitNode
Killed by : org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest.[engine:junit-jupiter]/[class:org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest]/[method:window_expression_should_be_replaced()]
removed call to java/util/List::forEach → KILLED

132

1.1
Location : visitAggregation
Killed by : org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest.[engine:junit-jupiter]/[class:org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest]/[method:aggregation_expression_should_be_replaced()]
removed call to java/util/List::forEach → KILLED

136

1.1
Location : visitAggregation
Killed by : org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest.[engine:junit-jupiter]/[class:org.opensearch.sql.analysis.ExpressionReferenceOptimizerTest]/[method:case_clause_should_be_replaced()]
removed call to java/util/List::forEach → KILLED

147

1.1
Location : visitWindow
Killed by : none
replaced return value with null for org/opensearch/sql/analysis/ExpressionReferenceOptimizer$ExpressionMapBuilder::visitWindow → SURVIVED

Active mutators

Tests examined


Report generated by PIT 1.9.0