1 | /* | |
2 | * Copyright OpenSearch Contributors | |
3 | * SPDX-License-Identifier: Apache-2.0 | |
4 | */ | |
5 | ||
6 | ||
7 | package org.opensearch.sql.analysis; | |
8 | ||
9 | import static org.opensearch.sql.ast.tree.Sort.SortOption.DEFAULT_ASC; | |
10 | import static org.opensearch.sql.ast.tree.Sort.SortOption.DEFAULT_DESC; | |
11 | import static org.opensearch.sql.ast.tree.Sort.SortOrder.ASC; | |
12 | import static org.opensearch.sql.ast.tree.Sort.SortOrder.DESC; | |
13 | ||
14 | import java.util.List; | |
15 | import java.util.stream.Collectors; | |
16 | import lombok.RequiredArgsConstructor; | |
17 | import org.apache.commons.lang3.tuple.ImmutablePair; | |
18 | import org.apache.commons.lang3.tuple.Pair; | |
19 | import org.opensearch.sql.ast.AbstractNodeVisitor; | |
20 | import org.opensearch.sql.ast.expression.Alias; | |
21 | import org.opensearch.sql.ast.expression.UnresolvedExpression; | |
22 | import org.opensearch.sql.ast.expression.WindowFunction; | |
23 | import org.opensearch.sql.ast.tree.Sort.SortOption; | |
24 | import org.opensearch.sql.expression.Expression; | |
25 | import org.opensearch.sql.expression.NamedExpression; | |
26 | import org.opensearch.sql.expression.window.WindowDefinition; | |
27 | import org.opensearch.sql.planner.logical.LogicalPlan; | |
28 | import org.opensearch.sql.planner.logical.LogicalSort; | |
29 | import org.opensearch.sql.planner.logical.LogicalWindow; | |
30 | ||
31 | /** | |
32 | * Window expression analyzer that analyzes window function expression in expression list | |
33 | * in project operator. | |
34 | */ | |
35 | @RequiredArgsConstructor | |
36 | public class WindowExpressionAnalyzer extends AbstractNodeVisitor<LogicalPlan, AnalysisContext> { | |
37 | ||
38 | /** | |
39 | * Expression analyzer. | |
40 | */ | |
41 | private final ExpressionAnalyzer expressionAnalyzer; | |
42 | ||
43 | /** | |
44 | * Child node to be wrapped by a new window operator. | |
45 | */ | |
46 | private final LogicalPlan child; | |
47 | ||
48 | /** | |
49 | * Analyze the given project item and return window operator (with child node inside) | |
50 | * if the given project item is a window function. | |
51 | * @param projectItem project item | |
52 | * @param context analysis context | |
53 | * @return window operator or original child if not windowed | |
54 | */ | |
55 | public LogicalPlan analyze(UnresolvedExpression projectItem, AnalysisContext context) { | |
56 | LogicalPlan window = projectItem.accept(this, context); | |
57 |
2
1. analyze : negated conditional → KILLED 2. analyze : replaced return value with null for org/opensearch/sql/analysis/WindowExpressionAnalyzer::analyze → KILLED |
return (window == null) ? child : window; |
58 | } | |
59 | ||
60 | @Override | |
61 | public LogicalPlan visitAlias(Alias node, AnalysisContext context) { | |
62 |
1
1. visitAlias : negated conditional → KILLED |
if (!(node.getDelegated() instanceof WindowFunction)) { |
63 | return null; | |
64 | } | |
65 | ||
66 | WindowFunction unresolved = (WindowFunction) node.getDelegated(); | |
67 | Expression windowFunction = expressionAnalyzer.analyze(unresolved, context); | |
68 | List<Expression> partitionByList = analyzePartitionList(unresolved, context); | |
69 | List<Pair<SortOption, Expression>> sortList = analyzeSortList(unresolved, context); | |
70 | ||
71 | WindowDefinition windowDefinition = new WindowDefinition(partitionByList, sortList); | |
72 | NamedExpression namedWindowFunction = | |
73 | new NamedExpression(node.getName(), windowFunction, node.getAlias()); | |
74 | List<Pair<SortOption, Expression>> allSortItems = windowDefinition.getAllSortItems(); | |
75 | ||
76 |
1
1. visitAlias : negated conditional → KILLED |
if (allSortItems.isEmpty()) { |
77 |
1
1. visitAlias : replaced return value with null for org/opensearch/sql/analysis/WindowExpressionAnalyzer::visitAlias → KILLED |
return new LogicalWindow(child, namedWindowFunction, windowDefinition); |
78 | } | |
79 |
1
1. visitAlias : replaced return value with null for org/opensearch/sql/analysis/WindowExpressionAnalyzer::visitAlias → KILLED |
return new LogicalWindow( |
80 | new LogicalSort(child, allSortItems), | |
81 | namedWindowFunction, | |
82 | windowDefinition); | |
83 | } | |
84 | ||
85 | private List<Expression> analyzePartitionList(WindowFunction node, AnalysisContext context) { | |
86 |
1
1. analyzePartitionList : replaced return value with Collections.emptyList for org/opensearch/sql/analysis/WindowExpressionAnalyzer::analyzePartitionList → KILLED |
return node.getPartitionByList() |
87 | .stream() | |
88 |
1
1. lambda$analyzePartitionList$0 : replaced return value with null for org/opensearch/sql/analysis/WindowExpressionAnalyzer::lambda$analyzePartitionList$0 → KILLED |
.map(expr -> expressionAnalyzer.analyze(expr, context)) |
89 | .collect(Collectors.toList()); | |
90 | } | |
91 | ||
92 | private List<Pair<SortOption, Expression>> analyzeSortList(WindowFunction node, | |
93 | AnalysisContext context) { | |
94 |
1
1. analyzeSortList : replaced return value with Collections.emptyList for org/opensearch/sql/analysis/WindowExpressionAnalyzer::analyzeSortList → KILLED |
return node.getSortList() |
95 | .stream() | |
96 |
1
1. lambda$analyzeSortList$1 : replaced return value with null for org/opensearch/sql/analysis/WindowExpressionAnalyzer::lambda$analyzeSortList$1 → KILLED |
.map(pair -> ImmutablePair |
97 | .of(analyzeSortOption(pair.getLeft()), | |
98 | expressionAnalyzer.analyze(pair.getRight(), context))) | |
99 | .collect(Collectors.toList()); | |
100 | } | |
101 | ||
102 | /** | |
103 | * Frontend creates sort option from query directly which means sort or null order may be null. | |
104 | * The final and default value for each is determined here during expression analysis. | |
105 | */ | |
106 | private SortOption analyzeSortOption(SortOption option) { | |
107 |
1
1. analyzeSortOption : negated conditional → KILLED |
if (option.getNullOrder() == null) { |
108 |
2
1. analyzeSortOption : negated conditional → KILLED 2. analyzeSortOption : replaced return value with null for org/opensearch/sql/analysis/WindowExpressionAnalyzer::analyzeSortOption → KILLED |
return (option.getSortOrder() == DESC) ? DEFAULT_DESC : DEFAULT_ASC; |
109 | } | |
110 |
1
1. analyzeSortOption : replaced return value with null for org/opensearch/sql/analysis/WindowExpressionAnalyzer::analyzeSortOption → KILLED |
return new SortOption( |
111 |
1
1. analyzeSortOption : negated conditional → KILLED |
(option.getSortOrder() == DESC) ? DESC : ASC, |
112 | option.getNullOrder()); | |
113 | } | |
114 | ||
115 | } | |
Mutations | ||
57 |
1.1 2.2 |
|
62 |
1.1 |
|
76 |
1.1 |
|
77 |
1.1 |
|
79 |
1.1 |
|
86 |
1.1 |
|
88 |
1.1 |
|
94 |
1.1 |
|
96 |
1.1 |
|
107 |
1.1 |
|
108 |
1.1 2.2 |
|
110 |
1.1 |
|
111 |
1.1 |