利用 druid 解析器解析SQL

摘要:
最近参与一个开源项目,一个功能的实现,用到了druid解析器来解析SQL,记录下如果使用druid来解析SQL,实现对SQL的拦截改写。onduplicate部分可以使用下面的语句获取:Listdku=insert.getDuplicateKeyUpdate();获得了这些,就而已重组得到原始SQL语句,并且对其进行各种改写。

最近参与一个开源项目,一个功能的实现,用到了 druid 解析器来解析SQL,记录下如果使用 druid 来解析SQL,实现对SQL的拦截改写。

1. 对 insert 语句进行解析:

	private static String convertInsertSQL(String sql){
		try{
			MySqlStatementParser parser = new MySqlStatementParser(sql);	 
			SQLStatement statement = parser.parseStatement();
			MySqlInsertStatement insert = (MySqlInsertStatement)statement; 
	        String tableName = StringUtil.removeBackquote(insert.getTableName().getSimpleName());
	        if(!isGlobalTable(tableName))
				return sql;
	        if(!isInnerColExist(tableName))
	        	return sql;
	        List<SQLExpr> columns = insert.getColumns();
	        if(columns == null || columns.size() <= 0)
	        	return sql;
	        if(insert.getQuery() != null)	// insert into tab select 
	        	return sql;
	    	StringBuilder sb = new StringBuilder(200)	// 指定初始容量可以提高性能
	    				.append("insert into ")
					.append(tableName).append("(");
			int idx = -1;
			for(int i = 0; i < columns.size(); i++) {
				if(i < columns.size() - 1)
					sb.append(columns.get(i).toString()).append(",");
				else
					sb.append(columns.get(i).toString());
				String column = StringUtil.removeBackquote(insert.getColumns().get(i).toString());
				if(column.equalsIgnoreCase(GLOBAL_TABLE_MYCAT_COLUMN))
					idx = i;
			}
			if(idx <= -1)
			sb.append(",").append(GLOBAL_TABLE_MYCAT_COLUMN);
			sb.append(")");
			sb.append(" values");
			List<ValuesClause> vcl = insert.getValuesList();
			if(vcl != null && vcl.size() > 1){	// 批量insert
				for(int j=0; j<vcl.size(); j++){
				   if(j != vcl.size() - 1)
					   appendValues(vcl.get(j).getValues(), sb, idx).append(",");
				   else
					   appendValues(vcl.get(j).getValues(), sb, idx);
				}
			}else{	// 非批量 insert
				List<SQLExpr> valuse = insert.getValues().getValues();
				appendValues(valuse, sb, idx);
			}
			List<SQLExpr> dku = insert.getDuplicateKeyUpdate();
			if(dku != null && dku.size() > 0){
				sb.append(" on duplicate key update ");
				for(int i=0; i<dku.size(); i++){
					SQLExpr exp = dku.get(i);
					if(exp != null){
						if(i < dku.size() - 1)
							sb.append(exp.toString()).append(",");
						else
							sb.append(exp.toString());
					}
				}
			}
			return sb.toString();
		}catch(Exception e){ // 发生异常,则返回原始 sql
			LOGGER.warn(e.getMessage());
			return sql;
		}
	}

三行代码就可以解析一条insert语句:

MySqlStatementParser parser = new MySqlStatementParser(sql);
SQLStatement statement = parser.parseStatement();
MySqlInsertStatement insert = (MySqlInsertStatement)statement;

然后使用解析得到的 insert ,就可以获得原始insert语句的各个部分:

List<SQLExpr> columns = insert.getColumns(); // 获得所有列名

insert.getQuery(); // 如果是 insert into select 语句,则可以获取 select查询

如果是批量插入的insert:insert into tab(id,name) values(1,'a'),(2,'b'),(3,'c');

则可以使用:

List<ValuesClause> vcl = insert.getValuesList();

获得素有的 values 子句部分。

非批量插入,则可以使用:

List<SQLExpr> valuse = insert.getValues().getValues();

获得 values 子句。

on duplicate 部分可以使用下面的语句获取:

List<SQLExpr> dku = insert.getDuplicateKeyUpdate();

获得了这些,就而已重组得到原始SQL语句,并且对其进行各种改写。

mysql 中的insert语法如下:

