package com.ctrip.platform.dal.dao.strategy;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.ctrip.platform.dal.common.enums.ParameterDirection;
import com.ctrip.platform.dal.dao.DalHintEnum;
import com.ctrip.platform.dal.dao.DalHints;
import com.ctrip.platform.dal.dao.StatementParameter;
import com.ctrip.platform.dal.dao.StatementParameters;
import com.ctrip.platform.dal.dao.configure.DalConfigure;
/**
* This strategy locate both db and table shard by mod shard value.
* The checking sequency is same for both DB and table shard:
* IMPORTANT NOTE: The table name and columns are all case sensitive!
* Shard id
* Shard value
* Shard column values
* Parameters
* Entity fields
*
* @author jhhe
*
*/
public class ShardColModShardStrategy extends AbstractRWSeparationStrategy implements DalShardingStrategy {
/**
* Key used to declared columns for locating DB shard.
*/
public static final String COLUMNS = "columns";
/**
* Key used to declared mod for locating DB shard.
*/
public static final String MOD = "mod";
/**
* Key used to declared tables that qualified for table shard. That's not every table is sharded
*/
public static final String SHARDED_TABLES = "shardedTables";
/**
* Key used to declared columns for locating table shard.
*/
public static final String TABLE_COLUMNS = "tableColumns";
/**
* Key used to declared mod for locating table shard.
*/
public static final String TABLE_MOD = "tableMod";
public static final String SEPARATOR = "separator";
private String[] columns;
private Integer mod;
private Set<String> shardedTables = new HashSet<String>();
private String[] tableColumns;
private Integer tableMod;
private String separator;
/**
* columns are separated by ','
* @Override
*/
public void initialize(Map<String, String> settings) {
if(settings.containsKey(COLUMNS)) {
columns = settings.get(COLUMNS).split(",");
}
if(settings.containsKey(MOD)) {
mod = Integer.parseInt(settings.get(MOD));
}
if(settings.containsKey(SHARDED_TABLES)) {
String[] tables = settings.get(SHARDED_TABLES).split(",");
for(String table: tables)
shardedTables.add(table);
}
if(settings.containsKey(TABLE_COLUMNS)) {
tableColumns = settings.get(TABLE_COLUMNS).split(",");
}
if(settings.containsKey(TABLE_MOD)) {
tableMod = Integer.parseInt(settings.get(TABLE_MOD));
}
if(settings.containsKey(SEPARATOR)) {
separator = settings.get(SEPARATOR);
}
}
@Override
public boolean isShardingByDb() {
return columns != null;
}
public String locateDbShard(DalConfigure configure, String logicDbName,
DalHints hints) {
if(!isShardingByDb())
throw new RuntimeException(String.format("Logic Db %s is not configured to be shard by database", logicDbName));
String shard = hints.getShardId();
if(shard != null)
return shard;
// Shard value take the highest priority
if(hints.is(DalHintEnum.shardValue)) {
Long id = getLongValue(hints.get(DalHintEnum.shardValue));
return String.valueOf(id%mod);
}
shard = locateByShardCol(hints, columns, mod);
if(shard != null)
return shard;
shard = locateByParameters(hints, columns, mod);
if(shard != null)
return shard;
shard = locateByEntityFields(hints, columns, mod);
if(shard != null)
return shard;
return null;
}
@Override
public boolean isShardingByTable() {
return tableColumns != null;
}
@Override
public String locateTableShard(DalConfigure configure, String logicDbName,
DalHints hints) {
if(!isShardingByTable())
throw new RuntimeException(String.format("Logic Db %s is not configured to be shard by table", logicDbName));
String shard = hints.getTableShardId();
if(shard != null)
return shard;
// Shard value take the highest priority
if(hints.is(DalHintEnum.tableShardValue)) {
Long id = getLongValue(hints.get(DalHintEnum.tableShardValue));
return String.valueOf(id%tableMod);
}
shard = locateByShardCol(hints, tableColumns, tableMod);
if(shard != null)
return shard;
shard = locateByParameters(hints, tableColumns, tableMod);
if(shard != null)
return shard;
shard = locateByEntityFields(hints, tableColumns, tableMod);
if(shard != null)
return shard;
return null;
}
private String locateByParameters(DalHints hints, String[] columns, int mod) {
StatementParameters parameters = (StatementParameters)hints.get(DalHintEnum.parameters);
if(parameters != null) {
for(String column: columns) {
StatementParameter param = parameters.get(column, ParameterDirection.Input);
if(param != null && param.getValue() != null) {
Long id = getLongValue(param.getValue());
if(id != null) {
return String.valueOf(id%mod);
}
}
}
}
return null;
}
private String locateByShardCol(DalHints hints, String[] columns, int mod) {
Map<String, ?> shardColValues = (Map<String, ?>)hints.get(DalHintEnum.shardColValues);
if(shardColValues != null) {
for(String column: columns) {
Long id = getLongValue(shardColValues.get(column));
if(id != null) {
return String.valueOf(id%mod);
}
}
}
return null;
}
private String locateByEntityFields(DalHints hints, String[] columns, int mod) {
Map<String, ?> shardColValues = (Map<String, ?>)hints.get(DalHintEnum.fields);
if(shardColValues != null) {
for(String column: columns) {
Long id = getLongValue(shardColValues.get(column));
if(id != null) {
return String.valueOf(id%mod);
}
}
}
return null;
}
private Long getLongValue(Object value) {
if(value == null)
return null;
if(value instanceof Long)
return (Long)value;
if(value instanceof Number)
return ((Number)value).longValue();
if(value instanceof String)
return new Long((String)value);
throw new RuntimeException(String.format("Shard value: %s can not be recoganized as int value", value.toString()));
}
@Override
public boolean isShardingEnable(String tableName) {
return shardedTables.contains(tableName);
}
@Override
public String getTableShardSeparator() {
return separator;
}
}