Druid学习--SQL parser-01 基本概念

简介

  • SQL Parser是Druid的一个重要组成部分,Druid内置使用SQL Parser来实现防御SQL注入(WallFilter)、合并统计没有参数化的SQL(StatFilter的mergeSql)、SQL格式化、分库分表
  • 使用场景

    • MySql SQL全量统计
    • Hive/ODPS SQL执行安全审计
    • 分库分表SQL解析引擎
    • 数据库引擎的SQL Parser
  • 各种语法的支持

  • 性能

    • Druid的SQL Parser是手工编写,性能非常好,目标就是在生产环境运行时使用的SQL Parser,性能比antlr、javacc之类工具生成的Parser快10倍甚至100倍以上。
  • SQL-92、SQL-99等都是标准SQL,mysql/oracle/pg/sqlserver/odps等都是方言,也就是dialect。parser/ast/visitor都需要针对不同的方言进行特别处理。

代码结构

  • Druid SQL Parser分三个模块:

    • Parser
    • AST
    • Visitor

parser

  • parser是将输入文本转换为ast(抽象语法树)

  • parser有包括两个部分,

    • Parser,Parser实现语法分析。
    • Lexer,其中Lexer实现词法分析,

AST

  • AST是Abstract Syntax Tree的缩写,也就是抽象语法树。AST是parser输出的结果。下面是获得抽象语法树的一个例子

    ini 复制代码
    final String dbType = JdbcConstants.MYSQL; // 可以是ORACLE、POSTGRESQL、SQLSERVER、ODPS等
    String sql = "select * from t";
    List<SQLStatement> stmtList = SQLUtils.parseStatements(sql, dbType);

Visitor

  • Visitor是遍历AST的手段,是处理AST最方便的模式,Visitor是一个接口,有缺省什么都没做的实现VistorAdapter。

  • 我们可以实现不同的Visitor来满足不同的需求,Druid内置提供了如下Visitor:

    • OutputVisitor用来把AST输出为字符串
    • WallVisitor 来分析SQL语意来防御SQL注入攻击
    • ParameterizedOutputVisitor用来合并未参数化的SQL进行统计
    • EvalVisitor 用来对SQL表达式求值
    • ExportParameterVisitor用来提取SQL中的变量参数
    • SchemaStatVisitor 用来统计SQL中使用的表、字段、过滤条件、排序表达式、分组表达式
    • SQL格式化 Druid内置了基于语义的SQL格式化功能
  • 自定义 Vistitor,每种方言的 Visitor 都有一个缺省的 VisitorAdapter,使得编写自定义的 Visitor 更方便。

AST

  • AST是abstract syntax tree的缩写,也就是抽象语法树。和所有的Parser一样,Druid Parser会生成一个抽象语法树

  • 主要包括,三种抽象类型

    • SQLObject

    • SQLExpr

    • SLQStatement

SQLStatment

  • 最常用的Statement当然是SELECT/UPDATE/DELETE/INSERT,他们分别是

    • SQLSelectStatement:查询语句
    • SQLUpdateStatement: 更新语句
    • SQLDeleteStatement:删除语句
    • SQLInsertStatement:新增语句

SQLSelectStatement

  • 内部 结构

    diff 复制代码
    SQLSelectStatement
    --- SQLSelect
    ------- SQLSelectQuery
    -------- SQLSelectQueryBlock(单表Sql查询)
    -------- SQLUnionQuery(union表查询)

SQLExpr

  • 主要的实现类

SQLTableSource

  • 常见的 SQLTableSource 包括

    • SQLExprTableSource

    • SQLJoinTableSource

    • SQLSubqueryTableSource

    • SQLWithSubqueryCluase.Entry

SQL 语句解析实例

查询

