问题
I need to add a user-defined function to Calcite that takes an integer as a parameter and returns an integer.
public class SquareFunction {
public int eval(int a) {
return a*a;
}
}
and the relevant code that creates a schema and adds the function is
SchemaPlus rootSchema = Frameworks.createRootSchema(false);
rootSchema.add("SQUARE_FUNC",
ScalarFunctionImpl.create(SquareFunction.class,"eval");
But a simple SQL like
select SQUARE_FUNC(1) from test;
fails during the validation with the following message:
No match found for function signature SQUARE_FUNC(<NUMERIC>)
The stack trace is:
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
at org.apache.calcite.runtime.Resources$ExInstWithCause.ex(Resources.java:463)
at org.apache.calcite.sql.SqlUtil.newContextException(SqlUtil.java:804)
at org.apache.calcite.sql.SqlUtil.newContextException(SqlUtil.java:789)
at org.apache.calcite.sql.validate.SqlValidatorImpl.newValidationError(SqlValidatorImpl.java:4386)
at org.apache.calcite.sql.validate.SqlValidatorImpl.handleUnresolvedFunction(SqlValidatorImpl.java:1670)
at org.apache.calcite.sql.SqlFunction.deriveType(SqlFunction.java:278)
at org.apache.calcite.sql.SqlFunction.deriveType(SqlFunction.java:223)
at org.apache.calcite.sql.validate.SqlValidatorImpl$DeriveTypeVisitor.visit(SqlValidatorImpl.java:4965)
at org.apache.calcite.sql.validate.SqlValidatorImpl$DeriveTypeVisitor.visit(SqlValidatorImpl.java:1)
at org.apache.calcite.sql.SqlCall.accept(SqlCall.java:137)
at org.apache.calcite.sql.validate.SqlValidatorImpl.deriveTypeImpl(SqlValidatorImpl.java:1586)
at org.apache.calcite.sql.validate.SqlValidatorImpl.deriveType(SqlValidatorImpl.java:1571)
at org.apache.calcite.sql.validate.SqlValidatorImpl.expandSelectItem(SqlValidatorImpl.java:453)
at org.apache.calcite.sql.validate.SqlValidatorImpl.validateSelectList(SqlValidatorImpl.java:3668)
at org.apache.calcite.sql.validate.SqlValidatorImpl.validateSelect(SqlValidatorImpl.java:3186)
at org.apache.calcite.sql.validate.SelectNamespace.validateImpl(SelectNamespace.java:60)
at org.apache.calcite.sql.validate.AbstractNamespace.validate(AbstractNamespace.java:84)
at org.apache.calcite.sql.validate.SqlValidatorImpl.validateNamespace(SqlValidatorImpl.java:937)
at org.apache.calcite.sql.validate.SqlValidatorImpl.validateQuery(SqlValidatorImpl.java:918)
at org.apache.calcite.sql.SqlSelect.validate(SqlSelect.java:220)
at org.apache.calcite.sql.validate.SqlValidatorImpl.validateScopedExpression(SqlValidatorImpl.java:893)
at org.apache.calcite.sql.validate.SqlValidatorImpl.validate(SqlValidatorImpl.java:603)
at org.apache.calcite.prepare.PlannerImpl.validate(PlannerImpl.java:188) ... 26 more
I followed the Calcite's UdfTest.testUserDefinedFunctionInView
implementation but still couldn't make it work. What am I doing wrong?
回答1:
How does Calcite validates that a function exists?
As I known, SqlOperatorTable
defines a directory interface for enumerating and looking up SQL operators and functions, and lookupOperatorOverloads
retrieves a list of operators with a given name and syntax.
For the Frameworks
, the default SqlOperatorTable
of FrameworkConfig
is SqlStdOperatorTable.instance()
. But the function you added only searched at CalciteCatalogReader
. So following code can work:
public class UdfTest {
private static final String SQL = "select SQUARE_FUNC(1)";
private static final String FUN_NAME = "SQUARE_FUNC";
public static void main(String[] args) throws Exception {
useFramworksExec();
}
private static void useFramworksExec() throws Exception {
CalciteSchema rootSchema = CalciteSchema.createRootSchema(false, false);
SchemaPlus schema = rootSchema.plus();
schema.add(FUN_NAME, ScalarFunctionImpl.create(SquareFunction.class, "eval"));
CalciteCatalogReader reader = new CalciteCatalogReader(rootSchema,
SqlParser.Config.DEFAULT.caseSensitive(),
rootSchema.path(null),
new JavaTypeFactoryImpl());
FrameworkConfig config = Frameworks.newConfigBuilder().operatorTable(reader).defaultSchema(schema).build();
Planner planner = Frameworks.getPlanner(config);
SqlNode ast = planner.parse(SQL);
SqlNode validatedAst = planner.validate(ast);
System.out.println(validatedAst.toString());
}
}
回答2:
Is SquareFunction
an inner class? If so, try making it static.
If that doesn't work, try making eval
a static method.
回答3:
As CalciteCatalogReader object requires context, There is another way to register custom Functions.
SqlFunction countRelation = new SqlFunction("COUNT_RELATION",
SqlKind.OTHER_FUNCTION,
ReturnTypes.INTEGER,
null,
OperandTypes.STRING,
SqlFunctionCategory.NUMERIC);
SqlStdOperatorTable sqlStdOperatorTable = SqlStdOperatorTable.instance();
sqlStdOperatorTable.register(countRelation);
this.frameworkConfig = Frameworks.newConfigBuilder()
.parserConfig(parserConfig)
.defaultSchema(schemaPlus)
.programs(Programs.sequence(Programs.ofRules(Programs.RULE_SET), Programs.CALC_PROGRAM))
.traitDefs(traitDefs)
.operatorTable(sqlStdOperatorTable)
.build();
来源:https://stackoverflow.com/questions/44147819/adding-a-user-defined-function-to-calcite