皆非的万事屋

【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。

当前页面是本站的「Google AMP」版。查看和发表评论请点击:完整版 »