Skip to content

Commit 92cb8ab

Browse files
authored
Implement ppl relation subquery command with Calcite (#3378)
Signed-off-by: Lantao Jin <ltjin@amazon.com>
1 parent 2591b15 commit 92cb8ab

File tree

9 files changed

+562
-85
lines changed

9 files changed

+562
-85
lines changed

integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteSortCommandIT.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,4 @@ public void init() throws IOException {
2222
@Ignore
2323
@Override
2424
public void testSortIpField() throws IOException {}
25-
26-
// TODO: Fix incorrect results for NULL values, addressed by issue:
27-
// https://github.com/opensearch-project/sql/issues/3375
28-
@Ignore
29-
@Override
30-
public void testSortWithNullValue() throws IOException {}
3125
}

integ-test/src/test/java/org/opensearch/sql/calcite/standalone/CalcitePPLJoinIT.java

Lines changed: 186 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.junit.Ignore;
1919
import org.junit.Test;
2020
import org.opensearch.client.Request;
21+
import org.opensearch.sql.legacy.TestsConstants;
2122

2223
public class CalcitePPLJoinIT extends CalcitePPLIntegTestCase {
2324

@@ -29,22 +30,22 @@ public void init() throws IOException {
2930
loadIndex(Index.OCCUPATION);
3031
loadIndex(Index.HOBBIES);
3132
Request request1 =
32-
new Request("PUT", "/opensearch-sql_test_index_state_country/_doc/5?refresh=true");
33+
new Request("PUT", "/" + TestsConstants.TEST_INDEX_STATE_COUNTRY + "/_doc/5?refresh=true");
3334
request1.setJsonEntity(
3435
"{\"name\":\"Jim\",\"age\":27,\"state\":\"B.C\",\"country\":\"Canada\",\"year\":2023,\"month\":4}");
3536
client().performRequest(request1);
3637
Request request2 =
37-
new Request("PUT", "/opensearch-sql_test_index_state_country/_doc/6?refresh=true");
38+
new Request("PUT", "/" + TestsConstants.TEST_INDEX_STATE_COUNTRY + "/_doc/6?refresh=true");
3839
request2.setJsonEntity(
3940
"{\"name\":\"Peter\",\"age\":57,\"state\":\"B.C\",\"country\":\"Canada\",\"year\":2023,\"month\":4}");
4041
client().performRequest(request2);
4142
Request request3 =
42-
new Request("PUT", "/opensearch-sql_test_index_state_country/_doc/7?refresh=true");
43+
new Request("PUT", "/" + TestsConstants.TEST_INDEX_STATE_COUNTRY + "/_doc/7?refresh=true");
4344
request3.setJsonEntity(
4445
"{\"name\":\"Rick\",\"age\":70,\"state\":\"B.C\",\"country\":\"Canada\",\"year\":2023,\"month\":4}");
4546
client().performRequest(request3);
4647
Request request4 =
47-
new Request("PUT", "/opensearch-sql_test_index_state_country/_doc/8?refresh=true");
48+
new Request("PUT", "/" + TestsConstants.TEST_INDEX_STATE_COUNTRY + "/_doc/8?refresh=true");
4849
request4.setJsonEntity(
4950
"{\"name\":\"David\",\"age\":40,\"state\":\"Washington\",\"country\":\"USA\",\"year\":2023,\"month\":4}");
5051
client().performRequest(request4);
@@ -213,9 +214,9 @@ public void testComplexLeftJoin() {
213214
actual,
214215
rows("Jane", 20, "Quebec", "Canada", "Scientist", "Canada", 90000),
215216
rows("John", 25, "Ontario", "Canada", "Doctor", "Canada", 120000),
216-
rows("Jim", 27, "B.C", "Canada", null, null, 0),
217-
rows("Peter", 57, "B.C", "Canada", null, null, 0),
218-
rows("Rick", 70, "B.C", "Canada", null, null, 0));
217+
rows("Jim", 27, "B.C", "Canada", null, null, null),
218+
rows("Peter", 57, "B.C", "Canada", null, null, null),
219+
rows("Rick", 70, "B.C", "Canada", null, null, null));
219220
}
220221

221222
@Test
@@ -240,10 +241,10 @@ public void testComplexRightJoin() {
240241
actual,
241242
rows("Jane", 20, "Quebec", "Canada", "Scientist", "Canada", 90000),
242243
rows("John", 25, "Ontario", "Canada", "Doctor", "Canada", 120000),
243-
rows(null, 0, null, null, "Engineer", "England", 100000),
244-
rows(null, 0, null, null, "Artist", "USA", 70000),
245-
rows(null, 0, null, null, "Doctor", "USA", 120000),
246-
rows(null, 0, null, null, "Unemployed", "Canada", 0));
244+
rows(null, null, null, null, "Engineer", "England", 100000),
245+
rows(null, null, null, null, "Artist", "USA", 70000),
246+
rows(null, null, null, null, "Doctor", "USA", 120000),
247+
rows(null, null, null, null, "Unemployed", "Canada", 0));
247248
}
248249

