1 | /* | |
2 | * Copyright OpenSearch Contributors | |
3 | * SPDX-License-Identifier: Apache-2.0 | |
4 | */ | |
5 | ||
6 | ||
7 | package org.opensearch.sql.expression.window.frame; | |
8 | ||
9 | import com.google.common.collect.PeekingIterator; | |
10 | import java.util.ArrayList; | |
11 | import java.util.Collections; | |
12 | import java.util.List; | |
13 | import java.util.stream.Collectors; | |
14 | import lombok.RequiredArgsConstructor; | |
15 | import org.apache.commons.lang3.tuple.Pair; | |
16 | import org.opensearch.sql.data.model.ExprValue; | |
17 | import org.opensearch.sql.expression.Expression; | |
18 | import org.opensearch.sql.expression.env.Environment; | |
19 | import org.opensearch.sql.expression.window.WindowDefinition; | |
20 | ||
21 | /** | |
22 | * Window frame that only keep peers (tuples with same value of fields specified in sort list | |
23 | * in window definition). See PeerWindowFrameTest for details about how this window frame | |
24 | * interacts with window operator and window function. | |
25 | */ | |
26 | @RequiredArgsConstructor | |
27 | public class PeerRowsWindowFrame implements WindowFrame { | |
28 | ||
29 | private final WindowDefinition windowDefinition; | |
30 | ||
31 | /** | |
32 | * All peer rows (peer means rows in a partition that share same sort key | |
33 | * based on sort list in window definition. | |
34 | */ | |
35 | private final List<ExprValue> peers = new ArrayList<>(); | |
36 | ||
37 | /** | |
38 | * Which row in the peer is currently being enriched by window function. | |
39 | */ | |
40 | private int position; | |
41 | ||
42 | /** | |
43 | * Does row at current position represents a new partition. | |
44 | */ | |
45 | private boolean isNewPartition = true; | |
46 | ||
47 | /** | |
48 | * If any more pre-fetched rows not returned to window operator yet. | |
49 | */ | |
50 | @Override | |
51 | public boolean hasNext() { | |
52 |
3
1. hasNext : changed conditional boundary → KILLED 2. hasNext : negated conditional → KILLED 3. hasNext : replaced boolean return with true for org/opensearch/sql/expression/window/frame/PeerRowsWindowFrame::hasNext → KILLED |
return position < peers.size(); |
53 | } | |
54 | ||
55 | /** | |
56 | * Move position and clear new partition flag. | |
57 | * Note that because all peer rows have same result from window function, | |
58 | * this is only returned at first time to change window function state. | |
59 | * Afterwards, empty list is returned to avoid changes until next peer loaded. | |
60 | * | |
61 | * @return all rows for the peer | |
62 | */ | |
63 | @Override | |
64 | public List<ExprValue> next() { | |
65 | isNewPartition = false; | |
66 |
2
1. next : Replaced integer addition with subtraction → KILLED 2. next : negated conditional → KILLED |
if (position++ == 0) { |
67 |
1
1. next : replaced return value with Collections.emptyList for org/opensearch/sql/expression/window/frame/PeerRowsWindowFrame::next → KILLED |
return peers; |
68 | } | |
69 | return Collections.emptyList(); | |
70 | } | |
71 | ||
72 | /** | |
73 | * Current row at the position. Because rows are pre-fetched here, | |
74 | * window operator needs to get them from here too. | |
75 | * @return row at current position that being enriched by window function | |
76 | */ | |
77 | @Override | |
78 | public ExprValue current() { | |
79 |
1
1. current : replaced return value with null for org/opensearch/sql/expression/window/frame/PeerRowsWindowFrame::current → KILLED |
return peers.get(position); |
80 | } | |
81 | ||
82 | /** | |
83 | * Preload all peer rows if last peer rows done. Note that when no more data in peeking iterator, | |
84 | * there must be rows in frame (hasNext()=true), so no need to check it.hasNext() in this method. | |
85 | * Load until: | |
86 | * 1. Different peer found (row with different sort key) | |
87 | * 2. Or new partition (row with different partition key) | |
88 | * 3. Or no more rows | |
89 | * @param it rows iterator | |
90 | */ | |
91 | @Override | |
92 | public void load(PeekingIterator<ExprValue> it) { | |
93 |
1
1. load : negated conditional → KILLED |
if (hasNext()) { |
94 | return; | |
95 | } | |
96 | ||
97 | // Reset state: reset new partition before clearing peers | |
98 |
1
1. load : negated conditional → KILLED |
isNewPartition = !isSamePartition(it.peek()); |
99 | position = 0; | |
100 |
1
1. load : removed call to java/util/List::clear → KILLED |
peers.clear(); |
101 | ||
102 |
1
1. load : negated conditional → KILLED |
while (it.hasNext()) { |
103 | ExprValue next = it.peek(); | |
104 |
1
1. load : negated conditional → KILLED |
if (peers.isEmpty()) { |
105 | peers.add(it.next()); | |
106 |
2
1. load : negated conditional → KILLED 2. load : negated conditional → KILLED |
} else if (isSamePartition(next) && isPeer(next)) { |
107 | peers.add(it.next()); | |
108 | } else { | |
109 | break; | |
110 | } | |
111 | } | |
112 | } | |
113 | ||
114 | @Override | |
115 | public boolean isNewPartition() { | |
116 |
2
1. isNewPartition : replaced boolean return with false for org/opensearch/sql/expression/window/frame/PeerRowsWindowFrame::isNewPartition → KILLED 2. isNewPartition : replaced boolean return with true for org/opensearch/sql/expression/window/frame/PeerRowsWindowFrame::isNewPartition → KILLED |
return isNewPartition; |
117 | } | |
118 | ||
119 | private boolean isPeer(ExprValue next) { | |
120 | List<Expression> sortFields = | |
121 | windowDefinition.getSortList() | |
122 | .stream() | |
123 | .map(Pair::getRight) | |
124 | .collect(Collectors.toList()); | |
125 | ||
126 |
1
1. isPeer : Replaced integer subtraction with addition → KILLED |
ExprValue last = peers.get(peers.size() - 1); |
127 |
2
1. isPeer : replaced boolean return with false for org/opensearch/sql/expression/window/frame/PeerRowsWindowFrame::isPeer → KILLED 2. isPeer : replaced boolean return with true for org/opensearch/sql/expression/window/frame/PeerRowsWindowFrame::isPeer → KILLED |
return resolve(sortFields, last).equals(resolve(sortFields, next)); |
128 | } | |
129 | ||
130 | private boolean isSamePartition(ExprValue next) { | |
131 |
1
1. isSamePartition : negated conditional → KILLED |
if (peers.isEmpty()) { |
132 |
1
1. isSamePartition : replaced boolean return with true for org/opensearch/sql/expression/window/frame/PeerRowsWindowFrame::isSamePartition → KILLED |
return false; |
133 | } | |
134 | ||
135 | List<Expression> partitionByList = windowDefinition.getPartitionByList(); | |
136 |
1
1. isSamePartition : Replaced integer subtraction with addition → KILLED |
ExprValue last = peers.get(peers.size() - 1); |
137 |
2
1. isSamePartition : replaced boolean return with false for org/opensearch/sql/expression/window/frame/PeerRowsWindowFrame::isSamePartition → KILLED 2. isSamePartition : replaced boolean return with true for org/opensearch/sql/expression/window/frame/PeerRowsWindowFrame::isSamePartition → KILLED |
return resolve(partitionByList, last).equals(resolve(partitionByList, next)); |
138 | } | |
139 | ||
140 | private List<ExprValue> resolve(List<Expression> expressions, ExprValue row) { | |
141 | Environment<Expression, ExprValue> valueEnv = row.bindingTuples(); | |
142 |
1
1. resolve : replaced return value with Collections.emptyList for org/opensearch/sql/expression/window/frame/PeerRowsWindowFrame::resolve → KILLED |
return expressions.stream() |
143 |
1
1. lambda$resolve$0 : replaced return value with null for org/opensearch/sql/expression/window/frame/PeerRowsWindowFrame::lambda$resolve$0 → KILLED |
.map(expr -> expr.valueOf(valueEnv)) |
144 | .collect(Collectors.toList()); | |
145 | } | |
146 | ||
147 | } | |
Mutations | ||
52 |
1.1 2.2 3.3 |
|
66 |
1.1 2.2 |
|
67 |
1.1 |
|
79 |
1.1 |
|
93 |
1.1 |
|
98 |
1.1 |
|
100 |
1.1 |
|
102 |
1.1 |
|
104 |
1.1 |
|
106 |
1.1 2.2 |
|
116 |
1.1 2.2 |
|
126 |
1.1 |
|
127 |
1.1 2.2 |
|
131 |
1.1 |
|
132 |
1.1 |
|
136 |
1.1 |
|
137 |
1.1 2.2 |
|
142 |
1.1 |
|
143 |
1.1 |