手写MySQL补充章(十二)SQL语法解析之语法树

目录

模块分析

AST节点类型

SQL词法解析

举个例子


之前写的在第九章写的sql解析太简单了,SQL规范还有复杂的开闭括号以及嵌套查询,复杂SQL几乎不可能通过字符串匹配来实现。

本章以Druid SQL Parser解析SQL为例,进行分析。

模块分析

Druid SQL Parser分三个模块:Parser,AST,Visitor。

parser有包括两个部分,Parser和Lexer,其中Lexer实现词法分析,Parser实现语法分析。

Druid Parser会生成一个AST抽象语法树

Visitor是遍历AST的手段,最后返回json形式结果

在使用之前不要忘记了加入依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.6</version>
    <scope>test</scope>
</dependency>

AST节点类型

在Druid中,AST节点类型主要包括SQLObject、SQLExpr、SQLStatement三种抽象类型

  • interface SQLObject {} 
  • interface SQLExpr extends SQLObject {} // 条件表达式相关的抽象,例如select * from  table where ID = 3 这里的ID是一个SQLIdentifierExpr
  • interface SQLStatement extends SQLObject {} //最常用的Statement当然是SELECT/UPDATE/DELETE/INSERT

SQL词法解析

我这里主要关注在SQLExpr, 因为这个跟条件表达式相关的解析。

常用的SQLExpr有哪些?

package com.alibaba.druid.sql.ast.expr;

// SQLName是一种的SQLExpr的Expr,包括SQLIdentifierExpr、SQLPropertyExpr等
public interface SQLName extends SQLExpr {}

// 例如 ID = 3 这里的ID是一个SQLIdentifierExpr
class SQLIdentifierExpr implements SQLExpr, SQLName {
    String name;
} 

// 例如 A.ID = 3 这里的A.ID是一个SQLPropertyExpr
class SQLPropertyExpr implements SQLExpr, SQLName {
    SQLExpr owner;
    String name;
} 

// 例如 ID = 3 这是一个SQLBinaryOpExpr
// left是ID (SQLIdentifierExpr)
// right是3 (SQLIntegerExpr)
class SQLBinaryOpExpr implements SQLExpr {
    SQLExpr left;
    SQLExpr right;
    SQLBinaryOperator operator;
}

// 例如 select * from where id = ?,这里的?是一个SQLVariantRefExpr,name是'?'
class SQLVariantRefExpr extends SQLExprImpl { 
    String name;
}

// 例如 ID = 3 这里的3是一个SQLIntegerExpr
public class SQLIntegerExpr extends SQLNumericLiteralExpr implements SQLValuableExpr { 
    Number number;

    // 所有实现了SQLValuableExpr接口的SQLExpr都可以直接调用这个方法求值
    @Override
    public Object getValue() {
        return this.number;
    }
}

// 例如 NAME = 'jobs' 这里的'jobs'是一个SQLCharExpr
public class SQLCharExpr extends SQLTextLiteralExpr implements SQLValuableExpr{
    String text;
}

举个例子

对以下代码进行调试

package src;

import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.util.JdbcConstants;

import java.util.List;

public class main {
    public static void main(String[] args) {
        String sql = "select * from t where id=1 or name='test' and age=14";
        List<SQLStatement> sqlStatements = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL);
        System.out.println(sqlStatements);
    }
}

可以得到 

 从最终的结果可以看出来,其实就是一个二叉树,父结点就是一个操作符,然后左右孩子结点就是表达式的左右两边的字段名和对应的值。

而且还可以通过SQLUtils.toSQLString打印节点

SQLExpr sqlExpr = SQLUtils.toSQLExpr("(id=1 or name='test' and age=14)", JdbcConstants.MYSQL);
System.out.println(SQLUtils.toSQLString(sqlExpr, JdbcConstants.MYSQL));
//id = 1
//OR name = 'test'
//AND age = 14

看到这里我们是不是有一点点思路了,前面我们说SQLUtils产生SQLExpr本质上就是一个二叉树,所以我们可以通过遍历二叉树的方式去获取每个结点,判断结点的类型,然后在把它转成一个我们JSON的一个对象。

那要遍历二叉树,很显然我们这里需要用后序遍历的方式,因为我想从最下往上去遍历,最后遍历根结点,才能把左右两棵树通过操作符合并起来。

可以简单的把树画出来

从图上看出来我们遍历左子树,在遍历condition 1这部分的子树的时候,先遍历ID和1,然后再遍历到父节点的=,叶子节点我们可以不看,我们只要判断到节点是SQLBinaryOperator,我们就可以把他们的左右节点拿出来构成出一个condition 1对象,一样的我们会遍历右子树,遍历出condition 2和condition 3两个对象,然后我们在遍历他们的父节点OR,这个时候我们只需要把它左右子树的两个condition 2 和condition 3放到list中,然后在给他加上一个operator 为OR即可变成一个新的condition 4。然后最后遍历到根结点,就把condition 1 和 condition 4通过AND连接变成一个condition 5,而这个condition 5就是我们最终的JSON结构了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
ES6(ECMAScript 2015)是JavaScript的一个重要版本,引入了许多新的语法和功能。下面是ES6中常用的一些语法。 1. 块级作用域变量声明(let和const):ES6引入了块级作用域的变量声明方式,使用let和const关键字来声明变量。let声明的变量具有块级作用域,而const声明的变量是常量,不能被重新赋值。 ```javascript let x = 10; const y = 20; ``` 2. 箭头函数:箭头函数是一种简化函数定义的方式,可以使用`=>`符号来定义函数。箭头函数没有自己的this值,它会继承上下文中的this值。 ```javascript const add = (a, b) => a + b; ``` 3. 默认参数:ES6允许函数参数设置默认值,当调用函数时没有传递对应参数时,会使用默认值。 ```javascript function greet(name = 'World') { console.log(`Hello, ${name}!`); } ``` 4. 解构赋值:解构赋值是一种从数组或对象中提取值并赋给变量的语法。 ```javascript const [x, y, z] = [1, 2, 3]; const { name, age } = { name: 'Alice', age: 20 }; ``` 5. 模板字符串:模板字符串是一种更灵活的字符串拼接方式,使用反引号(`)来包裹字符串,并使用${}来插入变量或表达式。 ```javascript const name = 'Alice'; console.log(`Hello, ${name}!`); ``` 6. 类和继承:ES6引入了class关键字,可以使用class来定义类和面向对象的继承关系。 ```javascript class Animal { constructor(name) { this.name = name; } speak() { console.log(`${this.name} makes a noise.`); } } class Dog extends Animal { speak() { console.log(`${this.name} barks.`); } } const dog = new Dog('Bobby'); dog.speak(); ``` 这些只是ES6中的一些常用语法,还有很多其他的语法和功能。ES6的出现主要是为了提升JavaScript的开发效率和可读性,以及引入一些更现代化的编程概念。ES6 的语法在现代前端开发中被广泛使用。 相关问题: 1. 什么是块级作用域?let和const关键字有什么特点? 2. 箭头函数和普通函数有什么区别? 3. 如何给函数参数设置默认值? 4. 解构赋值有哪些常见的用法? 5. 模板字符串的优势在哪里?它与普通字符串拼接有何不同? 6. class关键字在面向对象编程中有什么作用?如何实现继承? 7. 除了上述提到的特性,ES6还有哪些其他新增的语法和功能?
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值