@having 支持复杂条件组合,且新增 @having& 简化 AND 连接的写法

This commit is contained in:
TommyLemon 2022-03-21 00:26:48 +08:00
parent a3d9c90a8d
commit 9776408d63
4 changed files with 528 additions and 368 deletions

View File

@ -147,6 +147,7 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject {
public static final String KEY_COMBINE = "@combine"; //条件组合每个条件key前面可以放&,|,!逻辑关系 "id!{},&sex,!name&$" public static final String KEY_COMBINE = "@combine"; //条件组合每个条件key前面可以放&,|,!逻辑关系 "id!{},&sex,!name&$"
public static final String KEY_GROUP = "@group"; //分组方式 public static final String KEY_GROUP = "@group"; //分组方式
public static final String KEY_HAVING = "@having"; //聚合函数条件一般和@group一起用 public static final String KEY_HAVING = "@having"; //聚合函数条件一般和@group一起用
public static final String KEY_HAVING_AND = "@having&"; //聚合函数条件一般和@group一起用
public static final String KEY_ORDER = "@order"; //排序方式 public static final String KEY_ORDER = "@order"; //排序方式
public static final String KEY_RAW = "@raw"; // 自定义原始 SQL 片段 public static final String KEY_RAW = "@raw"; // 自定义原始 SQL 片段
public static final String KEY_JSON = "@json"; //SQL Server 把字段转为 JSON 输出 public static final String KEY_JSON = "@json"; //SQL Server 把字段转为 JSON 输出
@ -167,6 +168,7 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject {
TABLE_KEY_LIST.add(KEY_COMBINE); TABLE_KEY_LIST.add(KEY_COMBINE);
TABLE_KEY_LIST.add(KEY_GROUP); TABLE_KEY_LIST.add(KEY_GROUP);
TABLE_KEY_LIST.add(KEY_HAVING); TABLE_KEY_LIST.add(KEY_HAVING);
TABLE_KEY_LIST.add(KEY_HAVING_AND);
TABLE_KEY_LIST.add(KEY_ORDER); TABLE_KEY_LIST.add(KEY_ORDER);
TABLE_KEY_LIST.add(KEY_RAW); TABLE_KEY_LIST.add(KEY_RAW);
TABLE_KEY_LIST.add(KEY_JSON); TABLE_KEY_LIST.add(KEY_JSON);
@ -350,7 +352,14 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject {
* @return * @return
*/ */
public JSONObject setHaving(String keys) { public JSONObject setHaving(String keys) {
return puts(KEY_HAVING, keys); return setHaving(keys, false);
}
/**set keys for having
* @param keys "key0,key1,key2..."
* @return
*/
public JSONObject setHaving(String keys, boolean isAnd) {
return puts(isAnd ? KEY_HAVING_AND : KEY_HAVING, keys);
} }
/**set keys for order by /**set keys for order by

View File

@ -1063,13 +1063,13 @@ public abstract class AbstractParser<T> implements Parser<T>, ParserCreator<T>,
boolean isExplain = cfg.isExplain(); boolean isExplain = cfg.isExplain();
cfg.setExplain(false); cfg.setExplain(false);
Subquery subq = new Subquery(); Subquery subqy = new Subquery();
subq.setFrom(cfg.getTable()); subqy.setFrom(cfg.getTable());
subq.setConfig(cfg); subqy.setConfig(cfg);
SQLConfig countSQLCfg = createSQLConfig(); SQLConfig countSQLCfg = createSQLConfig();
countSQLCfg.setColumn(Arrays.asList("count(*):count")); countSQLCfg.setColumn(Arrays.asList("count(*):count"));
countSQLCfg.setFrom(subq); countSQLCfg.setFrom(subqy);
rp = executeSQL(countSQLCfg, false); rp = executeSQL(countSQLCfg, false);
@ -1358,6 +1358,7 @@ public abstract class AbstractParser<T> implements Parser<T>, ParserCreator<T>,
JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COMBINE); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COMBINE);
JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_GROUP); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_GROUP);
JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_HAVING); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_HAVING);
JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_HAVING_AND);
JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_ORDER); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_ORDER);
JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_RAW); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_RAW);
} }

View File

@ -15,6 +15,7 @@ import static apijson.JSONObject.KEY_EXPLAIN;
import static apijson.JSONObject.KEY_FROM; import static apijson.JSONObject.KEY_FROM;
import static apijson.JSONObject.KEY_GROUP; import static apijson.JSONObject.KEY_GROUP;
import static apijson.JSONObject.KEY_HAVING; import static apijson.JSONObject.KEY_HAVING;
import static apijson.JSONObject.KEY_HAVING_AND;
import static apijson.JSONObject.KEY_ID; import static apijson.JSONObject.KEY_ID;
import static apijson.JSONObject.KEY_JSON; import static apijson.JSONObject.KEY_JSON;
import static apijson.JSONObject.KEY_NULL; import static apijson.JSONObject.KEY_NULL;
@ -32,8 +33,8 @@ import static apijson.RequestMethod.POST;
import static apijson.RequestMethod.PUT; import static apijson.RequestMethod.PUT;
import static apijson.SQL.AND; import static apijson.SQL.AND;
import static apijson.SQL.NOT; import static apijson.SQL.NOT;
import static apijson.SQL.OR;
import static apijson.SQL.ON; import static apijson.SQL.ON;
import static apijson.SQL.OR;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -80,8 +81,20 @@ import apijson.orm.model.TestRecord;
public abstract class AbstractSQLConfig implements SQLConfig { public abstract class AbstractSQLConfig implements SQLConfig {
private static final String TAG = "AbstractSQLConfig"; private static final String TAG = "AbstractSQLConfig";
public static int MAX_COMBINE_DEPTH = 2; /**
* true 则兼容 5.0 之前 @having:"toId>0;avg(id)<100000" 默认 AND 连接 HAVING toId>0 AND avg(id)<100000
* 否则按 5.0+ 新版默认 OR 连接 HAVING toId>0 OR avg(id)<100000使用 @having& @having:{ @combine: null } 时才用 AND 连接
*/
public static boolean IS_HAVING_DEFAULT_AND = false;
/**
* true 则兼容 5.0 之前 @having:"toId>0" 这种不包含 SQL 函数的表达式
* 否则按 5.0+ 新版不允许可以用 @having:"(toId)>0" 替代
*/
public static boolean IS_HAVING_ALLOW_NOT_FUNCTION = false;
public static int MAX_HAVING_COUNT = 5;
public static int MAX_WHERE_COUNT = 10; public static int MAX_WHERE_COUNT = 10;
public static int MAX_COMBINE_DEPTH = 2;
public static int MAX_COMBINE_COUNT = 5; public static int MAX_COMBINE_COUNT = 5;
public static int MAX_COMBINE_KEY_COUNT = 2; public static int MAX_COMBINE_KEY_COUNT = 2;
public static float MAX_COMBINE_RATIO = 1.0f; public static float MAX_COMBINE_RATIO = 1.0f;
@ -750,7 +763,8 @@ public abstract class AbstractSQLConfig implements SQLConfig {
private String table; //表名 private String table; //表名
private String alias; //表别名 private String alias; //表别名
private String group; //分组方式的字符串数组','分隔 private String group; //分组方式的字符串数组','分隔
private String having; //聚合函数的字符串数组','分隔 private String havingCombine; //聚合函数的字符串数组','分隔
private Map<String, Object> having; //聚合函数的字符串数组','分隔
private String order; //排序方式的字符串数组','分隔 private String order; //排序方式的字符串数组','分隔
private List<String> raw; //需要保留原始 SQL 的字段','分隔 private List<String> raw; //需要保留原始 SQL 的字段','分隔
private List<String> json; //需要转为 JSON 的字段','分隔 private List<String> json; //需要转为 JSON 的字段','分隔
@ -1103,22 +1117,34 @@ public abstract class AbstractSQLConfig implements SQLConfig {
} }
@Override @Override
public String getHaving() { public String getHavingCombine() {
return havingCombine;
}
@Override
public SQLConfig setHavingCombine(String havingCombine) {
this.havingCombine = havingCombine;
return this;
}
@Override
public Map<String, Object> getHaving() {
return having; return having;
} }
@Override
public SQLConfig setHaving(Map<String, Object> having) {
this.having = having;
return this;
}
public AbstractSQLConfig setHaving(String... conditions) { public AbstractSQLConfig setHaving(String... conditions) {
return setHaving(StringUtil.getString(conditions)); return setHaving(StringUtil.getString(conditions));
} }
@Override
public AbstractSQLConfig setHaving(String having) {
this.having = having;
return this;
}
/**TODO @having 改为默认 | 或连接且支持 @having: { "key1>": 1, "key{}": "length(key2)>0", "@combine": "key1,key2" } /**TODO @having 改为默认 | 或连接且支持 @having: { "key1>": 1, "key{}": "length(key2)>0", "@combine": "key1,key2" }
* @return HAVING conditoin0 AND condition1 OR condition2 ... * @return HAVING conditoin0 AND condition1 OR condition2 ...
* @throws Exception
*/ */
@JSONField(serialize = false) @JSONField(serialize = false)
public String getHavingString(boolean hasPrefix) { public String getHavingString(boolean hasPrefix) throws Exception {
//加上子表的 having //加上子表的 having
String joinHaving = ""; String joinHaving = "";
if (joinList != null) { if (joinList != null) {
@ -1146,33 +1172,35 @@ public abstract class AbstractSQLConfig implements SQLConfig {
} }
} }
String[] keys = StringUtil.split(getHaving(), ";"); Map<String, Object> map = getHaving();
if (keys == null || keys.length <= 0) { Set<Entry<String, Object>> set = map == null ? null : map.entrySet();
if (set == null || set.isEmpty()) {
return StringUtil.isEmpty(joinHaving, true) ? "" : (hasPrefix ? " HAVING " : "") + joinHaving; return StringUtil.isEmpty(joinHaving, true) ? "" : (hasPrefix ? " HAVING " : "") + joinHaving;
} }
String quote = getQuote();
String tableAlias = getAliasWithQuote();
List<String> raw = getRaw(); List<String> raw = getRaw();
// 提前把 @having& 转为 @having或者干脆不允许 @raw:"@having&" boolean containRaw = raw != null && (raw.contains(KEY_HAVING) || raw.contains(KEY_HAVING_AND));
boolean containRaw = raw != null && raw.contains(KEY_HAVING); boolean containRaw = raw != null && raw.contains(KEY_HAVING);
String expression; // 直接把 having 类型从 Map<String, String> 定改为 Map<String, Object>避免额外拷贝
String method; // Map<String, Object> newMap = new LinkedHashMap<>(map.size());
//暂时不允许 String prefix; // for (Entry<String, String> entry : set) {
String suffix; // newMap.put(entry.getKey(), entry.getValue());
// }
//fun0(arg0,arg1,...);fun1(arg0,arg1,...) //fun0(arg0,arg1,...);fun1(arg0,arg1,...)
for (int i = 0; i < keys.length; i++) { String havingString = parseCombineExpression(getMethod(), getQuote(), getTable(), getAliasWithQuote(), map, getHavingCombine(), true, containRaw, true);
return (hasPrefix ? " HAVING " : "") + StringUtil.concat(havingString, joinHaving, AND);
}
protected String getHavingItem(String quote, String table, String alias, String key, String expression, boolean containRaw) {
//fun(arg0,arg1,...) //fun(arg0,arg1,...)
expression = keys[i];
if (containRaw) { if (containRaw) {
try { try {
String rawSQL = getRawSQL(KEY_HAVING, expression); String rawSQL = getRawSQL(KEY_HAVING, expression);
if (rawSQL != null) { if (rawSQL != null) {
keys[i] = rawSQL; return rawSQL;
continue;
} }
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { " Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { "
@ -1181,9 +1209,9 @@ public abstract class AbstractSQLConfig implements SQLConfig {
} }
} }
if (expression.length() > 50) { if (expression.length() > 100) {
throw new UnsupportedOperationException("@having:value 的 value 中字符串 " + expression + " 不合法!" throw new UnsupportedOperationException("@having:value 的 value 中字符串 " + expression + " 不合法!"
+ "不允许传超过 50 个字符的函数或表达式!请用 @raw 简化传参!"); + "不允许传超过 100 个字符的函数或表达式!请用 @raw 简化传参!");
} }
int start = expression.indexOf("("); int start = expression.indexOf("(");
@ -1193,7 +1221,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
+ "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\""
+ " 中 column?value 必须符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许空格!"); + " 中 column?value 必须符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许空格!");
} }
continue; return expression;
} }
int end = expression.lastIndexOf(")"); int end = expression.lastIndexOf(")");
@ -1202,7 +1230,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
+ "@having:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); + "@having:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!");
} }
method = expression.substring(0, start); String method = expression.substring(0, start);
if (method.isEmpty() == false) { if (method.isEmpty() == false) {
if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) {
if (StringUtil.isName(method) == false) { if (StringUtil.isName(method) == false) {
@ -1218,7 +1246,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
} }
} }
suffix = expression.substring(end + 1, expression.length()); String suffix = expression.substring(end + 1, expression.length());
if (isPrepared() && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { if (isPrepared() && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) {
throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!"
@ -1253,15 +1281,11 @@ public abstract class AbstractSQLConfig implements SQLConfig {
origin = getValue(origin).toString(); origin = getValue(origin).toString();
} }
ckeys[j] = (isName && isKeyPrefix() ? tableAlias + "." : "") + origin; ckeys[j] = (isName && isKeyPrefix() ? alias + "." : "") + origin;
} }
} }
keys[i] = method + "(" + StringUtil.getString(ckeys) + ")" + suffix; return method + "(" + StringUtil.getString(ckeys) + ")" + suffix;
}
//TODO 支持 OR, NOT 参考 @combine:"&key0,|key1,!key2"
return (hasPrefix ? " HAVING " : "") + StringUtil.concat(StringUtil.getString(keys, AND), joinHaving, AND);
} }
@Override @Override
@ -2193,6 +2217,9 @@ public abstract class AbstractSQLConfig implements SQLConfig {
//WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
protected int getMaxHavingCount() {
return MAX_HAVING_COUNT;
}
protected int getMaxWhereCount() { protected int getMaxWhereCount() {
return MAX_WHERE_COUNT; return MAX_WHERE_COUNT;
} }
@ -2392,38 +2419,60 @@ public abstract class AbstractSQLConfig implements SQLConfig {
*/ */
@JSONField(serialize = false) @JSONField(serialize = false)
public String getWhereString(boolean hasPrefix, RequestMethod method, Map<String, Object> where, String combine, List<Join> joinList, boolean verifyName) throws Exception { public String getWhereString(boolean hasPrefix, RequestMethod method, Map<String, Object> where, String combine, List<Join> joinList, boolean verifyName) throws Exception {
String whereString = parseCombineExpression(method, getQuote(), getTable(), getAliasWithQuote(), where, combine, verifyName, false, false);
whereString = concatJoinWhereString(whereString);
String result = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString;
if (result.isEmpty() && RequestMethod.isQueryMethod(method) == false) {
throw new UnsupportedOperationException("写操作请求必须带条件!!!");
}
return result;
}
protected String parseCombineExpression(RequestMethod method, String quote, String table, String alias
, Map<String, Object> conditioinMap, String combine, boolean verifyName, boolean containRaw, boolean isHaving) throws Exception {
String errPrefix = table + (isHaving ? ":{ @having:{ " : ":{ ") + "@combine:'" + combine + (isHaving ? "' } }" : "' }");
String s = StringUtil.getString(combine); String s = StringUtil.getString(combine);
if (s.startsWith(" ") || s.endsWith(" ") ) { if (s.startsWith(" ") || s.endsWith(" ") ) {
throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s throw new IllegalArgumentException(errPrefix + " 中字符 '" + s
+ "' 不合法!不允许首尾有空格,也不允许连续空格!空格不能多也不能少!" + "' 不合法!不允许首尾有空格,也不允许连续空格!空格不能多也不能少!"
+ "逻辑连接符 & | 左右必须各一个相邻空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!"); + "逻辑连接符 & | 左右必须各一个相邻空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!");
} }
if (where == null) { if (conditioinMap == null) {
where = new HashMap<>(); conditioinMap = new HashMap<>();
} }
int whereSize = where.size(); int size = conditioinMap.size();
int maxWhereCount = getMaxWhereCount(); int maxCount = isHaving ? getMaxHavingCount() : getMaxWhereCount();
if (maxWhereCount > 0 && whereSize > maxWhereCount) { if (maxCount > 0 && size > maxCount) {
throw new IllegalArgumentException(table + ":{ key0:value0, key1:value1... } 中条件 key:value 数量 " + whereSize throw new IllegalArgumentException(table + (isHaving ? ":{ @having:{ " : ":{ ") + "key0:value0, key1:value1... " + combine
+ " 已超过最大数量,必须在 0-" + maxWhereCount + " 内!"); + (isHaving ? " } }" : " }") + " 中条件 key:value 数量 " + size + " 已超过最大数量,必须在 0-" + maxCount + " 内!");
} }
String whereString = ""; String result = "";
List<Object> prepreadValues = getPreparedValueList();
Map<String, Integer> usedKeyCountMap = new HashMap<>(size);
int n = s.length();
if (n > 0) {
setPreparedValueList(new ArrayList<>());
int maxDepth = getMaxCombineDepth(); int maxDepth = getMaxCombineDepth();
int maxCombineCount = getMaxCombineCount(); int maxCombineCount = getMaxCombineCount();
int maxCombineKeyCount = getMaxCombineKeyCount(); int maxCombineKeyCount = getMaxCombineKeyCount();
float maxCombineRatio = getMaxCombineRatio(); float maxCombineRatio = getMaxCombineRatio();
List<Object> prepreadValues = getPreparedValueList();
setPreparedValueList(new ArrayList<>());
int depth = 0; int depth = 0;
int allCount = 0; int allCount = 0;
int n = s.length();
int i = 0; int i = 0;
char lastLogic = 0; char lastLogic = 0;
@ -2432,7 +2481,6 @@ public abstract class AbstractSQLConfig implements SQLConfig {
boolean isNot = false; boolean isNot = false;
String key = ""; String key = "";
Map<String, Integer> usedKeyCountMap = new HashMap<>(whereSize);
while (i <= n) { // "date> | (contactIdList<> & (name*~ | tag&$))" while (i <= n) { // "date> | (contactIdList<> & (name*~ | tag&$))"
boolean isOver = i >= n; boolean isOver = i >= n;
char c = isOver ? 0 : s.charAt(i); char c = isOver ? 0 : s.charAt(i);
@ -2440,51 +2488,51 @@ public abstract class AbstractSQLConfig implements SQLConfig {
if (isOver || isBlankOrRightParenthesis) { if (isOver || isBlankOrRightParenthesis) {
boolean isEmpty = StringUtil.isEmpty(key, true); boolean isEmpty = StringUtil.isEmpty(key, true);
if (isEmpty && last != ')') { if (isEmpty && last != ')') {
throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + (isOver ? s : s.substring(i)) throw new IllegalArgumentException(errPrefix + " 中字符 '" + (isOver ? s : s.substring(i))
+ "' 不合法!" + (c == ' ' ? "空格 ' ' " : "右括号 ')'") + " 左边缺少条件 key !逻辑连接符 & | 左右必须各一个相邻空格!" + "' 不合法!" + (c == ' ' ? "空格 ' ' " : "右括号 ')'") + " 左边缺少条件 key !逻辑连接符 & | 左右必须各一个相邻空格!"
+ "空格不能多也不能少!不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); + "空格不能多也不能少!不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!");
} }
if (isEmpty == false) { if (isEmpty == false) {
if (first == false && lastLogic <= 0) { if (first == false && lastLogic <= 0) {
throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 " throw new IllegalArgumentException(errPrefix + " 中字符 "
+ "'" + s.substring(i - key.length() - (isOver ? 1 : 0)) + "' 不合法!左边缺少 & | 其中一个逻辑连接符!"); + "'" + s.substring(i - key.length() - (isOver ? 1 : 0)) + "' 不合法!左边缺少 & | 其中一个逻辑连接符!");
} }
allCount ++; allCount ++;
if (allCount > maxCombineCount && maxCombineCount > 0) { if (allCount > maxCombineCount && maxCombineCount > 0) {
throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!" throw new IllegalArgumentException(errPrefix + " 中字符 '" + s + "' 不合法!"
+ "其中 key 数量 " + allCount + " 已超过最大值,必须在条件键值对数量 0-" + maxCombineCount + " 内!"); + "其中 key 数量 " + allCount + " 已超过最大值,必须在条件键值对数量 0-" + maxCombineCount + " 内!");
} }
if (1.0f*allCount/whereSize > maxCombineRatio && maxCombineRatio > 0) { if (1.0f*allCount/size > maxCombineRatio && maxCombineRatio > 0) {
throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!" throw new IllegalArgumentException(errPrefix + " 中字符 '" + s + "' 不合法!"
+ "其中 key 数量 " + allCount + " / 条件键值对数量 " + whereSize + " = " + (1.0f*allCount/whereSize) + "其中 key 数量 " + allCount + " / 条件键值对数量 " + size + " = " + (1.0f*allCount/size)
+ " 已超过 最大倍数,必须在条件键值对数量 0-" + maxCombineRatio + " 倍内!"); + " 已超过 最大倍数,必须在条件键值对数量 0-" + maxCombineRatio + " 倍内!");
} }
String column = key; String column = key;
Object value = where.get(column); Object value = conditioinMap.get(column);
if (value == null) { if (value == null) {
throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + key throw new IllegalArgumentException(errPrefix + " 中字符 '" + key
+ "' 对应的条件键值对 " + column + ":value 不存在!"); + "' 对应的条件键值对 " + column + ":value 不存在!");
} }
String wi = getWhereItem(column, value, method, verifyName); String wi = isHaving ? getHavingItem(quote, table, alias, column, (String) value, containRaw) : getWhereItem(column, value, method, verifyName);
if (StringUtil.isEmpty(wi, true)) { // 转成 1=1 ? if (StringUtil.isEmpty(wi, true)) { // 转成 1=1 ?
throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + key throw new IllegalArgumentException(errPrefix + " 中字符 '" + key
+ "' 对应的 " + column + ":value 不是有效条件键值对!"); + "' 对应的 " + column + ":value 不是有效条件键值对!");
} }
Integer count = usedKeyCountMap.get(column); Integer count = usedKeyCountMap.get(column);
count = count == null ? 1 : count + 1; count = count == null ? 1 : count + 1;
if (count > maxCombineKeyCount && maxCombineKeyCount > 0) { if (count > maxCombineKeyCount && maxCombineKeyCount > 0) {
throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!" throw new IllegalArgumentException(errPrefix + " 中字符 '" + s + "' 不合法!"
+ "其中 '" + column + "' 重复引用,次数 " + count + " 已超过最大值,必须在 0-" + maxCombineKeyCount + " 内!"); + "其中 '" + column + "' 重复引用,次数 " + count + " 已超过最大值,必须在 0-" + maxCombineKeyCount + " 内!");
} }
usedKeyCountMap.put(column, count); usedKeyCountMap.put(column, count);
whereString += "( " + getCondition(isNot, wi) + " )"; result += "( " + getCondition(isNot, wi) + " )";
isNot = false; isNot = false;
first = false; first = false;
} }
@ -2502,12 +2550,12 @@ public abstract class AbstractSQLConfig implements SQLConfig {
else if (c == '&') { else if (c == '&') {
if (last == ' ') { if (last == ' ') {
if (i >= n - 1 || s.charAt(i + 1) != ' ') { if (i >= n - 1 || s.charAt(i + 1) != ' ') {
throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + (i >= n - 1 ? s : s.substring(0, i + 1)) throw new IllegalArgumentException(errPrefix + " 中字符 '" + (i >= n - 1 ? s : s.substring(0, i + 1))
+ "' 不合法!逻辑连接符 & 右边缺少一个空格 !逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + "' 不合法!逻辑连接符 & 右边缺少一个空格 !逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!"
+ "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!");
} }
whereString += SQL.AND; result += SQL.AND;
lastLogic = c; lastLogic = c;
i ++; i ++;
} }
@ -2523,7 +2571,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
+ "不允许首尾有空格,也不允许连续空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!"); + "不允许首尾有空格,也不允许连续空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!");
} }
whereString += SQL.OR; result += SQL.OR;
lastLogic = c; lastLogic = c;
i ++; i ++;
} }
@ -2537,22 +2585,22 @@ public abstract class AbstractSQLConfig implements SQLConfig {
char next = i >= n - 1 ? 0 : s.charAt(i + 1); char next = i >= n - 1 ? 0 : s.charAt(i + 1);
if (last == ' ' || last == '(') { if (last == ' ' || last == '(') {
if (next == ' ') { if (next == ' ') {
throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(0, i + 1)
+ "' 不合法!非逻辑符 '!' 右边多了一个空格 ' ' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); + "' 不合法!非逻辑符 '!' 右边多了一个空格 ' ' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!");
} }
if (next == ')' || next == '&' || next == '!') { if (next == ')' || next == '&' || next == '!') {
throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(0, i + 1)
+ "' 不合法!非逻辑符 '!' 右边多了一个字符 '" + next + "' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); + "' 不合法!非逻辑符 '!' 右边多了一个字符 '" + next + "' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!");
} }
if (i > 0 && lastLogic <= 0 && last != '(') { if (i > 0 && lastLogic <= 0 && last != '(') {
throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(i)
+ "' 不合法!左边缺少 & | 逻辑连接符!逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + "' 不合法!左边缺少 & | 逻辑连接符!逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!"
+ "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!");
} }
} }
if (next == '(') { if (next == '(') {
whereString += SQL.NOT; result += SQL.NOT;
lastLogic = c; lastLogic = c;
} }
else if (last <= 0 || last == ' ' || last == '(') { else if (last <= 0 || last == ' ' || last == '(') {
@ -2565,29 +2613,29 @@ public abstract class AbstractSQLConfig implements SQLConfig {
} }
else if (c == '(') { else if (c == '(') {
if (key.isEmpty() == false || (i > 0 && lastLogic <= 0 && last != '(')) { if (key.isEmpty() == false || (i > 0 && lastLogic <= 0 && last != '(')) {
throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(i)
+ "' 不合法!左边缺少 & | 逻辑连接符!逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + "' 不合法!左边缺少 & | 逻辑连接符!逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!"
+ "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!");
} }
depth ++; depth ++;
if (depth > maxDepth && maxDepth > 0) { if (depth > maxDepth && maxDepth > 0) {
throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(0, i + 1)
+ "' 不合法!括号 (()) 嵌套层级 " + depth + " 已超过最大值,必须在 0-" + maxDepth + " 内!"); + "' 不合法!括号 (()) 嵌套层级 " + depth + " 已超过最大值,必须在 0-" + maxDepth + " 内!");
} }
whereString += c; result += c;
lastLogic = 0; lastLogic = 0;
first = true; first = true;
} }
else if (c == ')') { else if (c == ')') {
depth --; depth --;
if (depth < 0) { if (depth < 0) {
throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(0, i + 1)
+ "' 不合法!左括号 ( 比 右括号 ) 少!数量必须相等从而完整闭合 (...) "); + "' 不合法!左括号 ( 比 右括号 ) 少!数量必须相等从而完整闭合 (...) ");
} }
whereString += c; result += c;
lastLogic = 0; lastLogic = 0;
} }
else { else {
@ -2599,53 +2647,46 @@ public abstract class AbstractSQLConfig implements SQLConfig {
} }
if (depth != 0) { if (depth != 0) {
throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s throw new IllegalArgumentException(errPrefix + " 中字符 '" + s
+ "' 不合法!左括号 ( 比 右括号 ) 多!数量必须相等从而完整闭合 (...) "); + "' 不合法!左括号 ( 比 右括号 ) 多!数量必须相等从而完整闭合 (...) ");
} }
}
Set<Entry<String, Object>> set = where.entrySet(); Set<Entry<String, Object>> set = conditioinMap.entrySet();
String andWhere = ""; String andCond = "";
boolean isItemFirst = true; boolean isItemFirst = true;
for (Entry<String, Object> entry : set) { for (Entry<String, Object> entry : set) {
key = entry == null ? null : entry.getKey(); String key = entry == null ? null : entry.getKey();
if (key == null || usedKeyCountMap.containsKey(key)) { if (key == null || usedKeyCountMap.containsKey(key)) {
continue; continue;
} }
String wi = getWhereItem(key, where.get(key), method, verifyName); String wi = isHaving ? getHavingItem(quote, table, alias, key, (String) entry.getValue(), containRaw) : getWhereItem(key, entry.getValue(), method, verifyName);
if (StringUtil.isEmpty(wi, true)) {//避免SQL条件连接错误 if (StringUtil.isEmpty(wi, true)) {//避免SQL条件连接错误
continue; continue;
} }
andWhere += (isItemFirst ? "" : AND) + "(" + wi + ")"; andCond += (isItemFirst ? "" : AND) + "(" + wi + ")";
isItemFirst = false; isItemFirst = false;
} }
if (StringUtil.isEmpty(whereString, true)) { if (StringUtil.isEmpty(result, true)) {
whereString = andWhere; result = andCond;
} }
else if (StringUtil.isNotEmpty(andWhere, true)) { // andWhere 必须放后面否则 prepared 值顺序错误 else if (StringUtil.isNotEmpty(andCond, true)) { // andWhere 必须放后面否则 prepared 值顺序错误
// whereString = "( " + whereString + " )" + AND + andWhere; // result = "( " + result + " )" + AND + andCond;
result = andCond + AND + "( " + result + " )"; // 先暂存之前的 prepared 然后反向整合
whereString = andWhere + AND + "( " + whereString + " )"; // 先暂存之前的 prepared 然后反向整合 if (n > 0) {
prepreadValues.addAll(getPreparedValueList()); prepreadValues.addAll(getPreparedValueList());
setPreparedValueList(prepreadValues); setPreparedValueList(prepreadValues);
} }
whereString = concatJoinWhereString(whereString);
String result = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString;
if (result.isEmpty() && RequestMethod.isQueryMethod(method) == false) {
throw new UnsupportedOperationException("写操作请求必须带条件!!!");
} }
return result; return result;
} }
public String getWhereString(boolean hasPrefix, RequestMethod method, Map<String, Object> where, Map<String, List<String>> combine, List<Join> joinList, boolean verifyName) throws Exception { public String getWhereString(boolean hasPrefix, RequestMethod method, Map<String, Object> where, Map<String, List<String>> combine, List<Join> joinList, boolean verifyName) throws Exception {
Set<Entry<String, List<String>>> combineSet = combine == null ? null : combine.entrySet(); Set<Entry<String, List<String>>> combineSet = combine == null ? null : combine.entrySet();
if (combineSet == null || combineSet.isEmpty()) { if (combineSet == null || combineSet.isEmpty()) {
@ -4269,7 +4310,8 @@ public abstract class AbstractSQLConfig implements SQLConfig {
String cast = request.getString(KEY_CAST); String cast = request.getString(KEY_CAST);
String combine = request.getString(KEY_COMBINE); String combine = request.getString(KEY_COMBINE);
String group = request.getString(KEY_GROUP); String group = request.getString(KEY_GROUP);
String having = request.getString(KEY_HAVING); Object having = request.get(KEY_HAVING);
String havingAnd = request.getString(KEY_HAVING_AND);
String order = request.getString(KEY_ORDER); String order = request.getString(KEY_ORDER);
String raw = request.getString(KEY_RAW); String raw = request.getString(KEY_RAW);
String json = request.getString(KEY_JSON); String json = request.getString(KEY_JSON);
@ -4292,24 +4334,29 @@ public abstract class AbstractSQLConfig implements SQLConfig {
request.remove(KEY_COMBINE); request.remove(KEY_COMBINE);
request.remove(KEY_GROUP); request.remove(KEY_GROUP);
request.remove(KEY_HAVING); request.remove(KEY_HAVING);
request.remove(KEY_HAVING_AND);
request.remove(KEY_ORDER); request.remove(KEY_ORDER);
request.remove(KEY_RAW); request.remove(KEY_RAW);
request.remove(KEY_JSON); request.remove(KEY_JSON);
// @null <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
String[] nullKeys = StringUtil.split(nulls); String[] nullKeys = StringUtil.split(nulls);
if (nullKeys != null && nullKeys.length > 0) { if (nullKeys != null && nullKeys.length > 0) {
for (String nk : nullKeys) { for (String nk : nullKeys) {
if (StringUtil.isEmpty(nk, true)) { if (StringUtil.isEmpty(nk, true)) {
throw new IllegalArgumentException(table + ":{} 里的 @null: value 中的字符 '" + nk + "' 不合法!不允许为空!"); throw new IllegalArgumentException(table + ":{ @null: value } 中的字符 '" + nk + "' 不合法!不允许为空!");
} }
if (request.get(nk) != null) { if (request.get(nk) != null) {
throw new IllegalArgumentException(table + ":{} 里的 @null: value 中的字符 '" + nk + "' 已在当前对象有非 null 值!不允许对同一个 JSON key 设置不同值!"); throw new IllegalArgumentException(table + ":{ @null: value } 中的字符 '" + nk + "' 已在当前对象有非 null 值!不允许对同一个 JSON key 设置不同值!");
} }
request.put(nk, null); request.put(nk, null);
} }
} }
// @null >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// @cast <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
String[] casts = StringUtil.split(cast); String[] casts = StringUtil.split(cast);
Map<String, String> castMap = null; Map<String, String> castMap = null;
if (casts != null && casts.length > 0) { if (casts != null && casts.length > 0) {
@ -4330,6 +4377,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
castMap.put(p.getKey(), p.getValue()); castMap.put(p.getKey(), p.getValue());
} }
} }
// @cast >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
String[] rawArr = StringUtil.split(raw); String[] rawArr = StringUtil.split(raw);
@ -4523,8 +4571,15 @@ public abstract class AbstractSQLConfig implements SQLConfig {
List<String> cs = new ArrayList<>(); List<String> cs = new ArrayList<>();
List<String> rawList = config.getRaw(); List<String> rawList = config.getRaw();
boolean containColumnRaw = rawList != null && rawList.contains(KEY_COLUMN); boolean containColumnHavingAnd = rawList != null && rawList.contains(KEY_HAVING_AND);
if (containColumnHavingAnd) {
throw new IllegalArgumentException(table + ":{ @raw:value } 的 value 里字符 @having& 不合法!"
+ "@raw 不支持 @having&,请用 @having 替代!");
}
// TODO 这段是否必要如果 @column 只支持分段后的 SQL 片段也没问题
boolean containColumnRaw = rawList != null && rawList.contains(KEY_COLUMN);
String rawColumnSQL = null; String rawColumnSQL = null;
if (containColumnRaw) { if (containColumnRaw) {
try { try {
@ -4573,6 +4628,96 @@ public abstract class AbstractSQLConfig implements SQLConfig {
} }
// @having, @haivng& <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Object newHaving = having;
boolean isHavingAnd = false;
Map<String, Object> havingMap = new LinkedHashMap<>();
if (havingAnd != null) {
if (having != null) {
throw new IllegalArgumentException(table + ":{ @having: value1, @having&: value2 } "
+ "中 value1 与 value2 不合法!不允许同时传 @having 和 @having& ,两者最多传一个!");
}
newHaving = havingAnd;
isHavingAnd = true;
}
String havingKey = (isHavingAnd ? KEY_HAVING_AND : KEY_HAVING);
String havingCombine = "";
if (newHaving instanceof String) {
String[] havingss = StringUtil.split((String) newHaving, ";");
if (havingss != null) {
int ind = -1;
for (int i = 0; i < havingss.length; i++) {
String havingsStr = havingss[i];
int start = havingsStr == null ? -1 : havingsStr.indexOf("(");
int end = havingsStr == null ? -1 : havingsStr.lastIndexOf(")");
if (IS_HAVING_ALLOW_NOT_FUNCTION == false && (start < 0 || start >= end)) {
throw new IllegalArgumentException(table + ":{ " + havingKey + ":value } 里的 value 中的第 " + i +
" 个字符 '" + havingsStr + "' 不合法!里面没有包含 SQL 函数!必须为 fun(col1,col2..)?val 格式!");
}
String[] havings = start >= 0 && end > start ? new String[]{havingsStr} : StringUtil.split(havingsStr);
if (havings != null) {
for (int j = 0; j < havings.length; j++) {
ind ++;
String h = havings[j];
if (StringUtil.isEmpty(h, true)) {
throw new IllegalArgumentException(table + ":{ " + havingKey + ":value } 里的"
+ " value 中的第 " + ind + " 个字符 '" + h + "' 不合法!不允许为空!");
}
havingMap.put("having" + ind, h);
if (isHavingAnd == false && IS_HAVING_DEFAULT_AND == false) {
havingCombine += (ind <= 0 ? "" : " | ") + "having" + ind;
}
}
}
}
}
}
else if (newHaving instanceof JSONObject) {
if (isHavingAnd) {
throw new IllegalArgumentException(table + ":{ " + havingKey + ":value } 里的 value 类型不合法!"
+ "@having&:value 中 value 只能是 String@having:value 中 value 只能是 String 或 JSONObject ");
}
JSONObject havingObj = (JSONObject) newHaving;
Set<Entry<String, Object>> havingSet = havingObj.entrySet();
for (Entry<String, Object> entry : havingSet) {
String k = entry == null ? null : entry.getKey();
Object v = k == null ? null : entry.getValue();
if (v == null) {
continue;
}
if (v instanceof String == false) {
throw new IllegalArgumentException(table + ":{ " + havingKey + ":{ " + k + ":value } } 里的"
+ " value 不合法!类型只能是 String且不允许为空");
}
if (KEY_COMBINE.equals(k)) {
havingCombine = (String) v;
}
else if (StringUtil.isName(k) == false) {
throw new IllegalArgumentException(table + ":{ " + havingKey + ":{ " + k + ":value } } 里的"
+ " key 对应字符 " + k + " 不合法!必须为 英文字母 开头,且只包含 英文字母、下划线、数字 的合法变量名!");
}
else {
havingMap.put(k, (String) v);
}
}
}
else if (newHaving != null) {
throw new IllegalArgumentException(table + ":{ " + havingKey + ":value } 里的 value 类型不合法!"
+ "@having:value 中 value 只能是 String 或 JSONObject@having&:value 中 value 只能是 String ");
}
// @having, @haivng& >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
config.setExplain(explain); config.setExplain(explain);
config.setCache(getCache(cache)); config.setCache(getCache(cache));
config.setDistinct(distinct); config.setDistinct(distinct);
@ -4587,7 +4732,8 @@ public abstract class AbstractSQLConfig implements SQLConfig {
config.setCast(castMap); config.setCast(castMap);
config.setWhere(tableWhere); config.setWhere(tableWhere);
config.setGroup(group); config.setGroup(group);
config.setHaving(having); config.setHaving(havingMap);
config.setHavingCombine(havingCombine);
config.setOrder(order); config.setOrder(order);
String[] jsons = StringUtil.split(json); String[] jsons = StringUtil.split(json);
@ -4614,6 +4760,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
request.put(KEY_COMBINE, combine); request.put(KEY_COMBINE, combine);
request.put(KEY_GROUP, group); request.put(KEY_GROUP, group);
request.put(KEY_HAVING, having); request.put(KEY_HAVING, having);
request.put(KEY_HAVING_AND, havingAnd);
request.put(KEY_ORDER, order); request.put(KEY_ORDER, order);
request.put(KEY_RAW, raw); request.put(KEY_RAW, raw);
request.put(KEY_JSON, json); request.put(KEY_JSON, json);

View File

@ -159,8 +159,11 @@ public interface SQLConfig {
String getGroup(); String getGroup();
SQLConfig setGroup(String group); SQLConfig setGroup(String group);
String getHaving(); Map<String, Object> getHaving();
SQLConfig setHaving(String having); SQLConfig setHaving(Map<String, Object> having);
String getHavingCombine();
SQLConfig setHavingCombine(String havingCombine);
String getOrder(); String getOrder();
SQLConfig setOrder(String order); SQLConfig setOrder(String order);