解析 Where

  • 存在两个类型

    • SQLPropertyExpr:有别名的时候的类型
    • SQLIdentifierExpr:没有别名的时候的类型
  • 判断 where 要

    1. 注意是 SQLBinaryOpExpr(id= 1) or (u.id =1)需要注意是否使用了别名

    2. 注意如果只有一个产训添加 where 本身就是一个 SQLBinaryOpExpr,如果是多个就要用 where.getChildren()

      1. 如果有别名:SQLPropertyExpr(name=id,owerName=u)
      2. 如果没有别名:SQLIdentifierExpr(name=id)
    3. 值对象:SQLValuableExpr

解析 SQLSelectItem

  • 存在两个类型

    • SQLPropertyExpr:有别名的时候的类型
    • SQLIdentifierExpr:没有别名的时候的类型

源码

java 复制代码
public static void test_SQLBinaryOpExpr(){
​
  String sql = "select a.id as uId,username from users a where a.id > 1 and name = 'tom' ";
​
  List<SQLStatement> sqlStatements = SQLUtils.parseStatements(sql, DbType.mysql);
​
  SQLStatement statement = sqlStatements.get(0);
​
  SQLSelectStatement selectStatement = (SQLSelectStatement)statement;
​
  SQLSelect sqlSelect = selectStatement.getSelect();
​
  SQLSelectQueryBlock query = (SQLSelectQueryBlock)sqlSelect.getQuery();
​
  //解析 where
  parseWhere(query.getWhere());
​
  System.out.println("----------------------");
​
  //解析查询条件
  parseSQLSelectItem(query.getSelectList());
​
​
​
}
​
​
public static void parseWhere(SQLExpr where) {
  if (where instanceof SQLBinaryOpExpr) {
    parseSQLBinaryOpExpr(cast(where, SQLBinaryOpExpr.class));
  } else {
    List<SQLObject> childrenList = where.getChildren();
    for (SQLObject sqlObject : childrenList) {
      // 包含了 left 和 right
      SQLBinaryOpExpr conditionBinary = cast(sqlObject, SQLBinaryOpExpr.class);
      parseSQLBinaryOpExpr(conditionBinary);
    }
  }
}
​
​
​
​
private static void parseSQLSelectItem(List<SQLSelectItem> selectColumnList){
  for (SQLSelectItem sqlSelectItem : selectColumnList) {
    // 情况一:u.id as userId(selectColumnAlias)
    String selectColumnAlias = sqlSelectItem.getAlias();
​
    // 情况二:u.id = SQLPropertyExpr
    SQLExpr expr = sqlSelectItem.getExpr();
​
​
    //有别名的情况
    if (expr instanceof SQLPropertyExpr) {
      SQLPropertyExpr selectColumnExpr = cast(expr, SQLPropertyExpr.class);
      print("列名:%s,别名:%s,表别名:%s", selectColumnExpr.getName(), selectColumnAlias, selectColumnExpr.getOwnerName());
    }
​
    //没有别名的情况
    if (expr instanceof SQLIdentifierExpr) {
      SQLIdentifierExpr selectColumnExpr = cast(expr, SQLIdentifierExpr.class);
      print("列名:%s,别名:%s", selectColumnExpr.getName(), selectColumnAlias);
    }
  }
}
​
​
​
​
public static void parseSQLBinaryOpExpr(SQLBinaryOpExpr conditionBinary) {
  SQLExpr conditionExpr = conditionBinary.getLeft();
  SQLExpr conditionValueExpr = conditionBinary.getRight();
​
​
  /**
  * 如果多个条件就会需要递归去获取数据了
  */
  if(conditionExpr instanceof SQLBinaryOpExpr){
    parseSQLBinaryOpExpr(cast(conditionExpr,SQLBinaryOpExpr.class));
    System.out.println(conditionBinary.getOperator());
  }
​
  if(conditionValueExpr instanceof SQLBinaryOpExpr){
    parseSQLBinaryOpExpr(cast(conditionValueExpr,SQLBinaryOpExpr.class));
    System.out.println(conditionBinary.getOperator());
  }
​
  // 左边有别名所以是SQLPropertyExpr
  if (conditionExpr instanceof SQLPropertyExpr) {
    SQLPropertyExpr conditionColumnExpr = cast(conditionExpr, SQLPropertyExpr.class);
    // 右边根据类型进行转换 id是SQLIntegerExpr name是SQLCharExpr
    SQLValuableExpr conditionColumnValue = cast(conditionValueExpr, SQLValuableExpr.class);
    print("条件列名:%s,条件别名:%s,条件值:%s", conditionColumnExpr.getName(), conditionColumnExpr.getOwnerName(), conditionColumnValue);
  }
  // 如果没有别名
  if (conditionExpr instanceof SQLIdentifierExpr) {
    SQLIdentifierExpr conditionColumnExpr = cast(conditionExpr, SQLIdentifierExpr.class);
    SQLValuableExpr conditionColumnValue = cast(conditionValueExpr, SQLValuableExpr.class);
    print("条件列名:%s,条件值:%s", conditionColumnExpr.getName(), conditionColumnValue);
  }
}
​
​
​
public static <T> T cast(Object obj,Class<T> clazz){
  return clazz.cast(obj);
}
​
public static void print(String msg,Object... params){
  System.out.println(String.format(msg,params));
}

