上次说到时间戳和字符串比较时会把时间戳cast成string 再做比较关于spark -sql 时间戳类型比较的一个小坑,这个过程中对精度做了处理。项目组的大哥最近频繁使用毫秒值为0的数据进行查询比较。他觉得这是一个bug,让我去修改一下。
首先我分析了一下生成的物理计划,发现>=,<= 时会发生cast as string的强转。
<=>和的情况并没有显示调用cast进行强转,但是依旧无法进行比较。因此在面对毫秒值为0的情况,最直接的方法就是在比较时对值进行处理。
我们找到比较函数所在的scala文件,它名为predicates.scala。以<=为例打上断点调试一下:
它包含左右两个表达式:
我们在这种情况下对左右表达式进行一下判断:
为保证在 =,>= ,<=>代码可以复用,我在里面添加了一个object,写了一个方法,这里对毫秒是0的字符串进行了截断,用于匹配cast后的时间戳字符串:
object TransString {
/**
* trans timestamp string to a right form so that it can be compare with timestamp while its milliseconds are 0
*
* @param left
* @param right
* @return
*/
def trans(left: Expression, right: Expression): Expression = {
var ret = right
if (left.isInstanceOf[AttributeReference]) {
if (left.asInstanceOf[AttributeReference].dataType == TimestampType && right.dataType == StringType) {
if (right.isInstanceOf[Literal]) {
var value = (right.asInstanceOf[Literal]).value.toString
if (value.length > 19 && value.substring(19) == ".0") {
value = value.substring(0, 19)
}
ret = Literal(UTF8String.fromString(value), StringType)
}
}
}
ret
}
}
然后我们将构造的入参改成var,在执行比较每个的地方构造方法中调用该方法判断一下:
right = TransString.trans(left ,right),覆盖掉原来的right表达式。
本来一开始想直接把cast类中时间戳转字符串截断的代码注释掉,考虑到原生代码这么做应该有其原因,所以最后放弃了。此外,尝试在生成物理计划时让cast不要调用,调试了大半天,发现这个方式不是很靠谱,也可能是学艺不精的原因,没有能够实现。我觉得这么改代码不是很优雅,后续会尝试更优雅的解决办法。目前暂时提供这个解决方案。已提交代码给测试小姐姐,看下会不会对其他的功能产生影响。