這篇文嘗試近距離地** druid sql 解析器如何工作。
以這份**為例
一開始,需要初始化乙個 parser,在這裡/**
* ** @author beanlam
* @date 2023年1月10日 下午11:06:26
* @version 1.0
* */
public
class
parsermain
}
sqlstatementparser
是乙個父類,真正解析 sql 語句的 parser 實現是mysqlstatementparser
。
parser 的解析結果是乙個sqlstatement
,這是乙個內部維護了樹狀邏輯結構的類。
druid 的**裡,代表 語法分析 和 詞法分析 的類分別是sqlparser
和lexer
。並且, parser 擁有乙個 lexer。
public經過**後的 druid **,其 lexer 只有兩個,分別是class
sqlparser
public sqlparser(string sql)
public sqlparser(lexer lexer)
public sqlparser(lexer lexer, string dbtype)
}
lexer
,以及它的子類mysqllexer
lexer 作為詞法分析器,必然擁有其詞彙表,在lexer裡,以keywords
表示。
protected keywords keywods = keywords.default_keywords;keywords 實際上是 key 為單詞,value 為 token 的字典型結構,其中 token 是單詞的型別,比如說,「select」 的 token 型別就是 select token,而 「abc」 的 token 型別,則是識別符號,也表示為 identifier token。
而mysqllexer
類,除了沿用其父類的 keywords 外,自己還有自己的 keywords。可以理解為 lexer 所維護的關鍵字集合,是通用的;而 mysqllexer 除了有通用的關鍵字集合,也有屬於 mysql 資料庫 sql 方言的關鍵字集合。
parser 是 lexer 的使用者,站在 parser 的角度看,它會怎麼去使用 lexer,或者說,lexer 應該具備怎樣的功能,才能滿足 parser 的使用需求。
lexer 應該具備乙個函式,能讓使用者命令它解析乙個單詞,並且 lexer 還必須提供乙個函式,供使用者獲取 lexer 上一次解析到的單詞以及單詞的型別。
在 lexer 中,nexttoken()
這個方法提供了第乙個需求,只要被呼叫,它就按順序從 sql 語句的開頭到結尾,解析出下乙個單詞;token()
方法,則返回了上一次解析的單詞的 token 型別,如果 token 型別是識別符號(identifier),lexer 還提供了乙個stringval()
方法,讓使用者能拿到識別符號的值。
走進 lexer 的nexttoken()
方法,可以發現它的**充斥著if
語句和switch
語句,因為解析單詞的時候,是乙個字元乙個字元地解析,這就意味著,這個方法每次掃瞄乙個字元,都必須判斷單詞是否結束,應該用什麼方式來驗證這個單詞等等。這個過程,就是乙個 狀態機 運作的過程,每解析到乙個字元,都要判斷當前的狀態,以決定應該進入下乙個什麼狀態。
有了 lexer 這樣的犀利工具,接下來就是 parser 發揮的時候了,從 demo **裡可以看到,解析的開始,在於呼叫parser.parsestatement()
方法。進到這個方法看看,發現清一色是形似如下格式的**:
if (lexer.token() == token.***)顯然,如果是分析對 select 型別的語句的解析,那麼應該關注以下的**:if (lexer.token() == token.aaa)
if (lexer.token() == token.select)重點是
parseselect()
方法,mysqlstatementparser
過載了它的父類的這個方法,因此這個方法實際上的實現細節是這樣的
@override初始化乙個針對 mysql select 語句的 parser,然後呼叫public sqlstatement parseselect()
return
new sqlselectstatement(select, jdbcconstants.mysql);
}
select()
方法進行解析,把返回結果sqlselect
放到sqlselectstatement
裡,而這個sqlselectstatement
,便是我最關心的 ast 抽象語法樹,sqlselect 是它的第乙個子節點。
拋開解析的細節不談,實際上我會非常關心這棵 ast 的層次結構。
開啟sqlselectstatement
的**,掃瞄它的子成員,便分析出這樣的一棵語法樹:
這意味著,在 druid 眼裡,它是這樣看待一條 select 語句的所有成員部分的。
從 demo **中可以看到,有了 ast 語法樹後,則需要乙個 visitor 來訪問它
// 使用visitor來訪問aststatement 呼叫 accept 方法,以 visitor 作為引數,開始了訪問之旅。在這裡 statement 的實際型別是mysqlschemastatvisitor visitor = new mysqlschemastatvisitor();
statement.accept(visitor);
system.out.println(visitor.getcolumns());
system.out.println(visitor.getorderbycolumns());
sqlselectstatement
。
在 druid 中,一條 sql 語句中的元素,無論是高層次還是低層次的元素,都是乙個sqlobject
,statement 是一種 sqlobject,表示式 expr 也是一種 sqlobject,函式、字段、條件等等,這些都是一種 sqlobject,sqlobject 是乙個介面,accept
方法便是它定義的,目的是為了讓訪問者在訪問 sqlobject 時,告知訪問者一些事情,好讓訪問者在訪問的過程中能夠收集到關於該 sqlobject 的一些資訊。
具體的accept()
實現,在sqlobjectimpl
這個類中,**如下所示:
這是乙個 final 方法,意味著所有的子類都要遵循這個模板,首先 accept 方法前和後,visitor 都會做一些工作。真正的訪問流程定義在public
final
void
accept
(sqlastvisitor visitor)
visitor.previsit(this);
accept0(visitor);
visitor.postvisit(this);
}
accept0()
方法裡,而它是乙個 抽象方法 。
因此要知道 druid 中是如何訪問 ast 的,先拿 sqlselectstatement 的 accept0() 方法來探**竟。
protected void accept0(sqlastvisitor visitor)首先,使 visitor 訪問自己,訪問自己後,visitor 會決定是否還要訪問自己的子元素。visitor.endvisit(this);
}
開啟mysqlschemastatevisitor
的 visit 方法,可以看到,visitor 做了一些事,初始化了自己的 aliasmap,然後 return true,這意味著還要訪問 sqlselectstatement 的子節點。
接下來訪問子元素public
boolean
visit
(sqlselectstatement x)
由此可以看出,sqlobject 負責通知 visitor 要訪問自己的哪些元素,而 visitor 則負責訪問相應元素前,中,後三個過程的邏輯處理。protected
final
void
acceptchild
(sqlastvisitor visitor, sqlobject child)
child.accept(visitor);
}
pull解析器解析xml
利用pull解析xml檔案需要下面幾個步驟 1 獲取xmlpullparser物件。這裡有兩個方法 通過xmlpullparse ctory獲取xmlpullparser物件,或者直接使用xml.newpullparser 方法獲取。栗子如 一所示。2 通過xmlpullparser物件設定輸入流。...
XML解析 Jsoup解析器
jsoup快捷查詢方式 jsoup概念 跳轉到目錄 jsoup基本使用 提取碼 0uvi 獲取document物件 獲取對應標籤的element物件 獲取資料 public static void getfirstname throws exception jsoup中的物件 跳轉到目錄 獲取對應的...
使用解析器
使用解析器 使用解析器是非常簡單,可以使用它自己的詞法分析器,但是,用fsyacc.exe 產生的解析器總是要求詞法分析器。在這一小節,我們將討論如何使用自己的詞法分析器,以及與解析器聯合。警告記住f 編譯器不能直接使用.fsl 和 fsy 檔案,需要用fslex.exe 和 fsyacc.exe ...