Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.substrait.examples.IsthmusAppExamples.Action;
import io.substrait.extension.DefaultExtensionCatalog;
import io.substrait.extension.SimpleExtension;
import io.substrait.isthmus.ConverterProvider;
import io.substrait.isthmus.SubstraitToCalcite;
import io.substrait.isthmus.SubstraitTypeSystem;
import io.substrait.plan.Plan;
Expand All @@ -11,7 +12,6 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import org.apache.calcite.jdbc.JavaTypeFactoryImpl;
import org.apache.calcite.rel.rel2sql.RelToSqlConverter;
import org.apache.calcite.sql.SqlDialect;

Expand Down Expand Up @@ -52,9 +52,9 @@ public void run(String[] args) {

final SimpleExtension.ExtensionCollection extensions =
DefaultExtensionCatalog.DEFAULT_COLLECTION;
final SubstraitToCalcite converter =
new SubstraitToCalcite(
extensions, new JavaTypeFactoryImpl(SubstraitTypeSystem.TYPE_SYSTEM));
final ConverterProvider converterProvider =
new ConverterProvider(SubstraitTypeSystem.TYPE_FACTORY, extensions);
final SubstraitToCalcite converter = new SubstraitToCalcite(converterProvider);

// Determine which SQL Dialect we want the converted queries to be in
final SqlDialect sqlDialect = SqlDialect.DatabaseProduct.MYSQL.getDialect();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
package io.substrait.isthmus.cli;

import com.google.common.annotations.VisibleForTesting;
import com.google.protobuf.Message;
import com.google.protobuf.TextFormat;
import com.google.protobuf.util.JsonFormat;
import io.substrait.extension.DefaultExtensionCatalog;
import io.substrait.isthmus.FeatureBoard;
import io.substrait.isthmus.ImmutableFeatureBoard;
import io.substrait.isthmus.SqlExpressionToSubstrait;
import io.substrait.isthmus.SqlToSubstrait;
import io.substrait.isthmus.sql.SubstraitCreateStatementParser;
Expand All @@ -16,7 +13,6 @@
import java.io.IOException;
import java.util.List;
import java.util.concurrent.Callable;
import org.apache.calcite.avatica.util.Casing;
import org.apache.calcite.prepare.Prepare;
import picocli.CommandLine;
import picocli.CommandLine.Command;
Expand Down Expand Up @@ -56,11 +52,6 @@ enum OutputFormat {
BINARY, // protobuf BINARY format
}

@Option(
names = {"--unquotedcasing"},
description = "Calcite's casing policy for unquoted identifiers: ${COMPLETION-CANDIDATES}")
private Casing unquotedCasing = Casing.TO_UPPER;

public static void main(String... args) {
CommandLine commandLine = new CommandLine(new IsthmusEntryPoint());
commandLine.setCaseInsensitiveEnumValuesAllowed(true);
Expand All @@ -83,15 +74,14 @@ public static void main(String... args) {

@Override
public Integer call() throws Exception {
FeatureBoard featureBoard = buildFeatureBoard();
// Isthmus image is parsing SQL Expression if that argument is defined
if (sqlExpressions != null) {
SqlExpressionToSubstrait converter =
new SqlExpressionToSubstrait(featureBoard, DefaultExtensionCatalog.DEFAULT_COLLECTION);
new SqlExpressionToSubstrait(DefaultExtensionCatalog.DEFAULT_COLLECTION);
ExtendedExpression extendedExpression = converter.convert(sqlExpressions, createStatements);
printMessage(extendedExpression);
} else { // by default Isthmus image are parsing SQL Query
SqlToSubstrait converter = new SqlToSubstrait(featureBoard);
SqlToSubstrait converter = new SqlToSubstrait();
Prepare.CatalogReader catalog =
SubstraitCreateStatementParser.processCreateStatementsToCatalog(
createStatements.toArray(String[]::new));
Expand All @@ -110,9 +100,4 @@ private void printMessage(Message message) throws IOException {
message.writeTo(System.out);
}
}

@VisibleForTesting
FeatureBoard buildFeatureBoard() {
return ImmutableFeatureBoard.builder().unquotedCasing(unquotedCasing).build();
}
}
228 changes: 228 additions & 0 deletions isthmus/src/main/java/io/substrait/isthmus/ConverterProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
package io.substrait.isthmus;

import io.substrait.extension.DefaultExtensionCatalog;
import io.substrait.extension.SimpleExtension;
import io.substrait.isthmus.calcite.SubstraitOperatorTable;
import io.substrait.isthmus.expression.AggregateFunctionConverter;
import io.substrait.isthmus.expression.CallConverters;
import io.substrait.isthmus.expression.ExpressionRexConverter;
import io.substrait.isthmus.expression.FieldSelectionConverter;
import io.substrait.isthmus.expression.RexExpressionConverter;
import io.substrait.isthmus.expression.ScalarFunctionConverter;
import io.substrait.isthmus.expression.SqlArrayValueConstructorCallConverter;
import io.substrait.isthmus.expression.SqlMapValueConstructorCallConverter;
import io.substrait.isthmus.expression.WindowFunctionConverter;
import io.substrait.relation.Rel;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import org.apache.calcite.avatica.util.Casing;
import org.apache.calcite.config.CalciteConnectionConfig;
import org.apache.calcite.config.CalciteConnectionProperty;
import org.apache.calcite.jdbc.CalciteSchema;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.sql.SqlOperatorTable;
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.parser.ddl.SqlDdlParserImpl;
import org.apache.calcite.sql.validate.SqlConformanceEnum;
import org.apache.calcite.sql2rel.SqlToRelConverter;
import org.apache.calcite.tools.Frameworks;
import org.apache.calcite.tools.RelBuilder;

/**
* ConverterProvider provides a single-point of configuration for a number of conversions: {@code
* SQl <-> Calcite <-> Substrait}
*
* <p>It is consumed by all conversion classes as their primary source of configuration.
*
* <p>The no argument constructor {@link #ConverterProvider()} provides reasonable system defaults.
*
* <p>Other constructors allow for further customization of conversion behaviours.
*
* <p>More in-depth customization can be achieved by extending this class, as is done in {@link
* DynamicConverterProvider}.
*/
public class ConverterProvider {
Copy link
Member Author

@vbarua vbarua Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We wire in the same 4 converters (3 function converter + 1 type converter) in a number of places (SubstraitRelVisitor, SubstraitRelNodeConverter, ...). Instead of doing that, we can centralized the configuration of converters into one provider*. This would also allow us to provide extended providers, like the DynamicConverterProvider, that could implement the dynamic fallback behaviour in a single location rather that sprinkling it throughout the various parts of the codebase, which makes it very difficult to reason about.

* I'm not set on the name


protected RelDataTypeFactory typeFactory;

protected ScalarFunctionConverter scalarFunctionConverter;
protected AggregateFunctionConverter aggregateFunctionConverter;
protected WindowFunctionConverter windowFunctionConverter;

protected TypeConverter typeConverter;

public ConverterProvider() {
this(SubstraitTypeSystem.TYPE_FACTORY, DefaultExtensionCatalog.DEFAULT_COLLECTION);
}

public ConverterProvider(SimpleExtension.ExtensionCollection extensions) {
this(SubstraitTypeSystem.TYPE_FACTORY, extensions);
}

public ConverterProvider(
RelDataTypeFactory typeFactory, SimpleExtension.ExtensionCollection extensions) {
this(
typeFactory,
new ScalarFunctionConverter(extensions.scalarFunctions(), typeFactory),
new AggregateFunctionConverter(extensions.aggregateFunctions(), typeFactory),
new WindowFunctionConverter(extensions.windowFunctions(), typeFactory),
TypeConverter.DEFAULT);
}

public ConverterProvider(
RelDataTypeFactory typeFactory,
ScalarFunctionConverter sfc,
AggregateFunctionConverter afc,
WindowFunctionConverter wfc,
TypeConverter tc) {
this.typeFactory = typeFactory;
this.scalarFunctionConverter = sfc;
this.aggregateFunctionConverter = afc;
this.windowFunctionConverter = wfc;
this.typeConverter = tc;
}

// SQL to Calcite Processing

/**
* A {@link SqlParser.Config} is a Calcite class which controls SQL parsing behaviour like
* identifier casing.
*/
public SqlParser.Config getSqlParserConfig() {
return SqlParser.Config.DEFAULT
.withUnquotedCasing(Casing.TO_UPPER)
.withParserFactory(SqlDdlParserImpl.FACTORY)
.withConformance(SqlConformanceEnum.LENIENT);
}

/**
* A {@link CalciteConnectionConfig} is a Calcite class which controls SQL processing behaviour
* like table name case-sensitivity.
*/
public CalciteConnectionConfig getCalciteConnectionConfig() {
return CalciteConnectionConfig.DEFAULT.set(CalciteConnectionProperty.CASE_SENSITIVE, "false");
}

/**
* A {@link SqlToRelConverter.Config} is a Calcite class which controls SQL processing behaviour
* like field-trimming.
*/
public SqlToRelConverter.Config getSqlToRelConverterConfig() {
return SqlToRelConverter.config().withTrimUnusedFields(true).withExpand(false);
}

/**
* A {@link SqlOperatorTable} is a Calcite class which stores the {@link
* org.apache.calcite.sql.SqlOperator}s available and controls valid identifiers during SQL
* processing.
*/
public SqlOperatorTable getSqlOperatorTable() {
return SubstraitOperatorTable.INSTANCE;
}

// Calcite to Substrait Processing

/**
* A {@link SubstraitRelVisitor} converts Calcite {@link org.apache.calcite.rel.RelNode}s to
* Substrait {@link Rel}s
*/
public SubstraitRelVisitor getSubstraitRelVisitor() {
return new SubstraitRelVisitor(this);
}

/**
* A {@link RexExpressionConverter} converts Calcite {@link org.apache.calcite.rex.RexNode}s to
* Substrait equivalents.
*/
public RexExpressionConverter getRexExpressionConverter(SubstraitRelVisitor srv) {
return new RexExpressionConverter(
srv, getCallConverters(), getWindowFunctionConverter(), getTypeConverter());
}

/**
* {@link CallConverter}s are used to convert Calcite {@link org.apache.calcite.rex.RexCall}s to
* Substrait equivalents.
*/
public List<CallConverter> getCallConverters() {
ArrayList<CallConverter> callConverters = new ArrayList<>();
callConverters.add(new FieldSelectionConverter(typeConverter));
callConverters.add(CallConverters.CASE);
callConverters.add(CallConverters.CAST.apply(typeConverter));
callConverters.add(CallConverters.REINTERPRET.apply(typeConverter));
callConverters.add(new SqlArrayValueConstructorCallConverter(typeConverter));
callConverters.add(new SqlMapValueConstructorCallConverter());
callConverters.add(CallConverters.CREATE_SEARCH_CONV.apply(new RexBuilder(typeFactory)));
callConverters.add(scalarFunctionConverter);
return callConverters;
}

// Substrait To Calcite Processing

/**
* When converting from Substrait to Calcite, Calcite needs to have a schema available. The
* default strategy uses a {@link SchemaCollector} to generate a {@link CalciteSchema} on the fly
* based on the leaf nodes of a Substrait plan.
*
* <p>Override to customize the schema generation behaviour
*/
public Function<Rel, CalciteSchema> getSchemaResolver() {
SchemaCollector schemaCollector = new SchemaCollector(this);
return schemaCollector::toSchema;
}

/**
* A {@link SubstraitRelNodeConverter} is used when converting from Substrait {@link Rel}s to
* Calcite {@link org.apache.calcite.rel.RelNode}s.
*/
public SubstraitRelNodeConverter getSubstraitRelNodeConverter(RelBuilder relBuilder) {
return new SubstraitRelNodeConverter(relBuilder, this);
}

/**
* A {@link ExpressionRexConverter} converts Substrait {@link io.substrait.expression.Expression}
* to Calcite equivalents
*/
public ExpressionRexConverter getExpressionRexConverter(
SubstraitRelNodeConverter relNodeConverter) {
ExpressionRexConverter erc =
new ExpressionRexConverter(
getTypeFactory(),
getScalarFunctionConverter(),
getWindowFunctionConverter(),
getTypeConverter());
erc.setRelNodeConverter(relNodeConverter);
return erc;
}

/**
* A {@link RelBuilder} is a Calcite class used as a factory for creating {@link
* org.apache.calcite.rel.RelNode}s.
*/
public RelBuilder getRelBuilder(CalciteSchema schema) {
return RelBuilder.create(Frameworks.newConfigBuilder().defaultSchema(schema.plus()).build());
}

// Utility Getters

public RelDataTypeFactory getTypeFactory() {
return typeFactory;
}

public ScalarFunctionConverter getScalarFunctionConverter() {
return scalarFunctionConverter;
}

public AggregateFunctionConverter getAggregateFunctionConverter() {
return aggregateFunctionConverter;
}

public WindowFunctionConverter getWindowFunctionConverter() {
return windowFunctionConverter;
}

public TypeConverter getTypeConverter() {
return typeConverter;
}
}
Loading