新聞中心
以前的項目中很少去思考SQL解析這個事情,即使在saas系統(tǒng)或者分庫分表的時候有涉及到也會有專門的處理方案,這些方案也對使用者隱藏了實現(xiàn)細(xì)節(jié)。

創(chuàng)新互聯(lián)公司是一家集網(wǎng)站建設(shè),雜多企業(yè)網(wǎng)站建設(shè),雜多品牌網(wǎng)站建設(shè),網(wǎng)站定制,雜多網(wǎng)站建設(shè)報價,網(wǎng)絡(luò)營銷,網(wǎng)絡(luò)優(yōu)化,雜多網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競爭力。可充分滿足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時我們時刻保持專業(yè)、時尚、前沿,時刻以成就客戶成長自我,堅持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實用型網(wǎng)站。
而最近的這個數(shù)據(jù)項目里面卻頻繁涉及到了對SQL的處理,原來只是簡單地了解Druid的SqlParser模塊就可以解決,慢慢地問題變得越來越復(fù)雜,直到某天改動自己寫的SQL處理的代碼很痛苦的時候,意識到似乎有必要更加地了解一下相關(guān)的內(nèi)容才行。
在了解學(xué)習(xí)的過程中,發(fā)現(xiàn)學(xué)習(xí)使用SqlParser還是得先了解ast(抽象語法樹)這個概念,一搜索相關(guān)內(nèi)容要么是編譯原理相關(guān)的知識,要么是JavaScript的示例,光看Druid提供的SqlParser相關(guān)的Wiki文檔又似懂非懂,不知道從哪里下手。
不管怎么樣,看了不少碎片化的相關(guān)內(nèi)容以后也收獲了一些東西,這里記錄下來。
為什么要先了解ast?
ast全稱是abstract syntax tree,中文直譯抽象語法樹。
原先我覺得要使用SqlParser就照著wiki上的代碼步驟拷過來就好了唄,也確實如此,它快速解決了我的問題??墒钦缟厦嫠f,希望你的相關(guān)代碼寫得更好一點,或者更理解它是在干嗎了解了ast會有不少的幫助。
SQL解析,本質(zhì)上就是把SQL字符串給解析成ast,也就是說SqlParser的入?yún)⑹荢QL字符串,結(jié)果就是一個ast。你怎么使用這個ast結(jié)果又是另外一回事,你可以修改ast,也可以添加點東西等等,但整個過程都是圍繞著ast這個東西。
什么是ast?
上面提了好幾次ast,那ast又是個什么東西呢?
參照維基百科的說法,在計算機(jī)科學(xué)領(lǐng)域內(nèi),ast表示的是你寫的編程語言源代碼的抽象語法結(jié)構(gòu)。如圖:
左邊是一個非常簡單的編程語言源代碼:1 + 2,做了一個加法計算,而當(dāng)它被解析成ast以后如右邊的圖所示。我們可以看到ast存在三個節(jié)點,頂部的 + 表示一個加法節(jié)點,這個表達(dá)式組合了1、2兩個數(shù)值節(jié)點,由這三個組合在一起的節(jié)點就組成了1+2這樣的語法結(jié)構(gòu)。
我們看到ast很清晰地用數(shù)據(jù)結(jié)構(gòu)表示出了字符串源代碼,ast的每一個節(jié)點均表示源代碼當(dāng)中的一個語法結(jié)構(gòu)。反過來思考一下,我們可以知道源代碼解析出來的ast是由很多這樣簡單的語法結(jié)構(gòu)組合而成的,也就形成了一個復(fù)雜的語法樹。下面我們看一個稍微復(fù)雜一點的,來自維基百科的示例。
源代碼:
while b ≠ 0
if a > b
a = a ? b
else
b = b ? a
return a
語法樹:
這個語法樹也清晰地表示的源代碼程序,主要由一個while語法和if/else語法以及一些變量之類的組成。
到這里,似乎對源代碼和ast有了一個簡單的概念,但是還是存在困惑,我為什么要把好好的代碼搞成這樣?它有什么用?如果只是修改語法,我用正則表達(dá)式修改字符串不是簡單嗎?
確實,有的時候直接處理字符串會是更快速更好的解決方式,但是當(dāng)源程序語法非常復(fù)雜的時候字符串處理的復(fù)雜度已經(jīng)不是一個簡單的事了。而ast則把這些字符串變成結(jié)構(gòu)化的數(shù)據(jù)了,你可以精確地知道一段代碼里面有哪些變量名,函數(shù)名,參數(shù)等,你可以非常精準(zhǔn)地處理,相對于字符串處理來說,遍歷數(shù)據(jù)大大降低的處理難度。而ast也常常用在如IDE中錯誤提示、自動補(bǔ)全、編譯器、語法翻譯、重構(gòu)、代碼混淆壓縮轉(zhuǎn)換等。
SqlParser
我們知道了ast是一種結(jié)構(gòu)化的源代碼表示,那針對SQL來說ast就是把SQL語句用結(jié)構(gòu)化的數(shù)據(jù)來表示了。而SqlParser也就是把SQL解析成ast,這個解析過程則被SqlParser做了隱藏,我們不需要去實現(xiàn)這樣一個字符串解析過程。
由此可見,我們需要了解兩方面內(nèi)容:
- 怎么用SqlParser把SQL語句解析成ast;
- SqlParser解析出來的ast是什么樣的一個結(jié)構(gòu)。
下面需要一點代碼來說明,所以先引入一下maven依賴。
com.alibaba
druid
1.1.12
解析成ast
解析語句相對簡單,wiki上直接有示例,如:
String dbType = JdbcConstants.MYSQL;
ListstatementList = SQLUtils.parseStatements(sql, dbType);
SQLUtils的parseStatements方法會把你傳入的SQL語句給解析成SQLStatement對象集合,每一個SQLStatement代表一條完整的SQL語句,如:
SELECT id FROM user WHERE status = 1
多個SQLStatement,如:
SELECT id FROM user WHERE status = 1;
SELECT id FROM order WHERE create_time > '2018-01-01'
一般上我們只處理一條語句。
ast的結(jié)構(gòu)
SQLStatement表示一條SQL語句,我們知道常見的SQL語句有CRUD四種操作,所以SQLStatement會有四種主要實現(xiàn)類,如:
class SQLSelectStatement implements SQLStatement {
SQLSelect select;
}
class SQLUpdateStatement implements SQLStatement {
SQLExprTableSource tableSource;
List items;
SQLExpr where;
}
class SQLDeleteStatement implements SQLStatement {
SQLTableSource tableSource;
SQLExpr where;
}
class SQLInsertStatement implements SQLStatement {
SQLExprTableSource tableSource;
List columns;
SQLSelect query;
} 這里我們以SQLSelectStatement來說明,ast既然是SQL的語法結(jié)構(gòu)表示,我們先看一下ast和SQL select語法的主要對應(yīng)結(jié)構(gòu)。
SQLSelectStatement包含一個SQLSelect,SQLSelect包含一個SQLSelectQuery,都是組成的關(guān)系。SQLSelectQuery有主要的兩個派生類,分別是SQLSelectQueryBlock和SQLUnionQuery。
class SQLSelect extends SQLObjectImpl {
SQLWithSubqueryClause withSubQuery;
SQLSelectQuery query;
}
interface SQLSelectQuery extends SQLObject {}
class SQLSelectQueryBlock implements SQLSelectQuery {
List selectList;
SQLTableSource from;
SQLExpr where;
SQLSelectGroupByClause groupBy;
SQLOrderBy orderBy;
SQLLimit limit;
}
class SQLUnionQuery implements SQLSelectQuery {
SQLSelectQuery left;
SQLSelectQuery right;
SQLUnionOperator operator; // UNION/UNION_ALL/MINUS/INTERSECT
} 以下是SQLSelectQueryBlock中包含的主要節(jié)點:
表格中的這些ast節(jié)點都是SQL對應(yīng)語法的一些表示,相信大家都非常熟悉,根據(jù)名字也輕易能了解具體是語法。
這里需要細(xì)化一下SQLTableSource這個節(jié)點,它有著常見的實現(xiàn)SQLExprTableSource(from的表)、SQLJoinTableSource(join的表)、SQLSubqueryTableSource(子查詢的表),如:
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
// 其中l(wèi)eft '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;
}另外SQLExpr出現(xiàn)的地方也比較多,比如where語句,join條件,SQLSelectItem中等,因此,也需要細(xì)化了解一下,如:
// 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;
// 所有實現(xiàn)了SQLValuableExpr接口的SQLExpr都可以直接調(diào)用這個方法求值
@Override
public Object getValue() {
return this.number;
}
}
// 例如 NAME = 'jobs' 這里的'jobs'是一個SQLCharExpr
public class SQLCharExpr extends SQLTextLiteralExpr implements SQLValuableExpr{
String text;
}
SqlParser定義了完整的ast各個節(jié)點對象,一條SQL語句被解析成這些對象的樹形結(jié)構(gòu),而我們要做的就是根據(jù)這樣的一個樹形結(jié)構(gòu)去做相應(yīng)的處理。以上代碼片段摘取了部分wiki上的,并調(diào)整了一下順序,完整的wiki可以到druid的github上查閱。
使用示例:
public void enhanceSql(String sql) {
// 解析
List statements = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL);
// 只考慮一條語句
SQLStatement statement = statements.get(0);
// 只考慮查詢語句
SQLSelectStatement sqlSelectStatement = (SQLSelectStatement) statement;
SQLSelectQuery sqlSelectQuery = sqlSelectStatement.getSelect().getQuery();
// 非union的查詢語句
if (sqlSelectQuery instanceof SQLSelectQueryBlock) {
SQLSelectQueryBlock sqlSelectQueryBlock = (SQLSelectQueryBlock) sqlSelectQuery;
// 獲取字段列表
List selectItems = sqlSelectQueryBlock.getSelectList();
selectItems.forEach(x -> {
// 處理---------------------
});
// 獲取表
SQLTableSource table = sqlSelectQueryBlock.getFrom();
// 普通單表
if (table instanceof SQLExprTableSource) {
// 處理---------------------
// join多表
} else if (table instanceof SQLJoinTableSource) {
// 處理---------------------
// 子查詢作為表
} else if (table instanceof SQLSubqueryTableSource) {
// 處理---------------------
}
// 獲取where條件
SQLExpr where = sqlSelectQueryBlock.getWhere();
// 如果是二元表達(dá)式
if (where instanceof SQLBinaryOpExpr) {
SQLBinaryOpExpr sqlBinaryOpExpr = (SQLBinaryOpExpr) where;
SQLExpr left = sqlBinaryOpExpr.getLeft();
SQLBinaryOperator operator = sqlBinaryOpExpr.getOperator();
SQLExpr right = sqlBinaryOpExpr.getRight();
// 處理---------------------
// 如果是子查詢
} else if (where instanceof SQLInSubQueryExpr) {
SQLInSubQueryExpr sqlInSubQueryExpr = (SQLInSubQueryExpr) where;
// 處理---------------------
}
// 獲取分組
SQLSelectGroupByClause groupBy = sqlSelectQueryBlock.getGroupBy();
// 處理---------------------
// 獲取排序
SQLOrderBy orderBy = sqlSelectQueryBlock.getOrderBy();
// 處理---------------------
// 獲取分頁
SQLLimit limit = sqlSelectQueryBlock.getLimit();
// 處理---------------------
// union的查詢語句
} else if (sqlSelectQuery instanceof SQLUnionQuery) {
// 處理---------------------
}
} 以上示例中只是簡單判斷了一下類型,實際項目中你可能需要對整個ast做遞歸之類的方式來處理節(jié)點。其實當(dāng)SQL語句變成了ast結(jié)構(gòu)以后,我們只要知道這個ast結(jié)構(gòu)存在什么樣的節(jié)點,獲取節(jié)點判斷類型并做相應(yīng)的操作即可,至于你是遞歸還是訪問者模式還是別的什么方式去處理ast都可以。
當(dāng)前名稱:DruidSqlParser理解及使用入門
轉(zhuǎn)載來源:http://www.dlmjj.cn/article/djpgejg.html


咨詢
建站咨詢
