package com.taobao.tddl.optimizer.parse.hint;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.apache.commons.lang.StringUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.taobao.tddl.common.exception.TddlRuntimeException;
import com.taobao.tddl.common.jdbc.ParameterContext;
import com.taobao.tddl.common.model.ExtraCmd;
import com.taobao.tddl.common.utils.TStringUtil;
import com.taobao.tddl.rule.model.sqljep.Comparative;
import com.taobao.tddl.rule.model.sqljep.ComparativeAND;
import com.taobao.tddl.rule.model.sqljep.ComparativeBaseList;
import com.taobao.tddl.rule.model.sqljep.ComparativeOR;
import com.taobao.tddl.rule.utils.ComparativeStringAnalyser;
import com.taobao.tddl.common.utils.logger.Logger;
import com.taobao.tddl.common.utils.logger.LoggerFactory;
/**
* 简单hint解析
*
* <pre>
* 完整的例子:
*
* \/*+TDDL({"type":"condition","vtab":"vtabxxx","params":[{"relation":"and","expr":["pk>4","pk:long<10"],"paramtype":"int"}],"extra":"{"ALLOW_TEMPORARY_TABLE"="TRUE"})*\/
* 1. type取值是condition和direct
* 2. vtab取值是rule规则中的逻辑表名
* 3. params对应于rule规则中的分库字段的条件
* a. relation取值是and和or,可以不用,但是expr里面元素必须为一个。(即where pk>4)
* b. expr对应为分库条件
* c. paramtype对应分库子段类型
* 4. extra取值是针对{@linkplain ExtraCmd}中定义的扩展参数,比如ALLOW_TEMPORARY_TABLE=true代表开启临时表
*
* type为direct时
* a. \/*+TDDL({"type":"direct","vtab":"real_tab","dbid":"xxx_group","realtabs":["real_tab_0","real_tab_1"]})*\/select * from real_tab;
* 绕过解析器, 进行表名替换,然后在对应group ds上执行
* b. \/*+TDDL({"type":"direct","dbid":"xxx_group"})*\/select * from real_table_0;
* 直接将sql在对应group ds上执行
* </pre>
*
* @author jianghang 2014-1-13 下午6:16:31
* @since 5.0.0
*/
public class SimpleHintParser {
private static final Logger logger = LoggerFactory.getLogger(SimpleHintParser.class);
private static final String OR = "or";
private static final String AND = "and";
private static final String RELATION = "relation";
private static final String PARAMTYPE = "paramtype";
private static final String EXPR = "expr";
private static final String PARAMS = "params";
private static final String VTAB = "vtab";
private static final String DBID = "dbid";
private static final String REALTABS = "realtabs";
private static final String EXTRACMD = "extra";
public static RouteCondition convertHint2RouteCondition(String sql, Map<Integer, ParameterContext> parameterSettings) {
String tddlHint = extractHint(sql, parameterSettings);
if (StringUtils.isNotEmpty(tddlHint)) {
try {
JSONObject jsonObject = JSON.parseObject(tddlHint);
String type = jsonObject.getString("type");
if ("direct".equalsIgnoreCase(type)) {
return decodeDirect(jsonObject);
} else if ("condition".equalsIgnoreCase(type)) {
return decodeCondition(jsonObject);
} else {
return decodeExtra(jsonObject);
}
} catch (JSONException e) {
logger.error("convert tddl hint to RouteContion faild,check the hint string!", e);
throw e;
}
}
return null;
}
public static RouteCondition decodeDirect(JSONObject jsonObject) {
DirectlyRouteCondition rc = new DirectlyRouteCondition();
decodeExtra(rc, jsonObject);
String tableString = containsKvNotBlank(jsonObject, REALTABS);
if (tableString != null) {
JSONArray jsonTables = JSON.parseArray(tableString);
// 设置table的Set<String>
if (jsonTables.size() > 0) {
Set<String> tables = new HashSet<String>(jsonTables.size());
for (int i = 0; i < jsonTables.size(); i++) {
tables.add(jsonTables.getString(i));
}
rc.setTables(tables);
// direct只需要在实际表有的前提下解析即可。
decodeVtab(rc, jsonObject);
}
}
String dbId = containsKvNotBlank(jsonObject, DBID);
if (dbId == null) {
throw new RuntimeException("hint contains no property 'dbid'.");
}
rc.setDBId(dbId);
return rc;
}
private static void decodeVtab(RouteCondition rc, JSONObject jsonObject) throws JSONException {
String virtualTableName = containsKvNotBlank(jsonObject, VTAB);
if (virtualTableName == null) {
throw new TddlRuntimeException("hint contains no property 'vtab'.");
}
rc.setVirtualTableName(virtualTableName);
}
private static ExtraCmdRouteCondition decodeExtra(JSONObject jsonObject) throws JSONException {
ExtraCmdRouteCondition rc = new ExtraCmdRouteCondition();
String extraCmd = containsKvNotBlank(jsonObject, EXTRACMD);
if (StringUtils.isNotEmpty(extraCmd)) {
JSONObject extraCmds = JSON.parseObject(extraCmd);
for (Map.Entry<String, Object> entry : extraCmds.entrySet()) {
rc.getExtraCmds().put(StringUtils.upperCase(entry.getKey()), entry.getValue());
}
}
return rc;
}
private static void decodeExtra(RouteCondition rc, JSONObject jsonObject) throws JSONException {
String extraCmd = containsKvNotBlank(jsonObject, EXTRACMD);
if (StringUtils.isNotEmpty(extraCmd)) {
JSONObject extraCmds = JSON.parseObject(extraCmd);
rc.getExtraCmds().putAll(extraCmds);
}
}
public static RouteCondition decodeCondition(JSONObject jsonObject) {
RuleRouteCondition sc = new RuleRouteCondition();
decodeVtab(sc, jsonObject);
decodeExtra(sc, jsonObject);
String paramsStr = containsKvNotBlank(jsonObject, PARAMS);
if (paramsStr != null) {
JSONArray params = JSON.parseArray(paramsStr);
if (params != null) {
for (int i = 0; i < params.size(); i++) {
JSONObject o = params.getJSONObject(i);
JSONArray exprs = o.getJSONArray(EXPR);
String paramtype = o.getString(PARAMTYPE);
if (o.containsKey(RELATION)) {
String relation = o.getString(RELATION);
ComparativeBaseList comList = null;
if (relation != null && AND.equals(relation)) {
comList = new ComparativeAND();
} else if (relation != null && OR.equals(relation)) {
comList = new ComparativeOR();
} else {
throw new TddlRuntimeException("multi param but no relation,the hint is:" + sc.toString());
}
String key = null;
for (int j = 0; j < exprs.size(); j++) {
Comparative comparative = ComparativeStringAnalyser.decodeComparative(exprs.getString(j),
paramtype);
comList.addComparative(comparative);
String temp = ComparativeStringAnalyser.decodeComparativeKey(exprs.getString(j));
if (null == key) {
key = temp;
} else if (!temp.equals(key)) {
throw new TddlRuntimeException("decodeCondition not support one relation with multi key,the relation is:["
+ relation + "],expr list is:[" + exprs.toString());
}
}
sc.put(key, comList);
} else {
if (exprs.size() == 1) {
String key = ComparativeStringAnalyser.decodeComparativeKey(exprs.getString(0));
Comparative comparative = ComparativeStringAnalyser.decodeComparative(exprs.getString(0),
paramtype);
sc.put(key, comparative);
} else {
throw new TddlRuntimeException("relation neither 'and' nor 'or',but expr size is not 1");
}
}
}
}
}
return sc;
}
/**
* 从sql中解出hint,并且将hint里面的?替换为参数的String形式
*
* @param sql
* @param parameterSettings
* @return
*/
public static String extractHint(String sql, Map<Integer, ParameterContext> parameterSettings) {
String tddlHint = TStringUtil.getBetween(sql, "/*+TDDL(", ")*/");
if (null == tddlHint || "".endsWith(tddlHint)) {
return null;
}
StringBuffer sb = new StringBuffer();
int size = tddlHint.length();
int parameters = 1;
for (int i = 0; i < size; i++) {
if (tddlHint.charAt(i) == '?') {
// TDDLHINT只能设置简单值
if (parameterSettings == null) {
throw new TddlRuntimeException("hint中使用了'?'占位符,却没有设置setParameter()");
}
ParameterContext param = parameterSettings.get(parameters);
sb.append(param.getArgs()[1]);
// if (param.getParameterMethod() == ParameterMethod.setString)
// {
// sb.append("'");
// sb.append(parameterSettings.get(parameters).getArgs()[1]);
// sb.append("'");
// } else {
// sb.append(parameterSettings.get(parameters).getArgs()[1]);
// }
parameters++;
} else {
sb.append(tddlHint.charAt(i));
}
}
return sb.toString();
}
public static String extractTDDLGroupHintString(String sql) {
return TStringUtil.getBetween(sql, "/*+TDDL_GROUP({", "})*/");
}
public static String removeHint(String originsql, Map<Integer, ParameterContext> parameterSettings) {
String sql = originsql;
String tddlHint = TStringUtil.getBetween(sql, "/*+TDDL(", ")*/");
if (null == tddlHint || "".endsWith(tddlHint)) {
return originsql;
}
int size = tddlHint.length();
int parameters = 0;
for (int i = 0; i < size; i++) {
if (tddlHint.charAt(i) == '?') {
parameters++;
}
}
sql = TStringUtil.removeBetweenWithSplitor(sql, "/*+TDDL(", ")*/");
// TDDL的hint必需写在SQL语句的最前面,如果和ORACLE hint一起用,
// 也必需写在hint字符串的最前面,否则参数非常难以处理,也就会出错
// 如果parameters为0,说明TDDLhint中没有参数,所以直接返回sql即可
if (parameters == 0) {
return sql;
}
for (int i = 1; i <= parameters; i++) {
parameterSettings.remove(i);
}
Map<Integer, ParameterContext> newParameterSettings = new TreeMap<Integer, ParameterContext>();
for (Map.Entry<Integer, ParameterContext> entry : parameterSettings.entrySet()) {
// 重新计算一下parameters index
newParameterSettings.put(entry.getKey() - parameters, entry.getValue());
entry.getValue().getArgs()[0] = entry.getKey() - parameters;// args里的第一位也是下标
}
parameterSettings.clear();
parameterSettings.putAll(newParameterSettings);
return sql;
}
private static String containsKvNotBlank(JSONObject jsonObject, String key) throws JSONException {
if (!jsonObject.containsKey(key)) {
return null;
}
String value = jsonObject.getString(key);
if (TStringUtil.isBlank(value)) {
return null;
}
return value;
}
}