添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
精彩文章免费看

使用antlr4解析clickhouse sql获取表名

ANTLR是用java实现的一个语言识别器,它通过定义一个要识别目标语言的结构文件(DSL),然后用它的工具将该文件生成为可解析目标语言的java代码,利用生成的代码就可对目标语言进行解析。这种方式避免了自己编写java代码去解析文本,可利用ANTLR解析JSON,HTML,XML,EDIFACT,或自定义的报文格式。最出名的Spark计算引擎2.x就是用它来解析SQL的。

本文尝试使用antlr解析clickhouse,从而提取出查询sql中的表名,供数据安全项目鉴权。

参考文章 https://www.jianshu.com/p/df256de737b4 ,此文章是使用antlr解析mysql语句,基本步骤如下:

1)准备相应的DSL文件,对应sql即为语法、词法定义文件;clickhouse的文件从以下链接获取:

https://fossies.org/linux/ClickHouse/utils/antlr/ClickHouseLexer.g4

https://fossies.org/linux/ClickHouse/utils/antlr/ClickHouseParser.g4

  • 准备antlr的jar包:antlr-4.5.3-complete.jar, 改jar包需要和程序中引入的antlr-runtime包的版本一致。
  • 3)将antlr-4.5.3-complete.jar、语法词法文件放在一个目录中,执行如下命令即可生成java文件

    java -jar ./antlr-4.5.3-complete.jar -Dlanguage=Java -listener -visitor -o ./ck-parser -package x.x.x.x.x ClickHouseParser.g4 ClickHouseLexer.g4

    4) 拷贝生成的java包到自己的工程中,即可使用,注意生成的java文件里会有一些编译不过去的地方,如下图所示,生成的是c++语法的set变量及对应的操作方法,手工将其改为java的语法即可

    5) 客户端使用

    a、参考前同事的文章 https://www.jianshu.com/p/edd2d3eac1f0 ,自定义Listener,监听并记录下所有的表名,代码如下,分别重写enterTableIdentifier和enterAlias事件的原因是因为在enterTableIdentifier事件中监听到的表包含了表别名,客户端调用该类的getRealDataBaseTablenameAndOper方法即可获取所有真实表名。

    public class CustomClickHouseParserBaseListener extends ClickHouseParserBaseListener {
        private Map<String, Set<String>> dataBaseTablenameAndOper = new HashMap<>();//用来保存表与操作的对应关系
        private Map<String, Set<String>> dataBaseTablenameAliasAndOper = new HashMap<>();//用来保存表别名与操作的对应关系
        public Map<String, Set<String>> getDataBaseTablenameAndOper() {
            return dataBaseTablenameAndOper;
        public Map<String, Set<String>> getDataBaseTablenameAliasAndOper() {
            return dataBaseTablenameAliasAndOper;
        public Map<String, Set<String>> getRealDataBaseTablenameAndOper() {
            for (String key : dataBaseTablenameAliasAndOper.keySet()) {
                if (dataBaseTablenameAndOper.containsKey(key)) {
                    dataBaseTablenameAndOper.remove(key);
            return dataBaseTablenameAndOper;
        @Override
        public void enterQuery(ClickHouseParser.QueryContext ctx) {
            ParseTreeWalker queryWalker = new ParseTreeWalker();
            queryWalker.walk(new ClickHouseParserBaseListener() {
                public void enterTableIdentifier(ClickHouseParser.TableIdentifierContext tctx) {
                    if(tctx!=null) {
                        String table = tctx.getText()/*.toLowerCase()*/;
                        Set<String> oper;
                        if (dataBaseTablenameAndOper.containsKey(table)) {
                            oper = dataBaseTablenameAndOper.get(table);
                        } else {
                            oper = new HashSet<>();
                        oper.add("SELECT");
                        dataBaseTablenameAndOper.put(table, oper);
            }, ctx);
            queryWalker.walk(new ClickHouseParserBaseListener() {
                public void enterAlias(ClickHouseParser.AliasContext tctx) {
                    if(tctx!=null) {
                        String table = tctx.getText()/*.toLowerCase()*/;
                        Set<String> oper;
                        if (dataBaseTablenameAliasAndOper.containsKey(table)) {
                            oper = dataBaseTablenameAliasAndOper.get(table);
                        } else {
                            oper = new HashSet<>();
                        oper.add("SELECT");
                        dataBaseTablenameAliasAndOper.put(table, oper);
            }, ctx);
    

    b、客户端代码使用上面的监听类,代码如下:

    public class ClickhouseSqlUtil {
        public static Map<String, Set<String>> getDataBaseTablenameAndOper(String sql){
            ClickHouseLexer lexer = new ClickHouseLexer(new ANTLRInputStream(sql));
            CommonTokenStream tokenStream = new CommonTokenStream(lexer);
            ClickHouseParser parser = new ClickHouseParser(tokenStream);
            ParseTreeWalker walker = new ParseTreeWalker();
            CustomClickHouseParserBaseListener clickhouseBaseBaseListener = new CustomClickHouseParserBaseListener();
            walker.walk(clickhouseBaseBaseListener, parser.queryStmt());
            return clickhouseBaseBaseListener.getRealDataBaseTablenameAndOper();
    
    最后编辑于:2021-11-16 15:43