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

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


咨詢(xún)
建站咨詢(xún)
