-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
[CALCITE-5918] Add MAP function (enabled in Spark library) #3459
Changes from all commits
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 |
---|---|---|
|
@@ -97,7 +97,7 @@ public <T> RelNode translate(Queryable<T> queryable) { | |
public RelNode translate(Expression expression) { | ||
if (expression instanceof MethodCallExpression) { | ||
final MethodCallExpression call = (MethodCallExpression) expression; | ||
BuiltInMethod method = BuiltInMethod.MAP.get(call.method); | ||
BuiltInMethod method = BuiltInMethod.FUNCTIONS_MAPS.get(call.method); | ||
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. why call it 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. because this is a static field in one disadvantage I can think of here is that it may cause compatibility issues? If there is an upper-layer application/engine use java reflection to call this field. But is this as expected? I'm ok if we need to reset spark MAP method to 'MAP_FUNCTION' name in |
||
if (method == null) { | ||
throw new UnsupportedOperationException( | ||
"unknown method " + call.method); | ||
|
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -29,6 +29,7 @@ | |||
import org.apache.calcite.sql.SqlUtil; | ||||
import org.apache.calcite.sql.validate.SqlValidatorScope; | ||||
import org.apache.calcite.util.ImmutableIntList; | ||||
import org.apache.calcite.util.Pair; | ||||
import org.apache.calcite.util.Util; | ||||
|
||||
import com.google.common.collect.ImmutableList; | ||||
|
@@ -568,6 +569,9 @@ public static SqlOperandTypeChecker variadic( | |||
public static final SqlSingleOperandTypeChecker MAP_FROM_ENTRIES = | ||||
new MapFromEntriesOperandTypeChecker(); | ||||
|
||||
public static final SqlSingleOperandTypeChecker MAP_FUNCTION = | ||||
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. same naming comment as above 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. fixed. |
||||
new MapFunctionOperandTypeChecker(); | ||||
|
||||
/** | ||||
* Operand type-checking strategy where type must be a literal or NULL. | ||||
*/ | ||||
|
@@ -1221,6 +1225,62 @@ private static class MapFromEntriesOperandTypeChecker | |||
} | ||||
} | ||||
|
||||
/** | ||||
* Operand type-checking strategy for a MAP function, it allows empty map. | ||||
*/ | ||||
private static class MapFunctionOperandTypeChecker | ||||
extends SameOperandTypeChecker { | ||||
|
||||
MapFunctionOperandTypeChecker() { | ||||
// The args of map are non-fixed, so we set to -1 here. then operandCount | ||||
// can dynamically set according to the number of input args. | ||||
// details please see SameOperandTypeChecker#getOperandList. | ||||
super(-1); | ||||
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. where does the -1 come from? 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. The args of map are non-fixed, so we set to -1 here. then I've added some comments here for better readability. |
||||
} | ||||
|
||||
@Override public boolean checkOperandTypes(final SqlCallBinding callBinding, | ||||
final boolean throwOnFailure) { | ||||
final List<RelDataType> argTypes = | ||||
SqlTypeUtil.deriveType(callBinding, callBinding.operands()); | ||||
// allows empty map | ||||
if (argTypes.isEmpty()) { | ||||
return true; | ||||
} | ||||
// the size of map arg types must be even. | ||||
if (argTypes.size() % 2 != 0) { | ||||
throw callBinding.newValidationError(RESOURCE.mapRequiresEvenArgCount()); | ||||
} | ||||
final Pair<@Nullable RelDataType, @Nullable RelDataType> componentType = | ||||
getComponentTypes( | ||||
callBinding.getTypeFactory(), argTypes); | ||||
// check key type & value type | ||||
if (null == componentType.left || null == componentType.right) { | ||||
if (throwOnFailure) { | ||||
throw callBinding.newValidationError(RESOURCE.needSameTypeParameter()); | ||||
} | ||||
return false; | ||||
} | ||||
return true; | ||||
} | ||||
|
||||
/** | ||||
* Extract the key type and value type of arg types. | ||||
*/ | ||||
private static Pair<@Nullable RelDataType, @Nullable RelDataType> getComponentTypes( | ||||
RelDataTypeFactory typeFactory, | ||||
List<RelDataType> argTypes) { | ||||
// Util.quotientList(argTypes, 2, 0): | ||||
// This extracts all elements at even indices from argTypes. | ||||
// It represents the types of keys in the map as they are placed at even positions | ||||
// e.g. 0, 2, 4, etc. | ||||
// Symmetrically, Util.quotientList(argTypes, 2, 1) represents odd-indexed elements. | ||||
// details please see Util.quotientList. | ||||
return Pair.of( | ||||
typeFactory.leastRestrictive(Util.quotientList(argTypes, 2, 0)), | ||||
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. Where are these constants coming from and what do they signify? 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. Util.quotientList(argTypes, 2, 0): std MapValueConstructor has same logic. see: calcite/core/src/main/java/org/apache/calcite/sql/fun/SqlMapValueConstructor.java Line 90 in 5151168
I've added some comments here for better readability. 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. Can we add UT for How about? 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.
yes, added it. thanks for reviewing. |
||||
typeFactory.leastRestrictive(Util.quotientList(argTypes, 2, 1))); | ||||
} | ||||
} | ||||
|
||||
/** Operand type-checker that accepts period types. Examples: | ||||
* | ||||
* <ul> | ||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can check if the size of args is an even number.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hope to retain the original parser logic here, because it will be detected later on OperandTypeChecker. And here is similar with spark's array handling and calcite array constructor. pls see: https://github.com/apache/calcite/pull/3141/files
Another benefit is that we can use some more human-readable error messages in predefined CalciteResource.