@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;
  }
 /**
  * 判断两个表所在节点集合是否相等
  *
  * @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;
 }
  /**
   * 简单描述该方法的实现功能
   *
   * @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, List<String>> globalTableDataNodesMap = new LinkedHashMap<String, List<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 = RouterUtil.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()) {
        List<String> resultList = Lists.newArrayList();
        for (List<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, List<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);
    }
  }