zoukankan      html  css  js  c++  java
  • 利用 druid 解析器解析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> 
  • 相关阅读:
    N天学习一个linux命令之scp
    php svn仓库提交预处理
    NTP-网络时间协议
    N天学习一个linux命令之umask
    N天学习一个linux命令之xz
    N天学习一个linux命令之xargs
    N天学习一个Linux命令之hostnamectl
    jQuery easyUI的datagrid,如何在翻页以后仍能记录被选中的行
    multiselect2side:jQuery多选列表框插件
    springmvc+jquery实现省市区地址下拉框联动
  • 原文地址:https://www.cnblogs.com/digdeep/p/5071204.html
Copyright © 2011-2022 走看看