【StarRocks系列】 Trino 方言支持

发布于:2024-05-10 ⋅ 阅读:(31) ⋅ 点赞:(0)

我们在之前的文章中,介绍了 Doris 官方提供的两种方言转换工具,分别是 sql convertor 和方言 plugin。StarRocks 目前同样也提供了类似的方言转换功能。本文我们就一起来看一下这个功能的实现与 Doris 相比有何不同。

一、Trino 方言验证

我们可以通过如下 SQL 来验证 Trino 的方言转换在 SR 中的效果:

set enable_profile = true;
set sql_dialect = starrocks;
select BOROUGH, approx_count_distinct(ZIP_CODE) cnt from crashdata group by BOROUGH order by cnt desc;
set sql_dialect = trino;
select BOROUGH, approx_distinct("ZIP_CODE") cnt from crashdata group by BOROUGH order by cnt desc;

针对上述查询,我们在 SR 集群执行,结果如下所示:
1

可以看到,执行结果完全一致,说明方言转换已经生效,并且符合预期。通过 WebUI 查看提交的两条 SQL:
2

页面上显示的仍然是原始的 SQL 而不是改写之后的。如果在 SR 的方言下,执行 SQL2 的话,那么会直接报错,如下所示:
3

提示函数找不到,这也是符合预期的,因为 approx_distinct 这个函数在 SR 中是不存在的。下面我们就结合代码来看一下 SR 是如何实现这个方言转换功能的。

二、Trino AST 简介

由于 Trino 的方言支持,主要思路是将 Trino 的相关结构转换成 SR 的结构,因此这里先简单了解下 Trino 的 AST 相关结构:
4

后续提到的相关结构,就可以直接参考上述图片。

三、Trino Transformer 介绍

SR 在 FE 中实现了一套 transformer 可以将 Trino 的 function 转换为 SR 的 function,从而实现了方言转换的功能。

3.1 整体流程图

整个 parse 的相关流程如下所示:
5

上述流程可以分为如下几个步骤:

  1. FE 启动的时候,Trino2SRFunctionCallTransformer 会通过静态方法将所有 Trino 到 SR 的函数映射注册到 TRANSFORMER_MAP 这个 map 中;
  2. FE 调用 Trino 的 SqlParser 将 sql string 转换为 Trino 的 Statement 结构,可以参考上述的 AST 结构图;
  3. 在 trino/AstBuilder 中,将 Trino 的 Statement 转换为 SR 的 StatementBase,transformer 用于进行函数转换;
  4. 根据 sql 中的 Trino 函数名,在 Map 中进行匹配,找到对应的 FunctionCallTransformer;
  5. 在 FunctionCallTransformer 中构造 FunctionCallRewriter,最终返回至 trino/AstBuilder,完成函数的转换;
  6. 继续后续的其他操作,最终生成 SR 的 StatementBase结构,完成 parse 操作。

这里我们来一一看下对应的操作。

3.2 函数映射注册

Trino 到 SR 的函数映射注册代码位于 Trino2SRFunctionCallTransformer 中,这个类在加载的时候,会完成对应的函数映射注册,如下所示:

private static void registerAllFunctionTransformer() {
  registerAggregateFunctionTransformer();
  registerArrayFunctionTransformer();
  registerDateFunctionTransformer();
  registerStringFunctionTransformer();
  registerRegexpFunctionTransformer();
  registerJsonFunctionTransformer();
  registerURLFunctionTransformer();
  registerBitwiseFunctionTransformer();
  registerUnicodeFunctionTransformer();
  registerMapFunctionTransformer();
  registerBinaryFunctionTransformer();
  // todo: support more function transform
}

以 registerAggregateFunctionTransformer 为例,这里负责对聚合函数的映射进行注册,相关代码如下所示:

