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.
*
- *
- * - Calcite → Substrait: Subclasses implement {@code convert()} methods to convert
- * Calcite calls to Substrait function invocations
- *
- Substrait → Calcite: {@link #getSqlOperatorFromSubstraitFunc} converts Substrait
- * function keys to Calcite {@link SqlOperator}s
- *
- *
- * 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.
*
- *
- * - Variadic arguments all have the same input type
- *
- Matched wildcard arguments (i.e.`any`, `any1`, `any2`, etc) all have the same input
- * type
- *
+ * 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 extends FunctionArg> 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) {