【Presto/Trino】解析模块parser源码梳理
Trino 是 PrestoSQL在2020年改名后的项目,二者都是都一个创世团队开发的
概览
Presto地址:https://github.com/prestodb/presto
Presto Parser模块:
g4
Presto也是使用Antlr解析的,词法规则和语法规则在一个文件里:SqlBase.g4。TypeCalculation.g4 处理的内容比较简单,就是 SQL列的四则运算,但是这个g4应该是已经废弃了(2015年),这块的定义已经在SqlBase里了:(包括基于g4生成的实现类也只有SqlBase的)
(注:presto使用antlr的版本是4.7.1,Trino为4.11.1)
SQL解析器和AST构建器
parser包下放解析器入口(SqlParser)和AST构建器(AstBuilder),包括SQL进行词法解析语法解析会使用到的辅助类。其中核心类是SqlParser和AstBuilder
SqlParser
其中SqlParser作为解析入口,主要工作是对SQL进行词法分析和语法分析生成解析树 ParseTree。
核心方法是 invokeParser()
private Node invokeParser(String name, String sql, Function<SqlBaseParser, ParserRuleContext> parseFunction, ParsingOptions parsingOptions)
{
try {
// 词法解析(这里CaseInsensitive进行了大写处理)
SqlBaseLexer lexer = new SqlBaseLexer(new CaseInsensitiveStream(CharStreams.fromString(sql)));
CommonTokenStream tokenStream = new CommonTokenStream(lexer);
// 语法解析
SqlBaseParser parser = new SqlBaseParser(tokenStream);
initializer.accept(lexer, parser);
// Override the default error strategy to not attempt inserting or deleting a token.
// Otherwise, it messes up error reporting
parser.setErrorHandler(new DefaultErrorStrategy()
{
@Override
public Token recoverInline(Parser recognizer)
throws RecognitionException
{
if (nextTokensContext == null) {
throw new InputMismatchException(recognizer);
}
else {
throw new InputMismatchException(recognizer, nextTokensState, nextTokensContext);
}
}
});
parser.addParseListener(new PostProcessor(Arrays.asList(parser.getRuleNames()), parsingOptions.getWarningConsumer()));
lexer.removeErrorListeners();
lexer.addErrorListener(LEXER_ERROR_LISTENER);
parser.removeErrorListeners();
if (enhancedErrorHandlerEnabled) {
parser.addErrorListener(PARSER_ERROR_HANDLER);
}
else {
parser.addErrorListener(LEXER_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);
}
// 通过AstBuilder生成自定义AST
return new AstBuilder(parsingOptions).visit(tree);
}
catch (StackOverflowError e) {
throw new ParsingException(name + " is too large (stack overflow while parsing)");
}
}
这里的返回值Node是Presto的自定义AST
这里需要说明的是 Presto 定义的 SqlBase.g4 里只接受大写的关键字Keywords和Identifier,CaseInsensitiveStream通过实现CharStream重写了LA方法:
@Override
public int LA(int i)
{
int result = stream.LA(i);
switch (result) {
case 0:
case IntStream.EOF:
return result;
default:
return Character.toUpperCase(result);
}
}
例如:
select a as c1, 'a' as c2 from t1
这个直接交给 SqlBase.g4 识别是会报错的:
全部大写后:
SELECT A AS C1, 'a' AS C2 FROM T2
AstBuilder
SqlParser和AstBuilder是结合用的,我们可以看到 invokeParser() 最下面为
return new AstBuilder(parsingOptions).visit(tree);
class AstBuilder
extends SqlBaseBaseVisitor<Node>
{
private int parameterPosition;
private final ParsingOptions parsingOptions;
private final Consumer<ParsingWarning> warningConsumer;
AstBuilder(ParsingOptions parsingOptions)
{
this.parsingOptions = requireNonNull(parsingOptions, "parsingOptions is null");
this.warningConsumer = requireNonNull(parsingOptions.getWarningConsumer(), "warningConsumer is null");
}
@Override
public Node visitSingleStatement(SqlBaseParser.SingleStatementContext context)
{
return visit(context.statement());
}
// ...
}
AST
自定义的 AST 类都在 tree包下。(共177个类)
基类是 Node:
public abstract class Node
{
private final Optional<NodeLocation> location;
protected Node(Optional<NodeLocation> location)
{
this.location = requireNonNull(location, "location is null");
}
/**
* Accessible for {@link AstVisitor}, use {@link AstVisitor#process(Node, Object)} instead.
*/
protected <R, C> R accept(AstVisitor<R, C> visitor, C context)
{
return visitor.visitNode(this, context);
}
public Optional<NodeLocation> getLocation()
{
return location;
}
public abstract List<? extends Node> getChildren();
// Force subclasses to have a proper equals and hashcode implementation
@Override
public abstract int hashCode();
@Override
public abstract boolean equals(Object obj);
@Override
public abstract String toString();
}
Node 下面有相应的实现类:Statement、Expression、Relation等。Presto建的类比较多。
AstBuilder构建的过长就是构建这个Node的过程。
工具类与单测
SqlFormatter 和 ExpressionFormatter 可以将构建好的 Node / Expression 树结构还原为原始 SQL 或 表达式。其相应的单测可以参考 TestStatementBuilder 。
Sql转化为Node的单测可以参考:TestSqlParser。