private static void registerAggregateFunctionTransformer() {
  // 1.approx_distinct
  registerFunctionTransformer("approx_distinct", 1,
    "approx_count_distinct", ImmutableList.of(Expr.class));

  // 2. arbitrary
  registerFunctionTransformer("arbitrary", 1,
    "any_value", ImmutableList.of(Expr.class));

  // 3. approx_percentile
  registerFunctionTransformer("approx_percentile", 2,
    "percentile_approx", ImmutableList.of(Expr.class, Expr.class));

通过这个函数的映射,Trino 的 approx_distinct 函数就会被转换成 SR 的 approx_count_distinct,对应的结构体转换如下所示:
6

可以看到,最终在 map 中保存了 approx_distinct 这个 Trino 函数到 SR 的映射。需要注意的是,map 的 value 是一个 list,主要是为了处理参数不同的重载函数。通过 debug 可以直接查看已经注册的函数映射:
7

其中,PlaceholderExpr 就是用来保存 SR 函数的输入参数,主要就是 index 和 参数的类型,后续用于进行匹配,最终会被替换成实际的函数参数,位于 FunctionCallRewriter 的 sourceArguments 中。

3.3 Transformer 匹配

在 FE 启动之后,Trino 的函数映射已经全部注册完成。当我们通过设置方言为 trino 之后,首先需要根据 Trino 的函数名去 map 中进行匹配,由于重载函数的存在,因此还需要比较对应的参数类型。相关的函数调用如下所示:

parse(SqlParser.java):56
--parseWithTrinoDialect(SqlParser.java):68/74
---toStatement(TrinoParserUtils.java):42
----accept(io/trino/sql/tree/Statement.java)
// 省略部分函数调用栈
-visitFunctionCall(trino/AstBuilder.java):713
--convert(Trino2SRFunctionCallTransformer.java):47
---convertRegisterFn(Trino2SRFunctionCallTransformer.java):62
----match(FunctionCallTransformer.java)

其中,match函数的参数为 List<Expr>,对应的就是 2.1 中 List<Expression> 转换后的结果,即将 Trino 的 Expression 转换为 SR 中的 Expr 结构。匹配的过程主要分为两步:

  1. 比较参数个数是否一致;
  2. 比较每一个 PlaceholderExpr 的类型,是否是实际参数类型的超类。

两个条件都满足的话,则证明这个 transformer 是相符的,则继续进行后续的转换。

3.4 函数转换

转换操作主要就是生成一个 FunctionCallRewriter 对象,相关的函数调用如下所示:

visitFunctionCall(trino/AstBuilder.java):713
-convert(Trino2SRFunctionCallTransformer.java):47
--convertRegisterFn(Trino2SRFunctionCallTransformer.java):69
---transform(FunctionCallTransformer.java):113
----ctor(FunctionCallRewriter.java)

我们通过 debug 分别对比下 Trino 和 SR 中的function call 的结构,如下所示:
8
9

可以看到,最终生成的 FunctionCallRewriter 中,已经包含了具体的参数,即 ZIP_CODE 这个列,对应的类型是 SlotRef,而 2.2 中的 PlaceholderExpr 只有类型信息,即 Expr(SlotRef是其一个子类,所以类型可以匹配上)。

四、总结

4.1 与 Doris 方言功能比较

由于 Doris 的 sql convertor 工具是借助 SqlGlot 实现的,因此与 Doris 本身关系不大。这里我们主要比较下 Doris 的方言 plugin 与 SR 的 transformer 优缺点:

方言功能 优点 缺点
SR Transformer 实现比较完善,支持各种函数转换 与SR代码耦合紧,并且仅支持Trino,扩展性一般
Doris Plugin 通过Plugin的方法与Doris代码进行了解耦,扩展性相对较好,目前支持Trino和Spark 实现比较简单,函数转换未提供,并且存在一些问题,可用性较差

4.2 思考小结

由于目前 SR 官方只支持 Trino 的方言转换,并且与源码耦合比较紧,如下所示:

// SqlParser.java
public static List<StatementBase> parse(String sql, SessionVariable sessionVariable) {
  if (sessionVariable.getSqlDialect().equalsIgnoreCase("trino")) {
    return parseWithTrinoDialect(sql, sessionVariable);
  } else {
    return parseWithStarRocksDialect(sql, sessionVariable);
  }
}

如果想要完整的支持一种新的方言转换,需要实现对应的 FunctionCallTransformer 和 AstBuilder,并修改上述的 if-else,总体代码开发量比较大。此外,本文的所有内容是笔者基于 StarRocks-3.3 版本分析、总结而来,如有错误,欢迎指正。

五、参考文档


网站公告

今日签到

点亮在社区的每一天
去签到