PolarDB-X的扩展函数执行过程中有两个关键步骤,分别是sql语句解析以及函数执行。
SQL语句解析
sql语句解析发生在sql语句执行过程的开始部分,对于用户输入的sql语句,PolarDB-X会首先对sql语句进行词法分析,并生成相应的sql执行计划。
在词法分析过程中,词法分析器Lexer.java
类将会按照Token.java
类中的Token类型进行识别,以确定用户输入的函数语句是否合法。
- 以sql语句
select 1 add 1
为例,词法分析器将add
解析为一个运算符operator,因此需要在Token
类中进行声明,并在SqlBinaryOperator
以及SQLExprParser
等位置进行case处理 - 以sql语句
select foo(1)
为例,对于这种函数类型的语句,词法分析器将foo解析为一个Function
类型的operator,因此无需在Token
类中进行查找,只需要在TddlOperatorTable
中进行注册,并在com.alibaba.polardbx.optimizer.core.function
中添加相应函数即可。 在执行过程中,ReflectiveSqlOperatorTable.init()
会利用反射机制在TddlOperatorTable
中进行读取,将所有函数进行注册,以便后续使用。
函数执行过程
一个扩展函数需要继承AbstractScalarFunction
,并实现compute()
以及getFunctionNames()
函数以便后续的统一调用。
在PolarDB-X的初始化阶段,CobarServer.warmup()
触发ExtraFunctionManager.getExtraFunction("warmup", null, null);
来加载ExtraFunctionManager类。
ExtraFunctionManager中的静态方法initFunctions()
将会做以下两件事:
- 自动扫描IExtraFunction对应Package目录下的所有Function实现
- 自动扫描Extension扩展方式下的自定义实现,比如在META-INF/tddl 或 META-INF/services 添加扩展配置文件
并通过addFunction()
函数来进行函数签名的注册工作。
在函数的执行过程中,使用了一个HashMap来储存函数签名以及对应的构造器,定义如下:
private static Map<FunctionSignature, Constructor<?>> functionCaches = new HashMap<>();
调用链如下所示:
Rex2VectorizedExpressionVisitor.createGeneralVectorizedExpression()
—>
ExtraFunctionManager.getExtraFunction(functionName, operandTypes, resultType);
来获取函数
expression.eval(evaluationContext);
—>
scalarFunction.compute(args, executionContext)
执行相应的函数计算
总结
我认为对于用户自定义函数而言,可以分为两种情况来看:
- 如select 1 ADD 1;这种运算符类型的函数,那么需要解决其在sql语句解析过程中的问题,相对复杂
- 对于select Foo(xxx);这种函数,只需要在TddlOperatorTable中进行注册,并让其继承AbstractScalarFunction,实现相应方法,并在functionCache中添加即可,相对简单。