249250
@Test
@@ -392,6 +393,54 @@ public void testMultipleJoins() {
392393
TEST_INDEX_STATE_COUNTRY));
393394
}
394395

396+
@Ignore // TODO seems a calcite bug
397+
public void testMultipleJoinsWithRelationSubquery() {
398+
JSONObject actual =
399+
executeQuery(
400+
String.format(
401+
"""
402+
source = %s
403+
| where country = 'Canada' OR country = 'England'
404+
| inner join left=a, right=b
405+
ON a.name = b.name AND a.year = 2023 AND a.month = 4 AND b.year = 2023 AND b.month = 4
406+
[
407+
source = %s
408+
]
409+
| eval a_name = a.name
410+
| eval a_country = a.country
411+
| eval b_country = b.country
412+
| fields a_name, age, state, a_country, occupation, b_country, salary
413+
| left join left=a, right=b
414+
ON a.a_name = b.name
415+
[
416+
source = %s
417+
]
418+
| eval aa_country = a.a_country
419+
| eval ab_country = a.b_country
420+
| eval bb_country = b.country
421+
| fields a_name, age, state, aa_country, occupation, ab_country, salary, bb_country, hobby, language
422+
| cross join left=a, right=b
423+
[
424+
source = %s
425+
]
426+
| eval new_country = a.aa_country
427+
| eval new_salary = b.salary
428+
| stats avg(new_salary) as avg_salary by span(age, 5) as age_span, state
429+
| left semi join left=a, right=b
430+
ON a.state = b.state
431+
[
432+
source = %s
433+
]
434+
| eval new_avg_salary = floor(avg_salary)
435+
| fields state, age_span, new_avg_salary
436+
""",
437+
TEST_INDEX_STATE_COUNTRY,
438+
TEST_INDEX_OCCUPATION,
439+
TEST_INDEX_HOBBIES,
440+
TEST_INDEX_OCCUPATION,
441+
TEST_INDEX_STATE_COUNTRY));
442+
}
443+
395444
@Test
396445
public void testMultipleJoinsWithoutTableAliases() {
397446
JSONObject actual =
@@ -442,7 +491,7 @@ public void testMultipleJoinsWithPartTableAliases() {
442491
}
443492

444493
@Test
445-
public void testMultipleJoinsWithSelfJoin1() {
494+
public void testMultipleJoinsWithSelfJoin() {
446495
JSONObject actual =
447496
executeQuery(
448497
String.format(
@@ -469,8 +518,8 @@ public void testMultipleJoinsWithSelfJoin1() {
469518
rows("John", "John", "John", "John"));
470519
}
471520

472-
@Ignore // TODO table subquery not support
473-
public void testMultipleJoinsWithSelfJoin2() {
521+
@Test
522+
public void testMultipleJoinsWithSubquerySelfJoin() {
474523
JSONObject actual =
475524
executeQuery(
476525
String.format(
@@ -481,10 +530,24 @@ public void testMultipleJoinsWithSelfJoin2() {
481530
TEST_INDEX_OCCUPATION,
482531
TEST_INDEX_HOBBIES,
483532
TEST_INDEX_STATE_COUNTRY));
533+
verifySchema(
534+
actual,
535+
schema("name", "string"),
536+
schema("name0", "string"),
537+
schema("name1", "string"),
538+
schema("name2", "string"));
539+
verifyDataRows(
540+
actual,
541+
rows("David", "David", "David", "David"),
542+
rows("David", "David", "David", "David"),
543+
rows("Hello", "Hello", "Hello", "Hello"),
544+
rows("Jake", "Jake", "Jake", "Jake"),
545+
rows("Jane", "Jane", "Jane", "Jane"),
546+
rows("John", "John", "John", "John"));
484547
}
485548

486549
@Test
487-
public void testCheckAccessTheReferenceByAliases1() {
550+
public void testCheckAccessTheReferenceByAliases() {
488551
String res1 =
489552
execute(
490553
String.format(
@@ -520,8 +583,8 @@ public void testCheckAccessTheReferenceByAliases1() {
520583
assertEquals(res4, res5);
521584
}
522585

523-
@Ignore // TODO table subquery not support
524-
public void testCheckAccessTheReferenceByAliases2() {
586+
@Test
587+
public void testCheckAccessTheReferenceBySubqueryAliases() {
525588
String res1 =
526589
execute(
527590
String.format(
@@ -559,7 +622,7 @@ public void testCheckAccessTheReferenceByAliases2() {
559622
}
560623

561624
@Test
562-
public void testCheckAccessTheReferenceByAliases3() {
625+
public void testCheckAccessTheReferenceByOverrideAliases() {
563626
String res1 =
564627
execute(
565628
String.format(
@@ -581,4 +644,109 @@ public void testCheckAccessTheReferenceByAliases3() {
581644
assertEquals(res1, res2);
582645
assertEquals(res1, res3);
583646
}
647+
648+
@Test
649+
public void testCheckAccessTheReferenceByOverrideSubqueryAliases() {
650+
String res1 =
651+
execute(
652+
String.format(
653+
"source = %s | JOIN left = t1 right = t2 ON t1.name = t2.name [ source = %s as tt ]"
654+
+ " | fields tt.name",
655+
TEST_INDEX_STATE_COUNTRY, TEST_INDEX_OCCUPATION));
656+
String res2 =
657+
execute(
658+
String.format(
659+
"source = %s | JOIN left = t1 right = t2 ON t1.name = t2.name [ source = %s as tt ]"
660+
+ " as t2 | fields tt.name",
661+
TEST_INDEX_STATE_COUNTRY, TEST_INDEX_OCCUPATION));
662+
String res3 =
663+
execute(
664+
String.format(
665+
"source = %s | JOIN left = t1 right = t2 ON t1.name = t2.name [ source = %s ] as tt"
666+
+ " | fields tt.name",
667+
TEST_INDEX_STATE_COUNTRY, TEST_INDEX_OCCUPATION));
668+
assertEquals(res1, res2);
669+
assertEquals(res1, res3);
670+
}
671+
672+
@Test
673+
public void testCheckAccessTheReferenceByOverrideSubqueryAliases2() {
674+
String res1 =
675+
execute(
676+
String.format(
677+
"source = %s | JOIN left = t1 right = t2 ON t1.name = t2.name [ source = %s as tt ]"
678+
+ " | fields t2.name",
679+
TEST_INDEX_STATE_COUNTRY, TEST_INDEX_OCCUPATION));
680+
String res2 =
681+
execute(
682+
String.format(
683+
"source = %s | JOIN left = t1 right = t2 ON t1.name = t2.name [ source = %s as tt ]"
684+
+ " as t2 | fields t2.name",
685+
TEST_INDEX_STATE_COUNTRY, TEST_INDEX_OCCUPATION));
686+
String res3 =
687+
execute(
688+
String.format(
689+
"source = %s | JOIN left = t1 right = t2 ON t1.name = t2.name [ source = %s ] as tt"
690+
+ " | fields t2.name",
691+
TEST_INDEX_STATE_COUNTRY, TEST_INDEX_OCCUPATION));
692+
assertEquals(res1, res2);
693+
assertEquals(res1, res3);
694+
}
695+
696+
@Test
697+
public void testInnerJoinWithRelationSubquery() {
698+
JSONObject actual =
699+
executeQuery(
700+
String.format(
701+
"""
702+
source = %s
703+
| where country = 'USA' OR country = 'England'
704+
| inner join left=a, right=b
705+
ON a.name = b.name
706+
[
707+
source = %s
708+
| where salary > 0
709+
| fields name, country, salary
710+
| sort salary
711+
| head 3
712+
]
713+
| stats avg(salary) by span(age, 10) as age_span, b.country
714+
""",
715+
TEST_INDEX_STATE_COUNTRY, TEST_INDEX_OCCUPATION));
716+
verifySchema(
717+
actual,
718+
schema("b.country", "string"),
719+
schema("age_span", "double"),
720+
schema("avg(salary)", "double"));
721+
verifyDataRows(actual, rows("USA", 30, 70000.0), rows("England", 70, 100000));
722+
}
723+
724+
@Test
725+
public void testLeftJoinWithRelationSubquery() {
726+
JSONObject actual =
727+
executeQuery(
728+
String.format(
729+
"""
730+
source = %s
731+
| where country = 'USA' OR country = 'England'
732+
| left join left=a, right=b
733+
ON a.name = b.name
734+
[
735+
source = %s
736+
| where salary > 0
737+
| fields name, country, salary
738+
| sort salary
739+
| head 3
740+
]
741+
| stats avg(salary) by span(age, 10) as age_span, b.country
742+
""",
743+
TEST_INDEX_STATE_COUNTRY, TEST_INDEX_OCCUPATION));
744+
verifySchema(
745+
actual,
746+
schema("b.country", "string"),
747+
schema("age_span", "double"),
748+
schema("avg(salary)", "double"));
749+
verifyDataRows(
750+
actual, rows("USA", 30, 70000.0), rows("England", 70, 100000), rows(null, 40, 0));
751+
}
584752
}

integ-test/src/test/java/org/opensearch/sql/calcite/standalone/CalcitePPLSortIT.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
package org.opensearch.sql.calcite.standalone;
77

88
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_BANK;
9+
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_BANK_WITH_NULL_VALUES;
910
import static org.opensearch.sql.util.MatcherUtils.rows;
1011
import static org.opensearch.sql.util.MatcherUtils.schema;
1112
import static org.opensearch.sql.util.MatcherUtils.verifyDataRows;
13+
import static org.opensearch.sql.util.MatcherUtils.verifyOrder;
1214
import static org.opensearch.sql.util.MatcherUtils.verifySchema;
1315

1416
import java.io.IOException;
@@ -22,6 +24,7 @@ public void init() throws IOException {
2224
super.init();
2325

2426
loadIndex(Index.BANK);
27+
loadIndex(Index.BANK_WITH_NULL_VALUES);
2528
}
2629

2730
@Test
@@ -191,4 +194,22 @@ public void testSortAgeNameAndFieldsNameAge() {
191194
rows("Amber JOHnny", 32),
192195
rows("Nanette", 28));
193196
}
197+
198+
@Test
199+
public void testSortWithNullValue() throws IOException {
200+
JSONObject result =
201+
executeQuery(
202+
String.format(
203+
"source=%s | sort balance | fields firstname, balance",
204+
TEST_INDEX_BANK_WITH_NULL_VALUES));
205+
verifyOrder(
206+
result,
207+
rows("Dale", 4180),
208+
rows("Nanette", 32838),
209+
rows("Amber JOHnny", 39225),
210+
rows("Dillard", 48086),
211+
rows("Hattie", null),
212+
rows("Elinor", null),
213+
rows("Virginia", null));
214+
}
194215
}

opensearch/src/main/java/org/opensearch/sql/opensearch/storage/scan/CalciteOpenSearchIndexScan.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ public CalciteOpenSearchIndexScan pushDownFilter(Filter filter) {
167167
// TODO: handle the case where condition contains a score function
168168
return newScan;
169169
} catch (Exception e) {
170-
LOG.warn("Cannot analyze the filter condition {}", filter.getCondition(), e);
170+
LOG.warn("Cannot pushdown the filter condition {}, ", filter.getCondition());
171171
}
172172
return null;
173173
}

0 commit comments

Comments
 (0)