-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
[Feature] Support function registry with arg types #9397
Open
61yao
wants to merge
4
commits into
apache:master
Choose a base branch
from
61yao:function_registry
base: master
Could not load branches
Branch not found: {{ refName }}
Could not load tags
Nothing to show
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,31 +18,77 @@ | |
*/ | ||
package org.apache.pinot.common.function; | ||
|
||
import com.google.common.base.Preconditions; | ||
import java.lang.reflect.Method; | ||
import java.lang.reflect.Modifier; | ||
import java.util.ArrayList; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import javax.annotation.Nullable; | ||
import org.apache.commons.lang3.StringUtils; | ||
import org.apache.pinot.common.request.Expression; | ||
import org.apache.pinot.common.request.Function; | ||
import org.apache.pinot.common.request.context.ExpressionContext; | ||
import org.apache.pinot.common.request.context.FunctionContext; | ||
import org.apache.pinot.common.utils.DataSchema; | ||
import org.apache.pinot.spi.annotations.ScalarFunction; | ||
import org.apache.pinot.spi.data.FieldSpec; | ||
import org.apache.pinot.spi.utils.PinotReflectionUtils; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
|
||
/** | ||
* Registry for scalar functions. | ||
* | ||
* The registry registers functions using canonical functionName and argument type as DataType. | ||
* It doesn't differentiate function name in different canonical forms or argument types whose DataType is the same such | ||
* as primitive numerical type and its wrapper class. | ||
* | ||
* To be backward compatible, the registry falls back functionName + param number matching when there are no type match | ||
* for parameters. | ||
* <p>TODO: Merge FunctionRegistry and FunctionDefinitionRegistry to provide one single registry for all functions. | ||
*/ | ||
public class FunctionRegistry { | ||
private FunctionRegistry() { | ||
} | ||
|
||
private static final Logger LOGGER = LoggerFactory.getLogger(FunctionRegistry.class); | ||
|
||
// Map from function name to parameter types to function info. | ||
private static final Map<String, Map<List<FieldSpec.DataType>, FunctionInfo>> FUNC_PARAM_INFO_MAP = new HashMap<>(); | ||
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. We might want to use |
||
|
||
// FUNCTION_INFO_MAP is still used to be backward compatible. | ||
// When we support all sql data types, we can deprecate this. | ||
@Deprecated | ||
private static final Map<String, Map<Integer, FunctionInfo>> FUNCTION_INFO_MAP = new HashMap<>(); | ||
|
||
private static List<FieldSpec.DataType> getParamTypes(Class<?>[] types) { | ||
List<FieldSpec.DataType> paramTypes = new ArrayList<>(); | ||
for (Class<?> t : types) { | ||
paramTypes.add(FunctionUtils.getDataType(t)); | ||
} | ||
return paramTypes; | ||
} | ||
|
||
/** | ||
* Returns the {@link FunctionInfo} associated with the given function name and number of parameters, or {@code null} | ||
* if there is no matching method. This method should be called after the FunctionRegistry is initialized and all | ||
* methods are already registered. | ||
* | ||
* Assuming functionName is canonicalized. | ||
*/ | ||
@Nullable | ||
private static FunctionInfo getFunctionInfo(String functionName, int numParameters) { | ||
Map<Integer, FunctionInfo> functionInfoMap = FUNCTION_INFO_MAP.get(functionName); | ||
return functionInfoMap != null ? functionInfoMap.get(numParameters) : null; | ||
} | ||
|
||
private static String canonicalize(String functionName) { | ||
return StringUtils.remove(functionName, '_').toLowerCase(); | ||
} | ||
|
||
/** | ||
* Registers the scalar functions via reflection. | ||
* NOTE: In order to plugin methods using reflection, the methods should be inside a class that includes ".function." | ||
|
@@ -92,11 +138,17 @@ public static void registerFunction(Method method, boolean nullableParameters) { | |
* Registers a method with the given function name. | ||
*/ | ||
public static void registerFunction(String functionName, Method method, boolean nullableParameters) { | ||
FunctionInfo functionInfo = new FunctionInfo(method, method.getDeclaringClass(), nullableParameters); | ||
String canonicalName = canonicalize(functionName); | ||
final FunctionInfo functionInfo = new FunctionInfo(method, method.getDeclaringClass(), nullableParameters); | ||
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. (convention) We don't usually put |
||
final String canonicalName = canonicalize(functionName); | ||
Map<Integer, FunctionInfo> functionInfoMap = FUNCTION_INFO_MAP.computeIfAbsent(canonicalName, k -> new HashMap<>()); | ||
Preconditions.checkState(functionInfoMap.put(method.getParameterCount(), functionInfo) == null, | ||
"Function: %s with %s parameters is already registered", functionName, method.getParameterCount()); | ||
// Only put one default implementation for # of params. | ||
if (!functionInfoMap.containsKey(method.getParameterCount())) { | ||
functionInfoMap.put(method.getParameterCount(), functionInfo); | ||
} | ||
List<FieldSpec.DataType> paramTypes = getParamTypes(method.getParameterTypes()); | ||
Map<List<FieldSpec.DataType>, FunctionInfo> functionParamInfoMap = | ||
FUNC_PARAM_INFO_MAP.computeIfAbsent(canonicalName, k -> new HashMap<>()); | ||
functionParamInfoMap.put(paramTypes, functionInfo); | ||
} | ||
|
||
/** | ||
|
@@ -107,17 +159,48 @@ public static boolean containsFunction(String functionName) { | |
} | ||
|
||
/** | ||
* Returns the {@link FunctionInfo} associated with the given function name and number of parameters, or {@code null} | ||
* if there is no matching method. This method should be called after the FunctionRegistry is initialized and all | ||
* methods are already registered. | ||
* All functions should be directly or indirectly registered via this call to ensure function name is canonical. | ||
*/ | ||
@Nullable | ||
public static FunctionInfo getFunctionInfo(String functionName, int numParameters) { | ||
Map<Integer, FunctionInfo> functionInfoMap = FUNCTION_INFO_MAP.get(canonicalize(functionName)); | ||
return functionInfoMap != null ? functionInfoMap.get(numParameters) : null; | ||
public static FunctionInfo getFunctionInfo(String functionName, List<FieldSpec.DataType> dataTypes) { | ||
String canonicalFunctionName = canonicalize(functionName); | ||
Map<List<FieldSpec.DataType>, FunctionInfo> paramMaps = | ||
FUNC_PARAM_INFO_MAP.getOrDefault(canonicalFunctionName, null); | ||
FunctionInfo info = paramMaps.getOrDefault(dataTypes, null); | ||
if (info == null) { | ||
return getFunctionInfo(canonicalFunctionName, dataTypes.size()); | ||
} | ||
return info; | ||
} | ||
|
||
private static String canonicalize(String functionName) { | ||
return StringUtils.remove(functionName, '_').toLowerCase(); | ||
@Nullable | ||
public static FunctionInfo getFunctionInfo(Function function) { | ||
String functionName = function.getOperator(); | ||
List<Expression> operands = function.getOperands(); | ||
List<FieldSpec.DataType> args = new ArrayList<>(); | ||
for (Expression exp : operands) { | ||
ExpressionContext ctx = ExpressionContext.forLiteralContext(exp.getLiteral()); | ||
args.add(ctx.getLiteralContext().getType()); | ||
} | ||
return getFunctionInfo(functionName, args); | ||
} | ||
|
||
@Nullable | ||
public static FunctionInfo getFunctionInfo(FunctionContext function) { | ||
List<ExpressionContext> args = function.getArguments(); | ||
List<FieldSpec.DataType> argTypes = new ArrayList<>(); | ||
for (ExpressionContext exp : args) { | ||
argTypes.add(exp.getLiteralContext().getType()); | ||
} | ||
return getFunctionInfo(function.getFunctionName(), argTypes); | ||
} | ||
|
||
@Nullable | ||
public static FunctionInfo getFunctionInfo(String functionName, DataSchema.ColumnDataType[] argTypes) { | ||
List<FieldSpec.DataType> paramTypes = new ArrayList<>(); | ||
for (DataSchema.ColumnDataType type : argTypes) { | ||
paramTypes.add(FunctionUtils.getDataType(type)); | ||
} | ||
return getFunctionInfo(functionName, paramTypes); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Suggest making the value a separate class, which can be used to look up the
FunctionInfo
with a list of argument types. That way we don't need to maintain 2 maps, and can support type matching (match int argument to long parameter function) in the future.