Druid SQL解析原理分析(一)
概览
由于最近的开发功能涉及SQL解析模块,在网上查询了一些有关SQL解析器的解析工具,如:ANTLR、Druid,综合性能、语法支持度、学习成本等因素,选择Druid作为SQL解析的工具。
访问者【VISITOR】模式
Druid采用访问者模式解析SQL,访问者模式,是行为型设计模式之一。访问者模式是一种将数据操作与数据结构分离的设计模式。
访问者模式基本介绍:
- 访问者模式(Visitor Pattern):封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作
- 访问者模式主要将数据结构与数据操作分离,解决数据结构和操作耦合性问题
- 访问者模式的基本工作原理是:在被访问的类里面加一个对外提供接待访问者的接口
- 访问者模式主要应用场景是: 需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有关联),同时需要避免让这些操作污染这些对象的类,可以选用访问者模式解决
访问者模式的原理类图:
- Visitor 是抽象访问者,定义访问者的行为规范
- ConcreteVisitor :是一个具体的访问者,继承(或实现) Visitor,实现 Visitor 中定义的每个方法,实现具体的行为逻辑
- Element 定义一个accept 方法,用于接收一个访问者对象(Visitor 的具体实现类)
- ConcreteElement 为具体元素, 实现了 Element 接口中 accept 方法
- ObjectStructure 能枚举它里面所包含的元素(Element), 可以提供一个高层的接口,目的是允许访问者访问指定的元素
解析器组成
- Parser
- 词法分析
- 语法分析
- AST(Abstract Syntax Tree,抽象语法树)
- Visitor
Parser 由两部分组成,词法分析和语法分析。
当拿到一条形如 select id, name from user 的 SQL 语句后,首先需要解析出每个独立的单词,select,id,name,from,user。这一部分,称为词法分析,也叫作 Lexer。
通过词法分析后,便要进行语法分析了。
经常能听到很多人在调侃自己英文水平很一般时会说:26个字母我都知道,但是一组合在一起我就不知道是什么意思了。这说明他掌握了词法分析的技能,却没有掌握语法分析的技能。
那么对于 SQL 解析器来说呢,它不仅需要知道每个单词,而且要知道这些单词组合在一起后,表达了什么含义。语法分析的职责就是明确一个语句的语义,表达的是什意思。
自然语言和形式语言的一个重要区别是,自然语言的一个语句,可能有多重含义,而形式语言的一个语句,只能有一个语义;形式语言的语法是人为规定的,有了一定的语法规则,语法解析器就能根据语法规则,解析出一个语句的一个唯一含义。
AST 是 Parser 的产物,语句经过词法分析,语法分析后,它的结构需要以一种计算机能读懂的方式表达出来,最常用的就是抽象语法树。
树的概念很接近于一个语句结构的表示,一个语句,我们经常会对它这样看待:它由哪些部分组成?其中一个组成部分又有哪些部分组成?例如一条 select 语句,它由 select 列表、where 子句、排序字段、分组字段等组成,而 select 列表则由一个或多个 select 项组成,where 子句又由一个或者多个 where条件组成。
在我们人类的思维中,这种组成结构就是一个总分的逻辑结构,用树来表达,最合适不过。并且对于计算机来说,它显然比人类更擅长处理“树”。
AST 仅仅是语义的表示,但如何对这个语义进行表达,便需要去访问这棵 AST,看它到底表达什么含义。通常遍历语法树,使用 VISITOR 模式去遍历,从根节点开始遍历,一直到最后一个叶子节点,在遍历的过程中,便不断地收集信息到一个上下文中,整个遍历过程完成后,对这棵树所表达的语法含义,已经被保存到上下文了。有时候一次遍历还不够,需要二次遍历。遍历的方式,广度优先的遍历方式是最常见的。
快速上手
- 构建 Parser
- 使用 Parser 解析 SQL,生成 AST
- 构建 Visitor
- 使用 Visitor 访问 AST
- 获取解析信息
@Test
@DisplayName("测试Sql解析")
public void doParserSqlDemo() {
// 待解析 SQL
String selectSql = "select id from user_pay group by name";
// 新建 Parser
SQLStatementParser parser = new SQLStatementParser(selectSql, "mysql");
// 使用 Parser 解析 SQL,生成 AST
SQLStatement sqlStatement = parser.parseStatement();
// 生成访问者
MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();
// 使用 Visitor 访问 AST
sqlStatement.accept(visitor);
// 获取解析信息
System.out.println(visitor.getColumns());
}
结果
自定义VISITOR,获取SQL解析信息
有时我们需要获取SQL的一些特殊信息,无法通过已经实现好的VISITOR获取,通过解析AST语法树的方式处理起来很麻烦,可以通过visitor的方式去实现,重写对应的方法,将解析结果存储在visitor中,对外提供访问方法即可。比如:需要获取SQL语句的limit数量,可以通过以下方式重写visitor的方式实现。
步骤一:定义自定义VISITOR
// 自定义访问者
class SQLCustomedVisitor extends SQLASTVisitorAdapter {
protected boolean hasLimit = false;
public SQLCustomedVisitor() {
super();
}
@Override
public boolean visit(SQLLimit x) {
System.out.println("Limit限制条数: " + x.getRowCount());
hasLimit = true;
return false;
}
public boolean isHasLimit() {
return hasLimit;
}
}
步骤二:通过自定义访问者访问AST
public void doParserSql() {
// SQL
String limitSql = "select name from user limit 100";
// 解析器
SQLStatementParser parser = new SQLStatementParser(limitSql, "mysql");
// 生成AST
SQLStatement sqlStatement = parser.parseStatement(true);
// 构建自定义访问者
SQLCustomedVisitor sqlCustomedVisitor = new SQLCustomedVisitor();
// 访问抽象数,获取数据
sqlStatement.accept(sqlCustomedVisitor);
Assert.assertEquals(true, sqlCustomedVisitor.hasLimit);
System.out.println("SQL语句: " + sqlStatement);
}
结果
posted on 2021-08-26 16:51 bigstrong_code 阅读(2370) 评论(0) 编辑 收藏 举报