calcite问题之calcite生成sql字符串时中文字符乱码问题

举报
breakDawn 发表于 2021/04/23 11:47:30 2021/04/23
【摘要】 问题场景:某个需求里,需要使用calcite, 将优化后的calcite逻辑计划树转成sql语句。 static String toSql(RelNode root, DataSourceType dataSourceType) { SqlDialect dialect; // 根据对应数据库类型决定dialect dialect = dat...

问题场景:

某个需求里,需要使用calcite, 将优化后的calcite逻辑计划树转成sql语句。

    static String toSql(RelNode root, DataSourceType dataSourceType) {
        SqlDialect dialect;
        // 根据对应数据库类型决定dialect
        dialect = datasourceToSqlDialect(dataSourceType);

        // 逻辑计划转sql语法树的转换器
        final RelToSqlConverter converter = new RelToSqlConverter(dialect);
        // 把relNode转成sqlNode(sql语法树)
        final SqlNode sqlNode = converter.visitChild(0, root).asStatement();
        // 把sqlNode转成sql语句
        return sqlNode.toSqlString(dialect).getSql();
    }

结果发现作为where条件的字符串“研究生” ,变成了很奇怪的东西:

SELECT * FROM XUELI WHERE DEGREE = u&'\7814\7a76\751f'

表面上看起来是把 研究生三个字,转成了utf-16编码,并且加了各种转义符。

可能对于一些数据库来说, 支持这种识别方式。 但我项目的需求中暂时默认各库之间统一编码。 并且这个sql语句需要作为前台展示的, 如果搞出个这种编码串肯定不行

问题定位

先通过设置断点, 查看relNode中,  “研究生”这个字符串是什么个数据结构。
image.png

发现是一个RexLiteral, 里面有个value值,存放了NlsString。

看下这个NlsString是什么:
image.png

stringValue里存储的就是原文字符串,是正常的中文串,未经过修改的。
而charsetName是编码格式,这里被标记为UTF-8。

那么可以看出,此时原串未被处理, 那就可能是relNode转sqlNode期间发生变化。
于是跟读converter.visitChild源码, 看下这个NlsString是否在转换过程中被修改。
image.png

发现这里使用到了这个nlsString,并且发现该过程直接使用原value生成SqlLiteral, 以至于丢失了编码信息

image.png

上面可以看到charSet是个null。

但是原字符串仍然还在,只是编码信息没了。那什么时候真正把这个原字符串弄没了呢?
于是查看sqlNode转sql语句的过程,定位到位置在SqlDialect的quoteStringLiteral:
image.png

因为charsetName为null,为了安全起见,他把字符都转成unicode形式编码,并补充前缀。
至此,中文字符串的变换过程基本明了。

解决方式

如果对编码要求不严格,统一为UTF-8, 且需要展示到前台。
即默认生成的sql中,可以直接用原sql编码,不用特地在编码间切换。
那么只要保证在sqlNode转sql的时候, 使用原字符串即可,不要加一堆编码前缀。

只要修改SqlDialect这个转换器接口, 继承实现他的queteStringLiteral

public class TestMysqlSqlDialect extends MysqlSqlDialect {

    // 下发sql时,去除编码前缀,直接拼接原字符。 见用例testUtf8Sql
    @Override
    public void quoteStringLiteral(StringBuilder buf, String charsetName, String val) {
        buf.append(this.literalQuoteString);
        buf.append(val.replace(this.literalEndQuoteString, this.literalEscapedQuote));
        buf.append(this.literalEndQuoteString);
    }
}

然后构造RelToSqlConverter时,使用自己定义的这个新的SqlDialect即可。

final RelToSqlConverter converter = new RelToSqlConverter(dialect);

结果测试正确:
image.png

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。