更新

  • 涉及的类型

    • SQLUpdateStatement:更新的statement

    • SQLUpdateSetItem:更新的每个字段,肯定是key=value 或者 别名.key =value,可想而知,还有两种类型

      • SQLPropertyExpr:存在别名
      • SQLIdentifierExpr:不存在别名
    java 复制代码
    public static void main(String[] args) {
      test_Update();
    }
    ​
    public static void test_Update(){
    ​
      SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement("update users u set u.name = 'tom',age = 18 where u.id = 1 ");
    ​
      SQLUpdateStatement updateStatement = cast(sqlStatement, SQLUpdateStatement.class);
    ​
      parseSet(updateStatement.getItems());
    ​
      System.out.println("--------------------------------");
    ​
      parseWhere(updateStatement.getWhere());
    }
    ​
    private static void parseSet(List<SQLUpdateSetItem> setItems) {
    ​
      for (SQLUpdateSetItem setItem : setItems) {
        SQLExpr column = setItem.getColumn();
    ​
        //情况一:有别名
        if (column instanceof SQLPropertyExpr) {
          SQLPropertyExpr sqlPropertyExpr = cast(column, SQLPropertyExpr.class);
          SQLExpr value = setItem.getValue();
          print("column:%s,列owner:%s,value:%s", sqlPropertyExpr.getName(), sqlPropertyExpr.getOwnerName(), value);
        }
    ​
        //情况二:没有别名
        if (column instanceof SQLIdentifierExpr) {
          SQLExpr value = setItem.getValue();
          print("column:%s,value:%s", column, value);
        }
      }
    }
    ​
    ​
    public static void parseWhere(SQLExpr where) {
      if (where instanceof SQLBinaryOpExpr) {
        parseSQLBinaryOpExpr(cast(where, SQLBinaryOpExpr.class));
      } else {
        List<SQLObject> childrenList = where.getChildren();
        for (SQLObject sqlObject : childrenList) {
          // 包含了 left 和 right
          SQLBinaryOpExpr conditionBinary = cast(sqlObject, SQLBinaryOpExpr.class);
          parseSQLBinaryOpExpr(conditionBinary);
        }
      }
    }
    ​
    ​
    ​
    ​
    ​
    public static void parseSQLBinaryOpExpr(SQLBinaryOpExpr conditionBinary) {
      SQLExpr conditionExpr = conditionBinary.getLeft();
      SQLExpr conditionValueExpr = conditionBinary.getRight();
    ​
    ​
      /**
      * 如果多个条件就会需要递归去获取数据了
      */
      if(conditionExpr instanceof SQLBinaryOpExpr){
        parseSQLBinaryOpExpr(cast(conditionExpr,SQLBinaryOpExpr.class));
        System.out.println(conditionBinary.getOperator());
      }
    ​
      if(conditionValueExpr instanceof SQLBinaryOpExpr){
        parseSQLBinaryOpExpr(cast(conditionValueExpr,SQLBinaryOpExpr.class));
        System.out.println(conditionBinary.getOperator());
      }
    ​
      // 左边有别名所以是SQLPropertyExpr
      if (conditionExpr instanceof SQLPropertyExpr) {
        SQLPropertyExpr conditionColumnExpr = cast(conditionExpr, SQLPropertyExpr.class);
        // 右边根据类型进行转换 id是SQLIntegerExpr name是SQLCharExpr
        SQLValuableExpr conditionColumnValue = cast(conditionValueExpr, SQLValuableExpr.class);
        print("条件列名:%s,条件别名:%s,条件值:%s", conditionColumnExpr.getName(), conditionColumnExpr.getOwnerName(), conditionColumnValue);
      }
      // 如果没有别名
      if (conditionExpr instanceof SQLIdentifierExpr) {
        SQLIdentifierExpr conditionColumnExpr = cast(conditionExpr, SQLIdentifierExpr.class);
        SQLValuableExpr conditionColumnValue = cast(conditionValueExpr, SQLValuableExpr.class);
        print("条件列名:%s,条件值:%s", conditionColumnExpr.getName(), conditionColumnValue);
      }
    }
    ​
    ​
    ​
    public static <T> T cast(Object obj,Class<T> clazz){
      return clazz.cast(obj);
    }
    ​
    ​
    public static void print(String msg,Object... params){
      System.out.println(String.format(msg,params));
    }

