Presto兼容Hive隐式类型转换

2022-02-10 00:00:00 规则 字符串 类型 转换 隐式

Presto对数据类型的要求比较严格,比如在Hive中常见的数字与字符串进行比较的查询语句,Presto会直接抛类型不一致的错误:

presto> select 1='1.0';

Query 20210214_063233_00001_t3gk8 failed: line 1:9: Cannot apply operator: integer = varchar(1)

Presto CLI 查询结果


hive> select 1='1.0';

OK

_c0

true

Time taken: 1.2 seconds, Fetched: 1 row(s)

Hive CLI 查询结果


而Hive之所以能够成功执行,是因为Hive支持字符串类型到数字类型的隐式转换,将字符串'1'转化为数字1,然后进行比较操作。这就是所谓的隐式类型类型转换。


Hive隐式类型转换规则

那Hive都支持哪些类型的隐式转换呢?下图是Hive官方文档给出的隐式类型转换规则。

不过,仅靠这张表格的话,是搞不明白Hive的隐式类型转换规则的。以文章开篇的查询语句为例,等号两边分别是varchar类型和double类型,通过查找上表不难发现:double可以隐式转换为varchar,varchar也可以通过隐式转换变成double。那这时候应该应用哪条规则呢?通过查询结果为true反推,这里是应用了varchar到double的转换规则,因为如果是double转换为varchar的话,字符串'1'和'1.0'是不相等的。当表格中的类型可以相互转换时,我们可以用这种查询结果倒推的方法来判断Hive使用了哪一条隐式类型转换规则。一般的经验是,在涉及数学运算时(关系运算、四则运算、数学函数等),都会将字符串转为数字。


Presto隐式类型转换

其实Presto也有自己的一套隐式类型转换规则,在源码的 public Optional<TypecoerceTypeBase(Type sourceTypeString resultTypeBase) 方法(presto sql该方法在io.prestosql.type.TypeCoercion类中,presto db该方法在com.facebook.presto.type.TypeRegistry类中)上有以下注释:

/**

* coerceTypeBase and isCovariantParametrizedType defines all hand-coded rules for type coercion.

* Other methods should reference these two functions instead of hand-code new rules.

*/

coerceTypeBase方法注释


可以看出,Presto中只要涉及类型转换,都会调用这个方法,将代码中的类型转换规则整理到Hive的类型转换表中,可以得到下表,其中蓝色区域是Presto和Hive都支持的类型转换,绿色区域是Presto不支持但是Hive支持的类型转换,红色区域是两者都不支持的类型转换。

可以看到,Presto与Hive的隐式类型转换不兼容的地方主要是其他类型到字符串类型的转换和字符串到double类型的转换。所以,要兼容Hive的隐式转换规则,只需要在Presto原有的规则上添加缺少的转换规则,值得注意的是,在调用 coerceTypeBase方法前,还需要根据当前调用的函数名来判断是否能使用字符串到数字的转换规则。


Presto兼容Hive隐式类型转换实现

根据前文分析,以presto sql 340为例,我们首先在coerceTypeBase 中添加字符串和其他类型之间的转换规则,部分关键代码如下:

public Optional<Type> coerceTypeBase(Type sourceType, String resultTypeBase){    String sourceTypeName = sourceType.getBaseName();    if (sourceTypeName.equals(resultTypeBase)) {        return Optional.of(sourceType);    }
switch (sourceTypeName) { //...省略 case Standard*.BIGINT: { switch (resultTypeBase) { //...省略 case Standard*.VARCHAR: if (coerceToVarchar) { return Optional.of(createUnboundedVarcharType()); } default: return Optional.empty(); } } case Standard*.DECIMAL: { switch (resultTypeBase) { //...省略 case Standard*.VARCHAR: if (coerceToVarchar) { return Optional.of(createUnboundedVarcharType()); } default: return Optional.empty(); } } //...省略 case Standard*.DOUBLE: { switch (resultTypeBase) { case Standard*.VARCHAR: if (coerceToVarchar) { return Optional.of(createUnboundedVarcharType()); } default: return Optional.empty(); } } //...省略 case Standard*.TIMESTAMP: { switch (resultTypeBase) { //...省略 case Standard*.VARCHAR: return Optional.of(createUnboundedVarcharType()); default: return Optional.empty(); } } case Standard*.VARCHAR: { switch (resultTypeBase) { //...省略 case Standard*.DOUBLE: if (coerceToVarchar) { return Optional.empty(); } return Optional.of(DOUBLE); default: return Optional.empty(); } } //...省略 default: return Optional.empty(); }}

从以上代码中可以看出,在添加规则的同时,如果是数字类型和字符串类型进行转换,还需要进行 if coerceToVarchar 判断,如果coerceToVarchar为false,则数字类型不能转换为字符串类型,而字符串可以转换为double类型。每次类型转换之前,都需要根据当前调用的函数来判断数字类型是否可以转为字符串,所以在函数签名匹配中新增一个常量方法来进行判断,代码如下:

public static boolean canCoerceToVarchar(String functionName){    if (isComparableOperator(functionName)) {        return false;    }
switch (functionName) { case "json_array_contains": case "round": case "greatest": case "least": case "avg": return false; default: return true; }}

可以看出,当关系运算符和数学函数被调用时,数字类型不能隐式转化为字符串类型。

在一次SQL执行的过程中,可能进行多次的隐式类型转换,所以需要调用多次canCoerceToVarchar方法,用其返回值来设置TypeCoercion对象中的coerceToVarchar属性,下面给出源码中需要调用该方法的几个点。

Class

Method

Desc

SignatureBinder

SignatureBinder

函数签名匹配的构造函数

ExpressionAnalyzer

visitIfExpression

if字句中的类型转换

ExpressionAnalyzer

visitSearchedCaseExpression

case when子句中的类型转换

ExpressionAnalyzer

visitFunctionCall

函数调用前的类型转换

ExpressionAnalyzer

visitInPredicate

in中的类型转换

ExpressionAnalyzer

getOperator

普通运算中的类型转换


来源 https://mp.weixin.qq.com/s/1hn3nVBdBtBeiPl3wxvHfQ


相关文章