Presto 标量函数注册和调用过程简述
在Presto 函数开发一文中已经介绍过如何进行函数开发,本文主要讲述标量函数(Scalar Function)实现之后,是如何在Presto内部进行注册和被调用的。主要讲述标量函数是因为:三类函数的注册和调用过程略有不同,而实际查询中调用最多的是标量函数。
标量函数注册
函数在能够调用之前,首先要进行注册,上一篇文章已经介绍过函数注册的方法,那么函数在注册时究竟注册了哪些信息呢?函数注册实际上是维护FunctinoRegistry类中的一个 MultiMap,Key 为函数的限定名(QualifiedName,可以简单地理解为函数名),Value 为SqlFunction
接口的实现类,实际主要为SqlAggregationFunction
、SqlWindowFunction
和SqlScalarFunction
这三个类的子类。SqlScalarFunction
是一个抽象类,定义如下:
public abstract class SqlScalarFunction
implements SqlFunction
{
private final Signature signature;
protected SqlScalarFunction(Signature signature)
{
this.signature = requireNonNull(signature, "signature is null");
checkArgument(signature.getKind() == SCALAR, "function kind must be SCALAR");
}
@Override
public final Signature getSignature()
{
return signature;
}
public abstract ScalarFunctionImplementation specialize(BoundVariables boundVariables, int arity, TypeManager typeManager, FunctionRegistry functionRegistry);
public static PolymorphicScalarFunctionBuilder builder(Class<?> clazz)
{
return new PolymorphicScalarFunctionBuilder(clazz);
}
}
可以看出,其子类需要获取Signature
和实现specialize
方法。
首先来看Signature
:
public final class Signature
{
private final String name;
private final FunctionKind kind;
private final List<TypeVariableConstraint> typeVariableConstraints;
private final List<LongVariableConstraint> longVariableConstraints;
private final TypeSignature returnType;
private final List<TypeSignature> argumentTypes;
private final boolean variableArity;
....
}
类的成员变量说明如下:
name
:函数名,不包括参数类型和结果类型,例如:函数isnull(T):boolean
的函数名为isnull
kind
:枚举类型,有 SCALAR、AGGREGATE 和 WINDOW三种取值,用于区分函数类型typeVariableConstraints
:类型变量约束,记录函数中的类型变量名,以及类型变量所需要满足的约束条件:类型是否为comparable、orderable 和是否绑定具体类型。例如:contains<T:comparable>(array(T),T):boolean
函数要求类型T
满足comparable
;array_sort<E:orderable>(array(E)):array(E)
函数要求类型E
满足orderable
;判断两个ROW类型是否相等的操作符(操作符也属于标量函数)$operator$EQUAL<T:comparable:row<*>>(T,T):boolean
要求类型T
为ROW类型。LongVariableConstraint
:长整型变量约束,记录函数中带有约束的长整型变量的计算表达式(一般用于计算返回类型中的长整型变量)。例如:函数concat<u:x + y>(char(x),char(y)):char(u)
的返回类型中长整型变量u
的计算表达式为x + y
returnType
:函数的返回类型argumentTypes
:函数参数类型variableArity
:标记是否为变长参数
以上成员变量都可以从函数实现的类对象中,根据注解规则解析获得。除了获取Signature
,由于同一个函数可能会有多个实现(例如上一篇文章介绍的isnull<T>(T):boolean
函数,因为传入的参数类型可能不同,所以有五个实现方法),所以还要记录函数的实现方法。源码中将实现方法分为三类:
exactimplementation
:函数中不包含类型变量,即函数的参数类型和返回类型都是确定的specializedImplementation
:函数中包含类型变量,但类型变量作用在具体的 Java 类型(Native Container Type)上genericImplement
:函数中包含类型变量,但是类型变量作用在 Object 类型上
Presto 保存的是实现方法的MethodHandle
,通过反射获取Method
,再保存Method
对应的MethodHandle
(MethodHandle
在JDK1.7引入,调用的效率比反射高),如果该方法不是静态方法,还要将MethodHandle
的中的this
参数改为Object
来避免调用时的类加载问题。所以,抽象方法specialize
的本质是通过传入的参数,来获取匹配到的MethodHandle
,这部分放到下一节的标量函数调用中进行讲解。
可以看出,标量函数注册的本质是保存函数的Signature
和MethodHandle
。开发者根据注解框架实现的标量函数,注册时再根据注解解析出Signature
和MethodHandle
,封装在ParametricScalar
对象中。当然,开发者也可以自行继承SqlScalarFunction
,自己定义Signature
和实现specialize
方法。
标量函数调用
标量函数调用的入口为InterpretedFunctionInvoker
类的public Object invoke(Signature function, ConnectorSession session, List<Object> arguments)
方法,形参里的Signature
是由语义分析时,根据词法分析得到函数QualifiedName和语法分析得到的参数类型,调用FunctionRegistry
中的public Signature resolveFunction(QualifiedName name, List<TypeSignatureProvider> parameterTypes)
方法得到。所以,标量函数调用的关键是resolveFunction
方法和invoke
方法。
首先来看resolveFunction
方法,该方法主要通过函数名和函数参数类型来确定Signature
,流程如下:
虚线红框中的三个匹配过程实际上是调用了同一个方法:Optional<Signature> matchFunction(Collection<SqlFunction> candidates, List<TypeSignatureProvider> parameters, boolean coercionAllowed)
,其中的coercionAllowed
为是否将实参类型转化为形参类型的标识。matchFunction
方法等价于为Signature
中的变量寻找赋值,不仅要满足变量类型是对应的实际参数类型的超类,而且对应的实际参数还要满足Signature
中声明的变量约束。将形参类型和实参进行绑定时,还会做一些约定性的检查:
- 一个类型不能既赋给类型变量(type parameter),又赋给字面变量(literal parameter,如
varchar(x)
中的x
) - 字面变量不允许跨类型使用
为了便于理解第二个规定,下面例举几个字面变量跨类型使用的例子:
- x 出现在不同的基本类型中:char(x)和varchar(x)
- x 出现在同一种基本类型的不同位置:decimal(x,y) 和 decimal(z,x)
- p 与不同的字面量、类型或者字面变量组合使用:decimal(p,s1) and decimal(p,s2)
还有一个限制是,如果尝试将实际参数类型decimal(1,0)
赋给Signature
中声明的decimal(x,2)
,会失败,但是使用decimal(3,1)
可以赋值成功。因为根据decimal
的定义,precision 必须大于 scale,即x
必须大于2。
经过一系列的规则匹配和变量求解,最终会返回一个具体的函数函数签名,签名中的类型都是具体类型(即不含变量)。比如简单 SQLselect isnull('a')
,最终得到的Signature
是isnull(varchar(1)):boolean
,实参中的类型varchar(1)
赋给了原先注册的isnull<T>(T):boolean
中的类型变量T
。
再来看invoke
方法,该方法首先会根据传入的Signature
调用FunctionRegistry
中的getScalarFunctionImplementation
来获取最终的MethodHandle
,然后使用具体的参数值来进行实际方法的调用(方法中若需要ConnectorSession
,也在此进行注入)。因为函数注册维护的是QualifiedName->SqlFunction
的映射关系,而调用getScalarFunctionImplementation
时传入的Signature
并没有记录变量与实参的绑定关系,所以这里需要再进行一次类型变量的求解,这一步的计算其实是可以避免的,因为在resolveFunction
中其实已经拿到了变量绑定的关系,可以进行复用,所以340版本中已改为传入带绑定关系的FunctionBinding
。函数注册时说明了一个函数可能有多个实现方法,接下来就是根据形参和实参的绑定关系,调用SqlFunction
的specialize
方法进行对应参数的 Java 类型的匹配,按照exactimplementation
类型->specializedImplementation
类型->genericImplement
类型的顺序进行匹配,一旦匹配成功则直接返回匹配到的实现方法,如果方法中需要传入依赖变量,也在此步骤中根据绑定关系对MethodHandle
进行参数值注入。因为对MethodHandle
的反复编译会导致full GC(怀疑是触发了 JVM Bug),所以 Presto 在FunctionRegistry
中为三类函数分别做了个大小为1000,有效时长为1小时的缓存来避免这个问题。
至此,函数的注册和调用的过程已经完成。熟悉这两个过程可以帮助我们在函数开发和调用中快速地定位问题,除此之外,求解Signature
时的类型转换匹配可以作为类型隐式转换的一个入口。
Presto 标量函数注册和调用过程简述的更多相关文章
- AsyncTask中各个函数详细的调用过程,初步实现异步任务
AsyncTask内部类可能会产生内存泄露的问题 解决上述内部类可能引起的内存泄露问题的方法 将AsyncTask或者Thread的子类作为单独的类文件,不持有Activity的强引用 将Async ...
- GDB踪函数的完整调用过程 及原理
http://www.lenky.info/archives/2013/02/2202 Breakpoint , .so. (gdb) bt # .so. # .so. # .so. # .so. # ...
- SQL Server如何定位自定义标量函数被那个SQL调用次数最多浅析
前阵子遇到一个很是棘手的问题,监控系统DPA发现某个自定义标量函数被调用的次数非常高,高到一个离谱的程度.然后在Troubleshooting这个问题的时候,确实遇到了一些问题让我很是纠结,下文是解决 ...
- [Android Pro] 深入理解函数的调用过程——栈帧
cp :http://blog.csdn.net/x_perseverance/article/details/78897637 每一个函数被调用时,都会为函数开辟一块空间,这块空间就称为栈帧. 首先 ...
- 你好,C++(27)在一个函数内部调用它自己本身 5.1.5 函数的递归调用
5.1.5 函数的递归调用 在函数调用中,通常我们都是在一个函数中调用另外一个函数,以此来完成其中的某部分功能.例如,我们在main()主函数中调用PowerSum()函数来计算两个数的平方和,而在P ...
- 最原始的COM组件调用过程(不使用注册表信息)
最原始的COM组件调用过程(不使用注册表信息) 最近因为项目的关系开始研究COM组件了,以前都认为COM过时了,所以也没怎么接触. 现在好好补补课了. 一般调用COM都是通过注册表找到它的位置, 然后 ...
- (转)platform_driver_register,什么时候调用PROBE函数 注册后如何找到驱动匹配的设备
platform_driver_register,什么时候调用PROBE函数 注册后如何找到驱动匹配的设备 2011-10-24 19:47:07 分类: LINUX kernel_init中d ...
- 使用IDA PRO+OllyDbg+PEview 追踪windows API 动态链接库函数的调用过程
使用IDA PRO+OllyDbg+PEview 追踪windows API 动态链接库函数的调用过程 http://blog.csdn.net/liujiayu2/article/details/5 ...
- 在C语言中函数及其调用过程
目录 函数 C语言中的变参函数 函数的本质是什么 内存区域的区分技巧 函数的调用过程 栈帧的概念 调用过程细节 按照约定传参 函数 如果一个函数有声明没实现,那么就会出现链接错误: 以上代码会出现链接 ...
随机推荐
- python编程中的并发------多进程multiprocessing
任务例子:喝水.吃饭动作需要耗时1S 单任务:(耗时20s) for i in range(10): print('a正在喝水') time.sleep(1) print('a正在吃饭') time. ...
- Logistic回归分析之二元Logistic回归
在研究X对于Y的影响时,如果Y为定量数据,那么使用多元线性回归分析(SPSSAU通用方法里面的线性回归):如果Y为定类数据,那么使用Logistic回归分析. 结合实际情况,可以将Logistic回归 ...
- Istio的流量管理(实操三)
Istio的流量管理(实操三) 涵盖官方文档Traffic Management章节中的egress部分.其中有一小部分问题(已在下文标注)待官方解决. 目录 Istio的流量管理(实操三) 访问外部 ...
- zstd和zip操作6g的文本
ssd是在固态硬盘上的时间 1.txt 7038308223 bytes 都是默认级别 ======================================== zstd-v1.4.4-w ...
- Hexo-butterfly-magicv3.0.1(持续更新中....)
介绍 Hexo-butterfly魔改v3.0.1 软件架构 本项目是基于Hexo静态博客的个性主题---蝴蝶主题魔改版 安装教程 克隆 安装依赖 hexo命令生成public文件夹 启动hexo-s ...
- Spine学习五- spine动画融合
在许多地方,都需要用到动画融合,unity的新版动画系统已经能够很方便的进行动画融合,那么使用spine的动画状态机的情况下,如何来进行动画融合呢? 官方有两种方案,一种是使用混合动作实现,另一种是使 ...
- web自动化多次打开浏览器嫌烦?打开一次浏览器,pytest有个招
最近系统前端组件做了更新,我就把之前做的web自动化的代码做了一些修改,顺便优化了下用例,只保留少量的测试用例了,大头还是在接口自动化上.然后发现关于pytest的还有一个点应该比较常用,这里再介绍一 ...
- Django模型验证器详解和源码分析
转发请注明来源 在Django的模型字段参数中,有一个参数叫做validators,这个参数是用来指定当前字段需要使用的验证器,也就是对字段数据的合法性进行验证,比如大小.类型等. Django的验证 ...
- vue引入 lodash
vue main.js引入 // main.js 全局引入lodash import _ from 'lodash' Vue.prototype._ = _ // 使用 this._.debounce ...
- Html中让输入框input和紧接在后的按钮button在垂直方向上对齐
<table border="0px" width="360px"> <tr><td colspan="10" ...