-
Notifications
You must be signed in to change notification settings - Fork 180
Support function argument coercion with Calcite #3914
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
211c2c7
ae72725
4667384
93a56ab
25d069f
6c3faa4
42fd079
ec7ce78
86e3741
b126b87
bd9f3bb
c539056
260fd19
4ea73dc
3d32da0
00926b5
daaee69
c708ae1
5e8858a
abc5309
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| /* | ||
| * Copyright OpenSearch Contributors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package org.opensearch.sql.expression.function; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import javax.annotation.Nullable; | ||
| import org.apache.calcite.rex.RexBuilder; | ||
| import org.apache.calcite.rex.RexNode; | ||
| import org.opensearch.sql.calcite.utils.OpenSearchTypeFactory; | ||
| import org.opensearch.sql.data.type.ExprCoreType; | ||
| import org.opensearch.sql.data.type.ExprType; | ||
| import org.opensearch.sql.data.type.WideningTypeRule; | ||
| import org.opensearch.sql.exception.ExpressionEvaluationException; | ||
|
|
||
| public class CoercionUtils { | ||
|
|
||
| /** | ||
| * Casts the arguments to the types specified in the typeChecker. Returns null if no combination | ||
| * of parameter types matches the arguments or if casting fails. | ||
| * | ||
| * @param builder RexBuilder to create casts | ||
| * @param typeChecker PPLTypeChecker that provides the parameter types | ||
| * @param arguments List of RexNode arguments to be cast | ||
| * @return List of cast RexNode arguments or null if casting fails | ||
| */ | ||
| public static @Nullable List<RexNode> castArguments( | ||
| RexBuilder builder, PPLTypeChecker typeChecker, List<RexNode> arguments) { | ||
| List<List<ExprType>> paramTypeCombinations = typeChecker.getParameterTypes(); | ||
|
|
||
| // TODO: var args? | ||
|
|
||
| for (List<ExprType> paramTypes : paramTypeCombinations) { | ||
| List<RexNode> castedArguments = castArguments(builder, paramTypes, arguments); | ||
| if (castedArguments != null) { | ||
| return castedArguments; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| /** | ||
| * Widen the arguments to the widest type found among them. If no widest type can be determined, | ||
| * returns null. | ||
| * | ||
| * @param builder RexBuilder to create casts | ||
| * @param arguments List of RexNode arguments to be widened | ||
| * @return List of widened RexNode arguments or null if no widest type can be determined | ||
| */ | ||
| public static @Nullable List<RexNode> widenArguments( | ||
| RexBuilder builder, List<RexNode> arguments) { | ||
| // TODO: Add test on e.g. IP | ||
| ExprType widestType = findWidestType(arguments); | ||
| if (widestType == null) { | ||
| return null; // No widest type found, return null | ||
| } | ||
| return arguments.stream().map(arg -> cast(builder, widestType, arg)).toList(); | ||
| } | ||
|
|
||
| /** | ||
| * Casts the arguments to the types specified in paramTypes. Returns null if the number of | ||
| * parameters does not match or if casting fails. | ||
| */ | ||
| private static @Nullable List<RexNode> castArguments( | ||
| RexBuilder builder, List<ExprType> paramTypes, List<RexNode> arguments) { | ||
| if (paramTypes.size() != arguments.size()) { | ||
| return null; // Skip if the number of parameters does not match | ||
| } | ||
|
|
||
| List<RexNode> castedArguments = new ArrayList<>(); | ||
| for (int i = 0; i < paramTypes.size(); i++) { | ||
| ExprType toType = paramTypes.get(i); | ||
| RexNode arg = arguments.get(i); | ||
|
|
||
| RexNode castedArg = cast(builder, toType, arg); | ||
|
|
||
| if (castedArg == null) { | ||
| return null; | ||
| } | ||
| castedArguments.add(castedArg); | ||
| } | ||
| return castedArguments; | ||
| } | ||
|
|
||
| private static @Nullable RexNode cast(RexBuilder builder, ExprType targetType, RexNode arg) { | ||
| ExprType argType = OpenSearchTypeFactory.convertRelDataTypeToExprType(arg.getType()); | ||
| if (!argType.shouldCast(targetType)) { | ||
| return arg; | ||
| } | ||
|
|
||
| if (WideningTypeRule.distance(argType, targetType) != WideningTypeRule.IMPOSSIBLE_WIDENING) { | ||
| return builder.makeCast(OpenSearchTypeFactory.convertExprTypeToRelDataType(targetType), arg); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| /** | ||
| * Finds the widest type among the given arguments. The widest type is determined by applying the | ||
| * widening type rule to each pair of types in the arguments. | ||
| * | ||
| * @param arguments List of RexNode arguments to find the widest type from | ||
| * @return the widest ExprType if found, otherwise null | ||
| */ | ||
| private static @Nullable ExprType findWidestType(List<RexNode> arguments) { | ||
| if (arguments.isEmpty()) { | ||
| return null; // No arguments to process | ||
| } | ||
| ExprType widestType = | ||
| OpenSearchTypeFactory.convertRelDataTypeToExprType(arguments.getFirst().getType()); | ||
| if (arguments.size() == 1) { | ||
| return widestType; | ||
| } | ||
|
|
||
| // Iterate pairwise through the arguments and find the widest type | ||
| for (int i = 1; i < arguments.size(); i++) { | ||
| var type = OpenSearchTypeFactory.convertRelDataTypeToExprType(arguments.get(i).getType()); | ||
| try { | ||
| if (areDateAndTime(widestType, type)) { | ||
| // If one is date and the other is time, we consider timestamp as the widest type | ||
| widestType = ExprCoreType.TIMESTAMP; | ||
| } else { | ||
| widestType = WideningTypeRule.max(widestType, type); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [non-blocking]Calcite has similar operation by using We may deprecate ExprValue and ExprType in the future. But for now, it's OK to keep using this one.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea, we could override |
||
| } | ||
| } catch (ExpressionEvaluationException e) { | ||
| // the two types are not compatible, return null | ||
| return null; | ||
| } | ||
| } | ||
| return widestType; | ||
| } | ||
|
|
||
| private static boolean areDateAndTime(ExprType type1, ExprType type2) { | ||
| return (type1 == ExprCoreType.DATE && type2 == ExprCoreType.TIME) | ||
| || (type1 == ExprCoreType.TIME && type2 == ExprCoreType.DATE); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.