最近參與一個開源項目,一個功能的實現,用到了 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>