打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Druid SQL解析原理以及使用(一)

本篇文章主要以使用为主,比如通过解析分析一条SQL的组成来完成SQL改写等,现在流行的数据中间件中使用很广泛,Mycat、Dble等。


Druid的SQL解析器主要有三部分组成,如下:

1、Parser

         a、词法分析

         b、语法分析

2、AST(Abstract Syntax Tree,抽象语法树)

3、Visitor

1、什么是AST


在计算机科学中,抽象语法树(AbstractSyntaxTree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示,Druid 解析SQL也一样,会遵循一定的规则将SQL分析并构建成语法树AST。

Parser主要的作用是生成AST,Parser主要有两部分组成:词法分析、语法分析

Druid收到一条SQL后,比如select a, b , from userTable where user_id =10 需要解析出没一个单词,并记录单词位置等,词法解析阶段,并不需要理解这条SQL的含义,专业术语 Lexer

词法分析完成后就是语法分析,作用就是要明确SQL的含义,比如"name"这个单词,我们可能知道这个单词知道是由字母n、a、m、e组成,如果不知道含义,那就没实际意义,但是要知道是什么意思,就是语法分析要做的事了,语法分析的结果就是要明确这个单词的含义。

AST 仅仅是语义的表示,但如何对这个语义进行表达,便需要去访问这棵 AST,看它到底表达什么含义。通常遍历语法树,使用 VISITOR 模式去遍历,从根节点开始遍历,一直到最后一个叶子节点,在遍历的过程中,便不断地收集信息到一个上下文中,整个遍历过程完成后,对这棵树所表达的语法含义,已经被保存到上下文了。有时候一次遍历还不够,需要二次遍历。遍历的方式,广度优先的遍历方式是最常见的,或者是不用visitor,我们自己知道AST结构后自己手动遍历AST结构也是没有问题的,就是很繁琐,我觉得手动遍历和使用的visitor的区别就类似你用JQuery和JavaScript实现同一个功能的区别一个,有时实现一个功能使用Visitor可能就是十几行代码,自己遍历Statement可能要上百行代码,个人理解。

通过Parser生成完整的AST抽象语法树。

2、Druid SQL中AST节点类型


常用的AST节点主要有三种类型:SQLObject、SQLExpr、SQLStatement,其中最常使用的就莫过于SQLStatement ,其子类常见的有DruidSelectStatement、DruidInsertStatement、DruidUpdateStatement等

  1. public interface SQLStatement extends SQLObject {}
  2. public interface SQLObject {}
  3. public interface SQLExpr extends SQLObject, Cloneable {}

2.1、常见的SQLExpr


官网中给出的示例

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

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

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

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

  13. // 例如 ID = 3 这是一个SQLBinaryOpExpr
  14. // left是ID (SQLIdentifierExpr)
  15. // right是3 (SQLIntegerExpr)
  16. class SQLBinaryOpExpr implements SQLExpr {
  17. SQLExpr left;
  18. SQLExpr right;
  19. SQLBinaryOperator operator;
  20. }

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

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

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

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

2.2、常见的SQLStatement


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

  1. package com.alibaba.druid.sql.ast.statement;

  2. class SQLSelectStatement implements SQLStatement {
  3. SQLSelect select;
  4. }
  5. class SQLUpdateStatement implements SQLStatement {
  6. SQLExprTableSource tableSource;
  7. List<SQLUpdateSetItem> items;
  8. SQLExpr where;
  9. }
  10. class SQLDeleteStatement implements SQLStatement {
  11. SQLTableSource tableSource;
  12. SQLExpr where;
  13. }
  14. class SQLInsertStatement implements SQLStatement {
  15. SQLExprTableSource tableSource;
  16. List<SQLExpr> columns;
  17. SQLSelect query;
  18. }

2.3、常见的SQLTableSource


常见的SQLTableSource包括SQLExprTableSource、SQLJoinTableSource、SQLSubqueryTableSource、SQLWithSubqueryClause.Entry

  1. class SQLTableSourceImpl extends SQLObjectImpl implements SQLTableSource {
  2. String alias;
  3. }

  4. // 例如 select * from emp where i = 3,这里的from emp是一个SQLExprTableSource
  5. // 其中expr是一个name=emp的SQLIdentifierExpr
  6. class SQLExprTableSource extends SQLTableSourceImpl {
  7. SQLExpr expr;
  8. }

  9. // 例如 select * from emp e inner join org o on e.org_id = o.id
  10. // 其中left 'emp e' 是一个SQLExprTableSource,right 'org o'也是一个SQLExprTableSource
  11. // condition 'e.org_id = o.id'是一个SQLBinaryOpExpr
  12. class SQLJoinTableSource extends SQLTableSourceImpl {
  13. SQLTableSource left;
  14. SQLTableSource right;
  15. JoinType joinType; // INNER_JOIN/CROSS_JOIN/LEFT_OUTER_JOIN/RIGHT_OUTER_JOIN/...
  16. SQLExpr condition;
  17. }

  18. // 例如 select * from (select * from temp) a,这里第一层from(...)是一个SQLSubqueryTableSource
  19. SQLSubqueryTableSource extends SQLTableSourceImpl {
  20. SQLSelect select;
  21. }

  22. /*
  23. 例如
  24. WITH RECURSIVE ancestors AS (
  25. SELECT *
  26. FROM org
  27. UNION
  28. SELECT f.*
  29. FROM org f, ancestors a
  30. WHERE f.id = a.parent_id
  31. )
  32. SELECT *
  33. FROM ancestors;

  34. 这里的ancestors AS (...) 是一个SQLWithSubqueryClause.Entry
  35. */
  36. class SQLWithSubqueryClause {
  37. static class Entry extends SQLTableSourceImpl {
  38. SQLSelect subQuery;
  39. }
  40. }

2.4、SQLSelect & SQLSelectQuery


SQLSelectStatement包含一个SQLSelect,SQLSelect包含一个SQLSelectQuery,都是组成的关系。SQLSelectQuery有主要的两个派生类,分别是SQLSelectQueryBlock和SQLUnionQuery。

  1. class SQLSelect extends SQLObjectImpl {
  2. SQLWithSubqueryClause withSubQuery;
  3. SQLSelectQuery query;
  4. }

  5. interface SQLSelectQuery extends SQLObject {}

  6. class SQLSelectQueryBlock implements SQLSelectQuery {
  7. List<SQLSelectItem> selectList;
  8. SQLTableSource from;
  9. SQLExprTableSource into;
  10. SQLExpr where;
  11. SQLSelectGroupByClause groupBy;
  12. SQLOrderBy orderBy;
  13. SQLLimit limit;
  14. }

  15. class SQLUnionQuery implements SQLSelectQuery {
  16. SQLSelectQuery left;
  17. SQLSelectQuery right;
  18. SQLUnionOperator operator; // UNION/UNION_ALL/MINUS/INTERSECT
  19. }

2.5、SQLCreateTableStatement

建表语句包含了一系列方法,用于方便各种操作


  1. public class SQLCreateTableStatement extends SQLStatementImpl implements SQLDDLStatement, SQLCreateStatement {
  2. SQLExprTableSource tableSource;
  3. List<SQLTableElement> tableElementList;
  4. Select select;

  5. // 忽略大小写的查找SQLCreateTableStatement中的SQLColumnDefinition
  6. public SQLColumnDefinition findColumn(String columName) {}

  7. // 忽略大小写的查找SQLCreateTableStatement中的column关联的索引
  8. public SQLTableElement findIndex(String columnName) {}

  9. // 是否外键依赖另外一个表
  10. public boolean isReferenced(String tableName) {}
  11. }

3、生成抽象语法树(AST)


通过传入的文本即sql生成AST,使用了Mysql的Parser

  1. public SQLStatement parserSQL(String originSql) throws SQLSyntaxErrorException {
  2. SQLStatementParser parser = new MySqlStatementParser(originSql);

  3. /**
  4. * thrown SQL SyntaxError if parser error
  5. */
  6. try {
  7. List<SQLStatement> list = parser.parseStatementList();
  8. if (list.size() > 1) {
  9. throw new SQLSyntaxErrorException("MultiQueries is not supported,use single query instead ");
  10. }
  11. return list.get(0);
  12. } catch (Exception t) {
  13. LOGGER.info("routeNormalSqlWithAST", t);
  14. if (t.getMessage() != null) {
  15. throw new SQLSyntaxErrorException(t.getMessage());
  16. } else {
  17. throw new SQLSyntaxErrorException(t);
  18. }
  19. }
  20. }

或者根据数据库类型生成,Druid根据不同的数据库类型生成AST

  1. /**
  2. * @author Qi.qingshan
  3. */
  4. public class SqlParser {
  5. public static void main(String[] args) throws SQLSyntaxErrorException {
  6. String sql = "";
  7. String dbType = "oracle";
  8. SQLStatement statement = parser(sql, dbType);

  9. }
  10. public static SQLStatement parser(String sql,String dbType) throws SQLSyntaxErrorException {
  11. List<SQLStatement> list = SQLUtils.parseStatements(sql, dbType);
  12. if (list.size() > 1) {
  13. throw new SQLSyntaxErrorException("MultiQueries is not supported,use single query instead ");
  14. }
  15. return list.get(0);
  16. }
  17. }

语法树AST生成后,我们可以根据语法树的组成提取我们需要的东西,比如我们要提取 select name ,id from acct where id =10 这条语句的表名,并将语句中的表名替换为acct_1

  1. /**
  2. * @author Qi.qingshan
  3. */
  4. public class SqlParser {
  5. public static void main(String[] args) throws SQLSyntaxErrorException {
  6. String sql = "select name ,id from acct where id =10";
  7. String dbType = "mysql";
  8. System.out.println("原始SQL 为 : "+sql);
  9. SQLSelectStatement statement = (SQLSelectStatement) parser(sql, dbType);
  10. SQLSelect select = statement.getSelect();
  11. SQLSelectQueryBlock query = (SQLSelectQueryBlock) select.getQuery();
  12. SQLExprTableSource tableSource = (SQLExprTableSource) query.getFrom();
  13. String tableName = tableSource.getExpr().toString();
  14. System.out.println("获取的表名为 tableName :" + tableName);
  15. //修改表名为acct_1
  16. tableSource.setExpr("acct_1");
  17. System.out.println("修改表名后的SQL 为 : [" + statement.toString() +"]");
  18. }
  19. public static SQLStatement parser(String sql,String dbType) throws SQLSyntaxErrorException {
  20. List<SQLStatement> list = SQLUtils.parseStatements(sql, dbType);
  21. if (list.size() > 1) {
  22. throw new SQLSyntaxErrorException("MultiQueries is not supported,use single query instead ");
  23. }
  24. return list.get(0);
  25. }
  26. }

在数据库中间件中实现分表功能基本都是基于此原理实现,改写Statement最好克隆一个新对象进行修改,内部已经提供了clone方法

4、Visitor


Druid提供了多种默认实现的Visitor,可以满足基本需求,如果默认提供的不满足需求,可自行实现自定义Visitor。

比如我们要统计下一条SQL中涉及了哪些表 select name ,id ,select money from user from acct where id =10,如果我们不用visitor,自行遍历AST,能实现,但是很繁琐,下边内部结构图

使用默认的Visitor实现

  1. /**
  2. * @author Qi.qingshan
  3. * @date 20190526
  4. */
  5. public class SqlParser {
  6. public static void main(String[] args) throws SQLSyntaxErrorException {
  7. String sql = "select name ,id ,select money from user from acct where id =10";
  8. String dbType = "mysql";
  9. System.out.println("原始SQL 为 : "+sql);
  10. SQLSelectStatement statement = (SQLSelectStatement) parser(sql, dbType);
  11. MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();
  12. statement.accept(visitor);
  13. System.out.println(visitor.getTables().toString());
  14. }
  15. public static SQLStatement parser(String sql,String dbType) throws SQLSyntaxErrorException {
  16. List<SQLStatement> list = SQLUtils.parseStatements(sql, dbType);
  17. if (list.size() > 1) {
  18. throw new SQLSyntaxErrorException("MultiQueries is not supported,use single query instead ");
  19. }
  20. return list.get(0);
  21. }
  22. }

看起来是不是很简单,后面继续分享SQL组成以及自定义实现Visitor等内容。如果有不理解的地方和有错误地方,欢迎评论下方,互相讨论。对你有用,点个赞再走,^_^

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
从零开始写一个 wepy 转 Vue 的工具
在safekodo在线将 js编译为AST语法树
asp.net中优化操作数据库
druidSqlParser
SparkSQL – 从0到1认识Catalyst – 有态度的HBase/Spark/BigData
Tony Bai
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服