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?
Calcite is extensible. You can define each kind of function using user code. For each kind of function there are often several ways to define a function, varying from convenient to efficient. To implement a scalar function, there are 3 options: Create a class with one or more public static methods, and register each class/method combination.
Calcite deduces the parameter types and result type of a function from the parameter and return types of the Java method that implements it. Further, you can specify the name and optionality of each parameter using the Parameter annotation.
Calcite automatically converts a value from one datatype to another when such a conversion makes sense. The table below is a matrix of Calcite type conversions. The table shows all possible conversions, without regard to the context in which it is made.
Supported data types syntax: Calcite automatically converts a value from one datatype to another when such a conversion makes sense. The table below is a matrix of Calcite type conversions. The table shows all possible conversions, without regard to the context in which it is made.
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());
}
}
Is SquareFunction
an inner class? If so, try making it static.
If that doesn't work, try making eval
a static method.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With