SparkSQL解析SQL

SparkSQL采用Antlr来解析SQL,我们可以直接使用该工具,对SQL进行一个自定义的解析。Antlr全称为ANother Tool for Language Recognition

Antlr解析SQL的阶段

Antlr解析SQL一般可分为如下几个阶段:

  • 词法分析阶段(lexer)

词法分析,Antlr主要按照定义的词法规则来识别SQL中的词法单元(token),比如关键字,标识符,常量等。

  • 语法分析阶段

在该阶段,Antlr使用定义的语法规则来描述SQL的语法结构,并使用词法分析阶段中的token流来构建一个抽象语法树(AST)

  • 代码生成阶段

通过语法分析阶段得到的AST,来生成目标语言的代码。

遍历模式

  • Listener(观察者模式)

它将语法树的遍历过程和对语法树的处理分离开来。在该模式下,ANTLR 会自动遍历语法树,并在遍历过程中调用相应的观察者方法。

  • Visitor(访问者模式)

这种模式将遍历语法树和处理语法树的过程结合在一起。在该模式下,开发者编写一个自定义访问者类,该类实现了遍历语法树的过程和处理语法树的过程。

解析SQL,识别复杂SQL

这里的思路是,解析SQL,根据解析出的语法树,识别出SQL中对应的操作,如果某些复杂操作超过指定次数,那么就认为这个SQL是复杂的。下面使用Visitor来实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import org.apache.spark.sql.catalyst.parser.SqlBaseBaseVisitor;
import org.apache.spark.sql.catalyst.parser.SqlBaseParser;


public class QueryComplexityAnalyzer extends SqlBaseBaseVisitor<Void> {
    /**
     * 定义复杂度值初始为0
     */
    private int complexity = 0;

    public boolean isComplex() {
        return complexity > 5;
    }

    @Override
    public Void visitJoinRelation(SqlBaseParser.JoinRelationContext ctx) {
        complexity++;
        return super.visitJoinRelation(ctx);
    }

    @Override
    public Void visitSubqueryExpression(SqlBaseParser.SubqueryExpressionContext ctx) {
        complexity++;
        return super.visitSubqueryExpression(ctx);
    }

    @Override
    public Void visitWindowClause(SqlBaseParser.WindowClauseContext ctx) {
        complexity++;
        return super.visitWindowClause(ctx);
    }

    @Override
    public Void visitHavingClause(SqlBaseParser.HavingClauseContext ctx) {
        complexity++;
        return super.visitHavingClause(ctx);
    }

    @Override
    public Void visitFunctionCall(SqlBaseParser.FunctionCallContext ctx) {
        complexity++;
        return super.visitFunctionCall(ctx);
    }
}

这里,我们自定义了visitor,重写了部分方法,我们把涉及到join操作,子查询操作,窗口函数操作,having操作,函数操作的语句认定为复杂,如果解析出的SQL中,这些操作共有5个以上时,认定为复杂SQL。

接下来是主函数部分

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.apache.spark.sql.catalyst.parser.SqlBaseLexer;
import org.apache.spark.sql.catalyst.parser.SqlBaseParser;
import org.example.util.QueryComplexityAnalyzer;

public class ParseSQLComplex {
    public static void main(String[] args) {

        // SQL语句
        String query = "SELECT * \n" +
                "FROM TABLEA A\n" +
                "LEFT JOIN (\n" +
                "  SELECT * FROM TABLEB WHERE 1=0\n" +
                "  ) B\n" +
                "ON A.XX = B.XX\n" +
                "LEFT JOIN (\n" +
                "  SELECT * FROM TABLEB WHERE 1=0\n" +
                "  ) B\n" +
                "ON A.XX = B.XX\n" +
                "LEFT JOIN (\n" +
                "  SELECT * FROM TABLEB WHERE 1=0\n" +
                "  ) B\n" +
                "ON A.XX = B.XX\n" +
                "LEFT JOIN (\n" +
                "  SELECT * FROM TABLEB WHERE 1=0\n" +
                "  ) B\n" +
                "ON A.XX = B.XX\n" +
                "LEFT JOIN (\n" +
                "  SELECT * FROM TABLEB WHERE 1=0\n" +
                "  ) B\n" +
                "ON A.XX = B.XX";

        // 将SQL语句转为CharStream
        CharStream charStream = CharStreams.fromString(query);

        // 定义解析器
        SqlBaseLexer lexer = new SqlBaseLexer(charStream);

        // 定义tokenStream
        CommonTokenStream tokenStream = new CommonTokenStream(lexer);
        // 解析SQL
        SqlBaseParser parser = new SqlBaseParser(tokenStream);

        // 通过解析,得到语法树
        ParseTree tree = parser.singleStatement();

        // 自定义的visitor
        QueryComplexityAnalyzer queryComplexityAnalyzer = new QueryComplexityAnalyzer();


        // 传入tree
        queryComplexityAnalyzer.visit(tree);

        if (queryComplexityAnalyzer.isComplex()) {
            System.out.println("该查询是复杂的");

        } else {
            System.out.println("该查询是简单的");
        }
    }
}

上面代码中,CharStreams 用于读取字符序列作为词法分析的来源,传给Lexer进行词法解析。CommonTokenStream 则表示词法分析器生成的token序列,用以生成抽象语法树。