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

posted on 2021-08-26 16:51  bigstrong_code  阅读(2023)  评论(0编辑  收藏  举报

导航