mysql> ? insert
Name: 'INSERT'
Description:
Syntax:
INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name
    [PARTITION (partition_name,...)]
    [(col_name,...)]
    {VALUES | VALUE} ({expr | DEFAULT},...),(...),...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]
Or:
INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name
    [PARTITION (partition_name,...)]
    SET col_name={expr | DEFAULT}, ...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]
Or:
INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name
    [PARTITION (partition_name,...)]
    [(col_name,...)]
    SELECT ...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]

2. 解析 update 语句:

public static String convertUpdateSQL(String sql){
		try{
			MySqlStatementParser parser = new MySqlStatementParser(sql);	 
			SQLStatement stmt = parser.parseStatement();
			MySqlUpdateStatement update = (MySqlUpdateStatement)stmt;
			SQLTableSource ts = update.getTableSource();
			if(ts != null && ts.toString().contains(",")){
				System.out.println(ts.toString());
				LOGGER.warn("Do not support Multiple-table udpate syntax...");
				return sql;
			}
			String tableName = StringUtil.removeBackquote(update.getTableName().getSimpleName());
	        if(!isGlobalTable(tableName))
				return sql;
	        if(!isInnerColExist(tableName))
	        	return sql;		// 没有内部列
			StringBuilder sb = new StringBuilder(150);
			SQLExpr se = update.getWhere();
			// where中有子查询: update company set name='com' where id in (select id from xxx where ...)
			if(se instanceof SQLInSubQueryExpr){
				// return sql;
				int idx = sql.toUpperCase().indexOf(" SET ") + 5;
				sb.append(sql.substring(0, idx)).append(GLOBAL_TABLE_MYCAT_COLUMN)
				.append("=").append(operationTimestamp)
				.append(",").append(sql.substring(idx));
				return sb.toString();
			}
			String where = null;
			if(update.getWhere() != null)
				where = update.getWhere().toString();
			SQLOrderBy orderBy = update.getOrderBy();
			Limit limit = update.getLimit();
			sb.append("update ").append(tableName).append(" set ");
			List<SQLUpdateSetItem> items = update.getItems();
			boolean flag = false;
			for(int i=0; i<items.size(); i++){
				SQLUpdateSetItem item = items.get(i);
				String col = item.getColumn().toString();
				String val = item.getValue().toString();
				if(StringUtil.removeBackquote(col)
						.equalsIgnoreCase(GLOBAL_TABLE_MYCAT_COLUMN)){
					flag = true;
					sb.append(col).append("=");
					if(i != items.size() - 1)
						sb.append(operationTimestamp).append(",");
					else
						sb.append(operationTimestamp);
				}else{
					sb.append(col).append("=");
					if(i != items.size() -1 )
						sb.append(val).append(",");
					else
						sb.append(val);
				}
			}
			if(!flag){
				sb.append(",").append(GLOBAL_TABLE_MYCAT_COLUMN)
				.append("=").append(operationTimestamp);
			}
			sb.append(" where ").append(where);
			if(orderBy != null && orderBy.getItems()!=null 
								&& orderBy.getItems().size() > 0){
				sb.append(" order by ");
				for(int i=0; i<orderBy.getItems().size(); i++){
					SQLSelectOrderByItem item = orderBy.getItems().get(i);
					SQLOrderingSpecification os = item.getType();
					sb.append(item.getExpr().toString());
					if(i < orderBy.getItems().size() - 1){
						if(os != null)
							sb.append(" ").append(os.toString());
						sb.append(",");
					}else{
						if(os != null)
							sb.append(" ").append(os.toString());
					}
				}
			}
			if(limit != null){		// 分为两种情况: limit 10;   limit 10,10;
				sb.append(" limit ");
				if(limit.getOffset() != null)
					sb.append(limit.getOffset().toString()).append(",");
				sb.append(limit.getRowCount().toString());
			}
			return sb.toString();
		}catch(Exception e){
			LOGGER.warn(e.getMessage());
			return sql;
		}
	}

同样三行,解析update语句:

MySqlStatementParser parser = new MySqlStatementParser(sql);
SQLStatement stmt = parser.parseStatement();
MySqlUpdateStatement update = (MySqlUpdateStatement)stmt;

如果是 多表 udpate 语句,可以使用下面的语句进行判断:

