|
5 | 5 |
|
6 | 6 | package org.opensearch.sql.calcite.remote; |
7 | 7 |
|
8 | | -import org.json.JSONObject; |
9 | | -import org.junit.jupiter.api.Test; |
10 | | -import org.opensearch.sql.ppl.PPLIntegTestCase; |
11 | | - |
12 | | -import java.io.IOException; |
13 | | - |
14 | 8 | import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_BANK; |
| 9 | +import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_BANK_WITH_NULL_VALUES; |
| 10 | +import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_OTEL_LOGS; |
| 11 | +import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_TIME_DATA; |
15 | 12 | import static org.opensearch.sql.util.MatcherUtils.assertJsonEquals; |
16 | 13 | import static org.opensearch.sql.util.MatcherUtils.rows; |
17 | 14 | import static org.opensearch.sql.util.MatcherUtils.schema; |
18 | 15 | import static org.opensearch.sql.util.MatcherUtils.verifyDataRows; |
19 | 16 | import static org.opensearch.sql.util.MatcherUtils.verifySchema; |
20 | 17 |
|
| 18 | +import java.io.IOException; |
| 19 | +import org.json.JSONObject; |
| 20 | +import org.junit.jupiter.api.Test; |
| 21 | +import org.opensearch.sql.ppl.PPLIntegTestCase; |
| 22 | + |
21 | 23 | public class CalciteChartCommandIT extends PPLIntegTestCase { |
22 | | - @Override |
23 | | - protected void init() throws Exception { |
24 | | - super.init(); |
25 | | - enableCalcite(); |
26 | | - loadIndex(Index.BANK); |
27 | | - loadIndex(Index.BANK_WITH_NULL_VALUES); |
28 | | - loadIndex(Index.OTELLOGS); |
29 | | - } |
30 | | - |
31 | | - @Test |
32 | | - public void testChartWithSingleGroupKey() throws IOException { |
33 | | - JSONObject result1 = executeQuery(String.format("source=%s | chart avg(balance) by gender", TEST_INDEX_BANK)); |
34 | | - verifySchema( |
35 | | - result1, |
36 | | - schema("avg(balance)", "double"), |
37 | | - schema("gender", "string")); |
38 | | - verifyDataRows(result1, rows(40488, "F"), rows(16377.25, "M")); |
39 | | - JSONObject result2 = executeQuery(String.format("source=%s | chart avg(balance) over gender", TEST_INDEX_BANK)); |
40 | | - assertJsonEquals(result1.toString(), result2.toString()); |
41 | | - } |
42 | | - |
43 | | - @Test |
44 | | - public void testChartWithMultipleGroupKeys() throws IOException { |
45 | | - JSONObject result1 = executeQuery(String.format("source=%s | chart avg(balance) by gender, age", TEST_INDEX_BANK)); |
46 | | - verifySchema( |
47 | | - result1, |
48 | | - schema("avg(balance)", "double"), |
49 | | - schema("gender", "string"), |
50 | | - schema("age", "string")); |
51 | | - verifyDataRows(result1, rows(40488, "F", "36"), rows(16377.25, "M", 36)); |
52 | | - JSONObject result2 = executeQuery(String.format("source=%s | chart avg(balance) over gender, age", TEST_INDEX_BANK)); |
53 | | - assertJsonEquals(result1.toString(), result2.toString()); |
54 | | - } |
55 | | - |
56 | | - // TODOs: |
57 | | - // Param nullstr: source=opensearch-sql_test_index_bank_with_null_values | eval age = cast(age as string) | chart nullstr='nil' max(account_number) over gender by age |
58 | | - // Param usenull: source=opensearch-sql_test_index_bank_with_null_values | eval age = cast(age as string) | chart usenull=false nullstr='nil' max(account_number) over gender by age |
59 | | - // Param limit = 0: source=bank | chart limit=0 avg(balance) over state by gender |
60 | | - // SPAN: |
| 24 | + @Override |
| 25 | + public void init() throws Exception { |
| 26 | + super.init(); |
| 27 | + enableCalcite(); |
| 28 | + loadIndex(Index.BANK); |
| 29 | + loadIndex(Index.BANK_WITH_NULL_VALUES); |
| 30 | + loadIndex(Index.OTELLOGS); |
| 31 | + loadIndex(Index.TIME_TEST_DATA); |
| 32 | + } |
| 33 | + |
| 34 | + @Test |
| 35 | + public void testChartWithSingleGroupKey() throws IOException { |
| 36 | + JSONObject result1 = |
| 37 | + executeQuery(String.format("source=%s | chart avg(balance) by gender", TEST_INDEX_BANK)); |
| 38 | + verifySchema(result1, schema("avg(balance)", "double"), schema("gender", "string")); |
| 39 | + verifyDataRows(result1, rows(40488, "F"), rows(16377.25, "M")); |
| 40 | + JSONObject result2 = |
| 41 | + executeQuery(String.format("source=%s | chart avg(balance) over gender", TEST_INDEX_BANK)); |
| 42 | + assertJsonEquals(result1.toString(), result2.toString()); |
| 43 | + } |
| 44 | + |
| 45 | + @Test |
| 46 | + public void testChartWithMultipleGroupKeys() throws IOException { |
| 47 | + JSONObject result1 = |
| 48 | + executeQuery( |
| 49 | + String.format("source=%s | chart avg(balance) over gender by age", TEST_INDEX_BANK)); |
| 50 | + verifySchema( |
| 51 | + result1, |
| 52 | + schema("gender", "string"), |
| 53 | + schema("age", "string"), |
| 54 | + schema("avg(balance)", "double")); |
| 55 | + verifyDataRows( |
| 56 | + result1, |
| 57 | + rows("F", "28", 32838), |
| 58 | + rows("F", "39", 40540), |
| 59 | + rows("M", "32", 39225), |
| 60 | + rows("M", "33", 4180), |
| 61 | + rows("M", "36", 11052), |
| 62 | + rows("F", "34", 48086)); |
| 63 | + JSONObject result2 = |
| 64 | + executeQuery( |
| 65 | + String.format("source=%s | chart avg(balance) by gender, age", TEST_INDEX_BANK)); |
| 66 | + assertJsonEquals(result1.toString(), result2.toString()); |
| 67 | + } |
| 68 | + |
| 69 | + @Test |
| 70 | + public void testChartCombineOverByWithLimit0() throws IOException { |
| 71 | + JSONObject result = |
| 72 | + executeQuery( |
| 73 | + String.format( |
| 74 | + "source=%s | chart limit=0 avg(balance) over state by gender", TEST_INDEX_BANK)); |
| 75 | + verifySchema( |
| 76 | + result, |
| 77 | + schema("avg(balance)", "double"), |
| 78 | + schema("state", "string"), |
| 79 | + schema("gender", "string")); |
| 80 | + verifyDataRows( |
| 81 | + result, |
| 82 | + rows(39225.0, "IL", "M"), |
| 83 | + rows(48086.0, "IN", "F"), |
| 84 | + rows(4180.0, "MD", "M"), |
| 85 | + rows(40540.0, "PA", "F"), |
| 86 | + rows(5686.0, "TN", "M"), |
| 87 | + rows(32838.0, "VA", "F"), |
| 88 | + rows(16418.0, "WA", "M")); |
| 89 | + } |
| 90 | + |
| 91 | + @Test |
| 92 | + public void testChartMaxBalanceByAgeSpan() throws IOException { |
| 93 | + JSONObject result = |
| 94 | + executeQuery( |
| 95 | + String.format("source=%s | chart max(balance) by age span=10", TEST_INDEX_BANK)); |
| 96 | + verifySchema(result, schema("max(balance)", "bigint"), schema("age", "int")); |
| 97 | + verifyDataRows(result, rows(32838, 20), rows(48086, 30)); |
| 98 | + } |
| 99 | + |
| 100 | + @Test |
| 101 | + public void testChartMaxValueOverTimestampSpanWeekByCategory() throws IOException { |
| 102 | + JSONObject result = |
| 103 | + executeQuery( |
| 104 | + String.format( |
| 105 | + "source=%s | chart max(value) over timestamp span=1week by category", |
| 106 | + TEST_INDEX_TIME_DATA)); |
| 107 | + verifySchema( |
| 108 | + result, |
| 109 | + schema("timestamp", "timestamp"), |
| 110 | + schema("category", "string"), |
| 111 | + schema("max(value)", "int")); |
| 112 | + // Data spans from 2025-07-28 to 2025-08-01, all within same week |
| 113 | + verifyDataRows( |
| 114 | + result, |
| 115 | + rows("2025-07-28 00:00:00", "A", 9367), |
| 116 | + rows("2025-07-28 00:00:00", "B", 9521), |
| 117 | + rows("2025-07-28 00:00:00", "C", 9187), |
| 118 | + rows("2025-07-28 00:00:00", "D", 8736)); |
| 119 | + } |
| 120 | + |
| 121 | + @Test |
| 122 | + public void testChartMaxValueOverCategoryByTimestampSpanWeek() throws IOException { |
| 123 | + JSONObject result = |
| 124 | + executeQuery( |
| 125 | + String.format( |
| 126 | + "source=%s | chart max(value) over category by timestamp span=1week", |
| 127 | + TEST_INDEX_TIME_DATA)); |
| 128 | + verifySchema( |
| 129 | + result, |
| 130 | + schema("category", "string"), |
| 131 | + schema("timestamp", "string"), |
| 132 | + schema("max(value)", "int")); |
| 133 | + // All data within same week span |
| 134 | + verifyDataRows( |
| 135 | + result, |
| 136 | + rows("A", "2025-07-28 00:00:00", 9367), |
| 137 | + rows("B", "2025-07-28 00:00:00", 9521), |
| 138 | + rows("C", "2025-07-28 00:00:00", 9187), |
| 139 | + rows("D", "2025-07-28 00:00:00", 8736)); |
| 140 | + } |
| 141 | + |
| 142 | + @Test |
| 143 | + public void testChartMaxValueByTimestampSpanDayAndWeek() throws IOException { |
| 144 | + JSONObject result = |
| 145 | + executeQuery( |
| 146 | + String.format( |
| 147 | + "source=%s | chart max(value) by timestamp span=1day, @timestamp span=2weeks", |
| 148 | + TEST_INDEX_TIME_DATA)); |
| 149 | + // column split are converted to string in order to be compatible with nullstr and otherstr |
| 150 | + verifySchema( |
| 151 | + result, |
| 152 | + schema("timestamp", "timestamp"), |
| 153 | + schema("@timestamp", "string"), |
| 154 | + schema("max(value)", "int")); |
| 155 | + // Data grouped by day spans |
| 156 | + verifyDataRows( |
| 157 | + result, |
| 158 | + rows("2025-07-28 00:00:00", "2025-07-28 00:00:00", 9367), |
| 159 | + rows("2025-07-29 00:00:00", "2025-07-28 00:00:00", 9521), |
| 160 | + rows("2025-07-30 00:00:00", "2025-07-28 00:00:00", 9234), |
| 161 | + rows("2025-07-31 00:00:00", "2025-07-28 00:00:00", 9318), |
| 162 | + rows("2025-08-01 00:00:00", "2025-07-28 00:00:00", 9015)); |
| 163 | + } |
| 164 | + |
| 165 | + @Test |
| 166 | + public void testChartLimit0WithUseOther() throws IOException { |
| 167 | + JSONObject result = |
| 168 | + executeQuery( |
| 169 | + String.format( |
| 170 | + "source=%s | chart limit=0 useother=true otherstr='max_among_other'" |
| 171 | + + " max(severityNumber) over flags by severityText", |
| 172 | + TEST_INDEX_OTEL_LOGS)); |
| 173 | + verifySchema( |
| 174 | + result, |
| 175 | + schema("max(severityNumber)", "bigint"), |
| 176 | + schema("flags", "bigint"), |
| 177 | + schema("severityText", "string")); |
| 178 | + verifyDataRows( |
| 179 | + result, |
| 180 | + rows(5, 0, "DEBUG"), |
| 181 | + rows(6, 0, "DEBUG2"), |
| 182 | + rows(7, 0, "DEBUG3"), |
| 183 | + rows(8, 0, "DEBUG4"), |
| 184 | + rows(17, 0, "ERROR"), |
| 185 | + rows(18, 0, "ERROR2"), |
| 186 | + rows(19, 0, "ERROR3"), |
| 187 | + rows(20, 0, "ERROR4"), |
| 188 | + rows(21, 0, "FATAL"), |
| 189 | + rows(22, 0, "FATAL2"), |
| 190 | + rows(23, 0, "FATAL3"), |
| 191 | + rows(24, 0, "FATAL4"), |
| 192 | + rows(9, 0, "INFO"), |
| 193 | + rows(10, 0, "INFO2"), |
| 194 | + rows(11, 0, "INFO3"), |
| 195 | + rows(12, 0, "INFO4"), |
| 196 | + rows(2, 0, "TRACE2"), |
| 197 | + rows(3, 0, "TRACE3"), |
| 198 | + rows(4, 0, "TRACE4"), |
| 199 | + rows(13, 0, "WARN"), |
| 200 | + rows(14, 0, "WARN2"), |
| 201 | + rows(15, 0, "WARN3"), |
| 202 | + rows(16, 0, "WARN4"), |
| 203 | + rows(17, 1, "ERROR"), |
| 204 | + rows(9, 1, "INFO"), |
| 205 | + rows(1, 1, "TRACE")); |
| 206 | + } |
| 207 | + |
| 208 | + @Test |
| 209 | + public void testChartLimitTopWithUseOther() throws IOException { |
| 210 | + JSONObject result = |
| 211 | + executeQuery( |
| 212 | + String.format( |
| 213 | + "source=%s | chart limit=top 2 useother=true otherstr='max_among_other'" |
| 214 | + + " max(severityNumber) over flags by severityText", |
| 215 | + TEST_INDEX_OTEL_LOGS)); |
| 216 | + verifySchema( |
| 217 | + result, |
| 218 | + schema("flags", "bigint"), |
| 219 | + schema("severityText", "string"), |
| 220 | + schema("max(severityNumber)", "bigint")); |
| 221 | + verifyDataRows( |
| 222 | + result, |
| 223 | + rows(1, "max_among_other", 17), |
| 224 | + rows(0, "max_among_other", 22), |
| 225 | + rows(0, "FATAL3", 23), |
| 226 | + rows(0, "FATAL4", 24)); |
| 227 | + } |
| 228 | + |
| 229 | + @Test |
| 230 | + public void testChartLimitBottomWithUseOther() throws IOException { |
| 231 | + JSONObject result = |
| 232 | + executeQuery( |
| 233 | + String.format( |
| 234 | + "source=%s | chart limit=bottom 2 useother=false otherstr='other_small_not_shown'" |
| 235 | + + " max(severityNumber) over flags by severityText", |
| 236 | + TEST_INDEX_OTEL_LOGS)); |
| 237 | + verifySchema( |
| 238 | + result, |
| 239 | + schema("flags", "bigint"), |
| 240 | + schema("severityText", "string"), |
| 241 | + schema("max(severityNumber)", "bigint")); |
| 242 | + verifyDataRows(result, rows(1, "TRACE", 1), rows(0, "TRACE2", 2)); |
| 243 | + } |
| 244 | + |
| 245 | + @Test |
| 246 | + public void testChartLimitTopWithMinAgg() throws IOException { |
| 247 | + JSONObject result = |
| 248 | + executeQuery( |
| 249 | + String.format( |
| 250 | + "source=%s | chart limit=top 2 min(severityNumber) over flags by severityText", |
| 251 | + TEST_INDEX_OTEL_LOGS)); |
| 252 | + verifySchema( |
| 253 | + result, |
| 254 | + schema("flags", "bigint"), |
| 255 | + schema("severityText", "string"), |
| 256 | + schema("min(severityNumber)", "bigint")); |
| 257 | + verifyDataRows( |
| 258 | + result, |
| 259 | + rows(1, "OTHER", 9), |
| 260 | + rows(1, "TRACE", 1), |
| 261 | + rows(0, "OTHER", 3), |
| 262 | + rows(0, "TRACE2", 2)); |
| 263 | + } |
| 264 | + |
| 265 | + @Test |
| 266 | + public void testChartUseNullTrueWithNullStr() throws IOException { |
| 267 | + JSONObject result = |
| 268 | + executeQuery( |
| 269 | + String.format( |
| 270 | + "source=%s | chart nullstr='nil' avg(balance) over gender by age span=10", |
| 271 | + TEST_INDEX_BANK_WITH_NULL_VALUES)); |
| 272 | + verifySchema( |
| 273 | + result, |
| 274 | + schema("gender", "string"), |
| 275 | + schema("age", "string"), |
| 276 | + schema("avg(balance)", "double")); |
| 277 | + verifyDataRows( |
| 278 | + result, |
| 279 | + rows("M", "30", 21702.5), |
| 280 | + rows("F", "30", 48086.0), |
| 281 | + rows("F", "20", 32838.0), |
| 282 | + rows("F", "nil", null)); |
| 283 | + } |
61 | 284 |
|
| 285 | + @Test |
| 286 | + public void testChartUseNullFalseWithNullStr() throws IOException { |
| 287 | + JSONObject result = |
| 288 | + executeQuery( |
| 289 | + String.format( |
| 290 | + "source=%s | chart usenull=false nullstr='not_shown' count() over gender by age" |
| 291 | + + " span=10", |
| 292 | + TEST_INDEX_BANK_WITH_NULL_VALUES)); |
| 293 | + verifySchema( |
| 294 | + result, schema("gender", "string"), schema("age", "string"), schema("count()", "bigint")); |
| 295 | + verifyDataRows(result, rows("M", "30", 4), rows("F", "30", 1), rows("F", "20", 1)); |
| 296 | + } |
62 | 297 | } |
0 commit comments