【Doris系列】 SQL 多方言兼容

发布于:2024-05-07 ⋅ 阅读:(18) ⋅ 点赞:(0)

目前 Doris 引擎提供了两种方式实现对 sql 多方言的支持。即,提交指定方言的 sql,Doris 可以成功解析,并返回正确的计算结果。本文就简单来测试验证下这两种方式的效果。

一、Doris Sql Convertor

Doris 官方提供了一个 sql convertor 工具,这是一个单独的服务,与 Doris 服务本身是解耦的。我们可以参考官方文档:SQL 方言兼容,直接下载、启动服务即可。

1.1 功能验证

首先需要通过 set 来显示设置服务的地址,这样 FE 在执行 sql 的时候,就会将 sql 通过 HTTP 发送到转换工具,然后将转换之后的查询返回。设置语句如下:

set global sql_converter_service_url = "http://127.0.0.1:5001/api/v1/convert";

接着就可以指定方言来测试下这个工具的效果。

1.1.1 引用标识符改写测试

这里我们以 Trino 方言为例,来测试下引用标识符的改写效果(Doris 的引用标识符是反引号,Trino 的是双引号):

// 可以在 WebUI 看到查看 query profile
SET is_report_success=true;

// 获取正确的 sql 执行结果
set sql_dialect=doris;
select count(distinct user_id) from example_tbl;
select sex, count(distinct user_id) from example_tbl group by sex;

// 设置引用标识符为双引号,验证 sql convertor 改写效果
set sql_dialect=trino;
select count(distinct "user_id") from example_tbl;
select sex, count(distinct "user_id") from example_tbl group by sex;

执行结果如下所示:
1
2

可以看到,两种方言场景下,执行结果完全一致。这说明 sql convertor 工具成功改写了 sql,并且执行结果也是完全正确的。我们可以通过 WebUI 观察提交的 sql,如下所示:
3

可以看到,实际执行的 SQL 已经被改写成反引号,这符合我们的预期。

1.1.2 函数改写测试

接着我们测试下是否可以实现一些简单的函数改写,测试 case 如下所示:

set sql_dialect=trino;
// 测试函数替换,approx_distinct 是 Trino 中的近似去重函数,Doris 中没有此函数
select sex, approx_distinct("user_id") from example_tbl group by sex;

执行结果如下:
4

可以看到,approx_distinct被改写为APPROX_COUNT_DISTINCT,并且引用标识符也替换成了反引号,这说明改写生效,符合预期。

1.2 Doris 侧源码解析

由于 sql convertor 工具本身没有开源,因此我们主要看下 Doris 的 FE 中相关的代码流程。针对上述的 sql convertor 工具,Doris 在 FE 中加入了一个 HTTP 的 plugin 来实现与该工具的交互。在 FE 启动的时候,就会加载这个 plugin,主要的代码调用如下所示:

main(DorisFE.java):83
-start(DorisFE.java):168
--initialize(Env.java):1002
---init(PluginMgr.java):93
----initBuiltinPlugins(PluginMgr.java):122
-----ctor(HttpDialectConverterPlugin.java)

上述代码调用基于 Doris 2.1.2版本,不同的版本代码行数可能会有所不同。FE 启动之后,我们就可以通过 show plugins 命令来查看已经加载的 plugins:
5

上面的__builtin_SqlDialectConverter就是对应的这个 HTTP plugin。当我们通过 sql_converter_service_url 配置项设置了 sql convertor 工具的地址之后,下次 sql 执行就会通过 HTTP 请求将 sql 发到该服务进行方言转换,对应的代码调用如下所示:

handleQuery(MysqlConnectProcessor.java):176
-handleQuery(ConnectProcessor.java):184
--executeQuery(ConnectProcessor.java):195
---convertOriginStmt(ConnectProcessor.java):320
----convertSql(HttpDialectConverterPlugin.java):106
-----convertSql(HttpDialectUtils.java)

最终在convertSql方法中完成了与 sql convertor 工具的交互。

二、Plugin 方言转换插件

除了上面提到的 sql convertor 工具, Doris 还支持通过 plugin 的方式来实现方言转换。目前,Doris 内置了 Trino 和 Spark 两种方言 plugin,分别位于 /fe_plugins/trino-converter和 /fe_plugins/sparksql-converter 这两个路径下,需要自己编译和安装。

2.1 编译 Plugin

我们可以直接执行build_plugin.sh脚本来编译当前 Doris 自带的所有 plugin。需要注意的是,由于在编译 plugin 的时候,其依赖的 fe-core 和 fe-common jar 包是通过远端 maven 仓库下载的,因此可能与最新的 FE 代码不一致(仓库代码可能比较落后)。因此,建议直接编译 FE,然后将对应的 jar 部署到本地仓库:

mvn install:install-file -DgroupId=org.apache.doris -DartifactId=fe-core -Dversion=1.2-SNAPSHOT -Dpackaging=jar -Dfile=/root/doris/fe/fe-core/target/doris-fe.jar
mvn install:install-file -DgroupId=org.apache.doris -DartifactId=fe-common -Dversion=1.2-SNAPSHOT -Dpackaging=jar -Dfile=/root/doris/fe/fe-common/target/doris-fe-common.jar

