diff --git a/isthmus/src/main/java/io/substrait/isthmus/expression/RexExpressionConverter.java b/isthmus/src/main/java/io/substrait/isthmus/expression/RexExpressionConverter.java index 6993c8451..1a16a830e 100644 --- a/isthmus/src/main/java/io/substrait/isthmus/expression/RexExpressionConverter.java +++ b/isthmus/src/main/java/io/substrait/isthmus/expression/RexExpressionConverter.java @@ -32,6 +32,13 @@ import org.apache.calcite.sql.SqlKind; import org.apache.calcite.sql.fun.SqlStdOperatorTable; +/** + * Converts Calcite {@link RexNode} trees to Substrait {@link Expression}s. + * + *
Delegates function calls to registered {@link CallConverter}s and supports window function
+ * conversion via {@link WindowFunctionConverter}. Some Rex node kinds are intentionally unsupported
+ * and will throw {@link UnsupportedOperationException}.
+ */
public class RexExpressionConverter implements RexVisitor Missing {@code ScalarFunctionConverter} and {@code CallConverters.CREATE_SEARCH_CONV}.
*/
public RexExpressionConverter() {
this(null, CallConverters.defaults(TypeConverter.DEFAULT), null, TypeConverter.DEFAULT);
// TODO: Hide this AND/OR UPDATE tests
}
+ /**
+ * Converts a {@link RexInputRef} to a root struct field reference.
+ *
+ * @param inputRef the input reference
+ * @return a Substrait field reference expression
+ */
@Override
public Expression visitInputRef(RexInputRef inputRef) {
return FieldReference.newRootStructReference(
inputRef.getIndex(), typeConverter.toSubstrait(inputRef.getType()));
}
+ /**
+ * Converts a {@link RexCall} using registered {@link CallConverter}s.
+ *
+ * @param call the Rex call node
+ * @return the converted Substrait expression
+ * @throws IllegalArgumentException if no converter can handle the call
+ */
@Override
public Expression visitCall(RexCall call) {
for (CallConverter c : callConverters) {
@@ -84,6 +126,12 @@ public Expression visitCall(RexCall call) {
throw new IllegalArgumentException(callConversionFailureMessage(call));
}
+ /**
+ * Builds a concise failure message for an unsupported call conversion.
+ *
+ * @param call the Rex call node
+ * @return a human-readable message describing the failure
+ */
private String callConversionFailureMessage(RexCall call) {
return String.format(
"Unable to convert call %s(%s).",
@@ -93,11 +141,24 @@ private String callConversionFailureMessage(RexCall call) {
.collect(Collectors.joining(", ")));
}
+ /**
+ * Converts a {@link RexLiteral} to a Substrait literal expression.
+ *
+ * @param literal the Rex literal
+ * @return the converted Substrait expression
+ */
@Override
public Expression visitLiteral(RexLiteral literal) {
return (new LiteralConverter(typeConverter)).convert(literal);
}
+ /**
+ * Converts a {@link RexOver} window function call.
+ *
+ * @param over the windowed call
+ * @return the converted Substrait expression
+ * @throws IllegalArgumentException if {@code IGNORE NULLS} is used or conversion fails
+ */
@Override
public Expression visitOver(RexOver over) {
if (over.ignoreNulls()) {
@@ -109,21 +170,49 @@ public Expression visitOver(RexOver over) {
.orElseThrow(() -> new IllegalArgumentException(callConversionFailureMessage(over)));
}
+ /**
+ * Not supported.
+ *
+ * @param correlVariable the correl variable
+ * @return never returns
+ * @throws UnsupportedOperationException always
+ */
@Override
public Expression visitCorrelVariable(RexCorrelVariable correlVariable) {
throw new UnsupportedOperationException("RexCorrelVariable not supported");
}
+ /**
+ * Not supported.
+ *
+ * @param dynamicParam the dynamic parameter
+ * @return never returns
+ * @throws UnsupportedOperationException always
+ */
@Override
public Expression visitDynamicParam(RexDynamicParam dynamicParam) {
throw new UnsupportedOperationException("RexDynamicParam not supported");
}
+ /**
+ * Not supported.
+ *
+ * @param rangeRef the range ref
+ * @return never returns
+ * @throws UnsupportedOperationException always
+ */
@Override
public Expression visitRangeRef(RexRangeRef rangeRef) {
throw new UnsupportedOperationException("RexRangeRef not supported");
}
+ /**
+ * Converts a {@link RexFieldAccess} to a Substrait field reference expression.
+ *
+ * @param fieldAccess the field access
+ * @return the converted Substrait expression
+ * @throws UnsupportedOperationException for unsupported reference kinds
+ */
@Override
public Expression visitFieldAccess(RexFieldAccess fieldAccess) {
SqlKind kind = fieldAccess.getReferenceExpr().getKind();
@@ -155,6 +244,13 @@ public Expression visitFieldAccess(RexFieldAccess fieldAccess) {
}
}
+ /**
+ * Converts a {@link RexSubQuery} into a Substrait set or scalar subquery expression.
+ *
+ * @param subQuery the subquery node
+ * @return the converted Substrait expression
+ * @throws UnsupportedOperationException for unsupported subquery operators
+ */
@Override
public Expression visitSubQuery(RexSubQuery subQuery) {
Rel rel = relVisitor.apply(subQuery.rel);
@@ -185,31 +281,73 @@ public Expression visitSubQuery(RexSubQuery subQuery) {
throw new UnsupportedOperationException("RexSubQuery not supported");
}
+ /**
+ * Not supported.
+ *
+ * @param fieldRef the table input reference
+ * @return never returns
+ * @throws UnsupportedOperationException always
+ */
@Override
public Expression visitTableInputRef(RexTableInputRef fieldRef) {
throw new UnsupportedOperationException("RexTableInputRef not supported");
}
+ /**
+ * Not supported.
+ *
+ * @param localRef the local reference
+ * @return never returns
+ * @throws UnsupportedOperationException always
+ */
@Override
public Expression visitLocalRef(RexLocalRef localRef) {
throw new UnsupportedOperationException("RexLocalRef not supported");
}
+ /**
+ * Not supported.
+ *
+ * @param fieldRef the pattern field reference
+ * @return never returns
+ * @throws UnsupportedOperationException always
+ */
@Override
public Expression visitPatternFieldRef(RexPatternFieldRef fieldRef) {
throw new UnsupportedOperationException("RexPatternFieldRef not supported");
}
+ /**
+ * Not supported.
+ *
+ * @param rexLambda the lambda
+ * @return never returns
+ * @throws UnsupportedOperationException always
+ */
@Override
public Expression visitLambda(RexLambda rexLambda) {
throw new UnsupportedOperationException("RexLambda not supported");
}
+ /**
+ * Not supported.
+ *
+ * @param rexLambdaRef the lambda reference
+ * @return never returns
+ * @throws UnsupportedOperationException always
+ */
@Override
public Expression visitLambdaRef(RexLambdaRef rexLambdaRef) {
throw new UnsupportedOperationException("RexLambdaRef not supported");
}
+ /**
+ * Not supported.
+ *
+ * @param nodeAndFieldIndex the node/field index wrapper
+ * @return never returns
+ * @throws UnsupportedOperationException always
+ */
@Override
public Expression visitNodeAndFieldIndex(RexNodeAndFieldIndex nodeAndFieldIndex) {
throw new UnsupportedOperationException("RexNodeAndFieldIndex not supported");
diff --git a/isthmus/src/main/java/io/substrait/isthmus/expression/ScalarFunctionConverter.java b/isthmus/src/main/java/io/substrait/isthmus/expression/ScalarFunctionConverter.java
index 26d740f19..2f211d7dd 100644
--- a/isthmus/src/main/java/io/substrait/isthmus/expression/ScalarFunctionConverter.java
+++ b/isthmus/src/main/java/io/substrait/isthmus/expression/ScalarFunctionConverter.java
@@ -18,6 +18,13 @@
import org.apache.calcite.rex.RexCall;
import org.apache.calcite.rex.RexNode;
+/**
+ * Converts Calcite {@link RexCall} scalar functions to Substrait {@link Expression} using known
+ * Substrait {@link SimpleExtension.ScalarFunctionVariant} declarations.
+ *
+ * Supports custom function mappers for special cases (e.g., TRIM, SQRT), and falls back to
+ * default signature-based matching. Produces {@link Expression.ScalarFunctionInvocation}.
+ */
public class ScalarFunctionConverter
extends FunctionConverter<
SimpleExtension.ScalarFunctionVariant,
@@ -30,11 +37,25 @@ public class ScalarFunctionConverter
*/
private final List Provides operand stream and type info used by {@link FunctionFinder}.
+ */
protected static class WrappedScalarCall implements FunctionConverter.GenericCall {
private final RexCall delegate;
@@ -153,11 +208,21 @@ private WrappedScalarCall(RexCall delegate) {
this.delegate = delegate;
}
+ /**
+ * Returns the operand stream of the underlying {@link RexCall}.
+ *
+ * @return stream of operands
+ */
@Override
public Stream Handles sort direction and null ordering.
+ */
public class SortFieldConverter {
- /** Converts a {@link RexFieldCollation} to a {@link Expression.SortField}. */
+ /**
+ * Converts a Calcite {@link RexFieldCollation} to a Substrait {@link Expression.SortField}.
+ *
+ * @param rexFieldCollation The Calcite field collation to convert.
+ * @param rexExpressionConverter Converter for translating the field expression.
+ * @return A Substrait {@link Expression.SortField} with the appropriate direction and expression.
+ * @throws IllegalArgumentException if the collation direction is unsupported.
+ */
public static Expression.SortField toSortField(
RexFieldCollation rexFieldCollation, RexExpressionConverter rexExpressionConverter) {
Expression expr = rexFieldCollation.left.accept(rexExpressionConverter);
@@ -16,6 +29,13 @@ public static Expression.SortField toSortField(
return Expression.SortField.builder().expr(expr).direction(direction).build();
}
+ /**
+ * Determines the Substrait {@link Expression.SortDirection} based on Calcite collation details.
+ *
+ * @param collation The Calcite {@link RexFieldCollation}.
+ * @return The corresponding Substrait sort direction.
+ * @throws IllegalArgumentException if the direction is not ASCENDING or DESCENDING.
+ */
private static Expression.SortDirection asSortDirection(RexFieldCollation collation) {
RelFieldCollation.Direction direction = collation.getDirection();
diff --git a/isthmus/src/main/java/io/substrait/isthmus/expression/SqlArrayValueConstructorCallConverter.java b/isthmus/src/main/java/io/substrait/isthmus/expression/SqlArrayValueConstructorCallConverter.java
index c8805a901..3724b14f7 100644
--- a/isthmus/src/main/java/io/substrait/isthmus/expression/SqlArrayValueConstructorCallConverter.java
+++ b/isthmus/src/main/java/io/substrait/isthmus/expression/SqlArrayValueConstructorCallConverter.java
@@ -15,14 +15,36 @@
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.fun.SqlArrayValueConstructor;
+/** Converts Calcite {@link SqlArrayValueConstructor} calls into Substrait list literals. */
public class SqlArrayValueConstructorCallConverter implements CallConverter {
private final TypeConverter typeConverter;
+ /**
+ * Creates a converter for array value constructors using the supplied {@link TypeConverter}.
+ *
+ * @param typeConverter Converter for Calcite element types to Substrait {@link Type}.
+ */
public SqlArrayValueConstructorCallConverter(TypeConverter typeConverter) {
this.typeConverter = typeConverter;
}
+ /**
+ * Attempts to convert a Calcite {@link RexCall} of {@link SqlArrayValueConstructor} into a
+ * Substrait list expression.
+ *
+ * Empty arrays are converted using {@link ExpressionCreator#emptyList(boolean, Type)} based on
+ * the element type. Non-empty arrays are converted to a list of literals if all operands are
+ * {@link Expression.Literal}, otherwise to a {@link Expression.NestedList}.
+ *
+ * @param call The Calcite array constructor call.
+ * @param topLevelConverter Function converting {@link RexNode} operands to Substrait {@link
+ * Expression}s.
+ * @return An {@link Optional} containing the converted {@link Expression} if the operator is
+ * {@link SqlArrayValueConstructor}; otherwise {@link Optional#empty()}.
+ * @throws ClassCastException if non-empty operands are converted by {@code topLevelConverter}
+ * into non-literal expressions when a literal list is required.
+ */
@Override
public Optional> getMappedExpressionArguments(
.orElse(Optional.empty());
}
+ /**
+ * Wrapped view of a {@link RexCall} for signature matching.
+ *
+ *