package org.opencloudb.route.impl; import java.sql.SQLNonTransientException; import java.sql.SQLSyntaxErrorException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Random; import java.util.Set; import org.apache.log4j.Logger; import org.opencloudb.cache.LayerCachePool; import org.opencloudb.config.model.SchemaConfig; import org.opencloudb.config.model.TableConfig; import org.opencloudb.config.model.rule.RuleConfig; import org.opencloudb.mpp.ColumnRoutePair; import org.opencloudb.mpp.DDLParsInf; import org.opencloudb.mpp.DDLSQLAnalyser; import org.opencloudb.mpp.DeleteParsInf; import org.opencloudb.mpp.DeleteSQLAnalyser; import org.opencloudb.mpp.InsertParseInf; import org.opencloudb.mpp.InsertSQLAnalyser; import org.opencloudb.mpp.JoinRel; import org.opencloudb.mpp.SelectParseInf; import org.opencloudb.mpp.SelectSQLAnalyser; import org.opencloudb.mpp.ShardingParseInfo; import org.opencloudb.mpp.UpdateParsInf; import org.opencloudb.mpp.UpdateSQLAnalyser; import org.opencloudb.mysql.nio.handler.FetchStoreNodeOfChildTableHandler; import org.opencloudb.parser.SQLParserDelegate; import org.opencloudb.parser.fdb.FdbStrategy; import org.opencloudb.route.RouteResultset; import org.opencloudb.route.RouteResultsetNode; import org.opencloudb.route.function.AbstractPartionAlgorithm; import org.opencloudb.route.util.RouterUtil; import org.opencloudb.server.parser.ServerParse; import org.opencloudb.util.StringUtil; import com.foundationdb.sql.parser.CursorNode; import com.foundationdb.sql.parser.DDLStatementNode; import com.foundationdb.sql.parser.NodeTypes; import com.foundationdb.sql.parser.QueryTreeNode; import com.foundationdb.sql.parser.ResultSetNode; import com.foundationdb.sql.parser.SelectNode; public class FdbRouteStrategy extends AbstractRouteStrategy implements FdbStrategy { private static final Logger LOGGER = Logger.getLogger(FdbRouteStrategy.class); private static final Random rand = new Random(); private RouteResultset routeSelect(SchemaConfig schema,QueryTreeNode ast,RouteResultset rrs,String stmt, LayerCachePool cachePool) throws SQLNonTransientException{ ResultSetNode rsNode = ((CursorNode) ast).getResultSetNode(); if (rsNode instanceof SelectNode) { if (((SelectNode) rsNode).getFromList().isEmpty()) { // if it is a sql about system info, such as select charset etc. return RouterUtil.routeToSingleNode(rrs, schema.getRandomDataNode(),stmt); } } // standard SELECT operation SelectParseInf parsInf = new SelectParseInf(); parsInf.ctx = new ShardingParseInfo(); SelectSQLAnalyser.analyse(parsInf, ast); return tryRouteForTables(ast, true, rrs, schema, parsInf.ctx, stmt,cachePool); } private static RouteResultset routeChildTableInsert(SchemaConfig schema,TableConfig tc,QueryTreeNode ast,InsertParseInf parsInf,RouteResultset rrs,String stmt, LayerCachePool cachePool) throws SQLNonTransientException{ if (tc.isChildTable()) { String joinKeyVal = parsInf.columnPairMap.get(tc.getJoinKey()); if (joinKeyVal == null) { String inf = "joinKey not provided :" + tc.getJoinKey()+ "," + stmt; LOGGER.warn(inf); throw new SQLNonTransientException(inf); } // try to route by ER parent partion key RouteResultset theRrs = routeByERParentKey(stmt, rrs, tc,joinKeyVal); if (theRrs != null) { return theRrs; } // route by sql query root parent's datanode String findRootTBSql = tc.getLocateRTableKeySql() .toLowerCase() + joinKeyVal; if (LOGGER.isDebugEnabled()) { LOGGER.debug("find root parent's node sql "+ findRootTBSql); } FetchStoreNodeOfChildTableHandler fetchHandler = new FetchStoreNodeOfChildTableHandler(); String dn = fetchHandler.execute(schema.getName(),findRootTBSql, tc.getRootParent().getDataNodes()); if (dn == null) { throw new SQLNonTransientException("can't find (root) parent sharding node for sql:"+ stmt); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("found partion node for child table to insert "+ dn + " sql :" + stmt); } return RouterUtil.routeToSingleNode(rrs, dn, stmt); } return null; } private RouteResultset routeWithPartitionColumn(SchemaConfig schema,TableConfig tc,QueryTreeNode ast,InsertParseInf parsInf,RouteResultset rrs,String stmt, LayerCachePool cachePool) throws SQLNonTransientException{ String partColumn = tc.getPartitionColumn(); if (partColumn != null) { String sharindVal = parsInf.columnPairMap.get(partColumn); if (sharindVal != null) { Set<ColumnRoutePair> col2Val = new HashSet<ColumnRoutePair>(1); col2Val.add(new ColumnRoutePair(sharindVal)); return tryRouteForTable(ast, schema, rrs, false, stmt, tc, col2Val,null, cachePool); } else {// must provide sharding_id when insert String inf = "bad insert sql (sharding column:"+ partColumn + " not provided," + stmt; LOGGER.warn(inf); throw new SQLNonTransientException(inf); } } return null; } private RouteResultset routeNonGlobalInsert(SchemaConfig schema,TableConfig tc,QueryTreeNode ast,InsertParseInf parsInf,RouteResultset rrs,String stmt, LayerCachePool cachePool) throws SQLNonTransientException{ RouteResultset returnedSet=routeChildTableInsert(schema,tc,ast,parsInf,rrs,stmt,cachePool); if(returnedSet!=null){ return returnedSet; } return routeWithPartitionColumn(schema,tc,ast,parsInf,rrs,stmt,cachePool); } private RouteResultset routeInsert(SchemaConfig schema,QueryTreeNode ast,RouteResultset rrs,String stmt, LayerCachePool cachePool) throws SQLNonTransientException{ InsertParseInf parsInf = InsertSQLAnalyser.analyse(ast); if (parsInf.columnPairMap.isEmpty()) { String inf = "not supported inserq sql (columns not provided)," + stmt; LOGGER.warn(inf); throw new SQLNonTransientException(inf); } else if (parsInf.fromQryNode != null) { String inf = "not supported inserq sql (multi insert)," + stmt; LOGGER.warn(inf); throw new SQLNonTransientException(inf); } TableConfig tc = getTableConfig(schema, parsInf.tableName); // if is global table,set rss global table flag if (tc.getTableType() == TableConfig.TYPE_GLOBAL_TABLE) { rrs.setGlobalTable(true); }else {// for partition table ,partion column must provided RouteResultset returned=routeNonGlobalInsert(schema,tc,ast,parsInf,rrs,stmt, cachePool); if(returned!=null){ return returned; } } return tryRouteForTable(ast, schema, rrs, false, stmt, tc, null,null, cachePool); } private RouteResultset routeUpdate(SchemaConfig schema,QueryTreeNode ast,RouteResultset rrs,String stmt, LayerCachePool cachePool) throws SQLNonTransientException{ UpdateParsInf parsInf = UpdateSQLAnalyser.analyse(ast); // check if sharding columns is updated TableConfig tc = getTableConfig(schema, parsInf.tableName); // if is global table,set rss global table flag if (tc.getTableType() == TableConfig.TYPE_GLOBAL_TABLE) { rrs.setGlobalTable(true); } if (parsInf.columnPairMap.containsKey(tc.getPartitionColumn())) { throw new SQLNonTransientException("partion key can't be updated " + parsInf.tableName+ "->" + tc.getPartitionColumn()); } else if (parsInf.columnPairMap.containsKey(tc.getJoinKey())) { // ,child and parent tables relation column can't be updated throw new SQLNonTransientException("parent relation column can't be updated "+ parsInf.tableName + "->" + tc.getJoinKey()); } if (parsInf.ctx == null) {// no where condtion return tryRouteForTable(ast, schema, rrs, false, stmt, tc,null, null, cachePool); } else if (tc.getTableType() == TableConfig.TYPE_GLOBAL_TABLE && parsInf.ctx.tablesAndConditions.size() > 1) { throw new SQLNonTransientException("global table not supported multi table related update "+ parsInf.tableName); } return tryRouteForTables(ast, false, rrs, schema, parsInf.ctx,stmt, cachePool); } private RouteResultset routeDelete(SchemaConfig schema,QueryTreeNode ast,RouteResultset rrs,String stmt, LayerCachePool cachePool) throws SQLNonTransientException{ DeleteParsInf parsInf = DeleteSQLAnalyser.analyse(ast); // if is global table,set rss global table flag TableConfig tc = getTableConfig(schema, parsInf.tableName); if (tc.getTableType() == TableConfig.TYPE_GLOBAL_TABLE) { rrs.setGlobalTable(true); } if (parsInf.ctx != null) { return tryRouteForTables(ast, false, rrs, schema, parsInf.ctx,stmt, cachePool); } else {// no where condtion return tryRouteForTable(ast, schema, rrs, false, stmt, tc,null, null, cachePool); } } private RouteResultset routeDDL(SchemaConfig schema,QueryTreeNode ast,RouteResultset rrs,String stmt, LayerCachePool cachePool) throws SQLNonTransientException{ DDLParsInf parsInf = DDLSQLAnalyser.analyse(ast); TableConfig tc = getTableConfig(schema, parsInf.tableName); // if is global table,set rss global table flag if (tc.getTableType() == TableConfig.TYPE_GLOBAL_TABLE) { rrs.setGlobalTable(true); } return routeToMultiNode(schema, false, false, ast, rrs,tc.getDataNodes(), stmt); } /** * 根据执行语句判断数据路由 * * @param schema * 数据库名 * @param rrs * 数据路由集合 * @param stmt * 执行sql * @return RouteResultset数据路由集合 * @throws SQLSyntaxErrorException * @author mycat */ private RouteResultset analyseDoubleAtSgin(SchemaConfig schema, RouteResultset rrs, String stmt) throws SQLSyntaxErrorException { String upStmt = stmt.toUpperCase(); int atSginInd = upStmt.indexOf(" @@"); if (atSginInd > 0) { return routeToMultiNode(schema, false, false, null, rrs, schema.getMetaDataNodes(), stmt); } return RouterUtil.routeToSingleNode(rrs, schema.getRandomDataNode(), stmt); } public RouteResultset routeSystemInfo(SchemaConfig schema,int sqlType,String stmt,RouteResultset rrs) throws SQLSyntaxErrorException{ switch(sqlType){ case ServerParse.SHOW:// if origSQL is like show tables return analyseShowSQL(schema, rrs, stmt); case ServerParse.SELECT://if origSQL is like select @@ if(stmt.contains("@@")){ return analyseDoubleAtSgin(schema, rrs, stmt); } break; case ServerParse.DESCRIBE:// if origSQL is meta SQL, such as describe table int ind = stmt.indexOf(' '); return analyseDescrSQL(schema, rrs, stmt, ind + 1); } return null; } public RouteResultset routeNormalSqlWithAST(SchemaConfig schema,String stmt,RouteResultset rrs,String charset,LayerCachePool cachePool) throws SQLNonTransientException{ // to generate and expand AST QueryTreeNode ast = SQLParserDelegate.parse(stmt,charset == null ? "utf-8" : charset); switch(ast.getNodeType()){ case NodeTypes.CURSOR_NODE://select return routeSelect(schema,ast,rrs,stmt, cachePool); case NodeTypes.INSERT_NODE: return routeInsert(schema,ast,rrs,stmt, cachePool); case NodeTypes.UPDATE_NODE: return routeUpdate(schema,ast,rrs,stmt, cachePool); case NodeTypes.DELETE_NODE: return routeDelete(schema,ast,rrs,stmt, cachePool); } if (ast instanceof DDLStatementNode) { return routeDDL(schema,ast,rrs,stmt, cachePool); } else { LOGGER.info("TODO ,support sql type "+ ast.getClass().getCanonicalName() + " ," + stmt); return rrs; } } /** * 根据 ER分片规则获取路由集合 * * @param stmt * 执行的语句 * @param rrs * 数据路由集合 * @param tc * 表实体 * @param joinKeyVal * 连接属性 * @return RouteResultset(数据路由集合) * @throws SQLNonTransientException * @author mycat */ private static RouteResultset routeByERParentKey(String stmt, RouteResultset rrs, TableConfig tc, String joinKeyVal) throws SQLNonTransientException { // only has one parent level and ER parent key is parent // table's partition key if (tc.isSecondLevel() && tc.getParentTC().getPartitionColumn() .equals(tc.getParentKey())) { // using // parent // rule to // find // datanode Set<ColumnRoutePair> parentColVal = new HashSet<ColumnRoutePair>(1); ColumnRoutePair pair = new ColumnRoutePair(joinKeyVal); parentColVal.add(pair); Set<String> dataNodeSet = ruleCalculate(tc.getParentTC(), parentColVal); if (dataNodeSet.isEmpty() || dataNodeSet.size() > 1) { throw new SQLNonTransientException( "parent key can't find valid datanode ,expect 1 but found: " + dataNodeSet.size()); } String dn = dataNodeSet.iterator().next(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("found partion node (using parent partion rule directly) for child table to insert " + dn + " sql :" + stmt); } return RouterUtil.routeToSingleNode(rrs, dn, stmt); } return null; } /** * 获取语句中前关键字位置和占位个数表名位置 * * @param upStmt * 执行语句 * @param start * 开始位置 * @return int[]关键字位置和占位个数 * @author mycat */ private static int[] getSpecPos(String upStmt, int start) { String token1 = " FROM "; String token2 = " IN "; int tabInd1 = upStmt.indexOf(token1, start); int tabInd2 = upStmt.indexOf(token2, start); if (tabInd1 > 0) { if (tabInd2 < 0) { return new int[] { tabInd1, token1.length() }; } return (tabInd1 < tabInd2) ? new int[] { tabInd1, token1.length() } : new int[] { tabInd2, token2.length() }; } else { return new int[] { tabInd2, token2.length() }; } } /** * 根据表明获取表的对象 * * @param schema * 数据库名 * @param tableName * 表名 * @return TableConfig(表的对象) * @throws SQLNonTransientException * @author mycat */ private static TableConfig getTableConfig(SchemaConfig schema, String tableName) throws SQLNonTransientException { TableConfig tc = schema.getTables().get(tableName); if (tc == null) { String msg = "can't find table define in schema ,table:" + tableName + " schema:" + schema.getName(); LOGGER.warn(msg); throw new SQLNonTransientException(msg); } return tc; } /** * 获取开始位置后的 LIKE、WHERE 位置 如果不含 LIKE、WHERE 则返回执行语句的长度 * * @param upStmt * 执行sql * @param start * 开发位置 * @return int * @author mycat */ private static int getSpecEndPos(String upStmt, int start) { int tabInd = upStmt.indexOf(" LIKE ", start); if (tabInd < 0) { tabInd = upStmt.indexOf(" WHERE ", start); } if (tabInd < 0) { return upStmt.length(); } return tabInd; } /** * 根据show语句获取数据路由集合 * * @param schema * 数据库名 * @param rrs * 数据路由集合 * @param stmt * 执行的语句 * @return RouteResultset数据路由集合 * @throws SQLSyntaxErrorException * @author mycat */ public RouteResultset analyseShowSQL(SchemaConfig schema, RouteResultset rrs, String stmt) throws SQLSyntaxErrorException { String upStmt = stmt.toUpperCase(); int tabInd = upStmt.indexOf(" TABLES"); if (tabInd > 0) {// show tables int[] nextPost = getSpecPos(upStmt, 0); if (nextPost[0] > 0) {// remove db info int end = getSpecEndPos(upStmt, tabInd); if (upStmt.indexOf(" FULL") > 0) { stmt = "SHOW FULL TABLES" + stmt.substring(end); } else { stmt = "SHOW TABLES" + stmt.substring(end); } } return routeToMultiNode(schema, false, false, null, rrs,schema.getMetaDataNodes(), stmt); } // show index or column int[] indx = getSpecPos(upStmt, 0); if (indx[0] > 0) { // has table int[] repPos = { indx[0] + indx[1], 0 }; String tableName = RouterUtil.getTableName(stmt, repPos); // IN DB pattern int[] indx2 = getSpecPos(upStmt, indx[0] + indx[1] + 1); if (indx2[0] > 0) {// find LIKE OR WHERE repPos[1] = getSpecEndPos(upStmt, indx2[0] + indx2[1]); } stmt = stmt.substring(0, indx[0]) + " FROM " + tableName + stmt.substring(repPos[1]); RouterUtil.routeForTableMeta(rrs, schema, tableName, stmt); return rrs; } // show create table tableName int[] createTabInd = getCreateTablePos(upStmt, 0); if (createTabInd[0] > 0) { int tableNameIndex = createTabInd[0] + createTabInd[1]; if (upStmt.length() > tableNameIndex) { String tableName = stmt.substring(tableNameIndex).trim(); int ind2 = tableName.indexOf('.'); if (ind2 > 0) { tableName = tableName.substring(ind2 + 1); } RouterUtil.routeForTableMeta(rrs, schema, tableName, stmt); return rrs; } } return RouterUtil.routeToSingleNode(rrs, schema.getRandomDataNode(), stmt); } /** * 获取语句中前关键字位置和占位个数表名位置 * * @param upStmt * 执行语句 * @param start * 开始位置 * @return int[]关键字位置和占位个数 * @author mycat */ private static int[] getCreateTablePos(String upStmt, int start) { String token1 = " CREATE "; String token2 = " TABLE "; int createInd = upStmt.indexOf(token1, start); int tabInd = upStmt.indexOf(token2, start); // 既包含CREATE又包含TABLE,且CREATE关键字在TABLE关键字之前 if (createInd > 0 && tabInd > 0 && tabInd > createInd) { return new int[] { tabInd, token2.length() }; } else { return new int[] { -1, token2.length() };// 不满足条件时,只关注第一个返回值为-1,第二个任意 } } /** * 对Desc语句进行分析 返回数据路由集合 * * @param schema * 数据库名 * @param rrs * 数据路由集合 * @param stmt * 执行语句 * @param ind * 第一个' '的位置 * @return RouteResultset(数据路由集合) * @author mycat */ private static RouteResultset analyseDescrSQL(SchemaConfig schema, RouteResultset rrs, String stmt, int ind) { int[] repPos = { ind, 0 }; String tableName = RouterUtil.getTableName(stmt, repPos); stmt = stmt.substring(0, ind) + tableName + stmt.substring(repPos[1]); RouterUtil.routeForTableMeta(rrs, schema, tableName, stmt); return rrs; } private static String addSQLLmit(SchemaConfig schema, RouteResultset rrs, QueryTreeNode ast, String sql) throws SQLSyntaxErrorException { if (!rrs.hasPrimaryKeyToCache() && schema.getDefaultMaxLimit() != -1 && ast instanceof CursorNode && ((CursorNode) ast).getFetchFirstClause() == null) { String newstmt = SelectSQLAnalyser.addLimitCondtionForSelectSQL( rrs, (CursorNode) ast, schema.getDefaultMaxLimit()); if (newstmt != null) { return newstmt; } } return sql; } /** * 简单描述该方法的实现功能 * * @param ast * QueryTreeNode * @param schema * 数据库名 * @param rrs * 数据路由集合 * @param isSelect * 是否是select语句标志 * @param sql * 执行语句 * @param tc * 表实体 * @param ruleCol2Val * 一个ColumnRoutePair集合 * @param allColConds * 一个ColumnRoutePair集合 * @param cachePool * @return 一个数据路由集合 * @throws SQLNonTransientException * @author mycat */ private RouteResultset tryRouteForTable(QueryTreeNode ast, SchemaConfig schema, RouteResultset rrs, boolean isSelect, String sql, TableConfig tc, Set<ColumnRoutePair> ruleCol2Val, Map<String, Set<ColumnRoutePair>> allColConds, LayerCachePool cachePool) throws SQLNonTransientException { if (tc.getTableType() == TableConfig.TYPE_GLOBAL_TABLE && isSelect) { sql = addSQLLmit(schema, rrs, ast, sql); return RouterUtil.routeToSingleNode(rrs, tc.getRandomDataNode(), sql); } // no partion define or no where condtion for this table or no // partion column condtions boolean cache = isSelect; if (ruleCol2Val == null || ruleCol2Val.isEmpty()) { if (tc.isRuleRequired()) { throw new IllegalArgumentException("route rule for table " + tc.getName() + " is required: " + sql); } else if (allColConds != null && allColConds.size() == 1) { // try if can route by ER relation if (tc.isSecondLevel() && tc.getParentTC().getPartitionColumn() .equals(tc.getParentKey())) { Set<ColumnRoutePair> joinKeyPairs = allColConds.get(tc .getJoinKey()); if (joinKeyPairs != null) { Set<String> dataNodeSet = ruleCalculate( tc.getParentTC(), joinKeyPairs); if (dataNodeSet.isEmpty()) { throw new SQLNonTransientException( "parent key can't find any valid datanode "); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("found partion nodes (using parent partion rule directly) for child table to update " + Arrays.toString(dataNodeSet.toArray()) + " sql :" + sql); } if (dataNodeSet.size() > 1) { return routeToMultiNode(schema, isSelect, isSelect, ast, rrs, dataNodeSet, sql); } else { rrs.setCacheAble(true); return RouterUtil.routeToSingleNode(rrs, dataNodeSet .iterator().next(), sql); } } } // try by primary key if found in cache Set<ColumnRoutePair> primaryKeyPairs = allColConds.get(tc .getPrimaryKey()); if (primaryKeyPairs != null) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("try to find cache by primary key "); } cache = false; Set<String> dataNodes = new HashSet<String>( primaryKeyPairs.size()); boolean allFound = true; String tableKey = schema.getName() + '_' + tc.getName(); for (ColumnRoutePair pair : primaryKeyPairs) { String cacheKey = pair.colValue; String dataNode = (String) cachePool.get(tableKey, cacheKey); if (dataNode == null) { allFound = false; break; } else { dataNodes.add(dataNode); } } if (allFound) { return routeToMultiNode(schema, isSelect, isSelect, ast, rrs, dataNodes, sql); } // need cache primary key ->datanode relation if (isSelect && tc.getPrimaryKey() != null) { rrs.setPrimaryKey(tableKey + '.' + tc.getPrimaryKey()); } } } return routeToMultiNode(schema, isSelect, cache, ast, rrs, tc.getDataNodes(), sql); } // match table with where condtion of partion colum values Set<String> dataNodeSet = ruleCalculate(tc, ruleCol2Val); if (dataNodeSet.size() == 1) { rrs.setCacheAble(isSelect); return RouterUtil.routeToSingleNode(rrs, dataNodeSet.iterator().next(), sql); } else { return routeToMultiNode(schema, isSelect, isSelect, ast, rrs, dataNodeSet, sql); } } /** * 简单描述该方法的实现功能 * * @param ast * QueryTreeNode * @param isSelect * 是否是select语句 * @param rrs * 数据路由集合 * @param schema * 数据库名 the name of datebase * @param ctx * ShardingParseInfo(分片) * @param sql * 执行sql * @param cachePool * @return 一个数据路由集合 * @throws SQLNonTransientException * @author mycat */ private RouteResultset tryRouteForTables(QueryTreeNode ast, boolean isSelect, RouteResultset rrs, SchemaConfig schema, ShardingParseInfo ctx, String sql, LayerCachePool cachePool) throws SQLNonTransientException { Map<String, TableConfig> tables = schema.getTables(); Map<String, Map<String, Set<ColumnRoutePair>>> tbCondMap = ctx.tablesAndConditions; if (tbCondMap.size() == 1) { // only one table in this sql Map.Entry<String, Map<String, Set<ColumnRoutePair>>> entry = tbCondMap .entrySet().iterator().next(); TableConfig tc = getTableConfig(schema, entry.getKey()); if (tc.getRule() == null && tc.getDataNodes().size() == 1) { rrs.setCacheAble(isSelect); // 20140625 修复 配置为全局表单节点的语句不会自动加上limit sql = addSQLLmit(schema, rrs, ast, sql); return RouterUtil.routeToSingleNode(rrs, tc.getDataNodes().get(0), sql); } Map<String, Set<ColumnRoutePair>> colConds = entry.getValue(); return tryRouteForTable(ast, schema, rrs, isSelect, sql, tc, colConds.get(tc.getPartitionColumn()), colConds, cachePool); } else if (!ctx.joinList.isEmpty()) { for (JoinRel joinRel : ctx.joinList) { TableConfig rootc = schema.getJoinRel2TableMap().get( joinRel.joinSQLExp); if (rootc == null) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("can't find join relation in schema " + schema.getName() + " :" + joinRel.joinSQLExp + " maybe global table join"); } } else { if (rootc.getName().equals(joinRel.tableA)) { // table a is root table tbCondMap.remove(joinRel.tableB); } else if (rootc.getName().equals(joinRel.tableB)) { // table B is root table tbCondMap.remove(joinRel.tableA); } else if (tbCondMap.containsKey(rootc.getName())) { // contains root table in sql ,then remove all child tbCondMap.remove(joinRel.tableA); tbCondMap.remove(joinRel.tableB); } else {// both there A and B are not root table,remove any // one tbCondMap.remove(joinRel.tableA); } } } } if (tbCondMap.size() > 1) { Set<String> curRNodeSet = new LinkedHashSet<String>(); Set<String> routePairSet = new LinkedHashSet<String>();// 拆分字段后路由节点 String curTableName = null; Map<String, ArrayList<String>> globalTableDataNodesMap = new LinkedHashMap<String, ArrayList<String>>(); for (Entry<String, Map<String, Set<ColumnRoutePair>>> e : tbCondMap .entrySet()) { String tableName = e.getKey(); Map<String, Set<ColumnRoutePair>> col2ValMap = e.getValue(); TableConfig tc = tables.get(tableName); if (tc == null) { String msg = "can't find table define in schema " + tableName + " schema:" + schema.getName(); LOGGER.warn(msg); throw new SQLNonTransientException(msg); } else if (tc.getTableType() == TableConfig.TYPE_GLOBAL_TABLE) { // add to globalTablelist globalTableDataNodesMap .put(tc.getName(), tc.getDataNodes()); continue; } Collection<String> newDataNodes = null; String partColmn = tc.getPartitionColumn(); Set<ColumnRoutePair> col2Val = partColmn == null ? null : col2ValMap.get(partColmn); if (col2Val == null || col2Val.isEmpty()) { if (tc.isRuleRequired()) { throw new IllegalArgumentException( "route rule for table " + tableName + " is required: " + sql); } newDataNodes = tc.getDataNodes(); } else { // match table with where condtion of partion colum values newDataNodes = ruleCalculate(tc, col2Val); } if (curRNodeSet.isEmpty()) { curTableName = tc.getName(); curRNodeSet.addAll(newDataNodes); if (col2Val != null && !col2Val.isEmpty()) { routePairSet.addAll(newDataNodes); } } else { if (col2Val == null || col2Val.isEmpty()) { if (curRNodeSet.retainAll(newDataNodes) && routePairSet.isEmpty()) { String errMsg = "invalid route in sql, multi tables found but datanode has no intersection " + " sql:" + sql; LOGGER.warn(errMsg); throw new SQLNonTransientException(errMsg); } } else { if (routePairSet.isEmpty()) { routePairSet.addAll(newDataNodes); } else if (!checkIfValidMultiTableRoute(routePairSet, newDataNodes) || (curRNodeSet.retainAll(newDataNodes) && routePairSet .isEmpty())) { String errMsg = "invalid route in sql, " + routePairSet + " route to :" + Arrays.toString(routePairSet.toArray()) + " ,but " + tc.getName() + " to " + Arrays.toString(newDataNodes.toArray()) + " sql:" + sql; LOGGER.warn(errMsg); throw new SQLNonTransientException(errMsg); } } // if (!checkIfValidMultiTableRoute(curRNodeSet, // newDataNodes)) { // String errMsg = "invalid route in sql, " + curTableName // + " route to :" // + Arrays.toString(curRNodeSet.toArray()) // + " ,but " + tc.getName() + " to " // + Arrays.toString(newDataNodes.toArray()) // + " sql:" + sql; // LOGGER.warn(errMsg); // throw new SQLNonTransientException(errMsg); // } } } // only global table contains in sql if (!globalTableDataNodesMap.isEmpty() && curRNodeSet.isEmpty()) { ArrayList<String> resultList = new ArrayList<String>(); for (ArrayList<String> nodeList : globalTableDataNodesMap .values()) { if (resultList.isEmpty()) { resultList = nodeList; } else { if (resultList.retainAll(nodeList) && resultList.isEmpty()) { String errMsg = "invalid route in sql, multi global tables found but datanode has no intersection " + " sql:" + sql; LOGGER.warn(errMsg); throw new SQLNonTransientException(errMsg); } } } if (resultList.size() == 1) { rrs.setCacheAble(true); sql = addSQLLmit(schema, rrs, ast, sql); rrs = RouterUtil.routeToSingleNode(rrs, resultList.get(0), sql); } else { // mulit routes ,not cache route result rrs.setCacheAble(false); rrs = RouterUtil.routeToSingleNode(rrs, getRandomDataNode(resultList), sql); } return rrs; } else if (!globalTableDataNodesMap.isEmpty() && !curRNodeSet.isEmpty()) { // judge if global table contains all dataNodes of other tables for (Map.Entry<String, ArrayList<String>> entry : globalTableDataNodesMap .entrySet()) { if (!entry.getValue().containsAll(curRNodeSet)) { String errMsg = "invalid route in sql, " + curTableName + " route to :" + Arrays.toString(curRNodeSet.toArray()) + " ,but " + entry.getKey() + " to " + Arrays.toString(entry.getValue().toArray()) + " sql:" + sql; LOGGER.warn(errMsg); throw new SQLNonTransientException(errMsg); } } } if (curRNodeSet.size() > 1) { LOGGER.warn("multi route tables found in this sql ,tables:" + Arrays.toString(tbCondMap.keySet().toArray()) + " sql:" + sql); return routeToMultiNode(schema, isSelect, isSelect, ast, rrs, curRNodeSet, sql); } else { return RouterUtil.routeToSingleNode(rrs, curRNodeSet.iterator().next(), sql); } } else {// only one table Map.Entry<String, Map<String, Set<ColumnRoutePair>>> entry = tbCondMap .entrySet().iterator().next(); Map<String, Set<ColumnRoutePair>> allColValues = entry.getValue(); TableConfig tc = getTableConfig(schema, entry.getKey()); return tryRouteForTable(ast, schema, rrs, isSelect, sql, tc, allColValues.get(tc.getPartitionColumn()), allColValues, cachePool); } } /** * 判断两个表所在节点集合是否相等 * * @param curRNodeSet * 当前表所在的节点集合 * @param newNodeSet * 新表所在节点集合 * @return 返回fase(不相等)或true(相等) * @author mycat */ private static boolean checkIfValidMultiTableRoute(Set<String> curRNodeSet, Collection<String> newNodeSet) { if (curRNodeSet.size() != newNodeSet.size()) { return false; } else { for (String dataNode : newNodeSet) { if (!curRNodeSet.contains(dataNode)) { return false; } } } return true; } @Override public RouteResultset routeToMultiNode(SchemaConfig schema, boolean isSelect, boolean cache, QueryTreeNode ast, RouteResultset rrs, Collection<String> dataNodes, String stmt) throws SQLSyntaxErrorException { if (isSelect) { String sql = SelectSQLAnalyser.analyseMergeInf(rrs, ast, true, schema.getDefaultMaxLimit()); if (sql != null) { stmt = sql; } } RouteResultsetNode[] nodes = new RouteResultsetNode[dataNodes.size()]; int i = 0; for (String dataNode : dataNodes) { nodes[i++] = new RouteResultsetNode(dataNode, rrs.getSqlType(), stmt); } rrs.setCacheAble(cache); rrs.setNodes(nodes); return rrs; } private static String getRandomDataNode(ArrayList<String> dataNodes) { int index = Math.abs(rand.nextInt()) % dataNodes.size(); return dataNodes.get(index); } /** * @return dataNodeIndex -> [partitionKeysValueTuple+] */ private static Set<String> ruleCalculate(TableConfig tc, Set<ColumnRoutePair> colRoutePairSet) { Set<String> routeNodeSet = new LinkedHashSet<String>(); String col = tc.getRule().getColumn(); RuleConfig rule = tc.getRule(); AbstractPartionAlgorithm algorithm = rule.getRuleAlgorithm(); for (ColumnRoutePair colPair : colRoutePairSet) { if (colPair.colValue != null) { Integer nodeIndx = algorithm.calculate(colPair.colValue); if (nodeIndx == null) { throw new IllegalArgumentException( "can't find datanode for sharding column:" + col + " val:" + colPair.colValue); } else { String dataNode = tc.getDataNodes().get(nodeIndx); routeNodeSet.add(dataNode); colPair.setNodeId(nodeIndx); } } else if (colPair.rangeValue != null) { Integer[] nodeRange = algorithm.calculateRange( String.valueOf(colPair.rangeValue.beginValue), String.valueOf(colPair.rangeValue.endValue)); if (nodeRange != null) { /** * 不能确认 colPair的 nodeid是否会有其它影响 */ if (nodeRange.length == 0) { routeNodeSet.addAll(tc.getDataNodes()); } else { ArrayList<String> dataNodes = tc.getDataNodes(); String dataNode = null; for (Integer nodeId : nodeRange) { dataNode = dataNodes.get(nodeId); routeNodeSet.add(dataNode); } } } } } return routeNodeSet; } public static void main(String[] args) { String origSQL="insert into user(name,code,password)values('name','code','password')"; int firstLeftBracketIndex = origSQL.indexOf("(") + 1; int firstRightBracketIndex = origSQL.indexOf(")"); int lastLeftBracketIndex = origSQL.lastIndexOf("(") + 1; String tableName = StringUtil.getTableName(origSQL).toUpperCase(); String primaryKey="ID"; String newSQL=null; long s=0; s=System.currentTimeMillis(); for(int i=0;i<100_0000;i++){ int primaryKeyLength=primaryKey.length(); int insertSegOffset=firstLeftBracketIndex; String mycatSeqPrefix="next value for MYCATSEQ_"; int mycatSeqPrefixLength=mycatSeqPrefix.length(); int tableNameLength=tableName.length(); StringBuilder newSQLBuilder=new StringBuilder(origSQL).insert(insertSegOffset,primaryKey); insertSegOffset+=primaryKeyLength; newSQLBuilder.insert(insertSegOffset,','); insertSegOffset=lastLeftBracketIndex+primaryKeyLength+1; newSQLBuilder.insert(insertSegOffset,mycatSeqPrefix); insertSegOffset+=mycatSeqPrefixLength; newSQLBuilder.insert(insertSegOffset, tableName).insert(insertSegOffset+tableNameLength, ','); newSQL=newSQLBuilder.toString(); } System.out.println(System.currentTimeMillis()-s); System.out.println(newSQL); s=System.currentTimeMillis(); for(int i=0;i<100_0000;i++){ int primaryKeyLength=primaryKey.length(); String mycatSeqPrefix="next value for MYCATSEQ_"; int mycatSeqPrefixLength=mycatSeqPrefix.length(); int tableNameLength=tableName.length(); StringBuilder newSQLBuilder=new StringBuilder( origSQL.length()+primaryKeyLength+mycatSeqPrefixLength+tableNameLength+4);//to prevent StringBuilder to expand capacity newSQLBuilder.append(origSQL,0,firstLeftBracketIndex) .append(primaryKey).append(',') .append(origSQL,firstLeftBracketIndex,lastLeftBracketIndex) .append(mycatSeqPrefix).append(tableName).append(',') .append(origSQL,lastLeftBracketIndex,origSQL.length()); newSQL=newSQLBuilder.toString(); } System.out.println(System.currentTimeMillis()-s); System.out.println(newSQL); s=System.currentTimeMillis(); for(int i=0;i<100_0000;i++){ int primaryKeyLength=primaryKey.length(); int insertSegOffset=firstLeftBracketIndex; String mycatSeqPrefix="next value for MYCATSEQ_"; int mycatSeqPrefixLength=mycatSeqPrefix.length(); int tableNameLength=tableName.length(); StringBuilder newSQLBuilder=new StringBuilder((origSQL.length()+primaryKeyLength+mycatSeqPrefixLength+tableNameLength+2)*2+2)//to prevent StringBuilder to expand capacity .append(origSQL).insert(insertSegOffset,primaryKey); insertSegOffset+=primaryKeyLength; newSQLBuilder.insert(insertSegOffset,','); insertSegOffset=lastLeftBracketIndex+primaryKeyLength+1; newSQLBuilder.insert(insertSegOffset,mycatSeqPrefix); insertSegOffset+=mycatSeqPrefixLength; newSQLBuilder.insert(insertSegOffset, tableName).insert(insertSegOffset+tableNameLength, ','); newSQL=newSQLBuilder.toString(); } System.out.println(System.currentTimeMillis()-s); System.out.println(newSQL); s=System.currentTimeMillis(); for(int i=0;i<100_0000;i++){ int primaryKeyLength=primaryKey.length(); int insertSegOffset=firstLeftBracketIndex; String mycatSeqPrefix="next value for MYCATSEQ_"; int mycatSeqPrefixLength=mycatSeqPrefix.length(); int tableNameLength=tableName.length(); StringBuilder newSQLBuilder=new StringBuilder(origSQL.length()+primaryKeyLength+mycatSeqPrefixLength+tableNameLength+4)//to prevent StringBuilder to expand capacity .append(origSQL).insert(insertSegOffset,primaryKey); insertSegOffset+=primaryKeyLength; newSQLBuilder.insert(insertSegOffset,','); insertSegOffset=lastLeftBracketIndex+primaryKeyLength+1; newSQLBuilder.insert(insertSegOffset,mycatSeqPrefix); insertSegOffset+=mycatSeqPrefixLength; newSQLBuilder.insert(insertSegOffset, tableName).insert(insertSegOffset+tableNameLength, ','); newSQL=newSQLBuilder.toString(); } System.out.println(System.currentTimeMillis()-s); System.out.println(newSQL); s=System.currentTimeMillis(); for(int i=0;i<100_0000;i++){ newSQL=origSQL.substring(0, firstLeftBracketIndex)+ primaryKey+','+ origSQL.substring(firstLeftBracketIndex,lastLeftBracketIndex)+ "next value for MYCATSEQ_"+tableName+','+ origSQL.substring(lastLeftBracketIndex,origSQL.length()); } System.out.println(System.currentTimeMillis()-s); System.out.println(newSQL); s=System.currentTimeMillis(); for(int i=0;i<100_0000;i++){ StringBuilder segSQL=new StringBuilder(); StringBuilder newSQLBuilder=new StringBuilder(origSQL).insert(firstLeftBracketIndex,segSQL.append(primaryKey).append(',')); segSQL.delete(0, segSQL.length()); newSQLBuilder.insert(lastLeftBracketIndex+primaryKey.length()+1, segSQL.append("next value for MYCATSEQ_").append(tableName).append(',')); newSQL=newSQLBuilder.toString(); } System.out.println(System.currentTimeMillis()-s); System.out.println(newSQL); s=System.currentTimeMillis(); for(int i=0;i<100_0000;i++){ int primaryKeyLength=primaryKey.length(); int insertSegOffset=firstLeftBracketIndex; String mycatSeqPrefix="next value for MYCATSEQ_"; int mycatSeqPrefixLength=mycatSeqPrefix.length(); int tableNameLength=tableName.length(); char[] newSQLBuf=new char[origSQL.length()+primaryKeyLength+mycatSeqPrefixLength+tableNameLength+2]; origSQL.getChars(0, firstLeftBracketIndex, newSQLBuf, 0); primaryKey.getChars(0,primaryKeyLength,newSQLBuf,insertSegOffset); insertSegOffset+=primaryKeyLength; newSQLBuf[insertSegOffset]=','; insertSegOffset++; origSQL.getChars(firstLeftBracketIndex,lastLeftBracketIndex,newSQLBuf,insertSegOffset); insertSegOffset+=lastLeftBracketIndex-firstLeftBracketIndex; mycatSeqPrefix.getChars(0, mycatSeqPrefixLength, newSQLBuf, insertSegOffset); insertSegOffset+=mycatSeqPrefixLength; tableName.getChars(0,tableNameLength,newSQLBuf,insertSegOffset); insertSegOffset+=tableNameLength; newSQLBuf[insertSegOffset]=','; insertSegOffset++; origSQL.getChars(lastLeftBracketIndex, origSQL.length(), newSQLBuf, insertSegOffset); newSQL=new String(newSQLBuf); } System.out.println(System.currentTimeMillis()-s); System.out.println(newSQL); s=System.currentTimeMillis(); for(int i=0;i<100_0000;i++){ StringBuilder sb = new StringBuilder(); sb.append(origSQL.substring(0, firstLeftBracketIndex)); sb.append(primaryKey); sb.append(","); sb.append(origSQL.substring(firstLeftBracketIndex, lastLeftBracketIndex)); sb.append("next value for MYCATSEQ_"); sb.append(tableName); sb.append(","); sb.append(origSQL.substring(lastLeftBracketIndex, origSQL.length())); newSQL=sb.toString(); } System.out.println(System.currentTimeMillis()-s); System.out.println(newSQL); s=System.currentTimeMillis(); for(int i=0;i<10_0000;i++){ String insertFieldsSQL = origSQL.substring( firstLeftBracketIndex, firstRightBracketIndex); String[] insertFields = insertFieldsSQL.split(","); boolean isPrimaryKeyInFields = false; for (String field : insertFields) { if (field.toUpperCase().equals(primaryKey)) { isPrimaryKeyInFields = true; break; } } } System.out.println(System.currentTimeMillis()-s); s=System.currentTimeMillis(); for(int i=0;i<10_0000;i++){ boolean result=false; int pkOffset=0; int primaryKeyLength=primaryKey.length(); String upperSQL=origSQL.substring(firstLeftBracketIndex,firstRightBracketIndex).toUpperCase(); do{ int pkStart=upperSQL.indexOf(primaryKey, pkOffset); if(pkStart>=0 && pkStart<firstRightBracketIndex){ char pkSide=origSQL.charAt(pkStart-1); if(pkSide<=' ' || pkSide=='`' || pkSide==',' || pkSide=='('){ pkSide=origSQL.charAt(pkStart+primaryKey.length()); result=pkSide<=' ' || pkSide=='`' || pkSide==',' || pkSide==')'; } }else{ break; } pkOffset+=primaryKeyLength; }while(!result); } System.out.println(System.currentTimeMillis()-s); s=System.currentTimeMillis(); for(int i=0;i<100_0000;i++){ String upperSQL=origSQL.substring(17,35).toUpperCase(); } System.out.println(System.currentTimeMillis()-s); s=System.currentTimeMillis(); for(int i=0;i<100_0000;i++){ String upperSQL=origSQL.toUpperCase(); } System.out.println(System.currentTimeMillis()-s); } }