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 | hive> select 1 = true; |
例2:int和bigint的最小公共类型为bigint,但是做除法运算时,将隐式转换为double,结果也为double类型
1 | hive> select cast(1 as int) / cast(2 as bigint); |
可以通过运算符、函数覆盖性测试,找出那些特殊的场景。
2. Presto实现隐式类型转换
Presto其实有一定隐式类型转换能力,只是局限在数字类型,包括整型和浮点型,转换规则与Hive一致。
Presto不支持boolean、数字、字符串之间的转换。
Presto社区关于此问题的讨论:https://github.com/prestosql/presto/issues/116
社区有所顾虑,但尽快支持隐式类型转换,无疑是Hive和Presto双重度用户的迫切需求。
思路:既然可以显式CAST转换,那么只需要隐式的插入一个CAST即可最轻量的完成转换。这相当于自动给SQL增加了CAST函数,兼容性上也绝对没有任何问题。
1 | 有两个原则需要牢记: |
这里直接列出几个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 | protected Type visitComparisonExpression(ComparisonExpression node, StackableAstVisitorContext<Context> context) |
node.getLeft()
和node.getRight()
分别对应着关系运算符的左值和右值。
在这里提前判断左值和右值是否需要转换,如需转换,则插入CAST,例如将右值转换为double:
1 | Cast cast = new Cast(node.getRight(), DoubleType.DOUBLE.getDisplayName()); |