业务需求:输入一条SQL,解析出对应的结果表字段列表与参数列表。
select t.id,t.name,date_format(update_time,'%Y-%m') as update_month
from t_user t
where t.account=#{account:varchar};
解析后需获得:
- 结果字段信息:id,name,update_month
- 涉及的表:t_user(别名>t)
- 参数信息:account
- 格式化处理后的SQL语句: select t.id,t.name,date_format(update_time,‘%Y-%m’) as update_month from t_user t where t.account=?;
- …
因为输入的SQL复杂度不一,可能存在子查询和连表查询,所以想借用轮子,再自己定制需要的部分。调研了几个SQL Parser,结合当前项目,还是选择了开源的druid sql parser。
Druid是Java语言中最好的数据库连接池。提供强大的监控和扩展功能。其中,SQL Parser是Druid的一个重要组成部分,服务器托管可用来实现防御SQL注入(WallFilter)、合并统计没有参数化的SQL(StatFilter的mergeSql)、SQL格式化、分库分表。
https://github.com/alibaba/druid
https://github.com/alibaba/druid/wiki/SQL-Parser
Druid的sql parser是目前支持各种数据语法最完备的SQL Parser。目前对各种数据库的支持如下:
数据库 | DML | DDL |
---|---|---|
odps | 完全支持 | 完全支持 |
mysql | 完全支持 | 完全支持 |
postgresql | 完全支持 | 完全支持 |
oracle | 支持大部分 | 支持大部分 |
sql server | 支持常用的 | 支持常用的ddl |
db2 | 支持常用的 | 支持常用的ddl |
hive | 支持常用的 | 支持常用的ddl |
对于本文中的业务场景,是支持。
Druid SQL Parser分三个模块:Parser、AST、Visitor。Parser将SQL文本解析为AST语法树,Visitor有遍历AST的能力。
在本文中因为输入的是相对规范的sql语法,Parser可以完全解析为需要的语法树。所以后续只需要自定义Visitor来获取需要的信息。
其中有提供SQLASTParameterizedVisitor(重点在:参数等)、SchemaStatVisitor(重点在:数据表关系、排序、分组等)等来获取AST的信息。因为没有整合开头所需的内容数据,所以本文自定义了一个。
注意:不要直接继承SQLASTParameterizedVisitor、SchemaStatVisitor这些已经实现SQLASTVisitor接口的类,因为会导致执行顺序偏差,反正在测试时,发现参数解析出现了丢失情况。
解析获得的结果类:
@Data
public class CustomSQLParser {
/**
* 格式化SQL语句
*/
private String formatSql;
/**
* 预处理后SQL语句
*/
private String prepareSql;
/**
* 涉及的表信息 tableName、tableAlias
*/
private ListCustomTable> tables;
/**
* 查询获得的结果字段信息 column、columnAlias、dataType
*/
private ListCustomColumn> columns;
/**
* 参数信息 paramName、paramType、expr、operation
*/
private ListCustomParameter> parameters;
/**
* 参数名称
*/
private ListString> parameterNames;
}
自定义Visitor
@Data
public class CustomSQLASTVisitor implements SQLASTVisitor {
public static final Pattern PARAMETER_PATTERN = Pattern.compile("[#\$]\{(\w+)(?::(\w+))?\}");
protected ListSQLSelectItem> selectItems = new ArrayList>();
protected ListCustomTable> selectTables = new ArrayList>();
protected ListCustomColumn> selectColumns = new ArrayList>();
protected ListString> parameterNames = new ArrayList>();
protected ListCustomParameter> parameters = new ArrayList>();
protected ListString> columnNames = new ArrayList>();
protected MapString, String> opera = new HashMap>();
@Override
public void endVisit(SQLSelectQueryBlock x) {
computeCustomColumnBeans();
}
@Override
public boolean visit(SQLExprTableSource sqlExprTableSource) {
selectTables.add(new CustomTable(sqlExprTableSource));
return false;
}
@Override
public boolean visit(SQLCharExpr sqlCharExpr ) {
parserParameter(sqlCharExpr.toString());
return false;
}
@Override
public boolean visit(SQLSelectItem sqlSelectItem) {
selectItems.add(sqlSelectItem );
return false;
}
@Override
public boolean visit(SQLVariantRefExpr sqlVariantRefExpr) {
parserParameter(sqlVariantRefExpr.getName());
return false;
}
@Override
public void endVisit(SQLBinaryOpExpr sqlBinaryOpExpr) {
parserWhereParam(sqlBinaryOpExpr);
this.parameters.forEach(p->{
if(opera.containsKey(p.getExpr())){
p.setOperation(opera.get(p.getExpr()));
}
});
}
protected void parserParameter(String expr) {
Matcher matcher = PARAMETER_PATTERN.matcher(expr);
if (matcher.find()) {
String field = matcher.group(1);
String fieldType = matcher.group(2);
parameterNames.add(field);
parameters.add(new CustomParameter(field, fieldType, expr));
}
}
protected void computeCustomColumnBeans() {
selectItems.forEach(item -> {
String alias = item.getAlias();
CustomColumn curColumn = null;
if (item.getExpr() instanceof SQLIdentifierExpr) {
SQLIdentifierExpr expr = (SQLIdentifierExpr) item.getExpr();
curColumn = new CustomColumn(selectTables.get(0).getTableName(), selectTables.get(0).getTableAlias(), expr.getName(), alias);
} else if (item.getExpr() instanceof SQLAllColumnExpr) {
SQLAllColumnExpr expr = (SQLAllColumnExpr) item.getExpr();
curColumn = new CustomColumn(selectTables.get(0).getTableName(), selectTables.get(0).getTableAlias(), expr.toString(), alias);
} else if (item.getExpr() instanceof SQLMethodInvokeExpr) {
SQLMethodInvokeExpr expr = (SQLMethodInvokeExpr) item.getExpr();
curColumn = new CustomColumn(selectTables.get(0).getTableName(), selectTables.get(0).getTableAlias(), expr.toString(), alias);
} else if (item.getExpr() instanceof SQLPropertyExpr) {
SQLPropertyExpr expr = (SQLPropertyExpr) item.getExpr();
curColumn = new CustomColumn(getTableNameByAlias(expr.getOwnerName()), expr.getOwnerName(), expr.getName(), item.getAlias());
}
if (null != curColumn) {
selectColumns.add(curColumn);
columnNames.add(!StringUtils.isNullOrEmpty(curColumn.getColumnAlias()) ? curColumn.getColumnAlias() : curColumn.getColumn());
}
});
}
/**
* 根据查询表别名获取查询表名
*
* @param alias 查询表别名
* @return 查询表名
*/
protected String getTableNameByAlias(String alias) {
return getTableByAlias(alias).map(CustomTable::getTableName).orElse(null);
}
/**
* 根据查询表别名获取查询表
*
* @param alias 查询表别名
* @return 查询表
*/
protected OptionalCustomTable> getTableByAlias(String alias) {
return selectTables.stream().filter(table -> alias.equals(table.getTableAlias())).findFirst();
}
/**
* 解析Where中的参数
*
* @param sqlBinaryOpExpr
*/
protected void parserWhereParam(SQLBinaryOpExpr sqlBinaryOpExpr) {
SQLExpr right = sqlBinaryOpExpr.getRight();
SQLExpr left = sqlBinaryOpExpr.getLeft();
if (left instanceof SQLIdentifierExpr || left instanceof SQLVariantRefExpr|| left instanceof SQLPropertyExpr) {
Matcher matcher = PARAMETER_PATTERN.matcher(sqlBinaryOpExpr.toString());
if (matcher.find()) {
String expr = matcher.group();
opera.put(expr, sqlBinaryOpExpr.getOperator().getName());
}
} else if(left instanceof SQLBinaryOpExpr){
Matcher matcherRight = PARAMETER_PATTERN.matcher(right.toString());
if (matcherRight.find()) {
String expr = matcherRight.group();
opera.put(expr, ((SQLBinaryOpExpr) right).getOperator().getName());
}
SQLExpr leftSubRight = ((SQLBinaryOpExpr) left).getRight();
// 解析右边
System.out.println("leftSubRight.toString():" + leftSubRight.toString());
Matcher matcherLeftSubRight = PARAMETER_PATTERN.matcher(leftSubRight.toString());
if (matcherLeftSubRight.find()) {
String expr = matcherLeftSubRight.group();
opera.put(expr, ((SQLBinaryOpExpr) leftSubRight).getOperator().getName());
}
SQLExpr leftSubLeft = ((SQLBinaryOpExpr) left).getLeft();
if (leftSubLeft instanceof SQLBinaryOpExpr) {
parserWhereParam((SQLBinaryOpExpr) leftSubLeft);
}
服务器托管 Matcher matcherLeftSubLeft = PARAMETER_PATTERN.matcher(leftSubLeft.toString());
if (matcherLeftSubLeft.find()) {
String expr = matcherLeftSubLeft.group();
opera.put(expr, ((SQLBinaryOpExpr) leftSubLeft).getOperator().getName());
}
}
}
}
使用
SQLStatement statement = SQLUtils.parseSingleStatement(sql, DbType.mysql);
CustomSQLASTVisitor visitor = new CustomSQLASTVisitor();
statement.accept(visitor);
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net