-
Notifications
You must be signed in to change notification settings - Fork 94
feat(isthmus): convert Calcite RelRoot to Substrait Plan.Root #370
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
baa5ec0
5477670
7da43af
0e7ad03
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| package io.substrait.isthmus; | ||
|
|
||
| import static io.substrait.isthmus.SqlConverterBase.EXTENSION_COLLECTION; | ||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
|
|
||
| import io.substrait.plan.Plan; | ||
| import io.substrait.relation.NamedScan; | ||
| import java.util.List; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| public class NameRoundtripTest extends PlanTestBase { | ||
|
|
||
| @Test | ||
| void preserveNamesFromSql() throws Exception { | ||
| List<String> creates = List.of("CREATE TABLE foo(a BIGINT, b BIGINT)"); | ||
|
|
||
| SqlToSubstrait s = new SqlToSubstrait(); | ||
| var substraitToCalcite = new SubstraitToCalcite(EXTENSION_COLLECTION, typeFactory); | ||
|
|
||
| String query = """ | ||
| SELECT "a", "B" FROM foo GROUP BY a, b | ||
| """; | ||
| List<String> expectedNames = List.of("a", "B"); | ||
|
|
||
| List<org.apache.calcite.rel.RelRoot> calciteRelRoots = s.sqlToRelNode(query, creates); | ||
| assertEquals(1, calciteRelRoots.size()); | ||
|
|
||
| org.apache.calcite.rel.RelRoot calciteRelRoot1 = calciteRelRoots.get(0); | ||
| assertEquals(expectedNames, calciteRelRoot1.validatedRowType.getFieldNames()); | ||
|
|
||
| io.substrait.plan.Plan.Root substraitRelRoot = | ||
| SubstraitRelVisitor.convert(calciteRelRoot1, EXTENSION_COLLECTION); | ||
| assertEquals(expectedNames, substraitRelRoot.getNames()); | ||
|
|
||
| org.apache.calcite.rel.RelRoot calciteRelRoot2 = substraitToCalcite.convert(substraitRelRoot); | ||
| assertEquals(expectedNames, calciteRelRoot2.validatedRowType.getFieldNames()); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was hoping to be able to use to the new |
||
| } | ||
|
|
||
| @Test | ||
| void preserveNamesFromSubstrait() { | ||
| NamedScan rel = | ||
| substraitBuilder.namedScan( | ||
| List.of("foo"), | ||
| List.of("i64", "struct", "struct0", "struct1"), | ||
| List.of(R.I64, R.struct(R.FP64, R.STRING))); | ||
|
|
||
| Plan.Root planRoot = | ||
| Plan.Root.builder().input(rel).names(List.of("i", "s", "s0", "s1")).build(); | ||
| assertFullRoundTrip(planRoot); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,15 +20,13 @@ | |
| import io.substrait.type.Type; | ||
| import io.substrait.type.TypeCreator; | ||
| import java.io.IOException; | ||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import java.util.List; | ||
| import org.apache.calcite.rel.RelNode; | ||
| import org.apache.calcite.rel.RelRoot; | ||
| import org.apache.calcite.rel.type.RelDataType; | ||
| import org.apache.calcite.rel.type.RelDataTypeFactory; | ||
| import org.apache.calcite.rex.RexBuilder; | ||
| import org.apache.calcite.sql.SqlKind; | ||
| import org.apache.calcite.sql.parser.SqlParseException; | ||
| import org.apache.calcite.tools.RelBuilder; | ||
| import org.junit.jupiter.api.Assertions; | ||
|
|
@@ -72,8 +70,9 @@ protected Plan assertProtoPlanRoundrip(String query, SqlToSubstrait s, List<Stri | |
| var rootRels = s.sqlToRelNode(query, creates); | ||
| assertEquals(rootRels.size(), plan.getRoots().size()); | ||
| for (int i = 0; i < rootRels.size(); i++) { | ||
| var rootRel = SubstraitRelVisitor.convert(rootRels.get(i), EXTENSION_COLLECTION); | ||
| assertEquals(rootRel.getRecordType(), plan.getRoots().get(i).getInput().getRecordType()); | ||
| Plan.Root rootRel = SubstraitRelVisitor.convert(rootRels.get(i), EXTENSION_COLLECTION); | ||
| assertEquals( | ||
| rootRel.getInput().getRecordType(), plan.getRoots().get(i).getInput().getRecordType()); | ||
| } | ||
| return plan; | ||
| } | ||
|
|
@@ -85,38 +84,36 @@ protected void assertPlanRoundtrip(Plan plan) { | |
| assertEquals(protoPlan1, protoPlan2); | ||
| } | ||
|
|
||
| protected List<RelNode> assertSqlSubstraitRelRoundTrip(String query) throws Exception { | ||
| protected RelRoot assertSqlSubstraitRelRoundTrip(String query) throws Exception { | ||
| return assertSqlSubstraitRelRoundTrip(query, tpchSchemaCreateStatements()); | ||
| } | ||
|
|
||
| protected List<RelNode> assertSqlSubstraitRelRoundTrip(String query, List<String> creates) | ||
| protected RelRoot assertSqlSubstraitRelRoundTrip(String query, List<String> creates) | ||
| throws Exception { | ||
| // sql <--> substrait round trip test. | ||
| // Assert (sql -> calcite -> substrait) and (sql -> substrait -> calcite -> substrait) are same. | ||
| // Return list of sql -> Substrait rel -> Calcite rel. | ||
| List<RelNode> relNodeList = new ArrayList<>(); | ||
|
|
||
| var substraitToCalcite = new SubstraitToCalcite(EXTENSION_COLLECTION, typeFactory); | ||
|
|
||
| SqlToSubstrait s = new SqlToSubstrait(); | ||
|
|
||
| // 1. SQL -> Calcite RelRoot | ||
| for (RelRoot relRoot : s.sqlToRelNode(query, creates)) { | ||
| // 2. Calcite RelRoot -> Substrait Rel | ||
| Rel pojo1 = SubstraitRelVisitor.convert(relRoot, EXTENSION_COLLECTION); | ||
| List<RelRoot> relRoots = s.sqlToRelNode(query, creates); | ||
| assertEquals(1, relRoots.size()); | ||
| RelRoot relRoot1 = relRoots.get(0); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In practice we only ever generate a single RelRoot. Capturing this here and removing the looping to simplify our test code. |
||
|
|
||
| // 3. Substrait Rel -> Calcite RelNode | ||
| RelNode relNode = substraitToCalcite.convert(pojo1); | ||
| // 2. Calcite RelRoot -> Substrait Rel | ||
| Plan.Root pojo1 = SubstraitRelVisitor.convert(relRoot1, EXTENSION_COLLECTION); | ||
|
|
||
| relNodeList.add(relNode); | ||
| // 3. Substrait Rel -> Calcite RelNode | ||
| RelRoot relRoot2 = substraitToCalcite.convert(pojo1); | ||
|
|
||
| // 4. Calcite RelNode -> Substrait Rel | ||
| Rel pojo2 = | ||
| SubstraitRelVisitor.convert(RelRoot.of(relNode, SqlKind.SELECT), EXTENSION_COLLECTION); | ||
| // 4. Calcite RelNode -> Substrait Rel | ||
| Plan.Root pojo2 = SubstraitRelVisitor.convert(relRoot2, EXTENSION_COLLECTION); | ||
|
|
||
| Assertions.assertEquals(pojo1, pojo2); | ||
| } | ||
| return relNodeList; | ||
| Assertions.assertEquals(pojo1, pojo2); | ||
| return relRoot2; | ||
| } | ||
|
|
||
| @Beta | ||
|
|
@@ -140,37 +137,36 @@ protected void assertFullRoundTrip(String query) throws IOException, SqlParseExc | |
| protected void assertFullRoundTrip(String sqlQuery, List<String> createStatements) | ||
| throws SqlParseException { | ||
| SqlToSubstrait sqlConverter = new SqlToSubstrait(); | ||
| List<RelRoot> relRoots = sqlConverter.sqlToRelNode(sqlQuery, createStatements); | ||
| ExtensionCollector extensionCollector = new ExtensionCollector(); | ||
|
|
||
| for (RelRoot calcite1 : relRoots) { | ||
| var extensionCollector = new ExtensionCollector(); | ||
| // SQL -> Calcite 1 | ||
| List<RelRoot> relRoots = sqlConverter.sqlToRelNode(sqlQuery, createStatements); | ||
| assertEquals(1, relRoots.size()); | ||
| RelRoot calcite1 = relRoots.get(0); | ||
|
|
||
| // Calcite 1 -> Substrait POJO 1 | ||
| io.substrait.relation.Rel pojo1 = SubstraitRelVisitor.convert(calcite1, EXTENSION_COLLECTION); | ||
| // Calcite 1 -> Substrait POJO 1 | ||
| Plan.Root pojo1 = SubstraitRelVisitor.convert(calcite1, EXTENSION_COLLECTION); | ||
|
|
||
| // Substrait POJO 1 -> Substrait Proto | ||
| io.substrait.proto.Rel proto = new RelProtoConverter(extensionCollector).toProto(pojo1); | ||
| // Substrait POJO 1 -> Substrait Proto | ||
| io.substrait.proto.RelRoot proto = new RelProtoConverter(extensionCollector).toProto(pojo1); | ||
|
|
||
| // Substrait Proto -> Substrait Pojo 2 | ||
| io.substrait.relation.Rel pojo2 = | ||
| new ProtoRelConverter(extensionCollector, EXTENSION_COLLECTION).from(proto); | ||
| // Substrait Proto -> Substrait Pojo 2 | ||
| Plan.Root pojo2 = new ProtoRelConverter(extensionCollector, EXTENSION_COLLECTION).from(proto); | ||
|
|
||
| // Verify that POJOs are the same | ||
| assertEquals(pojo1, pojo2); | ||
| // Verify that POJOs are the same | ||
| assertEquals(pojo1, pojo2); | ||
|
|
||
| // Substrait POJO 2 -> Calcite 2 | ||
| RelNode calcite2 = new SubstraitToCalcite(EXTENSION_COLLECTION, typeFactory).convert(pojo2); | ||
| // It would be ideal to compare calcite1 and calcite2, however there isn't a good mechanism to | ||
| // do so | ||
| assertNotNull(calcite2); | ||
| // Substrait POJO 2 -> Calcite 2 | ||
| RelRoot calcite2 = new SubstraitToCalcite(EXTENSION_COLLECTION, typeFactory).convert(pojo2); | ||
| // It would be ideal to compare calcite1 and calcite2, however there isn't a good mechanism to | ||
| // do so | ||
| assertNotNull(calcite2); | ||
|
|
||
| // Calcite 2 -> Substrait POJO 3 | ||
| io.substrait.relation.Rel pojo3 = | ||
| SubstraitRelVisitor.convert(RelRoot.of(calcite2, calcite1.kind), EXTENSION_COLLECTION); | ||
| // Calcite 2 -> Substrait POJO 3 | ||
| Plan.Root pojo3 = SubstraitRelVisitor.convert(calcite2, EXTENSION_COLLECTION); | ||
|
|
||
| // Verify that POJOs are the same | ||
| assertEquals(pojo1, pojo3); | ||
| } | ||
| // Verify that POJOs are the same | ||
| assertEquals(pojo1, pojo3); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -182,6 +178,7 @@ protected void assertFullRoundTrip(String sqlQuery, List<String> createStatement | |
| * </ul> | ||
| */ | ||
| protected void assertFullRoundTrip(Rel pojo1) { | ||
| // TODO: reuse the Plan.Root based assertFullRoundTrip by generating names | ||
| var extensionCollector = new ExtensionCollector(); | ||
|
|
||
| // Substrait POJO 1 -> Substrait Proto | ||
|
|
@@ -198,9 +195,38 @@ protected void assertFullRoundTrip(Rel pojo1) { | |
| RelNode calcite = new SubstraitToCalcite(EXTENSION_COLLECTION, typeFactory).convert(pojo2); | ||
|
|
||
| // Calcite -> Substrait POJO 3 | ||
| io.substrait.relation.Rel pojo3 = | ||
| // SqlKind.SELECT is used because the majority of our tests are SELECT queries | ||
| SubstraitRelVisitor.convert(RelRoot.of(calcite, SqlKind.SELECT), EXTENSION_COLLECTION); | ||
| io.substrait.relation.Rel pojo3 = SubstraitRelVisitor.convert(calcite, EXTENSION_COLLECTION); | ||
|
|
||
| // Verify that POJOs are the same | ||
| assertEquals(pojo1, pojo3); | ||
| } | ||
|
|
||
| /** | ||
| * Verifies that the given POJO can be converted: | ||
| * | ||
| * <ul> | ||
| * <li>From POJO to Proto and back | ||
| * <li>From POJO to Calcite and back | ||
| * </ul> | ||
| */ | ||
| protected void assertFullRoundTrip(Plan.Root pojo1) { | ||
| var extensionCollector = new ExtensionCollector(); | ||
|
|
||
| // Substrait POJO 1 -> Substrait Proto | ||
| io.substrait.proto.RelRoot proto = new RelProtoConverter(extensionCollector).toProto(pojo1); | ||
|
|
||
| // Substrait Proto -> Substrait Pojo 2 | ||
| io.substrait.plan.Plan.Root pojo2 = | ||
| new ProtoRelConverter(extensionCollector, EXTENSION_COLLECTION).from(proto); | ||
|
|
||
| // Verify that POJOs are the same | ||
| assertEquals(pojo1, pojo2); | ||
|
|
||
| // Substrait POJO 2 -> Calcite | ||
| RelRoot calcite = new SubstraitToCalcite(EXTENSION_COLLECTION, typeFactory).convert(pojo2); | ||
|
|
||
| // Calcite -> Substrait POJO 3 | ||
| io.substrait.plan.Plan.Root pojo3 = SubstraitRelVisitor.convert(calcite, EXTENSION_COLLECTION); | ||
|
|
||
| // Verify that POJOs are the same | ||
| assertEquals(pojo1, pojo3); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a breaking API change.
A Calcite
RelRootcorresponds to POJOPlan.Root, not a POJORel.