|
16 | 16 | import static org.mockito.Mockito.when; |
17 | 17 | import static org.opensearch.sql.ast.dsl.AstDSL.argument; |
18 | 18 | import static org.opensearch.sql.ast.dsl.AstDSL.booleanLiteral; |
| 19 | +import static org.opensearch.sql.ast.dsl.AstDSL.defaultTopArgs; |
19 | 20 | import static org.opensearch.sql.ast.dsl.AstDSL.field; |
20 | 21 | import static org.opensearch.sql.ast.dsl.AstDSL.fillNull; |
21 | 22 | import static org.opensearch.sql.ast.dsl.AstDSL.filter; |
22 | 23 | import static org.opensearch.sql.ast.dsl.AstDSL.map; |
23 | 24 | import static org.opensearch.sql.ast.dsl.AstDSL.mvcombine; |
24 | 25 | import static org.opensearch.sql.ast.dsl.AstDSL.project; |
25 | 26 | import static org.opensearch.sql.ast.dsl.AstDSL.projectWithArg; |
| 27 | +import static org.opensearch.sql.ast.dsl.AstDSL.rareTopN; |
26 | 28 | import static org.opensearch.sql.ast.dsl.AstDSL.relation; |
27 | 29 | import static org.opensearch.sql.ast.dsl.AstDSL.rename; |
28 | 30 | import static org.opensearch.sql.ast.dsl.AstDSL.stringLiteral; |
|
31 | 33 | import java.util.LinkedHashMap; |
32 | 34 | import java.util.List; |
33 | 35 | import java.util.Map; |
| 36 | +import java.util.Optional; |
34 | 37 | import java.util.Set; |
35 | 38 | import java.util.stream.Stream; |
36 | 39 | import org.apache.calcite.rel.RelNode; |
|
45 | 48 | import org.mockito.Mock; |
46 | 49 | import org.mockito.MockedStatic; |
47 | 50 | import org.mockito.junit.jupiter.MockitoExtension; |
| 51 | +import org.opensearch.sql.ast.expression.Argument.ArgumentMap; |
48 | 52 | import org.opensearch.sql.ast.expression.Field; |
49 | 53 | import org.opensearch.sql.ast.expression.UnresolvedExpression; |
50 | 54 | import org.opensearch.sql.ast.tree.AddTotals; |
| 55 | +import org.opensearch.sql.ast.tree.Join; |
| 56 | +import org.opensearch.sql.ast.tree.Lookup; |
| 57 | +import org.opensearch.sql.ast.tree.RareTopN; |
51 | 58 | import org.opensearch.sql.ast.tree.Replace; |
52 | 59 | import org.opensearch.sql.ast.tree.ReplacePair; |
| 60 | +import org.opensearch.sql.ast.tree.StreamWindow; |
53 | 61 | import org.opensearch.sql.ast.tree.UnresolvedPlan; |
54 | 62 | import org.opensearch.sql.calcite.utils.CalciteToolsHelper; |
55 | 63 | import org.opensearch.sql.calcite.utils.CalciteToolsHelper.OpenSearchRelBuilder; |
@@ -139,6 +147,69 @@ void testMvcombine() { |
139 | 147 | .shouldProject("doc.user.name"); |
140 | 148 | } |
141 | 149 |
|
| 150 | + @Test |
| 151 | + void testRareTopN() { |
| 152 | + givenMapPaths("doc.user.name", "doc.user.city") |
| 153 | + .whenCommand( |
| 154 | + rareTopN( |
| 155 | + DUMMY_CHILD, |
| 156 | + RareTopN.CommandType.TOP, |
| 157 | + defaultTopArgs(), |
| 158 | + List.of(field("doc.user.city")), |
| 159 | + field("doc.user.name"))) |
| 160 | + .shouldProject("doc.user.name", "doc.user.city"); |
| 161 | + } |
| 162 | + |
| 163 | + @Test |
| 164 | + void testStreamWindow() { |
| 165 | + givenMapPaths("doc.user.city") |
| 166 | + .whenCommand( |
| 167 | + new StreamWindow( |
| 168 | + List.of(), List.of(field("doc.user.city")), false, 2, true, false, null, null)) |
| 169 | + .shouldProject("doc.user.city"); |
| 170 | + } |
| 171 | + |
| 172 | + @Test |
| 173 | + void testLookup() { |
| 174 | + givenMapPaths("doc.user.name") |
| 175 | + .whenCommand( |
| 176 | + new Lookup( |
| 177 | + null, Map.of("name", "doc.user.name"), Lookup.OutputStrategy.REPLACE, Map.of())) |
| 178 | + .shouldProject("doc.user.name"); |
| 179 | + } |
| 180 | + |
| 181 | + @Test |
| 182 | + void testJoinWithFieldList() { |
| 183 | + givenMapPaths("doc.user.name") |
| 184 | + .whenCommand( |
| 185 | + new Join( |
| 186 | + DUMMY_CHILD, |
| 187 | + Optional.empty(), |
| 188 | + Optional.empty(), |
| 189 | + Join.JoinType.INNER, |
| 190 | + Optional.empty(), |
| 191 | + new Join.JoinHint(), |
| 192 | + Optional.of(List.of(field("doc.user.name"))), |
| 193 | + new ArgumentMap(List.of()))) |
| 194 | + .shouldProject("doc.user.name"); |
| 195 | + } |
| 196 | + |
| 197 | + @Test |
| 198 | + void testJoinWithoutFieldList() { |
| 199 | + givenMapPaths("doc.user.name") |
| 200 | + .whenCommand( |
| 201 | + new Join( |
| 202 | + DUMMY_CHILD, |
| 203 | + Optional.empty(), |
| 204 | + Optional.empty(), |
| 205 | + Join.JoinType.INNER, |
| 206 | + Optional.empty(), |
| 207 | + new Join.JoinHint(), |
| 208 | + Optional.empty(), |
| 209 | + new ArgumentMap(List.of()))) |
| 210 | + .shouldNotProject(); |
| 211 | + } |
| 212 | + |
142 | 213 | // ---- Multiple fields cases ---- |
143 | 214 |
|
144 | 215 | @Test |
@@ -166,7 +237,7 @@ void testMixedMapAndNonMapFields() { |
166 | 237 | .shouldProject("doc.user.name"); |
167 | 238 | } |
168 | 239 |
|
169 | | - // ---- No-op cases ---- |
| 240 | + // ---- No-op and edge cases ---- |
170 | 241 |
|
171 | 242 | @Test |
172 | 243 | void testNoOpForFilter() { |
@@ -198,6 +269,30 @@ void testNoOpWhenFieldResolvesToNonItemAccess() { |
198 | 269 | .shouldNotProject(); |
199 | 270 | } |
200 | 271 |
|
| 272 | + @Test |
| 273 | + void testSkipsErrorField() { |
| 274 | + givenMapPaths() |
| 275 | + .givenErrorPaths("message.process.name", new AssertionError()) |
| 276 | + .whenCommand( |
| 277 | + new Replace( |
| 278 | + List.of(new ReplacePair(stringLiteral("a"), stringLiteral("b"))), |
| 279 | + Set.of(field("message.process.name")))) |
| 280 | + .shouldNotProject(); |
| 281 | + } |
| 282 | + |
| 283 | + @Test |
| 284 | + void testSkipsErrorFieldButProjectsValidMapPath() { |
| 285 | + givenMapPaths("doc.user.name") |
| 286 | + .givenErrorPaths("bad.field", new AssertionError()) |
| 287 | + .whenCommand( |
| 288 | + fillNull( |
| 289 | + DUMMY_CHILD, |
| 290 | + List.of( |
| 291 | + Pair.of(field("doc.user.name"), stringLiteral("N/A")), |
| 292 | + Pair.of(field("bad.field"), stringLiteral("0"))))) |
| 293 | + .shouldProject("doc.user.name"); |
| 294 | + } |
| 295 | + |
201 | 296 | // ---- Fluent test helper ---- |
202 | 297 |
|
203 | 298 | private MapPathAssertion givenMapPaths(String... fieldNames) { |
@@ -227,6 +322,11 @@ MapPathAssertion givenNonMapPaths(String... fieldNames) { |
227 | 322 | return this; |
228 | 323 | } |
229 | 324 |
|
| 325 | + MapPathAssertion givenErrorPaths(String fieldName, Throwable error) { |
| 326 | + lenient().when(rexVisitor.analyze(fieldMatching(fieldName), eq(context))).thenThrow(error); |
| 327 | + return this; |
| 328 | + } |
| 329 | + |
230 | 330 | MapPathAssertion whenCommand(UnresolvedPlan command) { |
231 | 331 | materializer.materializePaths(command, context); |
232 | 332 | return this; |
|
0 commit comments