Druid SQL 解析器的解析過程

2021-08-01 05:34:45 字數 4630 閱讀 7026

這篇文嘗試近距離地** druid sql 解析器如何工作。

以這份**為例

/**

* ** @author beanlam

* @date 2023年1月10日 下午11:06:26

* @version 1.0

* */

public

class

parsermain

}

一開始,需要初始化乙個 parser,在這裡sqlstatementparser是乙個父類,真正解析 sql 語句的 parser 實現是mysqlstatementparser

parser 的解析結果是乙個sqlstatement,這是乙個內部維護了樹狀邏輯結構的類。

druid 的**裡,代表 語法分析 和 詞法分析 的類分別是sqlparserlexer。並且, parser 擁有乙個 lexer。

public

class

sqlparser

public sqlparser(string sql)

public sqlparser(lexer lexer)

public sqlparser(lexer lexer, string dbtype)

}

經過**後的 druid **,其 lexer 只有兩個,分別是lexer,以及它的子類mysqllexerlexer 作為詞法分析器,必然擁有其詞彙表,在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.***) 

if (lexer.token() == token.aaa)

顯然,如果是分析對 select 型別的語句的解析,那麼應該關注以下的**:

if (lexer.token() == token.select)
重點是parseselect()方法,mysqlstatementparser過載了它的父類的這個方法,因此這個方法實際上的實現細節是這樣的

@override

public sqlstatement parseselect()

return

new sqlselectstatement(select, jdbcconstants.mysql);

}

初始化乙個針對 mysql select 語句的 parser,然後呼叫select()方法進行解析,把返回結果sqlselect放到sqlselectstatement裡,而這個sqlselectstatement,便是我最關心的 ast 抽象語法樹,sqlselect 是它的第乙個子節點。

拋開解析的細節不談,實際上我會非常關心這棵 ast 的層次結構。

開啟sqlselectstatement的**,掃瞄它的子成員,便分析出這樣的一棵語法樹:

這意味著,在 druid 眼裡,它是這樣看待一條 select 語句的所有成員部分的。

從 demo **中可以看到,有了 ast 語法樹後,則需要乙個 visitor 來訪問它

// 使用visitor來訪問ast

mysqlschemastatvisitor visitor = new mysqlschemastatvisitor();

statement.accept(visitor);

system.out.println(visitor.getcolumns());

system.out.println(visitor.getorderbycolumns());

statement 呼叫 accept 方法,以 visitor 作為引數,開始了訪問之旅。在這裡 statement 的實際型別是sqlselectstatement

在 druid 中,一條 sql 語句中的元素,無論是高層次還是低層次的元素,都是乙個sqlobject,statement 是一種 sqlobject,表示式 expr 也是一種 sqlobject,函式、字段、條件等等,這些都是一種 sqlobject,sqlobject 是乙個介面,accept方法便是它定義的,目的是為了讓訪問者在訪問 sqlobject 時,告知訪問者一些事情,好讓訪問者在訪問的過程中能夠收集到關於該 sqlobject 的一些資訊。

具體的accept()實現,在sqlobjectimpl這個類中,**如下所示:

public

final

void

accept

(sqlastvisitor visitor)

visitor.previsit(this);

accept0(visitor);

visitor.postvisit(this);

}

這是乙個 final 方法,意味著所有的子類都要遵循這個模板,首先 accept 方法前和後,visitor 都會做一些工作。真正的訪問流程定義在accept0()方法裡,而它是乙個 抽象方法 。

因此要知道 druid 中是如何訪問 ast 的,先拿 sqlselectstatement 的 accept0() 方法來探**竟。

protected void accept0(sqlastvisitor visitor) 

visitor.endvisit(this);

}

首先,使 visitor 訪問自己,訪問自己後,visitor 會決定是否還要訪問自己的子元素。

開啟mysqlschemastatevisitor的 visit 方法,可以看到,visitor 做了一些事,初始化了自己的 aliasmap,然後 return true,這意味著還要訪問 sqlselectstatement 的子節點。

public

boolean

visit

(sqlselectstatement x)

接下來訪問子元素

protected

final

void

acceptchild

(sqlastvisitor visitor, sqlobject child)

child.accept(visitor);

}

由此可以看出,sqlobject 負責通知 visitor 要訪問自己的哪些元素,而 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 ...