Presto隐式类型转换轻量级方案

Presto是一款优秀的开源OLAP组件,其MPP架构有很好的数据量和灵活性支持。相信大部分使用Presto的同学都有丰富的Hive使用经验,习惯了HQL的语法,写Presto SQL时常常遇到类型不匹配的报错。这是因为,Hive会隐式的对数据类型做兼容性转换,而Presto严格要求数据类型,必须显式的使用CAST强制转换。这为日常使用带来不少麻烦,特别是有业务需要从Hive迁移到Presto时。

让Presto像Hive一样支持隐式类型转换,是个不错的解决方案。

1. Hive的隐式类型转换规则

先了解一下Hive的隐式类型转换规则,参见Apache Wiki,简单整理后如下表所示,多个不同的数据类型,将转换为最小公共类型

type bl tinyint si int bigint float double dm string vc ts date ba
boolean TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
tinyint FALSE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE FALSE FALSE
smallint FALSE FALSE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE FALSE FALSE
int FALSE FALSE FALSE TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE FALSE FALSE
bigint FALSE FALSE FALSE FALSE TRUE TRUE TRUE TRUE TRUE TRUE FALSE FALSE FALSE
float FALSE FALSE FALSE FALSE FALSE TRUE TRUE TRUE TRUE TRUE FALSE FALSE FALSE
double FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE TRUE TRUE FALSE FALSE FALSE
decimal FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE TRUE FALSE FALSE FALSE
string FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE TRUE TRUE FALSE FALSE FALSE
varchar FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE TRUE TRUE FALSE FALSE FALSE
ts FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE TRUE FALSE FALSE
date FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE FALSE TRUE FALSE
binary FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE

当然这里只整理了常用的数据类型,足够覆盖99%的业务场景了。

如果需要,Hive将使用上面的转换规则,对各种运算符、函数进行隐式类型转换。例如在Hive中最常用时间作为分区字段,一般按天分区,将时间format为yyyyMMdd格式,数据类型为字符串。执行如下HQL:

1
select * from table where dt=20190329 limit 10;

dt分区字段数据类型为string,值20190329数据类型为int,查表可知,Hive会把dt和20190329都转换为double类型做关系计算

该SQL在Presto中会直接抛出异常,因为关系运算符=的左值和右值不匹配,修改如下:

1
select * from table where dt=cast(20190329 as varchar); //注意presto中没有string类型

这里不一定要转换为varchar,只要是公共类型即可。

规则并不是绝对的,在某些情况下,Hive转换规则会被打破,或者说这本身也是规则的一部分。

例1:boolean类型无法隐式转换为其他任何类型,但是进行关系运算时,将隐式转换为数字1

1
2
3
4
5
6
7
8
hive> select 1 = true;
OK
true
Time taken: 1.512 seconds, Fetched: 1 row(s)
hive> select 1 > true;
OK
false
Time taken: 0.306 seconds, Fetched: 1 row(s)

例2:int和bigint的最小公共类型为bigint,但是做除法运算时,将隐式转换为double,结果也为double类型

1
2
3
4
hive> select cast(1 as int) / cast(2 as bigint);
OK
0.5
Time taken: 0.208 seconds, Fetched: 1 row(s)

可以通过运算符、函数覆盖性测试,找出那些特殊的场景。

2. Presto实现隐式类型转换

Presto其实有一定隐式类型转换能力,只是局限在数字类型,包括整型和浮点型,转换规则与Hive一致。

Presto不支持boolean、数字、字符串之间的转换。

Presto社区关于此问题的讨论:https://github.com/prestosql/presto/issues/116

社区有所顾虑,但尽快支持隐式类型转换,无疑是Hive和Presto双重度用户的迫切需求。

思路:既然可以显式CAST转换,那么只需要隐式的插入一个CAST即可最轻量的完成转换。这相当于自动给SQL增加了CAST函数,兼容性上也绝对没有任何问题。

1
2
3
有两个原则需要牢记:
- 我们并不打算重新造一套隐式类型转换规则,与Hive保持一致即可
- 我们也没有必要实现全部场景的隐式类型转换,目标是满足业务需求,可逐步迭代

这里直接列出几个Presto源码修改点,根据我的业务场景,实现了必要的几个类型转换:

class method desc support
ExpressionAnalyzer visitComparisonExpression 关系运算符
ExpressionAnalyzer visitArithmeticBinary 算术二元运算符
ExpressionAnalyzer visitArithmeticUnary 算术一元运算符
ExpressionAnalyzer visitBetweenPredicate between … and …
ExpressionAnalyzer visitSubscriptExpression 下标表达式
ExpressionAnalyzer visitLogicalBinaryExpression 逻辑运算符
ExpressionAnalyzer visitInPredicate in
ExpressionAnalyzer visitFunctionCall 函数(sum avg concat)

例如,关系运算会调用visitComparisonExpression方法做类型校验,源码如下:

1
2
3
4
5
protected Type visitComparisonExpression(ComparisonExpression node, StackableAstVisitorContext<Context> context)
{
OperatorType operatorType = OperatorType.valueOf(node.getType().name());
return getOperator(context, node, operatorType, node.getLeft(), node.getRight());
}

node.getLeft()node.getRight()分别对应着关系运算符的左值和右值。

在这里提前判断左值和右值是否需要转换,如需转换,则插入CAST,例如将右值转换为double:

1
2
3
Cast cast = new Cast(node.getRight(), DoubleType.DOUBLE.getDisplayName());
node.setRight(cast);
process(cast, context);
如果这篇文章对你有帮助,那么不妨?
0%