Druid SQL解析原理分析(一)


概覽

由於最近的開發功能涉及SQL解析模塊,在網上查詢了一些有關SQL解析器的解析工具,如:ANTLR、Druid,綜合性能、語法支持度、學習成本等因素,選擇Druid作為SQL解析的工具。

訪問者【VISITOR】模式

Druid采用訪問者模式解析SQL,訪問者模式,是行為型設計模式之一。訪問者模式是一種將數據操作與數據結構分離的設計模式。

訪問者模式基本介紹:
  • 訪問者模式(Visitor Pattern):封裝一些作用於某種數據結構的各元素的操作,它可以在不改變數據結構的前提下定義作用於這些元素的新的操作
  • 訪問者模式主要將數據結構與數據操作分離,解決數據結構和操作耦合性問題
  • 訪問者模式的基本工作原理是:在被訪問的類里面加一個對外提供接待訪問者的接口
  • 訪問者模式主要應用場景是: 需要對一個對象結構中的對象進行很多不同操作(這些操作彼此沒有關聯),同時需要避免讓這些操作污染這些對象的類,可以選用訪問者模式解決
訪問者模式的原理類圖:
  • Visitor 是抽象訪問者,定義訪問者的行為規范
  • ConcreteVisitor :是一個具體的訪問者,繼承(或實現) Visitor,實現 Visitor 中定義的每個方法,實現具體的行為邏輯
  • Element 定義一個accept 方法,用於接收一個訪問者對象(Visitor 的具體實現類)
  • ConcreteElement 為具體元素, 實現了 Element 接口中 accept 方法
  • ObjectStructure 能枚舉它里面所包含的元素(Element), 可以提供一個高層的接口,目的是允許訪問者訪問指定的元素
    image
解析器組成
  • Parser
    • 詞法分析
    • 語法分析
  • AST(Abstract Syntax Tree,抽象語法樹)
  • Visitor

Parser 由兩部分組成,詞法分析和語法分析。
當拿到一條形如 select id, name from user 的 SQL 語句后,首先需要解析出每個獨立的單詞,select,id,name,from,user。這一部分,稱為詞法分析,也叫作 Lexer。
通過詞法分析后,便要進行語法分析了。
經常能聽到很多人在調侃自己英文水平很一般時會說:26個字母我都知道,但是一組合在一起我就不知道是什么意思了。這說明他掌握了詞法分析的技能,卻沒有掌握語法分析的技能。
那么對於 SQL 解析器來說呢,它不僅需要知道每個單詞,而且要知道這些單詞組合在一起后,表達了什么含義。語法分析的職責就是明確一個語句的語義,表達的是什意思。
自然語言和形式語言的一個重要區別是,自然語言的一個語句,可能有多重含義,而形式語言的一個語句,只能有一個語義;形式語言的語法是人為規定的,有了一定的語法規則,語法解析器就能根據語法規則,解析出一個語句的一個唯一含義。
AST 是 Parser 的產物,語句經過詞法分析,語法分析后,它的結構需要以一種計算機能讀懂的方式表達出來,最常用的就是抽象語法樹。
樹的概念很接近於一個語句結構的表示,一個語句,我們經常會對它這樣看待:它由哪些部分組成?其中一個組成部分又有哪些部分組成?例如一條 select 語句,它由 select 列表、where 子句、排序字段、分組字段等組成,而 select 列表則由一個或多個 select 項組成,where 子句又由一個或者多個 where條件組成。
在我們人類的思維中,這種組成結構就是一個總分的邏輯結構,用樹來表達,最合適不過。並且對於計算機來說,它顯然比人類更擅長處理“樹”。
AST 僅僅是語義的表示,但如何對這個語義進行表達,便需要去訪問這棵 AST,看它到底表達什么含義。通常遍歷語法樹,使用 VISITOR 模式去遍歷,從根節點開始遍歷,一直到最后一個葉子節點,在遍歷的過程中,便不斷地收集信息到一個上下文中,整個遍歷過程完成后,對這棵樹所表達的語法含義,已經被保存到上下文了。有時候一次遍歷還不夠,需要二次遍歷。遍歷的方式,廣度優先的遍歷方式是最常見的。

快速上手
  1. 構建 Parser
  2. 使用 Parser 解析 SQL,生成 AST
  3. 構建 Visitor
  4. 使用 Visitor 訪問 AST
  5. 獲取解析信息
    @Test
    @DisplayName("測試Sql解析")
    public void doParserSqlDemo() {

        // 待解析 SQL
        String selectSql = "select id from user_pay group by name";

        // 新建 Parser
        SQLStatementParser parser = new SQLStatementParser(selectSql, "mysql");

        // 使用 Parser 解析 SQL,生成 AST
        SQLStatement sqlStatement = parser.parseStatement();

        // 生成訪問者
        MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();

        // 使用 Visitor 訪問 AST
        sqlStatement.accept(visitor);

        // 獲取解析信息
        System.out.println(visitor.getColumns());
    }

結果
image

自定義VISITOR,獲取SQL解析信息

有時我們需要獲取SQL的一些特殊信息,無法通過已經實現好的VISITOR獲取,通過解析AST語法樹的方式處理起來很麻煩,可以通過visitor的方式去實現,重寫對應的方法,將解析結果存儲在visitor中,對外提供訪問方法即可。比如:需要獲取SQL語句的limit數量,可以通過以下方式重寫visitor的方式實現。

步驟一:定義自定義VISITOR
    // 自定義訪問者
    class SQLCustomedVisitor extends SQLASTVisitorAdapter {

        protected boolean hasLimit = false;

        public SQLCustomedVisitor() {
            super();
        }

        @Override
        public boolean visit(SQLLimit x) {
            System.out.println("Limit限制條數: " + x.getRowCount());
            hasLimit = true;
            return false;
        }

        public boolean isHasLimit() {
            return hasLimit;
        }
    }
步驟二:通過自定義訪問者訪問AST
    public void doParserSql() {
        // SQL
        String limitSql = "select name from user limit 100";

        // 解析器
        SQLStatementParser parser = new SQLStatementParser(limitSql, "mysql");

        // 生成AST
        SQLStatement sqlStatement = parser.parseStatement(true);

        // 構建自定義訪問者
        SQLCustomedVisitor sqlCustomedVisitor = new SQLCustomedVisitor();

        // 訪問抽象數,獲取數據
        sqlStatement.accept(sqlCustomedVisitor);

        Assert.assertEquals(true, sqlCustomedVisitor.hasLimit);

        System.out.println("SQL語句: " + sqlStatement);
    }

結果
image


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM