Skip to content
Merged
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 @@ -23,6 +23,21 @@
import org.apache.calcite.sql.type.SqlTypeFamily;
import org.apache.calcite.sql.type.SqlTypeName;

/**
* Utility class for converting Substrait {@link SimpleExtension} function definitions (scalar and
* aggregate) into Calcite {@link SqlOperator}s.
*
* <p>This enables Calcite to recognize and use Substrait-defined functions during query planning
* and execution. Conversion includes:
*
* <ul>
* <li>Mapping Substrait types to Calcite {@link SqlTypeName}
* <li>Building {@link SqlFunction} instances with proper argument families
* <li>Inferring return types based on Substrait type expressions and nullability rules
* </ul>
*
* <p>Currently supports scalar and aggregate functions; window functions are not yet implemented.
*/
public final class SimpleExtensionToSqlOperator {

private static final RelDataTypeFactory DEFAULT_TYPE_FACTORY =
Expand All @@ -32,15 +47,40 @@ public final class SimpleExtensionToSqlOperator {

private SimpleExtensionToSqlOperator() {}

/**
* Converts all functions in a Substrait {@link SimpleExtension.ExtensionCollection} (scalar and
* aggregate) into Calcite {@link SqlOperator}s using the default type factory.
*
* @param collection The Substrait extension collection containing function definitions.
* @return A list of Calcite {@link SqlOperator}s corresponding to the Substrait functions.
*/
public static List<SqlOperator> from(SimpleExtension.ExtensionCollection collection) {
return from(collection, DEFAULT_TYPE_FACTORY);
}

/**
* Converts all functions in a Substrait {@link SimpleExtension.ExtensionCollection} (scalar and
* aggregate) into Calcite {@link SqlOperator}s using a provided type factory.
*
* @param collection The Substrait extension collection containing function definitions.
* @param typeFactory Calcite {@link RelDataTypeFactory} for type creation and inference.
* @return A list of Calcite {@link SqlOperator}s corresponding to the Substrait functions.
*/
public static List<SqlOperator> from(
SimpleExtension.ExtensionCollection collection, RelDataTypeFactory typeFactory) {
return from(collection, typeFactory, TypeConverter.DEFAULT);
}

/**
* Converts all functions in a Substrait {@link SimpleExtension.ExtensionCollection} (scalar and
* aggregate) into Calcite {@link SqlOperator}s with a custom type factory and {@link
* TypeConverter}.
*
* @param collection The Substrait extension collection containing function definitions.
* @param typeFactory Calcite {@link RelDataTypeFactory} for type creation and inference.
* @param typeConverter Converter for Substrait/Calcite type mappings.
* @return A list of Calcite {@link SqlOperator}s corresponding to the Substrait functions.
*/
public static List<SqlOperator> from(
SimpleExtension.ExtensionCollection collection,
RelDataTypeFactory typeFactory,
Expand Down
21 changes: 21 additions & 0 deletions isthmus/src/main/java/io/substrait/isthmus/SqlConverterBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,41 @@
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql2rel.SqlToRelConverter;

/**
* Base class for Substrait SQL conversion pipelines.
*
* <p>Configures Calcite parser, connection, planner, and cluster. Holds the Substrait extensions
* and feature flags. Subclasses can build conversions from SQL to Calcite/Substrait using this
* shared setup.
*/
public class SqlConverterBase {
protected final ConverterProvider converterProvider;

/** Default Calcite connection config (case-insensitive). */
public static final CalciteConnectionConfig CONNECTION_CONFIG =
CalciteConnectionConfig.DEFAULT.set(
CalciteConnectionProperty.CASE_SENSITIVE, Boolean.FALSE.toString());

/** Calcite type factory using the Substrait type system. */
final RelDataTypeFactory factory;

/** Calcite optimization cluster with planner, type factory, and RexBuilder. */
final RelOptCluster relOptCluster;

/** Connection configuration used for SQL parsing and validation. */
final CalciteConnectionConfig config;

/** Configuration for SQL-to-Rel conversion. */
final SqlToRelConverter.Config converterConfig;

/** Parser configuration, including casing and DDL parser factory. */
final SqlParser.Config parserConfig;

/**
* Creates a converter base with explicit features and extensions.
*
* @param converterProvider Converter Provider for configuration
*/
protected SqlConverterBase(ConverterProvider converterProvider) {
this.converterProvider = converterProvider;
this.factory = converterProvider.getTypeFactory();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,47 @@
import org.apache.calcite.sql2rel.SqlToRelConverter;
import org.apache.calcite.sql2rel.StandardConvertletTable;

/**
* Converts SQL expressions to Substrait {@link io.substrait.proto.ExtendedExpression} payloads.
*
* <p>Supports optional CREATE TABLE statements to provide schema and column bindings for expression
* validation and Rex conversion.
*/
public class SqlExpressionToSubstrait extends SqlConverterBase {

/** Converter for RexNodes to Substrait expressions. */
protected final RexExpressionConverter rexConverter;

/** Creates a converter with default configuration. */
public SqlExpressionToSubstrait() {
this(new ConverterProvider());
}

/**
* Creates a converter with the given converter provider.
*
* @param converterProvider Converter Provider to use for configuration
*/
public SqlExpressionToSubstrait(ConverterProvider converterProvider) {
super(converterProvider);
this.rexConverter = new RexExpressionConverter(converterProvider.getScalarFunctionConverter());
}

/** Bundled result carrying validator, catalog reader, and name/type and name/node maps. */
private static final class Result {
final SqlValidator validator;
final CalciteCatalogReader catalogReader;
final Map<String, RelDataType> nameToTypeMap;
final Map<String, RexNode> nameToNodeMap;

/**
* Creates a result bundle.
*
* @param validator SQL validator
* @param catalogReader Calcite catalog reader
* @param nameToTypeMap mapping from column name to Calcite type
* @param nameToNodeMap mapping from column name to Rex input ref
*/
Result(
SqlValidator validator,
CalciteCatalogReader catalogReader,
Expand All @@ -59,25 +81,25 @@ private static final class Result {
}

/**
* Converts the given SQL expression to an {@link io.substrait.proto.ExtendedExpression }
* Converts a single SQL expression to a Substrait {@link io.substrait.proto.ExtendedExpression}.
*
* @param sqlExpression a SQL expression
* @param createStatements table creation statements defining fields referenced by the expression
* @return a {@link io.substrait.proto.ExtendedExpression }
* @throws SqlParseException
* @return the Substrait extended expression proto
* @throws SqlParseException if parsing or validation fails
*/
public io.substrait.proto.ExtendedExpression convert(
String sqlExpression, List<String> createStatements) throws SqlParseException {
return convert(new String[] {sqlExpression}, createStatements);
}

/**
* Converts the given SQL expressions to an {@link io.substrait.proto.ExtendedExpression }
* Converts multiple SQL expressions to a Substrait {@link io.substrait.proto.ExtendedExpression}.
*
* @param sqlExpressions an array of SQL expressions
* @param createStatements table creation statements defining fields referenced by the expression
* @return a {@link io.substrait.proto.ExtendedExpression }
* @throws SqlParseException
* @param sqlExpressions array of SQL expressions
* @param createStatements table creation statements defining fields referenced by the expressions
* @return the Substrait extended expression proto
* @throws SqlParseException if parsing or validation fails
*/
public io.substrait.proto.ExtendedExpression convert(
String[] sqlExpressions, List<String> createStatements) throws SqlParseException {
Expand All @@ -90,6 +112,17 @@ public io.substrait.proto.ExtendedExpression convert(
result.nameToNodeMap);
}

/**
* Converts the given SQL expressions using the provided validator/catalog and column bindings.
*
* @param sqlExpressions array of SQL expressions
* @param validator SQL validator
* @param catalogReader Calcite catalog reader
* @param nameToTypeMap mapping from column name to Calcite type
* @param nameToNodeMap mapping from column name to Rex input ref
* @return the Substrait extended expression proto
* @throws SqlParseException if parsing or validation fails
*/
private io.substrait.proto.ExtendedExpression executeInnerSQLExpressions(
String[] sqlExpressions,
SqlValidator validator,
Expand Down Expand Up @@ -120,6 +153,17 @@ private io.substrait.proto.ExtendedExpression executeInnerSQLExpressions(
return new ExtendedExpressionProtoConverter().toProto(extendedExpression.build());
}

/**
* Parses and validates a SQL expression, then converts it to a {@link RexNode}.
*
* @param sql SQL expression string
* @param validator SQL validator
* @param catalogReader Calcite catalog reader
* @param nameToTypeMap mapping from column name to Calcite type
* @param nameToNodeMap mapping from column name to Rex input ref
* @return the converted RexNode
* @throws SqlParseException if parsing or validation fails
*/
private RexNode sqlToRexNode(
String sql,
SqlValidator validator,
Expand All @@ -141,6 +185,13 @@ private RexNode sqlToRexNode(
return converter.convertExpression(validSqlNode, nameToNodeMap);
}

/**
* Registers tables from CREATE statements and prepares validator, catalog, and column bindings.
*
* @param tables list of CREATE TABLE statements; may be null
* @return result bundle containing validator, catalog reader, and name/type and name/node maps
* @throws SqlParseException if any CREATE statement is invalid
*/
private Result registerCreateTablesForExtendedExpression(List<String> tables)
throws SqlParseException {
Map<String, RelDataType> nameToTypeMap = new LinkedHashMap<>();
Expand Down Expand Up @@ -177,6 +228,12 @@ private Result registerCreateTablesForExtendedExpression(List<String> tables)
return new Result(validator, catalogReader, nameToTypeMap, nameToNodeMap);
}

/**
* Converts a name-to-type map into a {@link NamedStruct} in Substrait types.
*
* @param nameToTypeMap mapping from column name to Calcite type
* @return a {@link NamedStruct} with non-nullable struct type
*/
private NamedStruct toNamedStruct(Map<String, RelDataType> nameToTypeMap) {
ArrayList<String> names = new ArrayList<String>();
ArrayList<Type> types = new ArrayList<Type>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,16 @@
public class SqlToSubstrait extends SqlConverterBase {
private final SqlOperatorTable operatorTable;

/** Creates a SQL-to-Substrait converter using the default configuration. */
public SqlToSubstrait() {
this(new ConverterProvider());
}

/**
* Creates a SQL-to-Substrait converter with explicit extensions and features.
*
* @param converterProvider Converter Provider for the configuration
*/
public SqlToSubstrait(ConverterProvider converterProvider) {
super(converterProvider);
this.operatorTable = converterProvider.getSqlOperatorTable();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,17 +90,36 @@
public class SubstraitRelNodeConverter
extends AbstractRelVisitor<RelNode, SubstraitRelNodeConverter.Context, RuntimeException> {

/** Calcite type factory used to construct row and field types. */
protected final RelDataTypeFactory typeFactory;

/** Converter for Substrait scalar functions to Calcite operators. */
protected final ScalarFunctionConverter scalarFunctionConverter;

/** Converter for Substrait aggregate functions to Calcite operators. */
protected final AggregateFunctionConverter aggregateFunctionConverter;

/** Converts Substrait {@code Expression}s into Calcite {@code RexNode}s. */
protected final ExpressionRexConverter expressionRexConverter;

/** Calcite {@link RelBuilder} used to construct relational expressions during conversion. */
protected final RelBuilder relBuilder;

/** Calcite {@link RexBuilder} used to build Rex nodes (e.g., input refs, literals). */
protected final RexBuilder rexBuilder;

/** Type converter to translate between Calcite and Substrait type systems. */
private final TypeConverter typeConverter;

/** Use {@link #SubstraitRelNodeConverter(RelBuilder, ConverterProvider)} instead */
/**
* Creates a new SubstraitRelNodeConverter with the specified extensions, type factory, and
* relation builder.
*
* @param extensions the Substrait extension collection
* @param typeFactory the Calcite type factory
* @param relBuilder the Calcite relation builder
* @deprecated Use {@link #SubstraitRelNodeConverter(RelBuilder, ConverterProvider)} instead
*/
@Deprecated
public SubstraitRelNodeConverter(
SimpleExtension.ExtensionCollection extensions,
Expand All @@ -109,6 +128,13 @@ public SubstraitRelNodeConverter(
this(relBuilder, new ConverterProvider(extensions, typeFactory));
}

/**
* Creates a new SubstraitRelNodeConverter with the specified relation builder and converter
* provider.
*
* @param relBuilder the Calcite relation builder
* @param converterProvider the converter provider containing configuration and converters
*/
public SubstraitRelNodeConverter(RelBuilder relBuilder, ConverterProvider converterProvider) {
this.typeFactory = converterProvider.getTypeFactory();
this.typeConverter = converterProvider.getTypeConverter();
Expand All @@ -119,6 +145,14 @@ public SubstraitRelNodeConverter(RelBuilder relBuilder, ConverterProvider conver
this.expressionRexConverter = converterProvider.getExpressionRexConverter(this);
}

/**
* Converts a Substrait {@link Rel} plan to a Calcite {@link RelNode}.
*
* @param relRoot the root Substrait relation to convert
* @param catalogReader the Calcite catalog reader for schema resolution
* @param converterProvider the converter provider containing configuration and converters
* @return the converted Calcite {@link RelNode}
*/
public static RelNode convert(
Rel relRoot, Prepare.CatalogReader catalogReader, ConverterProvider converterProvider) {
RelBuilder relBuilder =
Expand Down Expand Up @@ -736,6 +770,16 @@ public RelNode visitFallback(Rel rel, Context context) throws RuntimeException {
rel, rel.getClass().getCanonicalName(), this.getClass().getCanonicalName()));
}

/**
* Applies an optional field remap to the given node.
*
* <p>If {@code remap} is present, the node is projected according to the provided indices;
* otherwise the original node is returned unchanged.
*
* @param relNode the node to remap
* @param remap optional field index remap
* @return remapped node or original node if no remap is present
*/
protected RelNode applyRemap(RelNode relNode, Optional<Rel.Remap> remap) {
if (remap.isPresent()) {
return applyRemap(relNode, remap.get());
Expand All @@ -759,8 +803,10 @@ private RelNode applyRemap(RelNode relNode, Rel.Remap remap) {

/** A shared context for the Substrait to RelNode conversion. */
public static class Context implements VisitationContext {
/** Stack of outer row type range maps used to resolve correlated references. */
protected final Stack<RangeMap<Integer, RelDataType>> outerRowTypes = new Stack<>();

/** Stack of correlation ids collected while visiting subqueries. */
protected final Stack<java.util.Set<CorrelationId>> correlationIds = new Stack<>();

private int subqueryDepth;
Expand Down Expand Up @@ -798,6 +844,7 @@ public void pushOuterRowType(final RelDataType... inputs) {
this.correlationIds.push(new HashSet<>());
}

/** Pops the most recent outer row type from the stack. */
public void popOuterRowType() {
outerRowTypes.pop();
}
Expand Down
Loading