From 6439e9fead5d752a4da202370346533ac50e9856 Mon Sep 17 00:00:00 2001 From: MBWhite Date: Wed, 18 Mar 2026 13:46:32 +0000 Subject: [PATCH] fix(javadoc): partial isthmus expression Signed-off-by: MBWhite --- .../expression/RexExpressionConverter.java | 140 +++++++++++++++++- .../expression/ScalarFunctionConverter.java | 65 ++++++++ .../expression/SortFieldConverter.java | 22 ++- ...SqlArrayValueConstructorCallConverter.java | 22 +++ 4 files changed, 247 insertions(+), 2 deletions(-) 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 { private final List callConverters; @@ -39,14 +46,34 @@ public class RexExpressionConverter implements RexVisitor { private final TypeConverter typeConverter; private WindowFunctionConverter windowFunctionConverter; + /** + * Creates a converter with an explicit {@link SubstraitRelVisitor} and one or more call + * converters. + * + * @param relVisitor visitor used to convert subqueries/relations + * @param callConverters converters for Rex calls + */ public RexExpressionConverter(SubstraitRelVisitor relVisitor, CallConverter... callConverters) { this(relVisitor, Arrays.asList(callConverters), null, TypeConverter.DEFAULT); } + /** + * Creates a converter with the given call converters and default {@link TypeConverter}. + * + * @param callConverters converters for Rex calls + */ public RexExpressionConverter(CallConverter... callConverters) { this(null, Arrays.asList(callConverters), null, TypeConverter.DEFAULT); } + /** + * Creates a converter with full configuration. + * + * @param relVisitor visitor used to convert subqueries/relations; may be {@code null} + * @param callConverters converters for Rex calls + * @param windowFunctionConverter converter for window functions; may be {@code null} + * @param typeConverter converter from Calcite types to Substrait types + */ public RexExpressionConverter( SubstraitRelVisitor relVisitor, List callConverters, @@ -59,19 +86,34 @@ public RexExpressionConverter( } /** - * Only used for testing. Missing `ScalarFunctionConverter`, `CallConverters.CREATE_SEARCH_CONV` + * Testing-only constructor that wires default converters. + * + *

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 mappers; + /** + * Creates a converter with the given functions and type factory. + * + * @param functions available Substrait scalar function variants + * @param typeFactory Calcite type factory for type conversions + */ public ScalarFunctionConverter( List functions, RelDataTypeFactory typeFactory) { this(functions, Collections.emptyList(), typeFactory, TypeConverter.DEFAULT); } + /** + * Creates a converter with additional signatures and a custom type converter. + * + * @param functions available Substrait scalar function variants + * @param additionalSignatures extra Calcite-to-Substrait signature mappings + * @param typeFactory Calcite type factory for type conversions + * @param typeConverter converter for Calcite {@link RelDataType} to Substrait {@link Type} + */ public ScalarFunctionConverter( List functions, List additionalSignatures, @@ -53,11 +74,24 @@ public ScalarFunctionConverter( new StrptimeTimestampFunctionMapper(functions)); } + /** + * Returns the set of known scalar function signatures. + * + * @return immutable list of scalar signatures + */ @Override protected ImmutableList getSigs() { return FunctionMappings.SCALAR_SIGS; } + /** + * Converts a {@link RexCall} into a Substrait {@link Expression}, applying any registered custom + * mapping first, then default matching if needed. + * + * @param call the Calcite function call to convert + * @param topLevelConverter converter for nested operands + * @return the converted expression if a match is found; otherwise {@link Optional#empty()} + */ @Override public Optional convert( RexCall call, Function topLevelConverter) { @@ -117,6 +151,15 @@ private boolean isPotentialFunctionMatch(FunctionFinder finder, WrappedScalarCal return Objects.nonNull(finder) && finder.allowedArgCount((int) call.getOperands().count()); } + /** + * Builds an {@link Expression.ScalarFunctionInvocation} for a matched function. + * + * @param call the wrapped Calcite call providing operands and type + * @param function the Substrait scalar function declaration to invoke + * @param arguments converted argument list for the invocation + * @param outputType the Substrait output type for the invocation + * @return a scalar function invocation expression + */ @Override protected Expression generateBinding( WrappedScalarCall call, @@ -130,6 +173,13 @@ protected Expression generateBinding( .build(); } + /** + * Returns the Substrait arguments for a given scalar invocation, applying any custom mapping if + * present; otherwise returns the invocation's own arguments. + * + * @param expression the scalar function invocation + * @return the argument list, possibly remapped; never {@code null} + */ public List getExpressionArguments(Expression.ScalarFunctionInvocation expression) { // If a mapping applies to this expression, use it to get the arguments; otherwise default // behavior. @@ -145,6 +195,11 @@ private Optional> getMappedExpressionArguments( .orElse(Optional.empty()); } + /** + * Wrapped view of a {@link RexCall} for signature matching. + * + *

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 getOperands() { return delegate.getOperands().stream(); } + /** + * Returns the Calcite type of the underlying {@link RexCall}. + * + * @return call type + */ @Override public RelDataType getType() { return delegate.getType(); diff --git a/isthmus/src/main/java/io/substrait/isthmus/expression/SortFieldConverter.java b/isthmus/src/main/java/io/substrait/isthmus/expression/SortFieldConverter.java index 773632164..5c458f18b 100644 --- a/isthmus/src/main/java/io/substrait/isthmus/expression/SortFieldConverter.java +++ b/isthmus/src/main/java/io/substrait/isthmus/expression/SortFieldConverter.java @@ -5,9 +5,22 @@ import org.apache.calcite.rel.RelFieldCollation.Direction; import org.apache.calcite.rex.RexFieldCollation; +/** + * Utility for converting Calcite {@link RexFieldCollation} objects into Substrait {@link + * Expression.SortField} representations. + * + *

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 convert( RexCall call, Function topLevelConverter) {