SparkSQL解析SQL
SparkSQL采用Antlr来解析SQL,我们可以直接使用该工具,对SQL进行一个自定义的解析。Antlr全称为ANother Tool for Language Recognition
Antlr解析SQL的阶段
Antlr解析SQL一般可分为如下几个阶段:
词法分析,Antlr主要按照定义的词法规则来识别SQL中的词法单元(token),比如关键字,标识符,常量等。
在该阶段,Antlr使用定义的语法规则来描述SQL的语法结构,并使用词法分析阶段中的token流来构建一个抽象语法树(AST)
通过语法分析阶段得到的AST,来生成目标语言的代码。
遍历模式
它将语法树的遍历过程和对语法树的处理分离开来。在该模式下,ANTLR 会自动遍历语法树,并在遍历过程中调用相应的观察者方法。
这种模式将遍历语法树和处理语法树的过程结合在一起。在该模式下,开发者编写一个自定义访问者类,该类实现了遍历语法树的过程和处理语法树的过程。
解析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序列,用以生成抽象语法树。