解析Limit

  • 对于基本数字类型而言,如果没有Key的方式,只是有数字的时候,就是最简单的类型,下面只是列举了一些,还有Double这些的类型
  • 使用到的类型

    • SQLLimit
    • SQLIntegerExpr:对于Limit而言是int类型,那么就是SQLIntegerExpr类型
    java 复制代码
    private static void parseLimit(SQLLimit limit) {
      //偏移量
      SQLExpr offset = limit.getOffset();
    ​
      // 便宜数量
      SQLExpr rowCount = limit.getRowCount();
      print("偏移量:%s,偏移数量:%s", offset, rowCount);
    }

解析Group

  • 涉及的类型

    • SQLSelectGroupByClause,因为Group by可以使用别名,所以,可想而已的两种类型就出来了

      • SQLPropertyExpr:存在别名
      • SQLIdentifierExpr:不存在别名
    java 复制代码
    public static void groupBy() {
      SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement("select s.APPID,XGRQ from dev_cpjcpt.aps_app as s group by s.APPID,XGRQ;");
      SQLSelectStatement selectStatement = cast(sqlStatement, SQLSelectStatement.class);
      SQLSelect select = selectStatement.getSelect();
      SQLSelectQueryBlock query = cast(select.getQuery(), SQLSelectQueryBlock.class);
    ​
      //Clause:从句,子句
      SQLSelectGroupByClause groupBy = query.getGroupBy();
      List<SQLExpr> items = groupBy.getItems();
      for (SQLExpr item : items) {
        // group by name
        // group by age
    ​
        //存在别名
        if (item instanceof SQLPropertyExpr) {
          SQLPropertyExpr groupByColumn = cast(item, SQLPropertyExpr.class);
          print("group by %s", groupByColumn);
        }
    ​
        //没有别名的情况
        if (item instanceof SQLIdentifierExpr) {
          SQLIdentifierExpr groupByColumn = cast(item, SQLIdentifierExpr.class);
          print("group by %s", groupByColumn);
        }
      }
    }

