java词法分析_Presto SQL Parser源码分析

本文介绍Presto SQL解析器的设计与实现,包括ANTLR工具的使用、词法及语法分析流程,并通过具体案例加深理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

e750c953c1bf16c27c19f23d4ac2191f.png

Presto是一个高性能的分布式SQL查询引擎,应用于PB级数据的实时计算分析场景。Presto支持ANSI SQL, 用户可以直接使用SQL进行数据查询和计算。生成查询执行计算的主要流程如下图:

febca2d056be1de9db13bd79380034bc.png

从提交查询到执行计划生成包含了词法分析、语法分析、语义分析、逻辑执行计划生成、执行计划优化、分布式执行计划分生成等部分,Sql Parser主要提供了词法分析和语法分析功能,下面我们一起来探究一下Presto的SQL Parser实现。

ANTLR简介

ANTLR(全称:ANother Tool for Language Recognition)是目前非常流行的语言识别工具,使用Java语言编写,基于LL(*)解析方式,使用自上而下的递归下降分析方法。通过输入语法描述文件来自动构造自定义语言的词法分析器、语法分析器和树状分析器等各个模块。ANTLR使用上下无关文法描述语言,文法定义使用类似EBNF的方式。

ANTLR除能够自动构建语法分析树外,还能生成基于Listener(监听者模式,通过节点监听,触发处理方法)和Visitor(访问者模式,主动遍历)的树遍历器。访问者模式遍历语法树是一种更加灵活的方法,可以避免在文法文件中嵌入繁琐的动作,使解析与应用逻辑代码分离,这样不但文法的定义更加简洁清晰,而且可以在不重新编译生成语法分析器的情况下复用相同的语法,甚至能够采用不同的程序语言来实现这些动作。

语言识别工具还有很多种,比如Lex和Yacc, 还有Apache Calcite里面使用的JavaCC等等,在这里就不进行一一比较了。ANTLR的应用非常广泛,比如Hive、Presto和SparkSQL等的SQL Parser模块都是基于ANTLR构建的。

Visitor模式

访问者模式(Visitor Pattern)是一种将操作与对象结构分离的软件设计模式,提供作用于某种对象结构上各元素的操作,可以使我们在不改变元素结构的前提下,定义作用于元素的新操作。

这种模式的工作方法如下:假设有一个由许多元素Node构成的对象结构Tree,这些Node类都拥有一个accept方法用来接受访问者对象Visitor的访问;Visitor类是一个接口,它拥有一个visit方法,这个方法对访问到的Tree中不同类型的Node作出不同的反应;在对Tree的一次访问过程中会遍历整个Tree,对遍历到的每个Node都调用accept方法,在每个元素的accept方法中回调Visitor的visit方法,从而使Vistior得以处理Tree的每个Node;可以针对Tree设计不同的Visitor实现类来完成不同的操作。

在后面的学习中我们将会看到,在Presto引擎中许多地方都会用到Visitor模式。

关于ANTLR的使用入门实例大家可以去官网看一下“基于ANTLR4实现的计算器“案例,这里就不进行详细展开了,下面我们从源码层面来分析Presto的Sql Parser模块是如何实现的。

Presto Sql Parser源码分析

d09af0b0f3a004d843491f438a59eab0.png
Sql Parser内部结构图

Presto使用ANTLR4编写的SQL语法文法的定义在presto-parser模块的SqlBase.g4文件中,将Parser模块编译之后会如下图的一些类,其中比较重要的类有词法分析器(SqlBaseLexer)、语法分析器(SqlBaseParser)、和访问者类(SqlBaseVisitor接口与SqlBaseBaseVisitor类)。

53f8a4cf2ea268a3229d9997cd034a8e.png

词法分析

Presto的Sql词法分析发生在SqlParser类的invokeParser方法中。

 private Node invokeParser(String name, String sql, Function<SqlBaseParser, ParserRuleContext> parseFunction)
    {
        try {
            //使用词法分析器SqlBaseLexer进行词法分析,产生token序列
            SqlBaseLexer lexer = new SqlBaseLexer(new CaseInsensitiveStream(new ANTLRInputStream(sql)));
            CommonTokenStream tokenStream = new CommonTokenStream(lexer);
            
            SqlBaseParser parser = new SqlBaseParser(tokenStream);
            //在语法分析器上,添加一个异常处理的监听器PostProcessor
            parser.addParseListener(new PostProcessor());
            
            //词法分析器和语法分析器都添加出错时,抛运行时ParsingException异常的监听器ERROR_LISTENER
            lexer.removeErrorListeners();
            lexer.addErrorListener(ERROR_LISTENER);

            parser.removeErrorListeners();
            parser.addErrorListener(ERROR_LISTENER);

            //生成抽象语法树
            ParserRuleContext tree;
            try {
                // first, try parsing with potentially faster SLL mode
                parser.getInterpreter().setPredictionMode(PredictionMode.SLL);
                tree = parseFunction.apply(parser);
            }
            catch (ParseCancellationException ex) {
                // if we fail, parse with LL mode
                tokenStream.reset(); // rewind input stream
                parser.reset();

                parser.getInterpreter().setPredictionMode(PredictionMode.LL);
                tree = parseFunction.apply(parser);
            }
            //下面开始进行语法分析
            return new AstBuilder().visit(tree);
        }
        catch (StackOverflowError e) {
            throw new ParsingException(name + " is too large (stack overflow while parsing)");
        }
    }

语法分析

Presto使用Visitor模式对SQL语句进行语法分析,上面代码中AstBuilder类继承自访问者类SqlBaseBaseVisitor,调用visit方法开始对整个tree进行遍历分析,我们以下面这个Select语句进行深入了解。

SELECT custkey FROM orders WHERE totalprice > 100.0

这个SQL语句语法解析之后生成的结构可以通过IDEA的ANTLR Preview插件生成可视化的解析树结构如下图:

9f990d1c7e9bd65ccd60978a11cc3d6a.png

在Presto中,SQL语句经过解析,生成的抽象语法树节点都以Context结尾来命名,这个Sql语句生成的语法解析树如下图:

3e652d2b6e5b621fb3587f0871073691.png

其中SingleStatementContext是根节点,AstBuilder访问到SingleStatementContext时调用visitSingleStatement并没有做什么,只是递归访问子节点。遍历到QuerySpecificationContext节点时开始出现多个子节点,AstBuilder从左到右递归遍历访问子节点。左边这个SelectSingleContext子树对应Select表达式中选择的列custkey,中间的RelationDefaultContext子树对应数据表orders,右边的PredicateContext子树对应where条件中的表达式。

从这个实例中我们可以发现语法分析实际上是一个递归调用的过程,构造一个个的节点,最终形成一个抽象语法树,实例中的这个SQL语句经过语法分析之后会返回一个Statement的子类Query对象。

当我们需要开发新的语法支持时,首先需要在SqlBase.g4 中添加文法规则,重新编译生成词法分析器、语法分析器和访问者类,然后在AstBuilder等类中添加相应的访问逻辑,最后添加执行逻辑。

结语

本文简单介绍了ANTLR这一个语言识别工具,以及分析Presto Sql Parser模块的实现源码,最后,结合具体案例的语法树可视化展示,以加深大家对语法分析过程的理解。在接下来的学习中,还将计划给大家带来Analyzer、Planner、Optimizer等模块设计与实现的探究。

参考资料

ANTLR

Extended Backus-Naur form - Wikipedia

Presto | Distributed SQL Query Engine for Big Data

Visitor pattern - Wikipedia

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值