SQLTableSource ts = update.getTableSource();
if(ts != null && ts.toString().contains(",")){
System.out.println(ts.toString());
LOGGER.warn("Do not support Multiple-table udpate syntax...");
return sql;
}

如果是单表update语句:

获得 update 语句的 where 部分:

SQLExpr se = update.getWhere();
// where中有子查询: update company set name='com' where id in (select id from xxx where ...)
if(se instanceof SQLInSubQueryExpr){
// return sql;
int idx = sql.toUpperCase().indexOf(" SET ") + 5;
sb.append(sql.substring(0, idx)).append(GLOBAL_TABLE_MYCAT_COLUMN)
.append("=").append(operationTimestamp)
.append(",").append(sql.substring(idx));
return sb.toString();
}
String where = null;
if(update.getWhere() != null)
where = update.getWhere().toString();

如果where 部分由 select 语句,由:se instanceof SQLInSubQueryExpr 来判断。

order by 和 limit 部分分别由:

SQLOrderBy orderBy = update.getOrderBy();
Limit limit = update.getLimit();

获得。

update 对应的 列和值,有下面的代码获得:

boolean flag = false;
for(int i=0; i<items.size(); i++){
SQLUpdateSetItem item = items.get(i);
String col = item.getColumn().toString();
String val = item.getValue().toString();

解析得到了这些部分,就可以重组出原始的 update 语句,并且按照自己的需求进行SQL改写。

3. 解析 alter 语句:

String sql = "alter table t add colomn name varchar(30)";
MySqlStatementParser parser = new MySqlStatementParser(sql);	 
SQLStatement statement = parser.parseStatement();
MySqlAlterTableStatement alter = (MySqlAlterTableStatement)statement; 
SQLExprTableSource source = alter.getTableSource();
String tableName = source.toString();

解析器:

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId> 
            <version>1.0.14</version> 
        </dependency> 

免责声明:文章转载自《利用 druid 解析器解析SQL》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇git 无提交保存 直接切换分支造成代码丢失!IDEA查看第三方jar包的源代码时出现Decompiled.class file, bytecode version:52.0 (Java 8)的解决方案下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

记druid 在配置中心下的一个大坑: cpu 达到 100%

把我们的dubbo 应用移步到配置中心上去之后,发现我们的应用过一段时间就会出现cpu 100%的情况 (大概是12个小时),一开始cpu占用是2-5% 的样子,什么都没做,后面竟然用尽了cpu。。 把jvm 线程堆栈打印一下,发现线程数竟然达到了上万..... 发现最多是这样的一个线程: "com.alibaba.nacos.client.Worker....

Druid 集群方式部署 —— 启动服务

启动 Master 服务器 拷贝 Druid 的分发包和你修改过的配置到 Master 服务器上。 如果你已经在你的本地计算机上修改了配置,你可以使用 rsync 来进行拷贝。 rsync -az apache-druid-apache-druid-0.21.1/ MASTER_SERVER:apache-druid-apache-druid-0.21....

Apache NiFi之MySQL数据同步到HBase

一.说明 将Apache NiFi做为关系型数据与非关系型数据库的数据同步工具使用,在此场景中需要将mysql导出的avro数据格式转化为json入库HBase 二.开拔 Ⅰ).配置ExecuteSQLRecord a).选择ExecuteSQLRecord 在Processor中搜索ExecuteSQLRecord b).配置ExecuteSQLR...

Mysql连接池 [ druid ] 的坑

Mysql无效链接异常:com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure 背景分析 1、异常详情:异常的意思是,前一次成功的使用连接是699,944毫秒以前,也就是大概11分钟之前(空闲时间超过10分钟) 2019-06-29 14:16:02...

druid discard long time none received connection问题解析

最新项目中用的druid连接数据库遇到一个困扰很久的问题 1 开始用的druid版本是1.1.22版本,由于业务需求,单个连接需要执行很久,理论上不需要用到自动回收,但为了安全,还是加了自动回收,时间设置的2个小时。 随着程序运行,程序经常报The last packet successfully received from the server was...

数据连接池DruId的使用

1,下载druID.jar包 2,配置文件druid.properties配置相关信息 driverClassName : org.gjt.mm.mysql.Driverurl : jdbc:mysql://127.0.0.1:3306/e_testusername : rootpassword : admin#初始化物理连接数目#initialSize...