package cn.org.rapid_framework.generator.provider.db.sql; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import cn.org.rapid_framework.generator.provider.db.DataSourceProvider; import cn.org.rapid_framework.generator.provider.db.sql.model.Sql; import cn.org.rapid_framework.generator.provider.db.sql.model.SqlParameter; import cn.org.rapid_framework.generator.provider.db.table.TableFactory; import cn.org.rapid_framework.generator.provider.db.table.TableFactory.NotFoundTableException; import cn.org.rapid_framework.generator.provider.db.table.model.Column; import cn.org.rapid_framework.generator.provider.db.table.model.Table; import cn.org.rapid_framework.generator.util.BeanHelper; import cn.org.rapid_framework.generator.util.DBHelper; import cn.org.rapid_framework.generator.util.GLogger; import cn.org.rapid_framework.generator.util.StringHelper; import cn.org.rapid_framework.generator.util.sqlerrorcode.SQLErrorCodeSQLExceptionTranslator; import cn.org.rapid_framework.generator.util.sqlparse.BasicSqlFormatter; import cn.org.rapid_framework.generator.util.sqlparse.NamedParameterUtils; import cn.org.rapid_framework.generator.util.sqlparse.ParsedSql; import cn.org.rapid_framework.generator.util.sqlparse.ResultSetMetaDataHolder; import cn.org.rapid_framework.generator.util.sqlparse.SqlParseHelper; import cn.org.rapid_framework.generator.util.sqlparse.StatementCreatorUtils; import cn.org.rapid_framework.generator.util.sqlparse.SqlParseHelper.NameWithAlias; import cn.org.rapid_framework.generator.util.typemapping.JdbcType; /** * * 根据SQL语句生成Sql对象,用于代码生成器的生成<br /> * * 示例使用: * <pre> * Sql sql = new SqlFactory().parseSql("select * from user_info where username=#username# and password=#password#"); * </pre> * * @author badqiu * */ public class SqlFactory { public SqlFactory() { } public Sql parseSql(String sourceSql) { if(StringHelper.isBlank(sourceSql)) throw new IllegalArgumentException("sourceSql must be not empty"); String beforeProcessedSql = beforeParseSql(sourceSql); // String unscapedSourceSql = StringHelper.unescapeXml(beforeProcessedSql); String namedSql = SqlParseHelper.convert2NamedParametersSql(beforeProcessedSql,":",""); ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(namedSql); String executeSql = new BasicSqlFormatter().format(NamedParameterUtils.substituteNamedParameters(parsedSql)); Sql sql = new Sql(); sql.setSourceSql(sourceSql); sql.setExecuteSql(executeSql); GLogger.debug("\n*******************************"); GLogger.debug("sourceSql :"+sql.getSourceSql()); GLogger.debug("namedSql :"+namedSql); GLogger.debug("executeSql :"+sql.getExecuteSql()); GLogger.debug("*********************************"); Connection conn = null; PreparedStatement ps = null; try { conn = DataSourceProvider.getNewConnection(); conn.setAutoCommit(false); // if(DatabaseMetaDataUtils.isMysqlDataBase(conn.getMetaData())){ // conn.setReadOnly(true); // } ps = conn.prepareStatement(SqlParseHelper.removeOrders(executeSql)); SqlParametersParser sqlParametersParser = new SqlParametersParser(); sqlParametersParser.execute(parsedSql,sql); ResultSetMetaData resultSetMetaData = executeSqlForResultSetMetaData(executeSql,ps,sqlParametersParser.allParams); sql.setColumns(new SelectColumnsParser().convert2Columns(sql,resultSetMetaData)); sql.setParams(sqlParametersParser.params); return afterProcessedSql(sql); }catch(SQLException e) { throw new RuntimeException("execute sql occer error,\nexecutedSql:"+SqlParseHelper.removeOrders(executeSql),e); }catch(Exception e) { throw new RuntimeException("sql parse error,\nexecutedSql:"+SqlParseHelper.removeOrders(executeSql),e); }finally { try { DBHelper.rollback(conn); }finally { DBHelper.close(conn,ps,null); } } } protected Sql afterProcessedSql(Sql sql) { return sql; } protected String beforeParseSql(String sourceSql) { return sourceSql; } private ResultSetMetaData executeSqlForResultSetMetaData(String sql,PreparedStatement ps,List<SqlParameter> params)throws SQLException { // SqlParseHelper.setRandomParamsValueForPreparedStatement(SqlParseHelper.removeOrders(executeSql), ps); StatementCreatorUtils.setRandomParamsValueForPreparedStatement(sql, ps, params); try { ps.setMaxRows(3); ps.setFetchSize(3); ps.setQueryTimeout(20); ResultSet rs = null; if(ps.execute()) { rs = ps.getResultSet(); return rs.getMetaData(); } return null; }catch(SQLException e) { if(isDataIntegrityViolationException(e)) { GLogger.warn("ignore executeSqlForResultSetMetaData() SQLException,errorCode:"+e.getErrorCode()+" sqlState:"+e.getSQLState()+" message:"+e.getMessage()+ "\n executedSql:"+sql); return null; } String message = "errorCode:"+e.getErrorCode()+" SQLState:"+e.getSQLState()+" errorCodeTranslatorDataBaaseName:"+getErrorCodeTranslatorDataBaaseName()+" "+ e.getMessage(); throw new SQLException(message,e.getSQLState(),e.getErrorCode()); } } private String getErrorCodeTranslatorDataBaaseName() { SQLErrorCodeSQLExceptionTranslator transaltor = SQLErrorCodeSQLExceptionTranslator.getSQLErrorCodeSQLExceptionTranslator(DataSourceProvider.getDataSource()); if(transaltor.getSqlErrorCodes() == null) return "null"; return Arrays.toString(transaltor.getSqlErrorCodes().getDatabaseProductNames()); } /** 判断是否是外键,完整性约束等异常 引发的异常 */ protected boolean isDataIntegrityViolationException(SQLException sqlEx) { SQLErrorCodeSQLExceptionTranslator transaltor = SQLErrorCodeSQLExceptionTranslator.getSQLErrorCodeSQLExceptionTranslator(DataSourceProvider.getDataSource()); return transaltor.isDataIntegrityViolation(sqlEx); } public static Map<String,Table> cache = new HashMap<String,Table>(); public static Table getTableFromCache(String tableSqlName) { if(tableSqlName == null) throw new IllegalArgumentException("tableSqlName must be not null"); Table table = cache.get(tableSqlName.toLowerCase()); if(table == null) { table = TableFactory.getInstance().getTable(tableSqlName); cache.put(tableSqlName.toLowerCase(), table); } return table; } public static class SelectColumnsParser { private LinkedHashSet<Column> convert2Columns(Sql sql,ResultSetMetaData metadata) throws SQLException, Exception { if(metadata == null) return new LinkedHashSet(); LinkedHashSet<Column> columns = new LinkedHashSet(); for(int i = 1; i <= metadata.getColumnCount(); i++) { Column c = convert2Column(sql,metadata, i); if(c == null) throw new IllegalStateException("column must be not null"); columns.add(c); } return columns; } private Column convert2Column(Sql sql,ResultSetMetaData metadata, int i) throws SQLException, Exception { ResultSetMetaDataHolder m = new ResultSetMetaDataHolder(metadata, i); if(StringHelper.isNotBlank(m.getTableName())) { //FIXME 如果表有别名,将会找不到表,如 inner join user_info t1, tableName将为t1,应该转换为user_info Table table = foundTableByTableNameOrTableAlias(sql, m.getTableName()); if(table == null) { return newColumn(null,m); } Column column = table.getColumnBySqlName(m.getColumnLabelOrName()); if(column == null || column.getSqlType() != m.getColumnType()) { //可以再尝试解析sql得到 column以解决 password as pwd找不到column问题 column = newColumn(table,m); GLogger.trace("not found column:"+m.getColumnLabelOrName()+" on table:"+table.getSqlName()+" "+BeanHelper.describe(column)); //isInSameTable以此种判断为错误 }else { GLogger.trace("found column:"+m.getColumnLabelOrName()+" on table:"+table.getSqlName()+" "+BeanHelper.describe(column)); } return column; }else { return newColumn(null,m); } } private Column newColumn(Table table,ResultSetMetaDataHolder m) { //Table table, int sqlType, String sqlTypeName,String sqlName, int size, int decimalDigits, boolean isPk,boolean isNullable, boolean isIndexed, boolean isUnique,String defaultValue,String remarks Column column = new Column(null,m.getColumnType(),m.getColumnTypeName(),m.getColumnLabelOrName(),m.getColumnDisplaySize(),m.getScale(),false,false,false,false,null,null); GLogger.trace("not found on table by table emtpty:"+BeanHelper.describe(column)); return column; } private Table foundTableByTableNameOrTableAlias(Sql sql,String tableNameId) throws Exception { try { return getTableFromCache(tableNameId); }catch(NotFoundTableException e) { Set<NameWithAlias> tableNames = SqlParseHelper.getTableNamesByQuery(sql.getExecuteSql()); for(NameWithAlias tableName : tableNames) { if(tableName.getAlias().equalsIgnoreCase(tableNameId)) { return getTableFromCache(tableName.getName()); } } } return null; } } public static class SqlParametersParser { private static Map<String,Column> specialParametersMapping = new HashMap<String,Column>(); { specialParametersMapping.put("offset", new Column(null,JdbcType.INTEGER.TYPE_CODE,"INTEGER","offset",0,0,false,false,false,false,null,null)); specialParametersMapping.put("limit", new Column(null,JdbcType.INTEGER.TYPE_CODE,"INTEGER","limit",0,0,false,false,false,false,null,null)); specialParametersMapping.put("pageSize", new Column(null,JdbcType.INTEGER.TYPE_CODE,"INTEGER","pageSize",0,0,false,false,false,false,null,null)); specialParametersMapping.put("pageNo", new Column(null,JdbcType.INTEGER.TYPE_CODE,"INTEGER","pageNo",0,0,false,false,false,false,null,null)); specialParametersMapping.put("pageNumber", new Column(null,JdbcType.INTEGER.TYPE_CODE,"INTEGER","pageNumber",0,0,false,false,false,false,null,null)); specialParametersMapping.put("pageNum", new Column(null,JdbcType.INTEGER.TYPE_CODE,"INTEGER","pageNumber",0,0,false,false,false,false,null,null)); specialParametersMapping.put("page", new Column(null,JdbcType.INTEGER.TYPE_CODE,"INTEGER","page",0,0,false,false,false,false,null,null)); specialParametersMapping.put("beginRow", new Column(null,JdbcType.INTEGER.TYPE_CODE,"INTEGER","beginRow",0,0,false,false,false,false,null,null)); specialParametersMapping.put("beginRows", new Column(null,JdbcType.INTEGER.TYPE_CODE,"INTEGER","beginRows",0,0,false,false,false,false,null,null)); specialParametersMapping.put("startRow", new Column(null,JdbcType.INTEGER.TYPE_CODE,"INTEGER","startRow",0,0,false,false,false,false,null,null)); specialParametersMapping.put("startRows", new Column(null,JdbcType.INTEGER.TYPE_CODE,"INTEGER","startRows",0,0,false,false,false,false,null,null)); specialParametersMapping.put("endRow", new Column(null,JdbcType.INTEGER.TYPE_CODE,"INTEGER","endRow",0,0,false,false,false,false,null,null)); specialParametersMapping.put("endRows", new Column(null,JdbcType.INTEGER.TYPE_CODE,"INTEGER","endRows",0,0,false,false,false,false,null,null)); specialParametersMapping.put("lastRow", new Column(null,JdbcType.INTEGER.TYPE_CODE,"INTEGER","lastRow",0,0,false,false,false,false,null,null)); specialParametersMapping.put("lastRows", new Column(null,JdbcType.INTEGER.TYPE_CODE,"INTEGER","lastRows",0,0,false,false,false,false,null,null)); specialParametersMapping.put("orderBy", new Column(null,JdbcType.VARCHAR.TYPE_CODE,"VARCHAR","orderBy",0,0,false,false,false,false,null,null)); specialParametersMapping.put("orderby", new Column(null,JdbcType.VARCHAR.TYPE_CODE,"VARCHAR","orderby",0,0,false,false,false,false,null,null)); specialParametersMapping.put("sortColumns", new Column(null,JdbcType.VARCHAR.TYPE_CODE,"VARCHAR","sortColumns",0,0,false,false,false,false,null,null)); } public LinkedHashSet<SqlParameter> params = new LinkedHashSet<SqlParameter>(); public List<SqlParameter> allParams = new ArrayList<SqlParameter>(); private void execute(ParsedSql parsedSql,Sql sql) throws Exception { long start = System.currentTimeMillis(); for(int i = 0; i < parsedSql.getParameterNames().size(); i++) { String paramName = parsedSql.getParameterNames().get(i); Column column = findColumnByParamName(parsedSql, sql, paramName); if(column == null) { column = specialParametersMapping.get(paramName); if(column == null) { //FIXME 不能猜测的column类型 column = new Column(null,JdbcType.UNDEFINED.TYPE_CODE,"UNDEFINED",paramName,0,0,false,false,false,false,null,null); } } SqlParameter param = new SqlParameter(column); param.setParamName(paramName); if(isMatchListParam(sql.getSourceSql(), paramName)) { //FIXME 只考虑(:username)未考虑(#inUsernames#) and (#{inPassword}),并且可以使用 #inUsername[]# param.setListParam(true); } params.add(param); allParams.add(param); } GLogger.perf("parseForSqlParameters() cost:"+(System.currentTimeMillis()- start)); } public boolean isMatchListParam(String sql, String paramName) { return sql.matches("(?s).*\\sin\\s*\\([:#\\$&]\\{?"+paramName+"\\}?[$#}]?\\).*") // match in (:username) ,not in (#username#) || sql.matches("(?s).*[#$]"+paramName+"\\[]\\.?\\w*[#$].*") //match #user[]# $user[]$ #user[].age# for ibatis || sql.matches("(?s).*[#$]\\{"+paramName+"\\[[$\\{\\}\\w]+]\\}*.*"); //match #{user[index]}# ${user[${index}]} for mybatis } private Column findColumnByParamName(ParsedSql parsedSql,Sql sql, String paramName) throws Exception { Column column = sql.getColumnByName(paramName); if(column == null) { //FIXME 还未处理 t.username = :username的t前缀问题,应该直接根据 t.确定属于那一张表,不需要再猜测 String leftColumn = SqlParseHelper.getColumnNameByRightCondition(parsedSql.toString(), paramName); if(leftColumn != null) { column = findColumnByParseSql(parsedSql, leftColumn ); } } if(column == null) { column = findColumnByParseSql(parsedSql, paramName); } return column; } private Column findColumnByParseSql(ParsedSql sql, String paramName) throws Exception { if(paramName == null) throw new NullPointerException("'paramName' must be not null"); try { Collection<NameWithAlias> tableNames = SqlParseHelper.getTableNamesByQuery(sql.toString()); for(NameWithAlias tableName : tableNames) { Table t = getTableFromCache(tableName.getName()); if(t != null) { Column column = t.getColumnByName(paramName); if(column != null) { return column; } } } }catch(NotFoundTableException e) { throw new IllegalArgumentException("get tableNamesByQuery occer error:"+sql.toString(),e); } return null; } } }