接着再编译 FE plugin 模块,如果出现一些类或者方法名不存在的问题,需要根据 FE 的代码来调整 plugin 模块的相关代码。

2.2 加载 Plugin

编译完成之后,就可以通过install命令进行安装:

install plugin from "/xx/doris/fe_plugins/trino-converter/target/trino-converter.zip";

安装完成之后,我们就可以通过 show plugins 命令来查看已经安装的 plugin:
6

此时,在 FE 部署目录的/plugins下面也可以看到安装的这个 plugin。

2.3 Trino Plugin 测试

这里以 Trino 的 plugin 为例来测试下方言转换的效果:

// 为了验证效果,先屏蔽掉 sql convertor 工具
set global sql_converter_service_url = "";
2.3.1 引用标识符改写测试

同样,首先测试下引用标识符的改写效果:

// 获取正确的 sql 执行结果
set sql_dialect=doris;
select count(distinct user_id) from example_tbl;
select sex, count(distinct user_id) from example_tbl group by sex;
// 设置引用标识符为双引号,验证 trino plugin 的改写效果
set sql_dialect=trino;
select count(distinct "user_id") from example_tbl;
select sex, count(distinct "user_id") from example_tbl group by sex;

执行结果如下所示:

7
8

可以看到,设置 Trino 方言之后, 两条 sql 的执行效果与 Doris 方言的执行效果都不同,说明这里的改写是有问题的,sql 执行结果不正确。从 WebUI 看 sql 也没有被改写:
9

2.3.2 函数改写测试

执行如下 SQL 来检测函数改写的效果:

set sql_dialect=trino;
select sex, approx_distinct("user_id") from example_tbl group by sex;

执行结果如下所示:
10

可以看到,执行直接报错,找不到这个函数,说明 Trino 的 plugin 并没有改写函数。

2.3.3 源码解析

方言 Plugin 的代码流程与上面的 sql convertor 有所不同,这里简单看下相关的代码调用:

// FE 模块代码
-executeQuery(ConnectProcessor.java):205
--parseSQL(NereidsParser.java):71
---parseSQLWithDialect(NereidsParser.java):97
// FE Plugin 模块代码
parseSqlWithDialect(TrinoDialectConverterPlugin.java):43
-parse(TrinoParser.java):63
--parseSingle(TrinoParser.java):91
---parse(TrinoParser.java):79
----createStatement(io.trino.sql.parser.SqlParser)
--parseSingle(TrinoParser.java):92
---visit(TrinoLogicalPlanBuilder.java)

可以看到,方言 Plugin 是在 Doris 的新优化器 NereidsParser 中被调用的。首先用 Trino 的 SqlParser 将 sql string 转换成了 Trino 的 Statement,然后利用 TrinoLogicalPlanBuilder 将 Trino 的 AST 转换成了 Doris 自己的 StatementBase。由于这里是直接生成了 AST 结构,并没有像 sql convertor 那样生成一个新的 sql string,因此我们在 WebUI 看到的都是原始的 sql。我们可以通过 debug 调试来查看对应的执行计划是否被改写,如下所示:

11
12

可以看到,当方言是 Trino 的时候,sql 里面的 user_id 被解析成 UnboundSlot,而 Drois 方言下则解析成了 VarcharLiteral(这是一个常量),这说明 Trino 的 plugin 转换是生效了,只是在 WebUI 上没有提现。

三、总结

3.1 代码流程对比

我们在前面章节分别简单介绍了两种方言转换工具的代码调用,这里我们将整个流程串联到一起,如下所示:
13

可以看到,sql在整个 Doris 的 FE 中的相关流程如下:

  1. 首先通过 sql convertor 工具对 sql 进行改写,返回值仍然是一个 sql string 的形式;
  2. 然后这个 sql string 会通过 NereidsParser 进行 sql 解析,在这里面会用到方言的 Plugin 进行转换,此时的返回值就是一个 AST 了;
  3. 最终生成的 sql plan 会由 BE 实际执行。

因此,我们可以得知,这两个方言转换的工具是会同时执行的。也就是说,我们既可以部署 sql convertor,也可以安装方言 plugin。两种转换工具都存在的前提下,sql convertor 会优先执行,因此可能会覆盖方言 plugin 的转换。

3.2 两种工具优缺点对比

这里我们对两种方言转换工具的特点做一个简单的对比:

方言工具 兼容性 功能性 开放性 总结
Sql Convertor工具 兼容 Presto、Trino、ClickHouse、Hive 等近十种主流 SQL 方言 函数改写和引用标识符改写等case 验证通过 不开放源代码,实现可以参考 SqlGlot 功能性比较完备,但是依赖官方更新,完全黑盒|
方言 Plugin工具 只提供了 Trino 和 Spark 不支持函数改写,引用标识符改写也有问题,总体看问题比较多 代码完全开源 问题较多,生产环境没办法直接用,兼容性较差,需要自己开发完善

3.3 写在最后

本文简单测试了下当前 Doris 提供的两种方言转换工具,并介绍了相关的代码调用流程。我们可以发现,sql convertor 工具相对比较成熟,可用性比较好。方言 Plugin 插件问题较多,甚至改写后的查询结果也是有问题的,后续有时间再详细看下这块的问题,这里不再展开。本文的相关内容都是笔者个人基于代码分析和实际测试,如有错误,欢迎指正。