Presto 函数开发一文中已经介绍过如何进行函数开发,本文主要讲述标量函数(Scalar Function)实现之后,是如何在Presto内部进行注册和被调用的。主要讲述标量函数是因为:三类函数的注册和调用过程略有不同,而实际查询中调用最多的是标量函数。

标量函数注册

函数在能够调用之前,首先要进行注册,上一篇文章已经介绍过函数注册的方法,那么函数在注册时究竟注册了哪些信息呢?函数注册实际上是维护FunctinoRegistry类中的一个 MultiMap,Key 为函数的限定名(QualifiedName,可以简单地理解为函数名),Value 为SqlFunction接口的实现类,实际主要为SqlAggregationFunctionSqlWindowFunctionSqlScalarFunction这三个类的子类。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满足comparablearray_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对应的MethodHandleMethodHandle在JDK1.7引入,调用的效率比反射高),如果该方法不是静态方法,还要将MethodHandle的中的this参数改为Object来避免调用时的类加载问题。所以,抽象方法specialize的本质是通过传入的参数,来获取匹配到的MethodHandle,这部分放到下一节的标量函数调用中进行讲解。

可以看出,标量函数注册的本质是保存函数的SignatureMethodHandle。开发者根据注解框架实现的标量函数,注册时再根据注解解析出SignatureMethodHandle,封装在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'),最终得到的Signatureisnull(varchar(1)):boolean,实参中的类型varchar(1)赋给了原先注册的isnull<T>(T):boolean中的类型变量T

再来看invoke方法,该方法首先会根据传入的Signature调用FunctionRegistry中的getScalarFunctionImplementation来获取最终的MethodHandle,然后使用具体的参数值来进行实际方法的调用(方法中若需要ConnectorSession,也在此进行注入)。因为函数注册维护的是QualifiedName->SqlFunction的映射关系,而调用getScalarFunctionImplementation时传入的Signature并没有记录变量与实参的绑定关系,所以这里需要再进行一次类型变量的求解,这一步的计算其实是可以避免的,因为在resolveFunction中其实已经拿到了变量绑定的关系,可以进行复用,所以340版本中已改为传入带绑定关系的FunctionBinding。函数注册时说明了一个函数可能有多个实现方法,接下来就是根据形参和实参的绑定关系,调用SqlFunctionspecialize方法进行对应参数的 Java 类型的匹配,按照exactimplementation类型->specializedImplementation类型->genericImplement类型的顺序进行匹配,一旦匹配成功则直接返回匹配到的实现方法,如果方法中需要传入依赖变量,也在此步骤中根据绑定关系对MethodHandle进行参数值注入。因为对MethodHandle的反复编译会导致full GC(怀疑是触发了 JVM Bug),所以 Presto 在FunctionRegistry中为三类函数分别做了个大小为1000,有效时长为1小时的缓存来避免这个问题。

至此,函数的注册和调用的过程已经完成。熟悉这两个过程可以帮助我们在函数开发和调用中快速地定位问题,除此之外,求解Signature时的类型转换匹配可以作为类型隐式转换的一个入口。

