文章目錄
- 前言
- 介紹Druid
- 代碼目錄介紹
- 模塊一:Parser
- 模塊二:Druid_SQL_AST
- 在Druid SQL Parser中有哪些AST節點類型?
- 熟悉常用的AST節點組成
- 常用的SQLExpr有哪些?
- 常用的SQLStatemment?
- SQLTableSource
- SQLSelect & SQLSelectQuery
- SQLCreateTableStatement
- 怎樣產生AST
- 通過SQLUtils產生List<SQLStatement>
- 通過SQLUtils產生SQLExpr
- 怎樣打印AST節點?
- 通過SQLUtils工具類打印節點
- 如何自定義遍歷AST節點?
- 模塊三:Visitor
- 案例:統計SQL中涉及到的表、字段
- 參考文章
- 資料獲取

前言
博主介紹:?目前全網粉絲4W+,csdn博客專家、Java領域優質創作者,博客之星、阿里云平臺優質作者、專注于Java后端技術領域。
涵蓋技術內容:Java后端、大數據、算法、分布式微服務、中間件、前端、運維等。
博主所有博客文件目錄索引:博客目錄索引(持續更新)
CSDN搜索:長路
視頻平臺:b站-Coder長路
介紹Druid
Druid是數據庫連接池,能夠提供強大的監控和擴展功能。
SQL Parser是Druid的一個重要組成部分,Druid內置使用SQL Parser來實現防御SQL注入(WallFilter)、合并統計沒有參數化的SQL(StatFilter的mergeSql)、SQL格式化、分庫分表。
Druid SQL Parser分三個模塊:Parser,AST,Visitor。
- parser是將輸入文本轉換為ast(抽象語法樹),parser有包括兩個部分,Parser和Lexer,其中Lexer實現詞法分析,Parser實現語法分析。
- AST是Abstract Syntax Tree的縮寫,也就是抽象語法樹。
- Visitor是遍歷AST的手段,是處理AST最方便的模式。
生成語法樹過程:
生成語法樹一般過程:字符流->詞法解析器->token流->語法解析器->語法樹。
對于字符流經過詞法解析器后變成token流,如 SELECT A.ID,B.ID FROM A 將變成如下的token流。
字符 | 類型 |
---|---|
SELECT | 關鍵字 |
A | 變量 |
. | 點 |
ID | 變量 |
, | 逗號 |
B | 變量 |
. | ID |
FROM | 關鍵字 |
A | 變量 |
接下來語法解析器根據上面的token流生成一棵語法樹:
代碼目錄介紹
- com.alibaba.druid.sql.ast.* 為通用 ast
- com.alibaba.druid.sql.dialect.* 為方言 ast,如oracle,mysql
- *.expr為表達式
*.clause為子句
*.stmt為分析后的結果
*.parser為解析類,*StatementParser為主分析類
.visitor 為AST visitor類,注意其中OutputVisitor為輸出,stmt.toString實際調用此類,注意分析后輸出調用toString的輸出不一定是原始sql
Lexer語義
說明:增加新的類型可以尋找類似語法來實現,一般會牽涉到StatementParser及OutputVisitor修改
模塊一:Parser
parser是將輸入文本轉換為ast(抽象語法樹),parser有包括兩個部分,Parser和Lexer,其中Lexer實現詞法分析,Parser實現語法分析。
模塊二:Druid_SQL_AST
wiki學習:https://github.com/alibaba/druid/wiki/Druid_SQL_AST#2-%E5%9C%A8druid-sql-parser%E4%B8%AD%E6%9C%89%E5%93%AA%E4%BA%9Bast%E8%8A%82%E7%82%B9%E7%B1%BB%E5%9E%8B
在Druid SQL Parser中有哪些AST節點類型?
AST節點類型主要包括SQLObject、SQLExpr、SQLStatement三種抽象類型。
package com.alibaba.druid.sql.ast;interface SQLObject {}
interface SQLExpr extends SQLObject {} // 條件表達式相關的抽象,例如 ID = 3 這里的ID是一個SQLIdentifierExpr
interface SQLStatement extends SQLObject {} //最常用的Statement當然是SELECT/UPDATE/DELETE/INSERT,他們分別是SQLSelectStatement ,SQLUpdateStatement ,SQLDeleteStatement ,SQLInsertStatement interface SQLTableSource extends SQLObject {} //常見的SQLTableSource包括SQLExprTableSource、SQLJoinTableSource、SQLSubqueryTableSource、SQLWithSubqueryClause.Entry
class SQLSelect extends SQLObject {}
class SQLSelectQueryBlock extends SQLObject {} //SQLSelectStatement包含一個SQLSelect,SQLSelect包含一個SQLSelectQuery,都是組成的關系。SQLSelectQuery有主要的兩個派生類,分別是SQLSelectQueryBlock和SQLUnionQuery。
熟悉常用的AST節點組成
常用的SQLExpr有哪些?
package com.alibaba.druid.sql.ast.expr;// SQLName是一種的SQLExpr的Expr,包括SQLIdentifierExpr、SQLPropertyExpr等
public interface SQLName extends SQLExpr {}// 例如 ID = 3 這里的ID是一個SQLIdentifierExpr
class SQLIdentifierExpr implements SQLExpr, SQLName {String name;
} // 例如 A.ID = 3 這里的A.ID是一個SQLPropertyExpr
class SQLPropertyExpr implements SQLExpr, SQLName {SQLExpr owner;String name;
} // 例如 ID = 3 這是一個SQLBinaryOpExpr
// left是ID (SQLIdentifierExpr)
// right是3 (SQLIntegerExpr)
class SQLBinaryOpExpr implements SQLExpr {SQLExpr left;SQLExpr right;SQLBinaryOperator operator;
}// 例如 select * from where id = ?,這里的?是一個SQLVariantRefExpr,name是'?'
class SQLVariantRefExpr extends SQLExprImpl { String name;
}// 例如 ID = 3 這里的3是一個SQLIntegerExpr
public class SQLIntegerExpr extends SQLNumericLiteralExpr implements SQLValuableExpr { Number number;// 所有實現了SQLValuableExpr接口的SQLExpr都可以直接調用這個方法求值@Overridepublic Object getValue() {return this.number;}
}// 例如 NAME = 'jobs' 這里的'jobs'是一個SQLCharExpr
public class SQLCharExpr extends SQLTextLiteralExpr implements SQLValuableExpr{String text;
}
常用的SQLStatemment?
最常用的Statement當然是SELECT/UPDATE/DELETE/INSERT,他們分別是
package com.alibaba.druid.sql.ast.statement;class SQLSelectStatement implements SQLStatement {SQLSelect select;
}
class SQLUpdateStatement implements SQLStatement {SQLExprTableSource tableSource;List<SQLUpdateSetItem> items;SQLExpr where;
}
class SQLDeleteStatement implements SQLStatement {SQLTableSource tableSource; SQLExpr where;
}
class SQLInsertStatement implements SQLStatement {SQLExprTableSource tableSource;List<SQLExpr> columns;SQLSelect query;
}
SQLTableSource
常見的SQLTableSource包括SQLExprTableSource、SQLJoinTableSource、SQLSubqueryTableSource、SQLWithSubqueryClause.Entry
class SQLTableSourceImpl extends SQLObjectImpl implements SQLTableSource { String alias;
}// 例如 select * from emp where i = 3,這里的from emp是一個SQLExprTableSource
// 其中expr是一個name=emp的SQLIdentifierExpr
class SQLExprTableSource extends SQLTableSourceImpl {SQLExpr expr;
}// 例如 select * from emp e inner join org o on e.org_id = o.id
// 其中left 'emp e' 是一個SQLExprTableSource,right 'org o'也是一個SQLExprTableSource
// condition 'e.org_id = o.id'是一個SQLBinaryOpExpr
class SQLJoinTableSource extends SQLTableSourceImpl {SQLTableSource left;SQLTableSource right;JoinType joinType; // INNER_JOIN/CROSS_JOIN/LEFT_OUTER_JOIN/RIGHT_OUTER_JOIN/...SQLExpr condition;
}// 例如 select * from (select * from temp) a,這里第一層from(...)是一個SQLSubqueryTableSource
SQLSubqueryTableSource extends SQLTableSourceImpl {SQLSelect select;
}/*
例如
WITH RECURSIVE ancestors AS (SELECT *FROM orgUNIONSELECT f.*FROM org f, ancestors aWHERE f.id = a.parent_id
)
SELECT *
FROM ancestors;這里的ancestors AS (...) 是一個SQLWithSubqueryClause.Entry
*/
class SQLWithSubqueryClause {static class Entry extends SQLTableSourceImpl { SQLSelect subQuery;}
}
SQLSelect & SQLSelectQuery
SQLSelectStatement包含一個SQLSelect,SQLSelect包含一個SQLSelectQuery,都是組成的關系。SQLSelectQuery有主要的兩個派生類,分別是SQLSelectQueryBlock和SQLUnionQuery。
class SQLSelect extends SQLObjectImpl { SQLWithSubqueryClause withSubQuery;SQLSelectQuery query;
}interface SQLSelectQuery extends SQLObject {}class SQLSelectQueryBlock implements SQLSelectQuery {List<SQLSelectItem> selectList;SQLTableSource from;SQLExprTableSource into;SQLExpr where;SQLSelectGroupByClause groupBy;SQLOrderBy orderBy;SQLLimit limit;
}class SQLUnionQuery implements SQLSelectQuery {SQLSelectQuery left;SQLSelectQuery right;SQLUnionOperator operator; // UNION/UNION_ALL/MINUS/INTERSECT
}
SQLCreateTableStatement
建表語句包含了一系列方法,用于方便各種操作
public class SQLCreateTableStatement extends SQLStatementImpl implements SQLDDLStatement, SQLCreateStatement {SQLExprTableSource tableSource;List<SQLTableElement> tableElementList;Select select;// 忽略大小寫的查找SQLCreateTableStatement中的SQLColumnDefinitionpublic SQLColumnDefinition findColumn(String columName) {}// 忽略大小寫的查找SQLCreateTableStatement中的column關聯的索引public SQLTableElement findIndex(String columnName) {}// 是否外鍵依賴另外一個表public boolean isReferenced(String tableName) {}
}
怎樣產生AST
通過SQLUtils產生List
import com.alibaba.druid.util.JdbcConstants;String dbType = JdbcConstants.MYSQL;
List<SQLStatement> statementList = SQLUtils.parseStatements(sql, dbType);
通過SQLUtils產生SQLExpr
String dbType = JdbcConstants.MYSQL;
SQLExpr expr = SQLUtils.toSQLExpr("id=3", dbType);
怎樣打印AST節點?
通過SQLUtils工具類打印節點
package com.alibaba.druid.sql;public class SQLUtils {// 可以將SQLExpr/SQLStatement打印為String類型static String toSQLString(SQLObject sqlObj, String dbType);// 可以將一個<SQLStatement>打印為String類型static String toSQLString(List<SQLStatement> statementList, String dbType);
}
如何自定義遍歷AST節點?
所有的AST節點都支持Visitor模式,需要自定義遍歷邏輯,可以實現相應的ASTVisitorAdapter派生類,比如 https://github.com/alibaba/druid/wiki/SQL_Parser_Demo_visitor
模塊三:Visitor
Visitor是遍歷AST的手段,是處理AST最方便的模式,Visitor是一個接口,有缺省什么都沒做的實現VistorAdapter。
Druid內置提供了如下Visitor:
- OutputVisitor用來把AST輸出為字符串
- WallVisitor 來分析SQL語意來防御SQL注入攻擊
- ParameterizedOutputVisitor用來合并未參數化的SQL進行統計
- EvalVisitor 用來對SQL表達式求值
- ExportParameterVisitor用來提取SQL中的變量參數
- SchemaStatVisitor 用來統計SQL中使用的表、字段、過濾條件、排序表達式、分組表達式
- SQL格式化 Druid內置了基于語義的SQL格式化功能
Druid提供了多種默認實現的Visitor,可以滿足基本需求,如果默認提供的不滿足需求,可自行實現自定義Visitor。
案例:統計SQL中涉及到的表、字段
比如我們要統計下一條SQL中涉及了哪些表 select name ,id ,select money from user from acct where id =10,如果我們不用visitor,自行遍歷AST,能實現,但是很繁瑣。
但是我們用默認自帶的Visitor就可以很輕松的實現:MySqlSchemaStatVisitor
package com.changlu.visitor;import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor;
import com.alibaba.druid.sql.parser.SQLStatementParser;/*** 驗證MySqlSchemaStatVisitor*/
public class TestSchemaVisitor {public static void main(String[] args) {SQLStatementParser parser = new MySqlStatementParser("select name ,id ,select money from user from acct where id =10");SQLStatement sqlStatement = parser.parseStatement();// 定義visitorMySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();// 執行visitor訪問操作sqlStatement.accept(visitor);// 獲取到最終的字段名、tables、條件分支、db類型System.out.println(visitor.getColumns()); //[acct.name, acct.id, user.money]System.out.println(visitor.getTables()); //{acct=Select, user=Select}System.out.println(visitor.getConditions()); //[acct.id = 10]System.out.println(visitor.getDbType());//mysql}}
參考文章
[1]. 分析Druid 連接池中SQL語法樹的基本原理:https://zhuanlan.zhihu.com/p/411029742
[2]. 使用Druid SQL Parser解析SQL:https://blog.csdn.net/cckevincyh/article/details/125317977
[3]. Ali Druid Parser AST擴展及修改記錄:https://blog.csdn.net/weixin_40455124/article/details/91488294
[4]. SQL解析在美團的應用:https://tech.meituan.com/2018/05/20/sql-parser-used-in-mtdp.html
資料獲取
大家點贊、收藏、關注、評論啦~
精彩專欄推薦訂閱:在下方專欄👇🏻
- 長路-文章目錄匯總(算法、后端Java、前端、運維技術導航):博主所有博客導航索引匯總
- 開源項目Studio-Vue—校園工作室管理系統(含前后臺,SpringBoot+Vue):博主個人獨立項目,包含詳細部署上線視頻,已開源
- 學習與生活-專欄:可以了解博主的學習歷程
- 算法專欄:算法收錄
更多博客與資料可查看👇🏻獲取聯系方式👇🏻,🍅文末獲取開發資源及更多資源博客獲取🍅