package io.mycat.route.parser.druid.impl; import io.mycat.MycatServer; import io.mycat.cache.LayerCachePool; import io.mycat.route.RouteResultset; import io.mycat.route.RouteResultsetNode; import io.mycat.route.parser.druid.RouteCalculateUnit; import io.mycat.route.util.RouterUtil; import io.mycat.server.ErrorCode; import io.mycat.server.config.node.SchemaConfig; import io.mycat.server.config.node.TableConfig; import io.mycat.sqlengine.mpp.ColumnRoutePair; import io.mycat.sqlengine.mpp.HavingCols; import io.mycat.sqlengine.mpp.MergeCol; import io.mycat.sqlengine.mpp.OrderCol; import io.mycat.util.ObjectUtil; import io.mycat.util.StringUtil; import java.sql.SQLNonTransientException; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import com.alibaba.druid.sql.ast.SQLExpr; import com.alibaba.druid.sql.ast.SQLName; import com.alibaba.druid.sql.ast.SQLOrderingSpecification; import com.alibaba.druid.sql.ast.SQLStatement; import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr; import com.alibaba.druid.sql.ast.expr.SQLAllColumnExpr; import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr; import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; import com.alibaba.druid.sql.ast.expr.SQLNumericLiteralExpr; import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr; import com.alibaba.druid.sql.ast.expr.SQLTextLiteralExpr; import com.alibaba.druid.sql.ast.statement.SQLSelectGroupByClause; import com.alibaba.druid.sql.ast.statement.SQLSelectItem; import com.alibaba.druid.sql.ast.statement.SQLSelectOrderByItem; import com.alibaba.druid.sql.ast.statement.SQLSelectQuery; import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock; import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlSelectGroupByExpr; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock.Limit; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlUnionQuery; import com.alibaba.druid.util.JdbcConstants; import com.alibaba.druid.wall.spi.WallVisitorUtils; public class DruidSelectParser extends DefaultDruidParser { protected boolean isNeedParseOrderAgg=true; @Override public void statementParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt) { SQLSelectStatement selectStmt = (SQLSelectStatement)stmt; SQLSelectQuery sqlSelectQuery = selectStmt.getSelect().getQuery(); if(sqlSelectQuery instanceof MySqlSelectQueryBlock) { MySqlSelectQueryBlock mysqlSelectQuery = (MySqlSelectQueryBlock)selectStmt.getSelect().getQuery(); parseOrderAggGroupMysql(schema, stmt,rrs, mysqlSelectQuery); //更改canRunInReadDB属性 if ((mysqlSelectQuery.isForUpdate() || mysqlSelectQuery.isLockInShareMode()) && !rrs.isAutocommit()) { rrs.setCanRunInReadDB(false); } } else if (sqlSelectQuery instanceof MySqlUnionQuery) { // MySqlUnionQuery unionQuery = (MySqlUnionQuery)sqlSelectQuery; // MySqlSelectQueryBlock left = (MySqlSelectQueryBlock)unionQuery.getLeft(); // MySqlSelectQueryBlock right = (MySqlSelectQueryBlock)unionQuery.getLeft(); // System.out.println(); } } protected void parseOrderAggGroupMysql(SchemaConfig schema, SQLStatement stmt, RouteResultset rrs, MySqlSelectQueryBlock mysqlSelectQuery) { if(!isNeedParseOrderAgg) { return; } Map<String, String> aliaColumns = parseAggGroupCommon(schema, stmt, rrs, mysqlSelectQuery); //setOrderByCols if(mysqlSelectQuery.getOrderBy() != null) { List<SQLSelectOrderByItem> orderByItems = mysqlSelectQuery.getOrderBy().getItems(); rrs.setOrderByCols(buildOrderByCols(orderByItems,aliaColumns)); } isNeedParseOrderAgg=false; } protected Map<String, String> parseAggGroupCommon(SchemaConfig schema, SQLStatement stmt, RouteResultset rrs, SQLSelectQueryBlock mysqlSelectQuery) { Map<String, String> aliaColumns = new HashMap<String, String>(); Map<String, Integer> aggrColumns = new HashMap<String, Integer>(); List<SQLSelectItem> selectList = mysqlSelectQuery.getSelectList(); boolean isNeedChangeSql=false; int size = selectList.size(); boolean isDistinct=mysqlSelectQuery.getDistionOption()==2; for (int i = 0; i < size; i++) { SQLSelectItem item = selectList.get(i); if (item.getExpr() instanceof SQLAggregateExpr) { SQLAggregateExpr expr = (SQLAggregateExpr) item.getExpr(); String method = expr.getMethodName(); //只处理有别名的情况,无别名添加别名,否则某些数据库会得不到正确结果处理 int mergeType = MergeCol.getMergeType(method); if (MergeCol.MERGE_AVG == mergeType&&isRoutMultiNode(schema,rrs)) { //跨分片avg需要特殊处理,直接avg结果是不对的 String colName = item.getAlias() != null ? item.getAlias() : method + i; SQLSelectItem sum =new SQLSelectItem(); String sumColName = colName + "SUM"; sum.setAlias(sumColName); SQLAggregateExpr sumExp =new SQLAggregateExpr("SUM"); ObjectUtil.copyProperties(expr, sumExp); sumExp.getArguments().addAll(expr.getArguments()); sumExp.setMethodName("SUM"); sum.setExpr(sumExp); selectList.set(i, sum); aggrColumns.put(sumColName, MergeCol.MERGE_SUM); SQLSelectItem count =new SQLSelectItem(); String countColName = colName + "COUNT"; count.setAlias(countColName); SQLAggregateExpr countExp = new SQLAggregateExpr("COUNT"); ObjectUtil.copyProperties(expr,countExp); countExp.getArguments().addAll(expr.getArguments()); countExp.setMethodName("COUNT"); count.setExpr(countExp); selectList.add(count); aggrColumns.put(countColName, MergeCol.MERGE_COUNT); isNeedChangeSql=true; aggrColumns.put(colName, mergeType); rrs.setHasAggrColumn(true); } else if (MergeCol.MERGE_UNSUPPORT != mergeType) { if (item.getAlias() != null && item.getAlias().length() > 0) { aggrColumns.put(item.getAlias(), mergeType); } else { //如果不加,jdbc方式时取不到正确结果 ;修改添加别名 item.setAlias(method + i); aggrColumns.put(method + i, mergeType); isNeedChangeSql=true; } rrs.setHasAggrColumn(true); } } else { if (!(item.getExpr() instanceof SQLAllColumnExpr)) { String alia = item.getAlias(); String field = getFieldName(item); if (alia == null) { alia = field; } aliaColumns.put(field, alia); } } } if(aggrColumns.size() > 0) { rrs.setMergeCols(aggrColumns); } //通过优化转换成group by来实现 if(isDistinct) { mysqlSelectQuery.setDistionOption(0); SQLSelectGroupByClause groupBy=new SQLSelectGroupByClause(); for (String fieldName : aliaColumns.keySet()) { groupBy.addItem(new SQLIdentifierExpr(fieldName)); } mysqlSelectQuery.setGroupBy(groupBy); isNeedChangeSql=true; } //setGroupByCols if(mysqlSelectQuery.getGroupBy() != null) { List<SQLExpr> groupByItems = mysqlSelectQuery.getGroupBy().getItems(); String[] groupByCols = buildGroupByCols(groupByItems,aliaColumns); rrs.setGroupByCols(groupByCols); rrs.setHavings(buildGroupByHaving(mysqlSelectQuery.getGroupBy().getHaving())); rrs.setHasAggrColumn(true); } if (isNeedChangeSql) { String sql = stmt.toString(); rrs.changeNodeSqlAfterAddLimit(schema,getCurentDbType(),sql,0,-1, false); getCtx().setSql(sql); } return aliaColumns; } private HavingCols buildGroupByHaving(SQLExpr having){ if (having == null) { return null; } SQLBinaryOpExpr expr = ((SQLBinaryOpExpr) having); SQLExpr left = expr.getLeft(); SQLBinaryOperator operator = expr.getOperator(); SQLExpr right = expr.getRight(); String leftValue = null; if (left instanceof SQLAggregateExpr) { leftValue = ((SQLAggregateExpr) left).getMethodName() + "(" + ((SQLAggregateExpr) left).getArguments().get(0) + ")"; } else if (left instanceof SQLIdentifierExpr) { leftValue = ((SQLIdentifierExpr) left).getName(); } String rightValue = null; if (right instanceof SQLNumericLiteralExpr) { rightValue = right.toString(); }else if(right instanceof SQLTextLiteralExpr){ rightValue = StringUtil.removeBackquote(right.toString()); } return new HavingCols(leftValue,rightValue,operator.getName()); } private boolean isRoutMultiNode(SchemaConfig schema, RouteResultset rrs) { if(rrs.getNodes()!=null&&rrs.getNodes().length>1) { return true; } LayerCachePool tableId2DataNodeCache = (LayerCachePool) MycatServer.getInstance().getCacheService().getCachePool("TableID2DataNodeCache"); try { tryRoute(schema, rrs, tableId2DataNodeCache); if(rrs.getNodes()!=null&&rrs.getNodes().length>1) { return true; } } catch (SQLNonTransientException e) { throw new RuntimeException(e); } return false; } private String getFieldName(SQLSelectItem item){ if ((item.getExpr() instanceof SQLPropertyExpr)||(item.getExpr() instanceof SQLMethodInvokeExpr) || (item.getExpr() instanceof SQLIdentifierExpr) || item.getExpr() instanceof SQLBinaryOpExpr) { return item.getExpr().toString();//字段别名 } else return item.toString(); } /** * 改写sql:需要加limit的加上 */ @Override public void changeSql(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt,LayerCachePool cachePool) throws SQLNonTransientException { tryRoute(schema, rrs, cachePool); rrs.copyLimitToNodes(); SQLSelectStatement selectStmt = (SQLSelectStatement)stmt; SQLSelectQuery sqlSelectQuery = selectStmt.getSelect().getQuery(); if(sqlSelectQuery instanceof MySqlSelectQueryBlock) { MySqlSelectQueryBlock mysqlSelectQuery = (MySqlSelectQueryBlock)selectStmt.getSelect().getQuery(); int limitStart = 0; int limitSize = schema.getDefaultMaxLimit(); //clear group having SQLSelectGroupByClause groupByClause = mysqlSelectQuery.getGroupBy(); if(groupByClause != null && groupByClause.getHaving() != null){ groupByClause.setHaving(null); } Map<String, Map<String, Set<ColumnRoutePair>>> allConditions = getAllConditions(); boolean isNeedAddLimit = isNeedAddLimit(schema, rrs, mysqlSelectQuery, allConditions); if(isNeedAddLimit) { Limit limit = new Limit(); limit.setRowCount(new SQLIntegerExpr(limitSize)); mysqlSelectQuery.setLimit(limit); rrs.setLimitSize(limitSize); String sql= getSql(rrs, stmt, isNeedAddLimit); rrs.changeNodeSqlAfterAddLimit(schema, getCurentDbType(), sql, 0, limitSize, true); } Limit limit = mysqlSelectQuery.getLimit(); if(limit != null&&!isNeedAddLimit) { SQLIntegerExpr offset = (SQLIntegerExpr)limit.getOffset(); SQLIntegerExpr count = (SQLIntegerExpr)limit.getRowCount(); if(offset != null) { limitStart = offset.getNumber().intValue(); rrs.setLimitStart(limitStart); } if(count != null) { limitSize = count.getNumber().intValue(); rrs.setLimitSize(limitSize); } if(isNeedChangeLimit(rrs)) { Limit changedLimit = new Limit(); changedLimit.setRowCount(new SQLIntegerExpr(limitStart + limitSize)); if(offset != null) { if(limitStart < 0) { String msg = "You have an error in your SQL syntax; check the manual that " + "corresponds to your MySQL server version for the right syntax to use near '" + limitStart + "'"; throw new SQLNonTransientException(ErrorCode.ER_PARSE_ERROR + " - " + msg); } else { changedLimit.setOffset(new SQLIntegerExpr(0)); } } mysqlSelectQuery.setLimit(changedLimit); String sql= getSql(rrs, stmt, isNeedAddLimit); rrs.changeNodeSqlAfterAddLimit(schema,getCurentDbType(),sql,0, limitStart + limitSize, true); //设置改写后的sql ctx.setSql(sql); } else { rrs.changeNodeSqlAfterAddLimit(schema,getCurentDbType(),getCtx().getSql(),rrs.getLimitStart(), rrs.getLimitSize(), true); // ctx.setSql(nativeSql); } } rrs.setCacheAble(isNeedCache(schema, rrs, mysqlSelectQuery, allConditions)); } } /** * 获取所有的条件:因为可能被or语句拆分成多个RouteCalculateUnit,条件分散了 * @return */ private Map<String, Map<String, Set<ColumnRoutePair>>> getAllConditions() { Map<String, Map<String, Set<ColumnRoutePair>>> map = new HashMap<String, Map<String, Set<ColumnRoutePair>>>(); for(RouteCalculateUnit unit : ctx.getRouteCalculateUnits()) { if(unit != null && unit.getTablesAndConditions() != null) { map.putAll(unit.getTablesAndConditions()); } } return map; } private void tryRoute(SchemaConfig schema, RouteResultset rrs, LayerCachePool cachePool) throws SQLNonTransientException { if(rrs.isFinishedRoute()) { return;//避免重复路由 } //无表的select语句直接路由带任一节点 if((ctx.getTables() == null || ctx.getTables().size() == 0)&&(ctx.getTableAliasMap()==null||ctx.getTableAliasMap().isEmpty())) { rrs = RouterUtil.routeToSingleNode(rrs, schema.getRandomDataNode(), ctx.getSql()); rrs.setFinishedRoute(true); return; } // RouterUtil.tryRouteForTables(schema, ctx, rrs, true, cachePool); SortedSet<RouteResultsetNode> nodeSet = new TreeSet<RouteResultsetNode>(); boolean isAllGlobalTable = RouterUtil.isAllGlobalTable(ctx, schema); for (RouteCalculateUnit unit : ctx.getRouteCalculateUnits()) { RouteResultset rrsTmp = RouterUtil.tryRouteForTables(schema, ctx, unit, rrs, true, cachePool); if (rrsTmp != null) { for (RouteResultsetNode node : rrsTmp.getNodes()) { nodeSet.add(node); } } if(isAllGlobalTable) {//都是全局表时只计算一遍路由 break; } } if(nodeSet.size() == 0) { String msg = " find no Route:" + ctx.getSql(); LOGGER.warn(msg); throw new SQLNonTransientException(msg); } RouteResultsetNode[] nodes = new RouteResultsetNode[nodeSet.size()]; int i = 0; for (Iterator<RouteResultsetNode> iterator = nodeSet.iterator(); iterator.hasNext();) { nodes[i] = (RouteResultsetNode) iterator.next(); i++; } rrs.setNodes(nodes); rrs.setFinishedRoute(true); } protected String getCurentDbType() { return JdbcConstants.MYSQL; } protected String getSql( RouteResultset rrs,SQLStatement stmt, boolean isNeedAddLimit) { if(getCurentDbType().equalsIgnoreCase("mysql")&&(isNeedChangeLimit(rrs)||isNeedAddLimit)) { return stmt.toString(); } return getCtx().getSql(); } protected boolean isNeedChangeLimit(RouteResultset rrs) { if(rrs.getNodes() == null) { return false; } else { if(rrs.getNodes().length > 1) { return true; } return false; } } private boolean isNeedCache(SchemaConfig schema, RouteResultset rrs, MySqlSelectQueryBlock mysqlSelectQuery, Map<String, Map<String, Set<ColumnRoutePair>>> allConditions) { if(ctx.getTables() == null || ctx.getTables().size() == 0 ) { return false; } TableConfig tc = schema.getTables().get(ctx.getTables().get(0)); if(tc==null ||(ctx.getTables().size() == 1 && tc.isGlobalTable()) ) {//|| (ctx.getTables().size() == 1) && tc.getRule() == null && tc.getDataNodes().size() == 1 return false; } else { //单表主键查询 if(ctx.getTables().size() == 1) { String tableName = ctx.getTables().get(0); String primaryKey = schema.getTables().get(tableName).getPrimaryKey(); // schema.getTables().get(ctx.getTables().get(0)).getParentKey() != null; if(ctx.getRouteCalculateUnit().getTablesAndConditions().get(tableName) != null && ctx.getRouteCalculateUnit().getTablesAndConditions().get(tableName).get(primaryKey) != null && tc.getDataNodes().size() > 1) {//有主键条件 return false; } } return true; } } /** * 单表且是全局表 * 单表且rule为空且nodeNodes只有一个 * @param schema * @param rrs * @param mysqlSelectQuery * @return */ private boolean isNeedAddLimit(SchemaConfig schema, RouteResultset rrs, MySqlSelectQueryBlock mysqlSelectQuery, Map<String, Map<String, Set<ColumnRoutePair>>> allConditions) { // ctx.getTablesAndConditions().get(key)) if(rrs.getLimitSize()>-1) { return false; }else if(schema.getDefaultMaxLimit() == -1) { return false; } else if (mysqlSelectQuery.getLimit() != null) {//语句中已有limit return false; } else if(ctx.getTables().size() == 1) { String tableName = ctx.getTables().get(0); TableConfig tableConfig = schema.getTables().get(tableName); if(tableConfig==null) { return schema.getDefaultMaxLimit() > -1; // 找不到则取schema的配置 } boolean isNeedAddLimit= tableConfig.isNeedAddLimit(); if(!isNeedAddLimit) { return false;//优先从配置文件取 } if(schema.getTables().get(tableName).isGlobalTable()) { return true; } String primaryKey = schema.getTables().get(tableName).getPrimaryKey(); // schema.getTables().get(ctx.getTables().get(0)).getParentKey() != null; if(allConditions.get(tableName) == null) {//无条件 return true; } if (allConditions.get(tableName).get(primaryKey) != null) {//条件中带主键 return false; } return true; } else if(rrs.hasPrimaryKeyToCache() && ctx.getTables().size() == 1){//只有一个表且条件中有主键,不需要limit了,因为主键只能查到一条记录 return false; } else {//多表或无表 return false; } } private String getAliaColumn(Map<String, String> aliaColumns,String column ){ String alia=aliaColumns.get(column); if (alia==null){ if(column.indexOf(".") < 0) { String col = "." + column; String col2 = ".`" + column+"`"; //展开aliaColumns,将<c.name,cname>之类的键值对展开成<c.name,cname>和<name,cname> for(Map.Entry<String, String> entry : aliaColumns.entrySet()) { if(entry.getKey().endsWith(col)||entry.getKey().endsWith(col2)) { if(entry.getValue() != null && entry.getValue().indexOf(".") > 0) { return column; } return entry.getValue(); } } } return column; } else { return alia; } } private String[] buildGroupByCols(List<SQLExpr> groupByItems,Map<String, String> aliaColumns) { String[] groupByCols = new String[groupByItems.size()]; for(int i= 0; i < groupByItems.size(); i++) { SQLExpr sqlExpr = groupByItems.get(i); String column; if(sqlExpr instanceof SQLIdentifierExpr ) { column=((SQLIdentifierExpr) sqlExpr).getName(); } else { SQLExpr expr = ((MySqlSelectGroupByExpr) sqlExpr).getExpr(); if (expr instanceof SQLName) { column = StringUtil.removeBackquote(((SQLName) expr).getSimpleName());//不要转大写 2015-2-10 sohudo StringUtil.removeBackquote(expr.getSimpleName().toUpperCase()); } else { column = StringUtil.removeBackquote(expr.toString()); } } int dotIndex=column.indexOf(".") ; if(dotIndex!=-1) { //此步骤得到的column必须是不带.的,有别名的用别名,无别名的用字段名 column=column.substring(dotIndex+1) ; } groupByCols[i] = getAliaColumn(aliaColumns,column);//column; } return groupByCols; } protected LinkedHashMap<String, Integer> buildOrderByCols(List<SQLSelectOrderByItem> orderByItems,Map<String, String> aliaColumns) { LinkedHashMap<String, Integer> map = new LinkedHashMap<String, Integer>(); for(int i= 0; i < orderByItems.size(); i++) { SQLOrderingSpecification type = orderByItems.get(i).getType(); //orderColumn只记录字段名称,因为返回的结果集是不带表名的。 SQLExpr expr = orderByItems.get(i).getExpr(); String col; if (expr instanceof SQLName) { col = ((SQLName)expr).getSimpleName(); } else { col =expr.toString(); } if(type == null) { type = SQLOrderingSpecification.ASC; } col=getAliaColumn(aliaColumns,col);//此步骤得到的col必须是不带.的,有别名的用别名,无别名的用字段名 map.put(col, type == SQLOrderingSpecification.ASC ? OrderCol.COL_ORDER_TYPE_ASC : OrderCol.COL_ORDER_TYPE_DESC); } return map; } private boolean isConditionAlwaysTrue(SQLStatement statement) { SQLSelectStatement selectStmt = (SQLSelectStatement)statement; SQLSelectQuery sqlSelectQuery = selectStmt.getSelect().getQuery(); if(sqlSelectQuery instanceof MySqlSelectQueryBlock) { MySqlSelectQueryBlock mysqlSelectQuery = (MySqlSelectQueryBlock)selectStmt.getSelect().getQuery(); SQLExpr expr = mysqlSelectQuery.getWhere(); Object o = WallVisitorUtils.getValue(expr); if(Boolean.TRUE.equals(o)) { return true; } return false; } else {//union return false; } } protected void setLimitIFChange(SQLStatement stmt, RouteResultset rrs, SchemaConfig schema, SQLBinaryOpExpr one, int firstrownum, int lastrownum) { rrs.setLimitStart(firstrownum); rrs.setLimitSize(lastrownum - firstrownum); LayerCachePool tableId2DataNodeCache = (LayerCachePool) MycatServer.getInstance().getCacheService().getCachePool("TableID2DataNodeCache"); try { tryRoute(schema, rrs, tableId2DataNodeCache); } catch (SQLNonTransientException e) { throw new RuntimeException(e); } if (isNeedChangeLimit(rrs)) { one.setRight(new SQLIntegerExpr(0)); String sql = stmt.toString(); rrs.changeNodeSqlAfterAddLimit(schema,getCurentDbType(), sql,0,lastrownum, false); //设置改写后的sql getCtx().setSql(sql); } } }