diff --git a/isthmus/src/main/java/io/substrait/isthmus/expression/FunctionConverter.java b/isthmus/src/main/java/io/substrait/isthmus/expression/FunctionConverter.java index b5604d4d9..69257fb4d 100644 --- a/isthmus/src/main/java/io/substrait/isthmus/expression/FunctionConverter.java +++ b/isthmus/src/main/java/io/substrait/isthmus/expression/FunctionConverter.java @@ -17,7 +17,6 @@ import io.substrait.isthmus.TypeConverter; import io.substrait.isthmus.Utils; import io.substrait.isthmus.expression.FunctionMappings.Sig; -import io.substrait.isthmus.expression.FunctionMappings.TypeBasedResolver; import io.substrait.type.Type; import io.substrait.util.Util; import java.util.ArrayList; @@ -49,60 +48,63 @@ * Abstract base class for converting between Calcite {@link SqlOperator}s and Substrait function * invocations. * - *

This class handles bidirectional conversion: + *

Supports Calcite → Substrait conversion via signature matching/coercion and Substrait → + * Calcite lookup via function keys. * - *

- * - *

When multiple functions with the same name and signature are passed into the constructor, a - * last-wins precedence strategy is used for resolution. The last function in the input list - * takes precedence during Calcite to Substrait conversion. - * - * @param the function type (ScalarFunctionVariant, AggregateFunctionVariant, etc.) - * @param the return type for Calcite→Substrait conversion - * @param the call type being converted + * @param function variant type (e.g., ScalarFunctionVariant, AggregateFunctionVariant) + * @param return type produced when binding Substrait invocations + * @param generic call wrapper exposing operands and type */ public abstract class FunctionConverter< F extends SimpleExtension.Function, T, C extends FunctionConverter.GenericCall> { private static final Logger LOGGER = LoggerFactory.getLogger(FunctionConverter.class); + /** + * Maps Calcite {@link SqlOperator}s to {@link FunctionFinder}s for signature-based matching. Used + * to locate Substrait functions based on Calcite calls and operand shapes. + */ protected final Map signatures; + + /** Calcite {@link RelDataTypeFactory} used for creating and inspecting relational types. */ protected final RelDataTypeFactory typeFactory; + + /** Converter handling Substrait ↔ Calcite type mappings and nullability rules. */ protected final TypeConverter typeConverter; + + /** + * Calcite {@link org.apache.calcite.rex.RexBuilder} for constructing {@link + * org.apache.calcite.rex.RexNode}s. + */ protected final RexBuilder rexBuilder; + /** + * Multimap from Substrait function key (e.g., canonical name) to Calcite {@link SqlOperator}s. + * Enables reverse lookup when converting Substrait function invocations to Calcite operators. + */ protected final Multimap substraitFuncKeyToSqlOperatorMap; /** - * Creates a FunctionConverter with the given functions. + * Creates a converter with the given functions. * - *

If there are multiple functions provided with the same name and signature (e.g., from - * different extension URNs), the last one in the list will be given precedence during Calcite to - * Substrait conversion. + *

Last-wins precedence applies when multiple variants share the same name/signature. * - * @param functions the list of function variants to register - * @param typeFactory the Calcite type factory + * @param functions function variants to register + * @param typeFactory Calcite type factory */ public FunctionConverter(List functions, RelDataTypeFactory typeFactory) { this(functions, Collections.EMPTY_LIST, typeFactory, TypeConverter.DEFAULT); } /** - * Creates a FunctionConverter with the given functions and additional signatures. + * Creates a converter with functions and additional operator signatures. * - *

If there are multiple functions provided with the same name and signature (e.g., from - * different extension URNs), the last one in the list will be given precedence during Calcite to - * Substrait conversion. + *

Last-wins precedence applies when multiple variants share the same name/signature. * - * @param functions the list of function variants to register - * @param additionalSignatures additional Calcite operator signatures to map - * @param typeFactory the Calcite type factory - * @param typeConverter the type converter to use + * @param functions function variants to register + * @param additionalSignatures extra Calcite operator signatures to map + * @param typeFactory Calcite type factory + * @param typeConverter type converter to Substrait */ public FunctionConverter( List functions, @@ -155,18 +157,18 @@ public FunctionConverter( } /** - * Converts a Substrait function to a Calcite {@link SqlOperator} (Substrait → Calcite direction). + * Resolves a Calcite {@link SqlOperator} from a Substrait function key (Substrait → Calcite). * *

Given a Substrait function key (e.g., "concat:str_str") and output type, this method finds * the corresponding Calcite {@link SqlOperator}. When multiple operators match, the output type * is used to disambiguate. * - * @param key the Substrait function key (function name with type signature) - * @param outputType the expected output type - * @return the matching {@link SqlOperator}, or empty if no match found + * @param key Substrait function key (e.g., {@code concat:str_str}) + * @param outputType expected Substrait output type used for disambiguation + * @return matching {@link SqlOperator}, or empty if none */ public Optional getSqlOperatorFromSubstraitFunc(String key, Type outputType) { - Map resolver = getTypeBasedResolver(); + Map resolver = getTypeBasedResolver(); Collection operators = substraitFuncKeyToSqlOperatorMap.get(key); if (operators.isEmpty()) { return Optional.empty(); @@ -198,12 +200,30 @@ public Optional getSqlOperatorFromSubstraitFunc(String key, Type ou return Optional.empty(); } + /** + * Returns the resolver used to disambiguate Calcite operators by output type. + * + * @return map from {@link SqlOperator} to type-based resolver + */ private Map getTypeBasedResolver() { return FunctionMappings.OPERATOR_RESOLVER; } + /** + * Provides the set of Calcite operator signatures supported by this converter. + * + * @return immutable list of supported signatures + */ protected abstract ImmutableList getSigs(); + /** + * Helper class for locating and matching Calcite {@link org.apache.calcite.sql.SqlOperator} + * signatures to Substrait functions. + * + *

Used during expression conversion to determine if a given {@link + * org.apache.calcite.rex.RexCall} corresponds to a known Substrait function and to validate + * argument counts. + */ protected class FunctionFinder { private final String substraitName; private final SqlOperator operator; @@ -212,6 +232,13 @@ protected class FunctionFinder { private final Optional> singularInputType; private final Util.IntRange argRange; + /** + * Creates a function finder for a Substrait name/operator over given variants. + * + * @param substraitName canonical Substrait function name + * @param operator Calcite operator being matched + * @param functions registered function variants for this name + */ public FunctionFinder(String substraitName, SqlOperator operator, List functions) { this.substraitName = substraitName; this.operator = operator; @@ -232,10 +259,23 @@ public FunctionFinder(String substraitName, SqlOperator operator, List functi this.directMap = directMap.build(); } + /** + * Returns whether the given argument count is within this operator's allowed range. + * + * @param count number of operands + * @return {@code true} if allowed; otherwise {@code false} + */ public boolean allowedArgCount(int count) { return argRange.within(count); } + /** + * Attempts an exact signature match against required arguments and return type. + * + * @param inputTypes operand types (Substrait) + * @param outputType expected output type (Substrait) + * @return matching function variant if found; otherwise empty + */ private Optional signatureMatch(List inputTypes, Type outputType) { for (F function : functions) { List args = function.requiredArguments(); @@ -251,17 +291,13 @@ && inputTypesMatchDefinedArguments(inputTypes, args)) { } /** - * Checks to see if the given input types satisfy the function arguments given. Checks that + * Checks that input types satisfy the function's required arguments. * - *

+ *

Ensures variadic arguments share a type and matched wildcards (anyN) are consistent. * - * @param inputTypes input types to check against arguments + * @param inputTypes operand types to verify * @param args expected arguments as defined in a {@link SimpleExtension.Function} - * @return true if the {@code inputTypes} satisfy the {@code args}, false otherwise + * @return {@code true} if compatible; otherwise {@code false} */ private boolean inputTypesMatchDefinedArguments( List inputTypes, List args) { @@ -296,11 +332,10 @@ private boolean inputTypesMatchDefinedArguments( } /** - * If some of the function variants for this function name have single, repeated argument type, - * we will attempt to find matches using these patterns and least-restrictive casting. + * Derives singular-argument matchers for variants whose required arguments share one type. * - *

If this exists, the function finder will attempt to find a least-restrictive match using - * these. + * @param functions variants to inspect + * @return optional matcher chain; empty if none */ private Optional> getSingularInputType(List functions) { List> matchers = new ArrayList<>(); @@ -343,6 +378,13 @@ private Optional> getSingularInputType(List functi } } + /** + * Creates a matcher for a single repeated parameter type. + * + * @param function function variant + * @param type repeated parameter type + * @return matcher accepting input/output types + */ private SingularArgumentMatcher singular(F function, ParameterizedType type) { return (inputType, outputType) -> { boolean check = isMatch(inputType, type); @@ -353,6 +395,12 @@ private SingularArgumentMatcher singular(F function, ParameterizedType type) }; } + /** + * Chains multiple singular matchers, returning the first successful match. + * + * @param matchers matchers to try in order + * @return composite matcher + */ private SingularArgumentMatcher chained(List> matchers) { return (inputType, outputType) -> { for (SingularArgumentMatcher s : matchers) { @@ -370,6 +418,13 @@ private SingularArgumentMatcher chained(List> matc * In case of a `RexLiteral` of an Enum value try both `req` and `op` signatures * for that argument position. */ + /** + * Produces candidate signature keys considering enum literals as required/optional. + * + * @param rexOperands operand RexNodes + * @param opTypes operand type strings (Substrait) + * @return stream of candidate key suffixes to test + */ private Stream matchKeys(List rexOperands, List opTypes) { assert (rexOperands.size() == opTypes.size()); @@ -396,17 +451,13 @@ private Stream matchKeys(List rexOperands, List opTypes } /** - * Converts a Calcite call to a Substrait function invocation (Calcite → Substrait direction). + * Converts a Calcite call to a Substrait function invocation (Calcite → Substrait). * - *

This method tries to find a matching Substrait function for the given Calcite call using - * direct signature matching, type coercion, and least-restrictive type resolution. + *

Tries direct signature match, then coercion, then least-restrictive type resolution. * - *

If multiple registered function extensions have the same name and signature, the last one - * in the list passed into the constructor will be matched. - * - * @param call the Calcite call to match - * @param topLevelConverter function to convert RexNode operands to Substrait Expressions - * @return the matched Substrait function binding, or empty if no match found + * @param call generic call wrapper (operands and type) + * @param topLevelConverter converter from {@link RexNode} to Substrait {@link Expression} + * @return matched binding, or empty if none */ public Optional attemptMatch(C call, Function topLevelConverter) { @@ -480,6 +531,14 @@ public Optional attemptMatch(C call, Function topLevelCo return Optional.empty(); } + /** + * Tries matching using Calcite's least-restrictive type for operands. + * + * @param call generic call wrapper + * @param outputType expected output type (Substrait) + * @param operands converted operand expressions + * @return binding if a singular-type variant matches; otherwise empty + */ private Optional matchByLeastRestrictive( C call, Type outputType, List operands) { RelDataType leastRestrictive = @@ -499,6 +558,14 @@ private Optional matchByLeastRestrictive( }); } + /** + * Tries matching by coercing each operand to its Substrait type and checking signatures. + * + * @param call generic call wrapper + * @param outputType expected output type (Substrait) + * @param expressions operand expressions + * @return binding if a signature match is found; otherwise empty + */ private Optional matchCoerced(C call, Type outputType, List expressions) { // Convert the operands to the proper Substrait type List operandTypes = @@ -520,29 +587,64 @@ private Optional matchCoerced(C call, Type outputType, List expre return Optional.of(generateBinding(call, matchFunction.get(), coercedArgs, outputType)); } + /** + * Returns the canonical Substrait name this finder resolves. + * + * @return Substrait function name + */ protected String getSubstraitName() { return substraitName; } + /** + * Returns the Calcite operator associated with this finder. + * + * @return Calcite operator + */ public SqlOperator getOperator() { return operator; } } + /** + * Represents a generic function or operator call abstraction used during expression conversion. + * + *

Provides access to the operands and the resulting Calcite type of the call. + */ public interface GenericCall { + /** + * Returns the operand stream for this call. + * + * @return stream of {@link RexNode} operands + */ Stream getOperands(); + /** + * Returns the Calcite result type of the call. + * + * @return {@link RelDataType} for the call + */ RelDataType getType(); } /** - * Coerced types according to an expected output type. Coercion is only done for type mismatches, - * not for nullability or parameter mismatches. + * Coerces arguments to the target type when mismatched (ignores nullability/parameters). + * + * @param arguments input expressions + * @param targetType target Substrait type + * @return list of coerced expressions (casts applied as needed) */ private static List coerceArguments(List arguments, Type targetType) { return arguments.stream().map(a -> coerceArgument(a, targetType)).collect(Collectors.toList()); } + /** + * Coerces a single expression to the target type, if needed. + * + * @param argument expression to coerce + * @param type target Substrait type + * @return original expression or casted expression + */ private static Expression coerceArgument(Expression argument, Type type) { if (isMatch(type, argument.getType())) { return argument; @@ -551,14 +653,43 @@ private static Expression coerceArgument(Expression argument, Type type) { return ExpressionCreator.cast(type, argument, Expression.FailureBehavior.THROW_EXCEPTION); } + /** + * Creates the Substrait binding for a matched function variant. + * + * @param call generic call wrapper (operands and type) + * @param function matched extension function variant + * @param arguments converted function arguments + * @param outputType expected Substrait output type + * @return binding to return to the caller + */ protected abstract T generateBinding( C call, F function, List arguments, Type outputType); + /** + * Matcher for functions whose required arguments share a single repeated type. + * + * @param function variant type + */ @FunctionalInterface private interface SingularArgumentMatcher { + /** + * Attempts a match for the provided input/output types. + * + * @param type singular input type + * @param outputType expected output type + * @return matching function if successful; otherwise empty + */ Optional tryMatch(Type type, Type outputType); } + /** + * Compares parameterized types, allowing wildcards and ignoring nullability/parameters when + * appropriate. + * + * @param actualType actual parameterized type + * @param targetType target parameterized type + * @return {@code true} if compatible; otherwise {@code false} + */ private static boolean isMatch(ParameterizedType actualType, ParameterizedType targetType) { if (targetType.isWildcard()) { return true; diff --git a/isthmus/src/main/java/io/substrait/isthmus/expression/FunctionMappings.java b/isthmus/src/main/java/io/substrait/isthmus/expression/FunctionMappings.java index 003e5a571..6d1e81a61 100644 --- a/isthmus/src/main/java/io/substrait/isthmus/expression/FunctionMappings.java +++ b/isthmus/src/main/java/io/substrait/isthmus/expression/FunctionMappings.java @@ -9,10 +9,15 @@ import org.apache.calcite.sql.fun.SqlLibraryOperators; import org.apache.calcite.sql.fun.SqlStdOperatorTable; +/** + * Defines static mappings between Calcite {@link SqlOperator} signatures and Substrait base + * function names, including scalar, aggregate, and window function signatures. + * + *

Also provides type-based resolvers to disambiguate operators by output type. + */ public class FunctionMappings { - // Static list of signature mapping between Calcite SQL operators and Substrait base function - // names. + /** Scalar operator signatures mapped to Substrait function names. */ public static final ImmutableList SCALAR_SIGS = ImmutableList.builder() .add( @@ -103,6 +108,7 @@ public class FunctionMappings { s(SqlLibraryOperators.PARSE_DATE, "strptime_date")) .build(); + /** Aggregate operator signatures mapped to Substrait function names. */ public static final ImmutableList AGGREGATE_SIGS = ImmutableList.builder() .add( @@ -115,6 +121,7 @@ public class FunctionMappings { s(AggregateFunctions.AVG, "avg")) .build(); + /** Window function signatures (including supported aggregates) mapped to Substrait names. */ public static final ImmutableList WINDOW_SIGS = ImmutableList.builder() .add( @@ -133,7 +140,7 @@ public class FunctionMappings { .addAll(AGGREGATE_SIGS) .build(); - // contains return-type based resolver for both scalar and aggregator operator + /** Type-based resolvers to disambiguate Calcite operators by expected output type. */ public static final Map OPERATOR_RESOLVER = Map.of( SqlStdOperatorTable.PLUS, @@ -149,50 +156,111 @@ public class FunctionMappings { SqlStdOperatorTable.BIT_LEFT_SHIFT, resolver(SqlStdOperatorTable.BIT_LEFT_SHIFT, Set.of("i8", "i16", "i32", "i64"))); + /** + * Creates a signature mapping entry. + * + * @param operator the Calcite operator + * @param substraitName the Substrait canonical function name + * @return a {@link Sig} instance + */ public static Sig s(SqlOperator operator, String substraitName) { return new Sig(operator, substraitName.toLowerCase(Locale.ROOT)); } + /** + * Creates a signature mapping entry using the operator's own (lowercased) name. + * + * @param operator the Calcite operator + * @return a {@link Sig} instance + */ public static Sig s(SqlOperator operator) { return s(operator, operator.getName().toLowerCase(Locale.ROOT)); } + /** Simple signature tuple of operator to Substrait name. */ public static class Sig { + + /** SqlOperator. */ public final SqlOperator operator; + + /** Name. */ public final String name; + /** + * Constructs a signature entry. + * + * @param operator the Calcite operator + * @param name the Substrait function name + */ public Sig(final SqlOperator operator, final String name) { this.operator = operator; this.name = name; } + /** + * Returns the Substrait function name. + * + * @return the Substrait name + */ public String name() { return name; } + /** + * Returns the Calcite operator. + * + * @return the operator + */ public SqlOperator operator() { return operator; } } + /** + * Creates a type-based resolver for an operator. + * + * @param operator the Calcite operator + * @param outTypes the set of allowed output type strings (Substrait) + * @return a {@link TypeBasedResolver} + */ public static TypeBasedResolver resolver(SqlOperator operator, Set outTypes) { return new TypeBasedResolver(operator, outTypes); } + /** Disambiguates operators based on expected output type strings. */ public static class TypeBasedResolver { + /** SqlOperator. */ public final SqlOperator operator; + + /** Types. */ public final Set types; + /** + * Constructs a resolver. + * + * @param operator the Calcite operator + * @param types allowed output type strings + */ public TypeBasedResolver(final SqlOperator operator, final Set types) { this.operator = operator; this.types = types; } + /** + * Returns the operator this resolver applies to. + * + * @return the operator + */ public SqlOperator operator() { return operator; } + /** + * Returns the allowed output type strings. + * + * @return set of type strings + */ public Set types() { return types; } diff --git a/isthmus/src/main/java/io/substrait/isthmus/expression/IgnoreNullableAndParameters.java b/isthmus/src/main/java/io/substrait/isthmus/expression/IgnoreNullableAndParameters.java index f8b4be1dd..2d6903bef 100644 --- a/isthmus/src/main/java/io/substrait/isthmus/expression/IgnoreNullableAndParameters.java +++ b/isthmus/src/main/java/io/substrait/isthmus/expression/IgnoreNullableAndParameters.java @@ -4,232 +4,527 @@ import io.substrait.function.ParameterizedTypeVisitor; import io.substrait.type.Type; +/** + * Visitor that compares a given {@link ParameterizedType} against visited types, ignoring + * nullability and (where applicable) type parameters. + * + *

Intended for structural type compatibility checks where exact parameter values are not + * required. + */ public class IgnoreNullableAndParameters implements ParameterizedTypeVisitor { private final ParameterizedType typeToMatch; + /** + * Creates a visitor that will compare visited types against {@code typeToMatch}. + * + * @param typeToMatch the target type to compare against + */ public IgnoreNullableAndParameters(ParameterizedType typeToMatch) { this.typeToMatch = typeToMatch; } + /** + * Compares {@link Type.Bool} ignoring nullability. + * + * @param type boolean type + * @return {@code true} if {@code typeToMatch} is {@link Type.Bool} + */ @Override public Boolean visit(Type.Bool type) { return typeToMatch instanceof Type.Bool; } + /** + * Compares {@link Type.I8} ignoring nullability. + * + * @param type 8-bit integer type + * @return {@code true} if {@code typeToMatch} is {@link Type.I8} + */ @Override public Boolean visit(Type.I8 type) { return typeToMatch instanceof Type.I8; } + /** + * Compares {@link Type.I16} ignoring nullability. + * + * @param type 16-bit integer type + * @return {@code true} if {@code typeToMatch} is {@link Type.I16} + */ @Override public Boolean visit(Type.I16 type) { return typeToMatch instanceof Type.I16; } + /** + * Compares {@link Type.I32} ignoring nullability. + * + * @param type 32-bit integer type + * @return {@code true} if {@code typeToMatch} is {@link Type.I32} + */ @Override public Boolean visit(Type.I32 type) { return typeToMatch instanceof Type.I32; } + /** + * Compares {@link Type.I64} ignoring nullability. + * + * @param type 64-bit integer type + * @return {@code true} if {@code typeToMatch} is {@link Type.I64} + */ @Override public Boolean visit(Type.I64 type) { return typeToMatch instanceof Type.I64; } + /** + * Compares {@link Type.FP32} ignoring nullability. + * + * @param type 32-bit floating point type + * @return {@code true} if {@code typeToMatch} is {@link Type.FP32} + */ @Override public Boolean visit(Type.FP32 type) { return typeToMatch instanceof Type.FP32; } + /** + * Compares {@link Type.FP64} ignoring nullability. + * + * @param type 64-bit floating point type + * @return {@code true} if {@code typeToMatch} is {@link Type.FP64} + */ @Override public Boolean visit(Type.FP64 type) { return typeToMatch instanceof Type.FP64; } + /** + * Compares {@link Type.Str} ignoring nullability. + * + * @param type string type + * @return {@code true} if {@code typeToMatch} is {@link Type.Str} + */ @Override public Boolean visit(Type.Str type) { return typeToMatch instanceof Type.Str; } + /** + * Compares {@link Type.Binary} ignoring nullability. + * + * @param type binary (var-length) type + * @return {@code true} if {@code typeToMatch} is {@link Type.Binary} + */ @Override public Boolean visit(Type.Binary type) { return typeToMatch instanceof Type.Binary; } + /** + * Compares {@link Type.Date} ignoring nullability. + * + * @param type date type + * @return {@code true} if {@code typeToMatch} is {@link Type.Date} + */ @Override public Boolean visit(Type.Date type) { return typeToMatch instanceof Type.Date; } + /** + * Compares {@link Type.Time} ignoring nullability. + * + * @param type time type + * @return {@code true} if {@code typeToMatch} is {@link Type.Time} + */ @Override public Boolean visit(Type.Time type) { return typeToMatch instanceof Type.Time; } + /** + * Compares {@link Type.TimestampTZ} ignoring nullability. + * + * @param type timestamp-with-time-zone type + * @return {@code true} if {@code typeToMatch} is {@link Type.TimestampTZ} + */ @Override public Boolean visit(Type.TimestampTZ type) { return typeToMatch instanceof Type.TimestampTZ; } + /** + * Compares {@link Type.Timestamp} ignoring nullability. + * + * @param type timestamp type + * @return {@code true} if {@code typeToMatch} is {@link Type.Timestamp} + */ @Override public Boolean visit(Type.Timestamp type) { return typeToMatch instanceof Type.Timestamp; } + /** + * Compares {@link Type.IntervalYear} ignoring nullability. + * + * @param type year-month interval type + * @return {@code true} if {@code typeToMatch} is {@link Type.IntervalYear} + */ @Override public Boolean visit(Type.IntervalYear type) { return typeToMatch instanceof Type.IntervalYear; } + /** + * Compares {@link Type.IntervalDay} ignoring nullability and parameters. + * + * @param type day-time interval type + * @return {@code true} if {@code typeToMatch} is {@link Type.IntervalDay} or {@link + * ParameterizedType.IntervalDay} + */ @Override public Boolean visit(Type.IntervalDay type) { return typeToMatch instanceof Type.IntervalDay || typeToMatch instanceof ParameterizedType.IntervalDay; } + /** + * Compares {@link Type.IntervalCompound} ignoring nullability and parameters. + * + * @param type compound interval type + * @return {@code true} if {@code typeToMatch} is {@link Type.IntervalCompound} or {@link + * ParameterizedType.IntervalCompound} + */ @Override public Boolean visit(Type.IntervalCompound type) { return typeToMatch instanceof Type.IntervalCompound || typeToMatch instanceof ParameterizedType.IntervalCompound; } + /** + * Compares {@link Type.UUID} ignoring nullability. + * + * @param type UUID type + * @return {@code true} if {@code typeToMatch} is {@link Type.UUID} + */ @Override public Boolean visit(Type.UUID type) { return typeToMatch instanceof Type.UUID; } + /** + * Compares {@link Type.UserDefined} for exact equality (URI and name). + * + * @param type user-defined type + * @return {@code true} if {@code typeToMatch} equals {@code type} + */ @Override - public Boolean visit(Type.UserDefined type) throws RuntimeException { + public Boolean visit(Type.UserDefined type) { // Two user-defined types are equal if they have the same uri AND name return typeToMatch.equals(type); } + /** + * Compares {@link Type.FixedChar} ignoring parameters and nullability. + * + * @param type fixed CHAR type + * @return {@code true} if {@code typeToMatch} is {@link Type.FixedChar} or {@link + * ParameterizedType.FixedChar} + */ @Override public Boolean visit(Type.FixedChar type) { return typeToMatch instanceof Type.FixedChar || typeToMatch instanceof ParameterizedType.FixedChar; } + /** + * Compares {@link Type.VarChar} ignoring parameters and nullability. + * + * @param type variable CHAR type + * @return {@code true} if {@code typeToMatch} is {@link Type.VarChar} or {@link + * ParameterizedType.VarChar} + */ @Override public Boolean visit(Type.VarChar type) { return typeToMatch instanceof Type.VarChar || typeToMatch instanceof ParameterizedType.VarChar; } + /** + * Compares {@link Type.FixedBinary} ignoring parameters and nullability. + * + * @param type fixed BINARY type + * @return {@code true} if {@code typeToMatch} is {@link Type.FixedBinary} or {@link + * ParameterizedType.FixedBinary} + */ @Override public Boolean visit(Type.FixedBinary type) { return typeToMatch instanceof Type.FixedBinary || typeToMatch instanceof ParameterizedType.FixedBinary; } + /** + * Compares {@link Type.Decimal} ignoring parameters and nullability. + * + * @param type DECIMAL type + * @return {@code true} if {@code typeToMatch} is {@link Type.Decimal} or {@link + * ParameterizedType.Decimal} + */ @Override public Boolean visit(Type.Decimal type) { return typeToMatch instanceof Type.Decimal || typeToMatch instanceof ParameterizedType.Decimal; } + /** + * Compares {@link Type.PrecisionTime} ignoring parameters and nullability. + * + * @param type precision TIME type + * @return {@code true} if {@code typeToMatch} is {@link Type.PrecisionTime} or {@link + * ParameterizedType.PrecisionTime} + */ @Override public Boolean visit(Type.PrecisionTime type) { return typeToMatch instanceof Type.PrecisionTime || typeToMatch instanceof ParameterizedType.PrecisionTime; } + /** + * Compares {@link Type.PrecisionTimestamp} ignoring parameters and nullability. + * + * @param type precision TIMESTAMP type + * @return {@code true} if {@code typeToMatch} is {@link Type.PrecisionTimestamp} or {@link + * ParameterizedType.PrecisionTimestamp} + */ @Override public Boolean visit(Type.PrecisionTimestamp type) { return typeToMatch instanceof Type.PrecisionTimestamp || typeToMatch instanceof ParameterizedType.PrecisionTimestamp; } + /** + * Compares {@link Type.PrecisionTimestampTZ} ignoring parameters and nullability. + * + * @param type precision TIMESTAMP WITH LOCAL TIME ZONE type + * @return {@code true} if {@code typeToMatch} is {@link Type.PrecisionTimestampTZ} or {@link + * ParameterizedType.PrecisionTimestampTZ} + */ @Override public Boolean visit(Type.PrecisionTimestampTZ type) { return typeToMatch instanceof Type.PrecisionTimestampTZ || typeToMatch instanceof ParameterizedType.PrecisionTimestampTZ; } + /** + * Compares {@link Type.Struct} ignoring parameters and nullability. + * + * @param type STRUCT type + * @return {@code true} if {@code typeToMatch} is {@link Type.Struct} or {@link + * ParameterizedType.Struct} + */ @Override public Boolean visit(Type.Struct type) { return typeToMatch instanceof Type.Struct || typeToMatch instanceof ParameterizedType.Struct; } + /** + * Compares {@link Type.ListType} ignoring parameters and nullability. + * + * @param type LIST type + * @return {@code true} if {@code typeToMatch} is {@link Type.ListType} or {@link + * ParameterizedType.ListType} + */ @Override public Boolean visit(Type.ListType type) { return typeToMatch instanceof Type.ListType || typeToMatch instanceof ParameterizedType.ListType; } + /** + * Compares {@link Type.Map} ignoring parameters and nullability. + * + * @param type MAP type + * @return {@code true} if {@code typeToMatch} is {@link Type.Map} or {@link + * ParameterizedType.Map} + */ @Override public Boolean visit(Type.Map type) { return typeToMatch instanceof Type.Map || typeToMatch instanceof ParameterizedType.Map; } + /** + * Compares parameterized {@link ParameterizedType.FixedChar} ignoring parameters. + * + * @param expr fixed CHAR parameterized type + * @return {@code true} if {@code typeToMatch} is {@link Type.FixedChar} or {@link + * ParameterizedType.FixedChar} + * @throws RuntimeException if comparison cannot be performed + */ @Override public Boolean visit(ParameterizedType.FixedChar expr) throws RuntimeException { return typeToMatch instanceof Type.FixedChar || typeToMatch instanceof ParameterizedType.FixedChar; } + /** + * Compares parameterized {@link ParameterizedType.VarChar} ignoring parameters. + * + * @param expr VARCHAR parameterized type + * @return {@code true} if {@code typeToMatch} is {@link Type.VarChar} or {@link + * ParameterizedType.VarChar} + * @throws RuntimeException if comparison cannot be performed + */ @Override public Boolean visit(ParameterizedType.VarChar expr) throws RuntimeException { return typeToMatch instanceof Type.VarChar || typeToMatch instanceof ParameterizedType.VarChar; } + /** + * Compares parameterized {@link ParameterizedType.FixedBinary} ignoring parameters. + * + * @param expr fixed BINARY parameterized type + * @return {@code true} if {@code typeToMatch} is {@link Type.FixedBinary} or {@link + * ParameterizedType.FixedBinary} + * @throws RuntimeException if comparison cannot be performed + */ @Override public Boolean visit(ParameterizedType.FixedBinary expr) throws RuntimeException { return typeToMatch instanceof Type.FixedBinary || typeToMatch instanceof ParameterizedType.FixedBinary; } + /** + * Compares parameterized {@link ParameterizedType.Decimal} ignoring parameters. + * + * @param expr DECIMAL parameterized type + * @return {@code true} if {@code typeToMatch} is {@link Type.Decimal} or {@link + * ParameterizedType.Decimal} + * @throws RuntimeException if comparison cannot be performed + */ @Override public Boolean visit(ParameterizedType.Decimal expr) throws RuntimeException { return typeToMatch instanceof Type.Decimal || typeToMatch instanceof ParameterizedType.Decimal; } + /** + * Compares parameterized {@link ParameterizedType.IntervalDay} ignoring parameters. + * + * @param expr day-time interval parameterized type + * @return {@code true} if {@code typeToMatch} is {@link Type.IntervalDay} or {@link + * ParameterizedType.IntervalDay} + * @throws RuntimeException if comparison cannot be performed + */ @Override public Boolean visit(ParameterizedType.IntervalDay expr) throws RuntimeException { return typeToMatch instanceof Type.IntervalDay || typeToMatch instanceof ParameterizedType.IntervalDay; } + /** + * Compares parameterized {@link ParameterizedType.IntervalCompound} ignoring parameters. + * + * @param expr compound interval parameterized type + * @return {@code true} if {@code typeToMatch} is {@link Type.IntervalCompound} or {@link + * ParameterizedType.IntervalCompound} + * @throws RuntimeException if comparison cannot be performed + */ @Override public Boolean visit(ParameterizedType.IntervalCompound expr) throws RuntimeException { return typeToMatch instanceof Type.IntervalCompound || typeToMatch instanceof ParameterizedType.IntervalCompound; } + /** + * Compares parameterized {@link ParameterizedType.PrecisionTime} ignoring parameters. + * + * @param expr precision TIME parameterized type + * @return {@code true} if {@code typeToMatch} is {@link Type.PrecisionTime} or {@link + * ParameterizedType.PrecisionTime} + * @throws RuntimeException if comparison cannot be performed + */ @Override public Boolean visit(ParameterizedType.PrecisionTime expr) throws RuntimeException { return typeToMatch instanceof Type.PrecisionTime || typeToMatch instanceof ParameterizedType.PrecisionTime; } + /** + * Compares parameterized {@link ParameterizedType.PrecisionTimestamp} ignoring parameters. + * + * @param expr precision TIMESTAMP parameterized type + * @return {@code true} if {@code typeToMatch} is {@link Type.PrecisionTimestamp} or {@link + * ParameterizedType.PrecisionTimestamp} + * @throws RuntimeException if comparison cannot be performed + */ @Override public Boolean visit(ParameterizedType.PrecisionTimestamp expr) throws RuntimeException { return typeToMatch instanceof Type.PrecisionTimestamp || typeToMatch instanceof ParameterizedType.PrecisionTimestamp; } + /** + * Compares parameterized {@link ParameterizedType.PrecisionTimestampTZ} ignoring parameters. + * + * @param expr precision TIMESTAMP WITH LOCAL TIME ZONE parameterized type + * @return {@code true} if {@code typeToMatch} is {@link Type.PrecisionTimestampTZ} or {@link + * ParameterizedType.PrecisionTimestampTZ} + * @throws RuntimeException if comparison cannot be performed + */ @Override public Boolean visit(ParameterizedType.PrecisionTimestampTZ expr) throws RuntimeException { return typeToMatch instanceof Type.PrecisionTimestampTZ || typeToMatch instanceof ParameterizedType.PrecisionTimestampTZ; } + /** + * Compares parameterized {@link ParameterizedType.Struct} ignoring parameters. + * + * @param expr STRUCT parameterized type + * @return {@code true} if {@code typeToMatch} is {@link Type.Struct} or {@link + * ParameterizedType.Struct} + * @throws RuntimeException if comparison cannot be performed + */ @Override public Boolean visit(ParameterizedType.Struct expr) throws RuntimeException { return typeToMatch instanceof Type.Struct || typeToMatch instanceof ParameterizedType.Struct; } + /** + * Compares parameterized {@link ParameterizedType.ListType} ignoring parameters. + * + * @param expr LIST parameterized type + * @return {@code true} if {@code typeToMatch} is {@link Type.ListType} or {@link + * ParameterizedType.ListType} + * @throws RuntimeException if comparison cannot be performed + */ @Override public Boolean visit(ParameterizedType.ListType expr) throws RuntimeException { return typeToMatch instanceof Type.ListType || typeToMatch instanceof ParameterizedType.ListType; } + /** + * Compares parameterized {@link ParameterizedType.Map} ignoring parameters. + * + * @param expr MAP parameterized type + * @return {@code true} if {@code typeToMatch} is {@link Type.Map} or {@link + * ParameterizedType.Map} + * @throws RuntimeException if comparison cannot be performed + */ @Override public Boolean visit(ParameterizedType.Map expr) throws RuntimeException { return typeToMatch instanceof Type.Map || typeToMatch instanceof ParameterizedType.Map; } + /** + * String literal parameterized types are not considered a match in this visitor. + * + * @param stringLiteral string literal parameterized type + * @return always {@code false} + * @throws RuntimeException never thrown in current implementation + */ @Override public Boolean visit(ParameterizedType.StringLiteral stringLiteral) throws RuntimeException { return false; diff --git a/isthmus/src/main/java/io/substrait/isthmus/expression/LiteralConverter.java b/isthmus/src/main/java/io/substrait/isthmus/expression/LiteralConverter.java index 4296c1a45..2cf26e02f 100644 --- a/isthmus/src/main/java/io/substrait/isthmus/expression/LiteralConverter.java +++ b/isthmus/src/main/java/io/substrait/isthmus/expression/LiteralConverter.java @@ -28,6 +28,13 @@ import org.apache.calcite.util.TimeString; import org.apache.calcite.util.TimestampString; +/** + * Converts Calcite {@link RexLiteral} values to Substrait {@link Expression.Literal}, using {@link + * TypeConverter} for type resolution. + * + *

Supports numeric, boolean, character, binary, temporal, interval, ROW/ARRAY, and selected + * symbol/enums. Throws {@link UnsupportedOperationException} for unsupported types. + */ public class LiteralConverter { // TODO: Handle conversion of user-defined type literals @@ -52,6 +59,11 @@ public class LiteralConverter { private final TypeConverter typeConverter; + /** + * Creates a converter that uses the given {@link TypeConverter}. + * + * @param typeConverter converter for {@link RelDataType} to Substrait {@link Type} + */ public LiteralConverter(TypeConverter typeConverter) { this.typeConverter = typeConverter; } @@ -68,6 +80,16 @@ private static BigDecimal bd(RexLiteral literal) { return (BigDecimal) literal.getValue(); } + /** + * Converts a Calcite {@link RexLiteral} to a Substrait {@link Expression.Literal}. + * + *

Type conversion is performed first to ensure value compatibility. Null literals return a + * typed NULL. Unsupported cases throw an exception. + * + * @param literal the Calcite literal to convert + * @return the corresponding Substrait literal + * @throws UnsupportedOperationException if the literal type/value cannot be handled + */ public Expression.Literal convert(RexLiteral literal) { // convert type first to guarantee we can handle the value. final Type type = typeConverter.toSubstrait(literal.getType()); @@ -242,11 +264,28 @@ public Expression.Literal convert(RexLiteral literal, boolean nullable) { } } + /** + * Pads a Calcite {@link org.apache.calcite.avatica.util.ByteString} right with zeros to the + * expected length if needed. + * + * @param bytes the Calcite {@code ByteString} value + * @param length the expected fixed length + * @return a new byte array of {@code length} with original bytes and trailing zeros if needed + * @throws IllegalArgumentException if {@code length} is less than {@code bytes.length} + */ public static byte[] padRightIfNeeded( org.apache.calcite.avatica.util.ByteString bytes, int length) { return padRightIfNeeded(bytes.getBytes(), length); } + /** + * Pads a byte array right with zeros to the expected length if needed. + * + * @param value the byte array value + * @param length the expected fixed length + * @return a new byte array of {@code length} with original bytes and trailing zeros if needed + * @throws IllegalArgumentException if {@code length} is less than {@code value.length} + */ public static byte[] padRightIfNeeded(byte[] value, int length) { if (length < value.length) {