解析Having

  • 基本类型

    • 因为having的条件必然是group by所以,此处必须存在 SQLSelectGroupByClause,才能有having
    • 因为having是个条件,所以,会涉及到 SQLBinaryOpExpr,那么必然存在一个和多个的问题

      • 如果存在一个,那么left和right就是对应的 SQLIdentifierExpr 和 SQLValuableExpr
      • 如果存在多个,那么left和right就是 SQLBinaryOpExpr
    • 因为条件都是Key=value的方式,所以也会存在 SQLIdentifierExpr ,同样的,存在值,所以也就有SQLValuableExpr的类型了
  • 通过上面的查看,可知 SQLBinaryOpExpr 是用于条件的时候,就是此类型,包括 where 和 having

    java 复制代码
    ​
    public static void having() {
      SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement("select name,count(1) as count from users group by name,age having count > 2 and name is not null");
      SQLSelectStatement selectStatement = cast(sqlStatement, SQLSelectStatement.class);
      SQLSelect select = selectStatement.getSelect();
      SQLSelectQueryBlock query = cast(select.getQuery(), SQLSelectQueryBlock.class);
      SQLSelectGroupByClause groupBy = query.getGroupBy();
      SQLExpr having = groupBy.getHaving();
      // 因为只有一个条件,所以having就是SQLBinaryOpExpr
      SQLBinaryOpExpr havingExpr = cast(having, SQLBinaryOpExpr.class);
    ​
      parseHaving(havingExpr);
    }
    ​
    public static void parseHaving(SQLBinaryOpExpr havingExpr){
    ​
      SQLExpr left = havingExpr.getLeft();
      SQLExpr right = havingExpr.getRight();
    ​
      if(left instanceof SQLBinaryOpExpr){
        parseHaving(cast(havingExpr.getLeft(),SQLBinaryOpExpr.class));
      }
    ​
      if(right instanceof SQLBinaryOpExpr){
        parseHaving(cast(havingExpr.getRight(),SQLBinaryOpExpr.class));
      }
    ​
      SQLIdentifierExpr leftExpr = null;
      // 没有使用别名,所以就是SQLIdentifierExpr
      if(left instanceof SQLIdentifierExpr) {
        leftExpr = cast(left, SQLIdentifierExpr.class);
      }
    ​
      // 数字类型就是
      SQLValuableExpr rightValue = null;
      if(right instanceof SQLValuableExpr) {
        rightValue = cast(right, SQLValuableExpr.class);
      }
      SQLBinaryOperator operator = havingExpr.getOperator();
      // left:count, operator:>,right:2
      if(leftExpr !=null && rightValue!=null) {
        print("left:%s, operator:%s,right:%s",  leftExpr.getName()
              , operator.name,  rightValue.getValue());
      }
    }

AST的修改

增加一个条件

java 复制代码
public static void main(String[] args) {
  SQLDeleteStatement();
}
​
public static void SQLDeleteStatement(){
  SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement("delete from users where id = 1");
  SQLDeleteStatement sqlDeleteStatement = cast(sqlStatement, SQLDeleteStatement.class);
  SQLExpr sqlExpr = SQLUtils.toSQLExpr("name = '孙悟空'");
  sqlDeleteStatement.addCondition(sqlExpr);
  //        DELETE FROM users
  //        WHERE id = 1
  //        AND name = '孙悟空'
  System.out.println(SQLUtils.toSQLString(sqlDeleteStatement));
}

修改一个条件值

java 复制代码
public static void SQLDeleteStatement2(){
  SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement("delete from users where id = 1");
  SQLDeleteStatement sqlDeleteStatement = cast(sqlStatement, SQLDeleteStatement.class);
  SQLExpr where = sqlDeleteStatement.getWhere();
  SQLBinaryOpExpr sqlBinaryOpExpr = cast(where, SQLBinaryOpExpr.class);
  //        DELETE FROM users
  //        WHERE id = 2
  sqlBinaryOpExpr.setRight(SQLUtils.toSQLExpr("2"));
  System.out.println(SQLUtils.toSQLString(sqlDeleteStatement));
}

Visitor模式

后续继续

相关推荐
NiNg_1_2341 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
Chrikk2 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*2 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue2 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man2 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
customer084 小时前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
Yaml45 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
小码编匠6 小时前
一款 C# 编写的神经网络计算图框架
后端·神经网络·c#