本篇文章主要以使用为主,比如通过解析分析一条SQL的组成来完成SQL改写等,现在流行的数据中间件中使用很广泛,Mycat、Dble等。
Druid的SQL解析器主要有三部分组成,如下:
1、Parser
a、词法分析
b、语法分析
2、AST(Abstract Syntax Tree,抽象语法树)
3、Visitor
在计算机科学中,抽象语法树(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抽象语法树。
常用的AST节点主要有三种类型:SQLObject、SQLExpr、SQLStatement,其中最常使用的就莫过于SQLStatement ,其子类常见的有DruidSelectStatement、DruidInsertStatement、DruidUpdateStatement等
public interface SQLStatement extends SQLObject {} public interface SQLObject {} public interface SQLExpr extends SQLObject, Cloneable {}官网中给出的示例
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; }最常用的Statement当然是SELECT/UPDATE/DELETE/INSERT,他们分别是
package com.alibaba.druid.sql.ast.statement; class SQLSelectStatement implements SQLStatement { SQLSelect select; } class SQLUpdateStatement implements SQLStatement { SQLExprTableSource tableSource; List<SQLUpdateSetItem> items; SQLExpr where; } class SQLDeleteStatement implements SQLStatement { SQLTableSource tableSource; SQLExpr where; } class SQLInsertStatement implements SQLStatement { SQLExprTableSource tableSource; List<SQLExpr> columns; SQLSelect query; }常见的SQLTableSource包括SQLExprTableSource、SQLJoinTableSource、SQLSubqueryTableSource、SQLWithSubqueryClause.Entry
class SQLTableSourceImpl extends SQLObjectImpl implements SQLTableSource { String alias; } // 例如 select * from emp where i = 3,这里的from emp是一个SQLExprTableSource // 其中expr是一个name=emp的SQLIdentifierExpr class SQLExprTableSource extends SQLTableSourceImpl { SQLExpr expr; } // 例如 select * from emp e inner join org o on e.org_id = o.id // 其中left 'emp e' 是一个SQLExprTableSource,right 'org o'也是一个SQLExprTableSource // condition 'e.org_id = o.id'是一个SQLBinaryOpExpr class SQLJoinTableSource extends SQLTableSourceImpl { SQLTableSource left; SQLTableSource right; JoinType joinType; // INNER_JOIN/CROSS_JOIN/LEFT_OUTER_JOIN/RIGHT_OUTER_JOIN/... SQLExpr condition; } // 例如 select * from (select * from temp) a,这里第一层from(...)是一个SQLSubqueryTableSource SQLSubqueryTableSource extends SQLTableSourceImpl { SQLSelect select; } /* 例如 WITH RECURSIVE ancestors AS ( SELECT * FROM org UNION SELECT f.* FROM org f, ancestors a WHERE f.id = a.parent_id ) SELECT * FROM ancestors; 这里的ancestors AS (...) 是一个SQLWithSubqueryClause.Entry */ class SQLWithSubqueryClause { static class Entry extends SQLTableSourceImpl { SQLSelect subQuery; } }SQLSelectStatement包含一个SQLSelect,SQLSelect包含一个SQLSelectQuery,都是组成的关系。SQLSelectQuery有主要的两个派生类,分别是SQLSelectQueryBlock和SQLUnionQuery。
class SQLSelect extends SQLObjectImpl { SQLWithSubqueryClause withSubQuery; SQLSelectQuery query; } interface SQLSelectQuery extends SQLObject {} class SQLSelectQueryBlock implements SQLSelectQuery { List<SQLSelectItem> selectList; SQLTableSource from; SQLExprTableSource into; SQLExpr where; SQLSelectGroupByClause groupBy; SQLOrderBy orderBy; SQLLimit limit; } class SQLUnionQuery implements SQLSelectQuery { SQLSelectQuery left; SQLSelectQuery right; SQLUnionOperator operator; // UNION/UNION_ALL/MINUS/INTERSECT }建表语句包含了一系列方法,用于方便各种操作
通过传入的文本即sql生成AST,使用了Mysql的Parser
public SQLStatement parserSQL(String originSql) throws SQLSyntaxErrorException { SQLStatementParser parser = new MySqlStatementParser(originSql); /** * thrown SQL SyntaxError if parser error */ try { List<SQLStatement> list = parser.parseStatementList(); if (list.size() > 1) { throw new SQLSyntaxErrorException("MultiQueries is not supported,use single query instead "); } return list.get(0); } catch (Exception t) { LOGGER.info("routeNormalSqlWithAST", t); if (t.getMessage() != null) { throw new SQLSyntaxErrorException(t.getMessage()); } else { throw new SQLSyntaxErrorException(t); } } }或者根据数据库类型生成,Druid根据不同的数据库类型生成AST
/** * @author Qi.qingshan */ public class SqlParser { public static void main(String[] args) throws SQLSyntaxErrorException { String sql = ""; String dbType = "oracle"; SQLStatement statement = parser(sql, dbType); } public static SQLStatement parser(String sql,String dbType) throws SQLSyntaxErrorException { List<SQLStatement> list = SQLUtils.parseStatements(sql, dbType); if (list.size() > 1) { throw new SQLSyntaxErrorException("MultiQueries is not supported,use single query instead "); } return list.get(0); } }语法树AST生成后,我们可以根据语法树的组成提取我们需要的东西,比如我们要提取 select name ,id from acct where id =10 这条语句的表名,并将语句中的表名替换为acct_1
/** * @author Qi.qingshan */ public class SqlParser { public static void main(String[] args) throws SQLSyntaxErrorException { String sql = "select name ,id from acct where id =10"; String dbType = "mysql"; System.out.println("原始SQL 为 : "+sql); SQLSelectStatement statement = (SQLSelectStatement) parser(sql, dbType); SQLSelect select = statement.getSelect(); SQLSelectQueryBlock query = (SQLSelectQueryBlock) select.getQuery(); SQLExprTableSource tableSource = (SQLExprTableSource) query.getFrom(); String tableName = tableSource.getExpr().toString(); System.out.println("获取的表名为 tableName :" + tableName); //修改表名为acct_1 tableSource.setExpr("acct_1"); System.out.println("修改表名后的SQL 为 : [" + statement.toString() +"]"); } public static SQLStatement parser(String sql,String dbType) throws SQLSyntaxErrorException { List<SQLStatement> list = SQLUtils.parseStatements(sql, dbType); if (list.size() > 1) { throw new SQLSyntaxErrorException("MultiQueries is not supported,use single query instead "); } return list.get(0); } }在数据库中间件中实现分表功能基本都是基于此原理实现,改写Statement最好克隆一个新对象进行修改,内部已经提供了clone方法
Druid提供了多种默认实现的Visitor,可以满足基本需求,如果默认提供的不满足需求,可自行实现自定义Visitor。
比如我们要统计下一条SQL中涉及了哪些表 select name ,id ,select money from user from acct where id =10,如果我们不用visitor,自行遍历AST,能实现,但是很繁琐,下边内部结构图
使用默认的Visitor实现
/** * @author Qi.qingshan * @date 20190526 */ public class SqlParser { public static void main(String[] args) throws SQLSyntaxErrorException { String sql = "select name ,id ,select money from user from acct where id =10"; String dbType = "mysql"; System.out.println("原始SQL 为 : "+sql); SQLSelectStatement statement = (SQLSelectStatement) parser(sql, dbType); MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor(); statement.accept(visitor); System.out.println(visitor.getTables().toString()); } public static SQLStatement parser(String sql,String dbType) throws SQLSyntaxErrorException { List<SQLStatement> list = SQLUtils.parseStatements(sql, dbType); if (list.size() > 1) { throw new SQLSyntaxErrorException("MultiQueries is not supported,use single query instead "); } return list.get(0); } }看起来是不是很简单,后面继续分享SQL组成以及自定义实现Visitor等内容。如果有不理解的地方和有错误地方,欢迎评论下方,互相讨论。对你有用,点个赞再走,^_^