Skip to content

Commit bb58d42

Browse files
authored
feat: introduce ConverterProvider to control conversion behaviour (#649)
Introduces the ConverterProvider class as a single source of configuration for all conversion related classes between SQL, Calcite and Substrait. All conversion classes now have constructors consuming a ConverterProvider. The no-arg ConverterProvider constructor provides reasonable defaults for conversions. Other constructors can override some behaviours, and full customization can be achieved by extending the ConverterProvider, as is done in DynamicConverterProvider BREAKING CHANGE: removed FeatureBoard class BREAKING CHANGE: all constructors consuming a FeatureBoard have been removed BREAKING CHANGE: a number of methods/constructors have been replaced with ConverterProvider equivalents
1 parent 324394c commit bb58d42

18 files changed

Lines changed: 553 additions & 597 deletions

isthmus-cli/src/main/java/io/substrait/isthmus/cli/IsthmusEntryPoint.java

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
package io.substrait.isthmus.cli;
22

3-
import com.google.common.annotations.VisibleForTesting;
43
import com.google.protobuf.Message;
54
import com.google.protobuf.TextFormat;
65
import com.google.protobuf.util.JsonFormat;
7-
import io.substrait.extension.DefaultExtensionCatalog;
8-
import io.substrait.isthmus.FeatureBoard;
9-
import io.substrait.isthmus.ImmutableFeatureBoard;
6+
import io.substrait.isthmus.ConverterProvider;
107
import io.substrait.isthmus.SqlExpressionToSubstrait;
118
import io.substrait.isthmus.SqlToSubstrait;
129
import io.substrait.isthmus.sql.SubstraitCreateStatementParser;
@@ -18,6 +15,7 @@
1815
import java.util.concurrent.Callable;
1916
import org.apache.calcite.avatica.util.Casing;
2017
import org.apache.calcite.prepare.Prepare;
18+
import org.apache.calcite.sql.parser.SqlParser;
2119
import picocli.CommandLine;
2220
import picocli.CommandLine.Command;
2321
import picocli.CommandLine.Option;
@@ -62,6 +60,15 @@ enum OutputFormat {
6260
description = "Calcite's casing policy for unquoted identifiers: ${COMPLETION-CANDIDATES}")
6361
private Casing unquotedCasing = Casing.TO_UPPER;
6462

63+
private ConverterProvider converterProvider() {
64+
return new ConverterProvider() {
65+
@Override
66+
public SqlParser.Config getSqlParserConfig() {
67+
return super.getSqlParserConfig().withUnquotedCasing(unquotedCasing);
68+
}
69+
};
70+
}
71+
6572
/**
6673
* Standard Java Main method invoked by the isthmus CLI command.
6774
*
@@ -89,15 +96,13 @@ public static void main(String... args) {
8996

9097
@Override
9198
public Integer call() throws Exception {
92-
FeatureBoard featureBoard = buildFeatureBoard();
9399
// Isthmus image is parsing SQL Expression if that argument is defined
94100
if (sqlExpressions != null) {
95-
SqlExpressionToSubstrait converter =
96-
new SqlExpressionToSubstrait(featureBoard, DefaultExtensionCatalog.DEFAULT_COLLECTION);
101+
SqlExpressionToSubstrait converter = new SqlExpressionToSubstrait(converterProvider());
97102
ExtendedExpression extendedExpression = converter.convert(sqlExpressions, createStatements);
98103
printMessage(extendedExpression);
99104
} else { // by default Isthmus image are parsing SQL Query
100-
SqlToSubstrait converter = new SqlToSubstrait(featureBoard);
105+
SqlToSubstrait converter = new SqlToSubstrait(converterProvider());
101106
Prepare.CatalogReader catalog =
102107
SubstraitCreateStatementParser.processCreateStatementsToCatalog(
103108
createStatements.toArray(String[]::new));
@@ -116,9 +121,4 @@ private void printMessage(Message message) throws IOException {
116121
message.writeTo(System.out);
117122
}
118123
}
119-
120-
@VisibleForTesting
121-
FeatureBoard buildFeatureBoard() {
122-
return ImmutableFeatureBoard.builder().unquotedCasing(unquotedCasing).build();
123-
}
124124
}
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
package io.substrait.isthmus;
2+
3+
import io.substrait.extension.DefaultExtensionCatalog;
4+
import io.substrait.extension.SimpleExtension;
5+
import io.substrait.isthmus.calcite.SubstraitOperatorTable;
6+
import io.substrait.isthmus.expression.AggregateFunctionConverter;
7+
import io.substrait.isthmus.expression.CallConverters;
8+
import io.substrait.isthmus.expression.ExpressionRexConverter;
9+
import io.substrait.isthmus.expression.FieldSelectionConverter;
10+
import io.substrait.isthmus.expression.RexExpressionConverter;
11+
import io.substrait.isthmus.expression.ScalarFunctionConverter;
12+
import io.substrait.isthmus.expression.SqlArrayValueConstructorCallConverter;
13+
import io.substrait.isthmus.expression.SqlMapValueConstructorCallConverter;
14+
import io.substrait.isthmus.expression.WindowFunctionConverter;
15+
import io.substrait.relation.Rel;
16+
import java.util.ArrayList;
17+
import java.util.List;
18+
import java.util.function.Function;
19+
import org.apache.calcite.avatica.util.Casing;
20+
import org.apache.calcite.config.CalciteConnectionConfig;
21+
import org.apache.calcite.config.CalciteConnectionProperty;
22+
import org.apache.calcite.jdbc.CalciteSchema;
23+
import org.apache.calcite.prepare.Prepare;
24+
import org.apache.calcite.rel.type.RelDataTypeFactory;
25+
import org.apache.calcite.rex.RexBuilder;
26+
import org.apache.calcite.sql.SqlOperatorTable;
27+
import org.apache.calcite.sql.parser.SqlParser;
28+
import org.apache.calcite.sql.parser.ddl.SqlDdlParserImpl;
29+
import org.apache.calcite.sql.validate.SqlConformanceEnum;
30+
import org.apache.calcite.sql2rel.SqlToRelConverter;
31+
import org.apache.calcite.tools.Frameworks;
32+
import org.apache.calcite.tools.RelBuilder;
33+
34+
/**
35+
* ConverterProvider provides a single-point of configuration for a number of conversions: {@code
36+
* SQl <-> Calcite <-> Substrait}
37+
*
38+
* <p>It is consumed by all conversion classes as their primary source of configuration.
39+
*
40+
* <p>The no argument constructor {@link #ConverterProvider()} provides reasonable system defaults.
41+
*
42+
* <p>Other constructors allow for further customization of conversion behaviours.
43+
*
44+
* <p>More in-depth customization can be achieved by extending this class, as is done in {@link
45+
* DynamicConverterProvider}.
46+
*/
47+
public class ConverterProvider {
48+
49+
protected RelDataTypeFactory typeFactory;
50+
51+
protected final SimpleExtension.ExtensionCollection extensions;
52+
53+
protected ScalarFunctionConverter scalarFunctionConverter;
54+
protected AggregateFunctionConverter aggregateFunctionConverter;
55+
protected WindowFunctionConverter windowFunctionConverter;
56+
57+
protected TypeConverter typeConverter;
58+
59+
public ConverterProvider() {
60+
this(DefaultExtensionCatalog.DEFAULT_COLLECTION, SubstraitTypeSystem.TYPE_FACTORY);
61+
}
62+
63+
public ConverterProvider(SimpleExtension.ExtensionCollection extensions) {
64+
this(extensions, SubstraitTypeSystem.TYPE_FACTORY);
65+
}
66+
67+
public ConverterProvider(
68+
SimpleExtension.ExtensionCollection extensions, RelDataTypeFactory typeFactory) {
69+
this(
70+
typeFactory,
71+
extensions,
72+
new ScalarFunctionConverter(extensions.scalarFunctions(), typeFactory),
73+
new AggregateFunctionConverter(extensions.aggregateFunctions(), typeFactory),
74+
new WindowFunctionConverter(extensions.windowFunctions(), typeFactory),
75+
TypeConverter.DEFAULT);
76+
}
77+
78+
public ConverterProvider(
79+
RelDataTypeFactory typeFactory,
80+
SimpleExtension.ExtensionCollection extensions,
81+
ScalarFunctionConverter sfc,
82+
AggregateFunctionConverter afc,
83+
WindowFunctionConverter wfc,
84+
TypeConverter tc) {
85+
this.typeFactory = typeFactory;
86+
this.extensions = extensions;
87+
this.scalarFunctionConverter = sfc;
88+
this.aggregateFunctionConverter = afc;
89+
this.windowFunctionConverter = wfc;
90+
this.typeConverter = tc;
91+
}
92+
93+
// SQL to Calcite Processing
94+
95+
/**
96+
* {@link SqlParser.Config} is a Calcite class which controls SQL parsing behaviour like
97+
* identifier casing.
98+
*/
99+
public SqlParser.Config getSqlParserConfig() {
100+
return SqlParser.Config.DEFAULT
101+
.withUnquotedCasing(Casing.TO_UPPER)
102+
.withParserFactory(SqlDdlParserImpl.FACTORY)
103+
.withConformance(SqlConformanceEnum.LENIENT);
104+
}
105+
106+
/**
107+
* {@link CalciteConnectionConfig} is a Calcite class which controls SQL processing behaviour like
108+
* table name case-sensitivity.
109+
*/
110+
public CalciteConnectionConfig getCalciteConnectionConfig() {
111+
return CalciteConnectionConfig.DEFAULT.set(CalciteConnectionProperty.CASE_SENSITIVE, "false");
112+
}
113+
114+
/**
115+
* {@link SqlToRelConverter.Config} is a Calcite class which controls SQL processing behaviour
116+
* like field-trimming.
117+
*/
118+
public SqlToRelConverter.Config getSqlToRelConverterConfig() {
119+
return SqlToRelConverter.config().withTrimUnusedFields(true).withExpand(false);
120+
}
121+
122+
/**
123+
* {@link SqlOperatorTable} is a Calcite class which stores the {@link
124+
* org.apache.calcite.sql.SqlOperator}s available and controls valid identifiers during SQL
125+
* processing.
126+
*/
127+
public SqlOperatorTable getSqlOperatorTable() {
128+
return SubstraitOperatorTable.INSTANCE;
129+
}
130+
131+
// Substrait to Calcite Processing
132+
133+
/**
134+
* {@link SubstraitToCalcite} is an Isthmus class for converting a Substrait {@link Rel} or {@link
135+
* io.substrait.plan.Plan.Root} to a Calcite {@link org.apache.calcite.rel.RelNode} or {@link
136+
* org.apache.calcite.rel.RelRoot}
137+
*/
138+
protected SubstraitToCalcite getSubstraitToCalcite() {
139+
return new SubstraitToCalcite(this);
140+
}
141+
142+
/**
143+
* {@link SubstraitToCalcite} is an Isthmus class for converting a Substrait {@link Rel} or {@link
144+
* io.substrait.plan.Plan.Root} to a Calcite {@link org.apache.calcite.rel.RelNode} or {@link
145+
* org.apache.calcite.rel.RelRoot}
146+
*
147+
* @param catalogReader a Calcite {@link Prepare.CatalogReader} used to construct a {@link
148+
* RelBuilder} for use in creating Calcite {@link org.apache.calcite.rel.RelNode}s
149+
*/
150+
protected SubstraitToCalcite getSubstraitToCalcite(Prepare.CatalogReader catalogReader) {
151+
return new SubstraitToCalcite(this, catalogReader);
152+
}
153+
154+
// Calcite to Substrait Processing
155+
156+
/**
157+
* A {@link SubstraitRelVisitor} converts Calcite {@link org.apache.calcite.rel.RelNode}s to
158+
* Substrait {@link Rel}s
159+
*/
160+
public SubstraitRelVisitor getSubstraitRelVisitor() {
161+
return new SubstraitRelVisitor(this);
162+
}
163+
164+
/**
165+
* A {@link RexExpressionConverter} converts Calcite {@link org.apache.calcite.rex.RexNode}s to
166+
* Substrait equivalents.
167+
*/
168+
public RexExpressionConverter getRexExpressionConverter(SubstraitRelVisitor srv) {
169+
return new RexExpressionConverter(
170+
srv, getCallConverters(), getWindowFunctionConverter(), getTypeConverter());
171+
}
172+
173+
/**
174+
* {@link CallConverter}s are used to convert Calcite {@link org.apache.calcite.rex.RexCall}s to
175+
* Substrait equivalents.
176+
*/
177+
public List<CallConverter> getCallConverters() {
178+
ArrayList<CallConverter> callConverters = new ArrayList<>();
179+
callConverters.add(new FieldSelectionConverter(typeConverter));
180+
callConverters.add(CallConverters.CASE);
181+
callConverters.add(CallConverters.CAST.apply(typeConverter));
182+
callConverters.add(CallConverters.REINTERPRET.apply(typeConverter));
183+
callConverters.add(new SqlArrayValueConstructorCallConverter(typeConverter));
184+
callConverters.add(new SqlMapValueConstructorCallConverter());
185+
callConverters.add(CallConverters.CREATE_SEARCH_CONV.apply(new RexBuilder(typeFactory)));
186+
callConverters.add(scalarFunctionConverter);
187+
return callConverters;
188+
}
189+
190+
// Substrait To Calcite Processing
191+
192+
/**
193+
* When converting from Substrait to Calcite, Calcite needs to have a schema available. The
194+
* default strategy uses a {@link SchemaCollector} to generate a {@link CalciteSchema} on the fly
195+
* based on the leaf nodes of a Substrait plan.
196+
*
197+
* <p>Override to customize the schema generation behaviour
198+
*/
199+
public Function<Rel, CalciteSchema> getSchemaResolver() {
200+
SchemaCollector schemaCollector = new SchemaCollector(this);
201+
return schemaCollector::toSchema;
202+
}
203+
204+
/**
205+
* A {@link SubstraitRelNodeConverter} is used when converting from Substrait {@link Rel}s to
206+
* Calcite {@link org.apache.calcite.rel.RelNode}s.
207+
*/
208+
public SubstraitRelNodeConverter getSubstraitRelNodeConverter(RelBuilder relBuilder) {
209+
return new SubstraitRelNodeConverter(relBuilder, this);
210+
}
211+
212+
/**
213+
* A {@link ExpressionRexConverter} converts Substrait {@link io.substrait.expression.Expression}
214+
* to Calcite equivalents
215+
*/
216+
public ExpressionRexConverter getExpressionRexConverter(
217+
SubstraitRelNodeConverter relNodeConverter) {
218+
ExpressionRexConverter erc =
219+
new ExpressionRexConverter(
220+
getTypeFactory(),
221+
getScalarFunctionConverter(),
222+
getWindowFunctionConverter(),
223+
getTypeConverter());
224+
erc.setRelNodeConverter(relNodeConverter);
225+
return erc;
226+
}
227+
228+
/**
229+
* A {@link RelBuilder} is a Calcite class used as a factory for creating {@link
230+
* org.apache.calcite.rel.RelNode}s.
231+
*/
232+
public RelBuilder getRelBuilder(CalciteSchema schema) {
233+
return RelBuilder.create(Frameworks.newConfigBuilder().defaultSchema(schema.plus()).build());
234+
}
235+
236+
// Utility Getters
237+
238+
public RelDataTypeFactory getTypeFactory() {
239+
return typeFactory;
240+
}
241+
242+
public SimpleExtension.ExtensionCollection getExtensions() {
243+
return extensions;
244+
}
245+
246+
public ScalarFunctionConverter getScalarFunctionConverter() {
247+
return scalarFunctionConverter;
248+
}
249+
250+
public AggregateFunctionConverter getAggregateFunctionConverter() {
251+
return aggregateFunctionConverter;
252+
}
253+
254+
public WindowFunctionConverter getWindowFunctionConverter() {
255+
return windowFunctionConverter;
256+
}
257+
258+
public TypeConverter getTypeConverter() {
259+
return typeConverter;
260+
}
261+
}

0 commit comments

Comments
 (0)