Presto 标量函数注册和调用过程简述的更多相关文章

  1. AsyncTask中各个函数详细的调用过程,初步实现异步任务

     AsyncTask内部类可能会产生内存泄露的问题 解决上述内部类可能引起的内存泄露问题的方法 将AsyncTask或者Thread的子类作为单独的类文件,不持有Activity的强引用 将Async ...

  2. GDB踪函数的完整调用过程 及原理

    http://www.lenky.info/archives/2013/02/2202 Breakpoint , .so. (gdb) bt # .so. # .so. # .so. # .so. # ...

  3. SQL Server如何定位自定义标量函数被那个SQL调用次数最多浅析

    前阵子遇到一个很是棘手的问题,监控系统DPA发现某个自定义标量函数被调用的次数非常高,高到一个离谱的程度.然后在Troubleshooting这个问题的时候,确实遇到了一些问题让我很是纠结,下文是解决 ...

  4. [Android Pro] 深入理解函数的调用过程——栈帧

    cp :http://blog.csdn.net/x_perseverance/article/details/78897637 每一个函数被调用时,都会为函数开辟一块空间,这块空间就称为栈帧. 首先 ...

  5. 你好,C++(27)在一个函数内部调用它自己本身 5.1.5 函数的递归调用

    5.1.5 函数的递归调用 在函数调用中,通常我们都是在一个函数中调用另外一个函数,以此来完成其中的某部分功能.例如,我们在main()主函数中调用PowerSum()函数来计算两个数的平方和,而在P ...

  6. 最原始的COM组件调用过程(不使用注册表信息)

    最原始的COM组件调用过程(不使用注册表信息) 最近因为项目的关系开始研究COM组件了,以前都认为COM过时了,所以也没怎么接触. 现在好好补补课了. 一般调用COM都是通过注册表找到它的位置, 然后 ...

  7. (转)platform_driver_register,什么时候调用PROBE函数 注册后如何找到驱动匹配的设备

     platform_driver_register,什么时候调用PROBE函数 注册后如何找到驱动匹配的设备 2011-10-24 19:47:07 分类: LINUX   kernel_init中d ...

  8. 使用IDA PRO+OllyDbg+PEview 追踪windows API 动态链接库函数的调用过程

    使用IDA PRO+OllyDbg+PEview 追踪windows API 动态链接库函数的调用过程 http://blog.csdn.net/liujiayu2/article/details/5 ...

  9. 在C语言中函数及其调用过程

    目录 函数 C语言中的变参函数 函数的本质是什么 内存区域的区分技巧 函数的调用过程 栈帧的概念 调用过程细节 按照约定传参 函数 如果一个函数有声明没实现,那么就会出现链接错误: 以上代码会出现链接 ...

随机推荐

  1. 怎么给Ubuntu Server安装GUI桌面

    sudo apt update sudo apt upgrade sudo add-apt-repository universe sudo add-apt-repository multiverse ...

  2. Locust性能测试1--简介安装及基本使用

    1. Locust简介 Locust是易于使用的分布式用户负载测试工具,旨在对网站(或其他系统)进行负载测试,并弄清一个系统可以处理多少个并发用户,Locust翻译过来是蝗虫的意思,在测试期间,意在一 ...

  3. Java面试题(反射篇+对象拷贝篇)

    反射 57.什么是反射? 反射主要是指程序可以访问.检测和修改它本身状态或行为的一种能力 Java反射: 在Java运行时环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否 ...

  4. linux字符串转数字

    方法一: [root@ffcs211 test_dir]# echo "96.56"| awk '{print int($0)}' 输出结果 96 方法二: A="2&q ...

  5. 泊松分布算法的应用:开一家4S店

    王老板开了一家4S店,卖新车为主,车型也很单一,可是每个月销量都变化很大,他很头疼,该怎么备货,头疼的是: 1)备货少了,可以来了没货可能就不买,去别的店了 2)备货多了,占用库存不说,长久卖不出去就 ...

  6. MySQL 外部联结 内连接、左右外连接辨析

    内连接 在进行跨表内连接查询数据时,查询结果只返回符合查询条件的数据:跨表内连接查询的结果和使用where的多表查询结果相同,其实就是普通的查询,没啥好说的 -- 语法: SELECT 别名1.字段名 ...

  7. ACwing 你能回答这些问题吗(线段树求最大连续字段和)

    给定长度为N的数列A,以及M条指令,每条指令可能是以下两种之一: 1.“1 x y”,查询区间 [x,y] 中的最大连续子段和,即 maxx≤l≤r≤ymaxx≤l≤r≤y{∑ri=lA[i]∑i=l ...

  8. 深入了解Netty【七】Netty核心组件

    1.Bootstrap与ServerBootstrap bootstrap用于引导Netty的启动,Bootstrap是客户端的引导类,ServerBootstrap是服务端引导类.类继承关系: 2. ...

  9. 伪距定位算法(matlab版)

    在各种伪距定位算法中,最小二乘法是一种比较简单而广泛的方法,该算法可以分为以下几步: 1.准备数据与设置初始值 这里准备数据,主要是对于各颗可见卫星,收集到它们在同一时刻的伪距测量值,计算测量值的各项 ...

  10. CC2530的ADC采集外部电压

    初窥ZIGBEE 要在zigbee的组网中加入烟雾传感器的模块,所以需要用到cc2530的ADC对传感器输出的模拟信号进行采样.下面是自己对实现用CC2530的ADC采集外部电压的程序过程. 以下是A ...