package org.opencloudb.route.impl;
import java.sql.SQLNonTransientException;
import java.sql.SQLSyntaxErrorException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
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.mpp.ColumnRoutePair;
import org.opencloudb.parser.druid.DruidParser;
import org.opencloudb.parser.druid.DruidShardingParseInfo;
import org.opencloudb.parser.druid.DruidStrategy;
import org.opencloudb.parser.druid.impl.DefaultDruidParser;
import org.opencloudb.parser.druid.impl.DruidAlterTableParser;
import org.opencloudb.parser.druid.impl.DruidCreateTableParser;
import org.opencloudb.parser.druid.impl.DruidDeleteParser;
import org.opencloudb.parser.druid.impl.DruidInsertParser;
import org.opencloudb.parser.druid.impl.DruidSelectParser;
import org.opencloudb.parser.druid.impl.DruidUpdateParser;
import org.opencloudb.route.RouteResultset;
import org.opencloudb.route.util.RouterUtil;
import org.opencloudb.server.parser.ServerParse;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.statement.SQLSelectStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlAlterTableStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlDeleteStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlInsertStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlUpdateStatement;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser;
public class DruidMysqlRouteStrategy extends AbstractRouteStrategy implements DruidStrategy {
private static final Logger LOGGER = Logger.getLogger(DruidMysqlRouteStrategy.class);
@Override
public RouteResultset routeNormalSqlWithAST(SchemaConfig schema,
String stmt, RouteResultset rrs, String charset,
LayerCachePool cachePool) throws SQLNonTransientException {
MySqlStatementParser parser = new MySqlStatementParser(stmt);
SQLStatement statement = parser.parseStatement();
if(statement instanceof SQLSelectStatement) {
return analyseSelectSQL(schema, rrs, statement);
} else if(statement instanceof MySqlInsertStatement) {
return analyseInsertSQL(schema, rrs, statement);
} else if(statement instanceof MySqlDeleteStatement) {
return analyseDeleteSQL(schema, rrs, statement);
} else if(statement instanceof MySqlCreateTableStatement) {
return analyseCreateTable(schema, rrs, statement);
} else if(statement instanceof MySqlUpdateStatement) {
return analyseUpdateSQL(schema, rrs, statement);
} else if(statement instanceof MySqlAlterTableStatement) {
return analyseAlterTable(schema, rrs, statement);
} else {
return analyseDefault(schema, rrs, statement);
}
// String statementType = "";
// if(statement instanceof SQLSelectStatement) {
// statementType = StatementType.SELECT;
// }
// else if(statement instanceof MySqlInsertStatement) {
// statementType = StatementType.INSERT;
// }
// else if(statement instanceof MySqlDeleteStatement) {
// statementType = StatementType.DELETE;
// } else if (statement instanceof MySqlCreateTableStatement) {
// statementType = StatementType.CREATE_TABLE;
// }else if(statement instanceof MySqlUpdateStatement) {
// return analyseDefault(schema, rrs, statement);
// }
//
//
// DruidParser schemaParser = DruidParserFactory.getDruidParser(statementType);
// schemaParser.parser(schema, statement);
// rrs.setStatement(stmt.toString());
// rrs.setStatement( schemaParser.getSql());
// //没有from的的select语句
// if(schemaParser.getTables().size() == 0) {
// return RouterUtil.routeToSingleNode(rrs, schema.getRandomDataNode(),schemaParser.getSql());
// }
// return tryRouteForTables(schema, schemaParser.getCtx(), schemaParser.getTables(), rrs, true);
}
/**
*
*/
@Override
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 = RouterUtil.getSpecPos(upStmt, 0);
if (nextPost[0] > 0) {// remove db info
int end = RouterUtil.getSpecEndPos(upStmt, tabInd);
if (upStmt.indexOf(" FULL") > 0) {
stmt = "SHOW FULL TABLES" + stmt.substring(end);
} else {
stmt = "SHOW TABLES" + stmt.substring(end);
}
}
return RouterUtil.routeToMultiNode(false, rrs, schema.getMetaDataNodes(), stmt);
}
// show index or column
int[] indx = RouterUtil.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 = RouterUtil.getSpecPos(upStmt, indx[0] + indx[1] + 1);
if (indx2[0] > 0) {// find LIKE OR WHERE
repPos[1] = RouterUtil.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 = RouterUtil.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);
}
/**
*
* 为select语句找路由
*/
@Override
public RouteResultset analyseSelectSQL(SchemaConfig schema,
RouteResultset rrs, SQLStatement stmt) throws SQLNonTransientException {
DruidParser parser = new DruidSelectParser();
parser.parser(schema, rrs, stmt);
rrs.setStatement(parser.getCtx().getSql());
//没有from的的select语句
if(parser.getCtx().getTables().size() == 0) {
return RouterUtil.routeToSingleNode(rrs, schema.getRandomDataNode(),parser.getCtx().getSql());
}
return tryRouteForTables(schema, parser.getCtx(), parser.getCtx().getTables(), rrs, true);
}
/**
* 为insert语句找路由
*/
@Override
public RouteResultset analyseInsertSQL(SchemaConfig schema,
RouteResultset rrs, SQLStatement stmt) throws SQLNonTransientException {
DruidParser parser = new DruidInsertParser();
parser.parser(schema, rrs, stmt);
return tryRouteForTables(schema, parser.getCtx(), parser.getCtx().getTables(), rrs, false);
}
/**
* 为alter table语句找路由
*/
@Override
public RouteResultset analyseAlterTable(SchemaConfig schema,
RouteResultset rrs, SQLStatement stmt) throws SQLNonTransientException {
DruidAlterTableParser parser = new DruidAlterTableParser();
parser.parser(schema, rrs, stmt);
return tryRouteForTables(schema, parser.getCtx(), parser.getCtx().getTables(), rrs, false);
}
/**
* 为update语句找路由
*/
@Override
public RouteResultset analyseUpdateSQL(SchemaConfig schema,
RouteResultset rrs, SQLStatement stmt) throws SQLNonTransientException {
DruidParser parser = new DruidUpdateParser();
parser.parser(schema, rrs, stmt);
return tryRouteForTables(schema, parser.getCtx(), parser.getCtx().getTables(), rrs, false);
}
/**
* 为delete语句找路由
*/
@Override
public RouteResultset analyseDeleteSQL(SchemaConfig schema,
RouteResultset rrs, SQLStatement stmt) throws SQLNonTransientException {
DruidParser parser = new DruidDeleteParser();
parser.parser(schema, rrs, stmt);
return tryRouteForTables(schema, parser.getCtx(), parser.getCtx().getTables(), rrs, false);
}
/**
* 为create table语句找路由
*/
@Override
public RouteResultset analyseCreateTable(SchemaConfig schema,
RouteResultset rrs, SQLStatement stmt) throws SQLNonTransientException {
DruidParser parser = new DruidCreateTableParser();
parser.parser(schema, rrs, stmt);
if(parser.getCtx().getTables().size() == 0) {
return RouterUtil.routeToSingleNode(rrs, schema.getRandomDataNode(), parser.getCtx().getSql());
}
return tryRouteForTables(schema, parser.getCtx(), parser.getCtx().getTables(), rrs, false);
}
/**
* 不属于上面类型的语句,默认都走该分支找路由
*/
@Override
public RouteResultset analyseDefault(SchemaConfig schema,
RouteResultset rrs, SQLStatement stmt) throws SQLNonTransientException {
DruidParser parser = new DefaultDruidParser();
parser.parser(schema, rrs, stmt);
rrs.setStatement(parser.getCtx().getSql());
//没有from的的select语句
if(parser.getCtx().getTables().size() == 0) {
return RouterUtil.routeToSingleNode(rrs, schema.getRandomDataNode(),parser.getCtx().getSql());
}
return tryRouteForTables(schema, parser.getCtx(), parser.getCtx().getTables(), rrs, false);
}
/**
* 单表路由
* @param schema
* @param ctx
* @param tableName
* @param rrs
* @param isSelect
* @return
* @throws SQLNonTransientException
*/
private static RouteResultset tryRouteForOneTable(SchemaConfig schema, DruidShardingParseInfo ctx, String tableName, RouteResultset rrs,
boolean isSelect) throws SQLNonTransientException {
TableConfig tc = schema.getTables().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);
}
if(tc.isGlobalTable()) {//全局表
if(isSelect) {
return RouterUtil.routeToSingleNode(rrs, tc.getRandomDataNode(),ctx.getSql());
} else {
return RouterUtil.routeToMultiNode(false, rrs, tc.getDataNodes(), ctx.getSql());
}
} else {//单表或者分库表
if(tc.getPartitionColumn() == null) {//单表
return RouterUtil.routeToSingleNode(rrs, tc.getDataNodes().get(0),ctx.getSql());
} else {
//每个表对应的路由映射
Map<String,Set<String>> tablesRouteMap = new HashMap<String,Set<String>>();
findRouteForShardingTables(schema, ctx, tablesRouteMap);
if(tablesRouteMap.get(tableName) == null) {
return RouterUtil.routeToMultiNode(false, rrs, tc.getDataNodes(), ctx.getSql());
} else {
return RouterUtil.routeToMultiNode(false, rrs, tablesRouteMap.get(tableName), ctx.getSql());
}
}
}
}
/**
* 多表路由
* @param schema
* @param ctx
* @param tables
* @param rrs
* @param isSelect
* @return
* @throws SQLNonTransientException
*/
private static RouteResultset tryRouteForTables(SchemaConfig schema, DruidShardingParseInfo ctx, List<String> tables, RouteResultset rrs,
boolean isSelect) throws SQLNonTransientException {
//只有一个表的
if(tables.size() == 1) {
tryRouteForOneTable(schema, ctx, tables.get(0), rrs, isSelect);
}
Set<String> retNodesSet = new HashSet<String>();
//每个表对应的路由映射
Map<String,Set<String>> tablesRouteMap = new HashMap<String,Set<String>>();
//分库解析信息不为空
if(ctx != null) {
//为分库表找路由
findRouteForShardingTables(schema, ctx, tablesRouteMap);
}
//为全局表和单库表找路由
for(String tableName : tables) {
TableConfig tableConfig = schema.getTables().get(tableName.toUpperCase());
if(tableConfig == null) {
String msg = "can't find table define in schema "+ tableName + " schema:" + schema.getName();
LOGGER.warn(msg);
throw new SQLNonTransientException(msg);
}
if(tableConfig.isGlobalTable()) {//全局表
if(tablesRouteMap.get(tableName) == null) {
tablesRouteMap.put(tableName, new HashSet<String>());
}
tablesRouteMap.get(tableName).addAll(tableConfig.getDataNodes());
} else if(tablesRouteMap.get(tableName) == null) { //余下的表都是单库表
tablesRouteMap.put(tableName, new HashSet<String>());
tablesRouteMap.get(tableName).addAll(tableConfig.getDataNodes());
}
}
//所有表路由汇总分析,求交集
if(tables.size() == 1) {
TableConfig tableConfig = schema.getTables().get(tables.get(0).toUpperCase());
if(tableConfig.isGlobalTable()) {
if(isSelect) {
return RouterUtil.routeToSingleNode(rrs, schema.getRandomDataNode(),ctx.getSql());
}
}
}
boolean isFirstAdd = true;
for(Map.Entry<String, Set<String>> entry : tablesRouteMap.entrySet()) {
if(entry.getValue() == null || entry.getValue().size() == 0) {
throw new SQLNonTransientException("parent key can't find any valid datanode ");
} else {
if(isFirstAdd) {
retNodesSet.addAll(entry.getValue());
isFirstAdd = false;
} else {
retNodesSet.retainAll(entry.getValue());
if(retNodesSet.size() == 0) {//两个表的路由无交集
String errMsg = "invalid route in sql, multi tables found but datanode has no intersection "
+ " sql:" + ctx.getSql();
LOGGER.warn(errMsg);
throw new SQLNonTransientException(errMsg);
}
}
}
}
if(retNodesSet != null && retNodesSet.size() > 0) {
RouterUtil.routeToMultiNode(isSelect, rrs, retNodesSet, ctx.getSql());
}
return rrs;
}
/**
* 处理分库表路由
* @param schema
* @param ctx
* @param tablesRouteMap
* @throws SQLNonTransientException
*/
private static void findRouteForShardingTables(SchemaConfig schema,
DruidShardingParseInfo ctx, Map<String, Set<String>> tablesRouteMap)
throws SQLNonTransientException {
Map<String, Map<String, Set<ColumnRoutePair>>> tablesAndConditions = ctx.getTablesAndConditions();
//为分库表找路由
for(Map.Entry<String, Map<String, Set<ColumnRoutePair>>> entry : tablesAndConditions.entrySet()) {
String tableName = entry.getKey().toUpperCase();
TableConfig tableConfig = schema.getTables().get(tableName);
//全局表或者不分库的表略过(全局表后面再计算)
if(tableConfig.isGlobalTable() || schema.getTables().get(tableName).getDataNodes().size() == 1) {
continue;
} else {//需要分库的表
Map<String, Set<ColumnRoutePair>> columnsMap = entry.getValue();
//是否找到了拆分字段
boolean isFoundShardingCol = false;
for(Map.Entry<String, Set<ColumnRoutePair>> condition : columnsMap.entrySet()) {
String colName = condition.getKey();
//条件字段是拆分字段
if(colName.equals(tableConfig.getPartitionColumn())) {
isFoundShardingCol = true;
Set<ColumnRoutePair> columnPairs = condition.getValue();
for(ColumnRoutePair pair : columnPairs) {
if(pair.colValue != null) {
Integer nodeIndex = tableConfig.getRule().getRuleAlgorithm().calculate(pair.colValue);
if(nodeIndex == null) {
String msg = "can't find any valid datanode :" + tableConfig.getName()
+ " -> " + tableConfig.getPartitionColumn() + " -> " + pair.colValue;
LOGGER.warn(msg);
throw new SQLNonTransientException(msg);
}
String node = tableConfig.getDataNodes().get(nodeIndex);
if(node != null) {
if(tablesRouteMap.get(tableName) == null) {
tablesRouteMap.put(tableName, new HashSet<String>());
}
tablesRouteMap.get(tableName).add(node);
}
}
if(pair.rangeValue != null) {
Integer[] nodeIndexs = tableConfig.getRule().getRuleAlgorithm()
.calculateRange(pair.rangeValue.beginValue.toString(), pair.rangeValue.endValue.toString());
for(Integer idx : nodeIndexs) {
String node = tableConfig.getDataNodes().get(idx);
if(node != null) {
if(tablesRouteMap.get(tableName) == null) {
tablesRouteMap.put(tableName, new HashSet<String>());
}
tablesRouteMap.get(tableName).add(node);
}
}
}
}
} else {//条件字段不是拆分字段,略过
continue;
}
}
//找到了拆分字段,但是一个节点都没找到
if(isFoundShardingCol) {
if(tablesRouteMap.get(tableName).size() == 0) {
throw new SQLNonTransientException("parent key can't find any valid datanode ");
}
} else {//没找到拆分字段,该表的所有节点都路由
if(tablesRouteMap.get(tableName) == null) {
tablesRouteMap.put(tableName, new HashSet<String>());
}
tablesRouteMap.get(tableName).addAll(tableConfig.getDataNodes());
}
}
}
}
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;
}
/**
* 对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;
}
/**
* 根据执行语句判断数据路由
*
* @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 RouterUtil.routeToMultiNode(false, rrs,
schema.getMetaDataNodes(), stmt);
}
return RouterUtil.routeToSingleNode(rrs, schema.getRandomDataNode(), stmt);
}
}