Skip to content

Commit 0dc96b4

Browse files
committed
JOIN ON 新增支持比较运算符 >, <, >=, <= 和字符匹配 $ LIKE, ~ REGEXP
1 parent 0063721 commit 0dc96b4

File tree

5 files changed

+219
-61
lines changed

5 files changed

+219
-61
lines changed

APIJSONORM/src/main/java/apijson/SQL.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,16 @@ public static String search(String s, int type, boolean ignoreCase) {
388388
return "%" + s + "%";
389389
}
390390
}
391-
391+
392392
//search>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
393393

394+
395+
public static boolean isBooleanOrNumber(String type) {
396+
type = StringUtil.toUpperCase(type, true);
397+
return type.isEmpty() || (type.endsWith("INT") && type.endsWith("POINT") == false)
398+
|| type.endsWith("BOOLEAN") || type.endsWith("ENUM")
399+
|| type.endsWith("FLOAT") || type.endsWith("DOUBLE") || type.endsWith("DECIMAL");
400+
}
401+
402+
394403
}

APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java

Lines changed: 149 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
430430
SQL_FUNCTION_MAP.put("nullif", ""); // NULLIF(expr1, expr2) 比较两个字符串,如果字符串 expr1 与 expr2 相等 返回 NULL,否则返回 expr1
431431
SQL_FUNCTION_MAP.put("group_concat", ""); // GROUP_CONCAT([DISTINCT], s1, s2...) 聚合拼接字符串
432432
SQL_FUNCTION_MAP.put("match", ""); // MATCH (name,tag) AGAINST ('a b' IN NATURAL LANGUAGE MODE) 全文检索
433+
SQL_FUNCTION_MAP.put("any_value", ""); // any_value(userId) 解决 ONLY_FULL_GROUP_BY 报错
433434

434435

435436

@@ -3439,7 +3440,7 @@ else if (isOracle()) {
34393440
condition += (condition + "has(JSONExtractArrayRaw(assumeNotNull(" + getKey(column) + "))" + ", " + getValue(key, column, v) + (StringUtil.isEmpty(path, true) ? "" : ", " + getValue(key, column, path)) + ")");
34403441
}
34413442
else {
3442-
condition += ("json_contains(" + getKey(column) + ", " + getValue(key, column, v) + (StringUtil.isEmpty(path, true) ? "" : ", " + getValue(key, column, path)) + ")");
3443+
condition += ("json_contains(" + getKey(column) + ", " + getValue(key, column, v) + (StringUtil.isEmpty(path, true) ? "" : ", " + getValue(key, column, path)) + ")");
34433444
}
34443445
}
34453446
}
@@ -3490,7 +3491,17 @@ public String getSubqueryString(Subquery subquery) throws Exception {
34903491
* @return
34913492
*/
34923493
public static String getCondition(boolean not, String condition) {
3493-
return not ? NOT + "(" + condition + ")" : condition;
3494+
return getCondition(not, condition, false);
3495+
}
3496+
/**拼接条件
3497+
* @param not
3498+
* @param condition
3499+
* @param outerBreaket
3500+
* @return
3501+
*/
3502+
public static String getCondition(boolean not, String condition, boolean addOuterBracket) {
3503+
String s = not ? NOT + "(" + condition + ")" : condition;
3504+
return addOuterBracket ? "( " + s + " )" : s;
34943505
}
34953506

34963507

@@ -3800,32 +3811,7 @@ public String getJoinString() throws Exception {
38003811
jc.setMain(true).setKeyPrefix(false);
38013812
sql = ( "<".equals(type) ? " LEFT" : (">".equals(type) ? " RIGHT" : " CROSS") )
38023813
+ " JOIN ( " + jc.getSQL(isPrepared()) + " ) AS " + quote + jt + quote;
3803-
3804-
if (onList != null) {
3805-
boolean first = true;
3806-
for (On on : onList) {
3807-
String rt = on.getRelateType();
3808-
if (StringUtil.isEmpty(rt, false)) {
3809-
sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + " = "
3810-
+ quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote;
3811-
}
3812-
else if ("{}".equals(rt)) {
3813-
sql += (first ? ON : AND) + "json_contains(" + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote
3814-
// + ", concat('\\'', " + quote + jt + quote + "." + quote + on.getKey() + quote + ", '\\''), '$')";
3815-
+ ", cast(" + quote + jt + quote + "." + quote + on.getKey() + quote + " AS CHAR), '$')";
3816-
}
3817-
else if ("<>".equals(rt)) {
3818-
sql += (first ? ON : AND) + "json_contains(" + quote + jt + quote + "." + quote + on.getKey() + quote
3819-
// + ", concat('\\'', " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + ", '\\''), '$')";
3820-
+ ", cast(" + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + " AS CHAR), '$')";
3821-
}
3822-
else {
3823-
throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath()
3824-
+ " 中 JOIN ON 条件关联类型 " + rt + " 不合法!只支持 =, {}, <> 这几种!");
3825-
}
3826-
first = false;
3827-
}
3828-
}
3814+
sql = concatJoinOn(sql, quote, j, jt, onList);
38293815

38303816
jc.setMain(false).setKeyPrefix(true);
38313817

@@ -3841,32 +3827,7 @@ else if ("<>".equals(rt)) {
38413827
case "(": // ANTI JOIN: A & ! B
38423828
case ")": // FOREIGN JOIN: B & ! A
38433829
sql = " INNER JOIN " + jc.getTablePath();
3844-
if (onList != null) {
3845-
boolean first = true;
3846-
for (On on : onList) {
3847-
String rt = on.getRelateType();
3848-
if (StringUtil.isEmpty(rt, false)) {
3849-
sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + " = "
3850-
+ quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote;
3851-
}
3852-
else if ("{}".equals(rt)) {
3853-
sql += (first ? ON : AND) + "json_contains(" + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote
3854-
// + ", concat('\\'', " + quote + jt + quote + "." + quote + on.getKey() + quote + ", '\\''), '$')";
3855-
+ ", cast(" + quote + jt + quote + "." + quote + on.getKey() + quote + " AS CHAR), '$')";
3856-
}
3857-
else if ("<>".equals(rt)) {
3858-
sql += (first ? ON : AND) + "json_contains(" + quote + jt + quote + "." + quote + on.getKey() + quote
3859-
// + ", concat('\\'', " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + ", '\\''), '$')";
3860-
+ ", cast(" + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + " AS CHAR), '$')";
3861-
}
3862-
else {
3863-
throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath()
3864-
+ " 中 JOIN ON 条件关联类型 " + rt + " 不合法!只支持 =, {}, <> 这几种!");
3865-
}
3866-
3867-
first = false;
3868-
}
3869-
}
3830+
sql = concatJoinOn(sql, quote, j, jt, onList);
38703831
break;
38713832
default:
38723833
throw new UnsupportedOperationException(
@@ -3902,6 +3863,140 @@ else if ("<>".equals(rt)) {
39023863
return StringUtil.isEmpty(joinOns, true) ? "" : joinOns + " \n";
39033864
}
39043865

3866+
3867+
protected String concatJoinOn(@NotNull String sql, @NotNull String quote, @NotNull Join j, @NotNull String jt, List<On> onList) {
3868+
if (onList != null) {
3869+
boolean first = true;
3870+
for (On on : onList) {
3871+
Logic logic = on.getLogic();
3872+
boolean isNot = logic == null ? false : logic.isNot();
3873+
if (isNot) {
3874+
onJoinNotRelation(sql, quote, j, jt, onList, on);
3875+
}
3876+
3877+
String rt = on.getRelateType();
3878+
if (StringUtil.isEmpty(rt, false)) {
3879+
sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + (isNot ? " != " : " = ")
3880+
+ quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote;
3881+
}
3882+
else {
3883+
onJoinComplextRelation(sql, quote, j, jt, onList, on);
3884+
3885+
if (">=".equals(rt) || "<=".equals(rt) || ">".equals(rt) || "<".equals(rt)) {
3886+
if (isNot) {
3887+
throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath()
3888+
+ " 中 JOIN ON 条件关联逻辑符 " + rt + " 不合法! >, <, >=, <= 不支持与或非逻辑符 & | ! !");
3889+
}
3890+
3891+
sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + " " + rt + " "
3892+
+ quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote;
3893+
}
3894+
else if ("$".equals(rt)) {
3895+
sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + (isNot ? NOT : "")
3896+
+ " LIKE concat('%', " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + ", '%')";
3897+
}
3898+
else if (rt.endsWith("~")) {
3899+
boolean ignoreCase = "*~".equals(rt);
3900+
if (isPostgreSQL()) {
3901+
sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + (isNot ? NOT : "")
3902+
+ " ~" + (ignoreCase ? "* " : " ") + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote;
3903+
}
3904+
else if (isOracle()) {
3905+
sql += (first ? ON : AND) + "regexp_like(" + quote + jt + quote + "." + quote + on.getKey() + quote
3906+
+ ", " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + (ignoreCase ? ", 'i'" : ", 'c'") + ")";
3907+
}
3908+
else if (isClickHouse()) {
3909+
sql += (first ? ON : AND) + "match(" + (ignoreCase ? "lower(" : "") + quote + jt + quote + "." + quote + on.getKey() + quote + (ignoreCase ? ")" : "")
3910+
+ ", " + (ignoreCase ? "lower(" : "") + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + (ignoreCase ? ")" : "") + ")";
3911+
}
3912+
else if (isHive()) {
3913+
sql += (first ? ON : AND) + (ignoreCase ? "lower(" : "") + quote + jt + quote + "." + quote + on.getKey() + quote + (ignoreCase ? ")" : "")
3914+
+ " REGEXP " + (ignoreCase ? "lower(" : "") + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + (ignoreCase ? ")" : "");
3915+
}
3916+
else {
3917+
sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + (isNot ? NOT : "")
3918+
+ " REGEXP " + (ignoreCase ? "" : "BINARY ") + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote;
3919+
}
3920+
}
3921+
else if ("{}".equals(rt) || "<>".equals(rt)) {
3922+
String tt = on.getTargetTable();
3923+
String ta = on.getTargetAlias();
3924+
3925+
Map<String, String> cast = null;
3926+
if (tt.equals(getTable()) && ((ta == null && getAlias() == null) || ta.equals(getAlias()))) {
3927+
cast = getCast();
3928+
}
3929+
else {
3930+
boolean find = false;
3931+
for (Join jn : joinList) {
3932+
if (tt.equals(jn.getTable()) && ((ta == null && jn.getAlias() == null) || ta.equals(jn.getAlias()))) {
3933+
cast = getCast();
3934+
find = true;
3935+
break;
3936+
}
3937+
}
3938+
3939+
if (find == false) {
3940+
throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath()
3941+
+ " 中 JOIN ON 条件中找不到对应的 " + rt + " 不合法!只支持 =, {}, <> 这几种!");
3942+
}
3943+
}
3944+
3945+
boolean isBoolOrNum = SQL.isBooleanOrNumber(cast == null ? null : cast.get(on.getTargetKey()));
3946+
3947+
String arrKeyPath;
3948+
String itemKeyPath;
3949+
if ("{}".equals(rt)) {
3950+
arrKeyPath = quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote;
3951+
itemKeyPath = quote + jt + quote + "." + quote + on.getKey() + quote;
3952+
}
3953+
else {
3954+
arrKeyPath = quote + jt + quote + "." + quote + on.getKey() + quote;
3955+
itemKeyPath = quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote;
3956+
}
3957+
3958+
if (isPostgreSQL()) { //operator does not exist: jsonb @> character varying "[" + c + "]");
3959+
sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath
3960+
+ " IS NOT NULL AND " + arrKeyPath + " @> " + itemKeyPath) + (isNot ? ") " : "");
3961+
}
3962+
else if (isOracle()) {
3963+
sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath
3964+
+ " IS NOT NULL AND json_textcontains(" + arrKeyPath
3965+
+ ", '$', " + itemKeyPath + ")") + (isNot ? ") " : "");
3966+
}
3967+
else if (isClickHouse()) {
3968+
sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath
3969+
+ " IS NOT NULL AND has(JSONExtractArrayRaw(assumeNotNull(" + arrKeyPath + "))"
3970+
+ ", " + itemKeyPath + ")") + (isNot ? ") " : "");
3971+
}
3972+
else {
3973+
sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath
3974+
+ " IS NOT NULL AND json_contains(" + arrKeyPath
3975+
+ (isBoolOrNum ? ", cast(" + itemKeyPath + " AS CHAR), '$')"
3976+
: ", concat('\"', " + itemKeyPath + ", '\"'), '$')"
3977+
)
3978+
) + (isNot ? ") " : "");
3979+
}
3980+
}
3981+
else {
3982+
throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath()
3983+
+ " 中 JOIN ON 条件关联类型 " + rt + " 不合法!只支持 =, >, <, >=, <=, !=, $, ~, {}, <> 这几种!");
3984+
}
3985+
}
3986+
3987+
first = false;
3988+
}
3989+
}
3990+
3991+
return sql;
3992+
}
3993+
3994+
protected void onJoinNotRelation(String sql, String quote, Join j, String jt, List<On> onList, On on) {
3995+
// throw new UnsupportedOperationException("JOIN 已禁用 '!' 非逻辑连接符 !性能很差、需求极少,如要取消禁用可在后端重写相关方法!");
3996+
}
3997+
protected void onJoinComplextRelation(String sql, String quote, Join j, String jt, List<On> onList, On on) {
3998+
// throw new UnsupportedOperationException("JOIN 已禁用 {} 和 <> 等复杂关联 !性能很差、需求极少,默认只允许等价关联,如要取消禁用可在后端重写相关方法!");
3999+
}
39054000
protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException {
39064001
throw new UnsupportedOperationException("已禁用 * CROSS JOIN !性能很差、需求极少,如要取消禁用可在后端重写相关方法!");
39074002
}

APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,7 @@ protected void executeAppJoin(SQLConfig config, List<JSONObject> resultList, Map
745745
for (On on : onList) {
746746
String ok = on.getOriginKey();
747747
String vk = ok.substring(0, ok.length() - 1);
748+
//TODO 兼容复杂关联
748749
cc.putWhere(on.getKey(), result.get(on.getKey()), true);
749750
}
750751
}

APIJSONORM/src/main/java/apijson/orm/Join.java

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.alibaba.fastjson.JSONObject;
1111

1212
import apijson.NotNull;
13+
import apijson.StringUtil;
1314

1415
/**连表 配置
1516
* @author Lemon
@@ -163,7 +164,8 @@ public static class On {
163164
private String originKey;
164165
private String originValue;
165166

166-
private String relateType; // "" - 一对一, "{}" - 一对多, "<>" - 多对一
167+
private Logic logic; // & | !
168+
private String relateType; // "" - 一对一, "{}" - 一对多, "<>" - 多对一, > , <= , !=
167169
private String key; // id
168170
private String targetTable; // Moment
169171
private String targetAlias; // main
@@ -183,14 +185,19 @@ public void setOriginValue(String originValue) {
183185
}
184186

185187

188+
public Logic getLogic() {
189+
return logic;
190+
}
191+
public void setLogic(Logic logic) {
192+
this.logic = logic;
193+
}
186194
public String getRelateType() {
187195
return relateType;
188196
}
189197
public void setRelateType(String relateType) {
190198
this.relateType = relateType;
191199
}
192200

193-
194201
public String getKey() {
195202
return key;
196203
}
@@ -222,22 +229,63 @@ public void setKeyAndType(String joinType, String table, @NotNull String originK
222229
originKey = originKey.substring(0, originKey.length() - 1);
223230
}
224231
else { //TODO 暂时只允许 User.id = Moment.userId 字段关联,不允许 User.id = 82001 这种
225-
throw new IllegalArgumentException(joinType + "/.../" + table + "/" + originKey + " 不合法!join:'.../refKey'" + " 中 refKey 必须以 @ 结尾!");
232+
throw new IllegalArgumentException(joinType + "/.../" + table + "/" + originKey + " 中字符 " + originKey + " 不合法!join:'.../refKey'" + " 中 refKey 必须以 @ 结尾!");
226233
}
227234

235+
String k;
236+
228237
if (originKey.endsWith("{}")) {
229238
setRelateType("{}");
230-
setKey(originKey.substring(0, originKey.length() - 2));
239+
k = originKey.substring(0, originKey.length() - 2);
231240
}
232241
else if (originKey.endsWith("<>")) {
233242
setRelateType("<>");
234-
setKey(originKey.substring(0, originKey.length() - 2));
243+
k = originKey.substring(0, originKey.length() - 2);
244+
}
245+
else if (originKey.endsWith("$")) {
246+
setRelateType("$");
247+
k = originKey.substring(0, originKey.length() - 1);
248+
}
249+
else if (originKey.endsWith("~")) {
250+
boolean ignoreCase = originKey.endsWith("*~");
251+
setRelateType(ignoreCase ? "*~" : "~");
252+
k = originKey.substring(0, originKey.length() - (ignoreCase ? 2 : 1));
253+
}
254+
else if (originKey.endsWith(">=")) {
255+
setRelateType(">=");
256+
k = originKey.substring(0, originKey.length() - 2);
257+
}
258+
else if (originKey.endsWith("<=")) {
259+
setRelateType("<=");
260+
k = originKey.substring(0, originKey.length() - 2);
261+
}
262+
else if (originKey.endsWith(">")) {
263+
setRelateType(">");
264+
k = originKey.substring(0, originKey.length() - 1);
265+
}
266+
else if (originKey.endsWith("<")) {
267+
setRelateType("<");
268+
k = originKey.substring(0, originKey.length() - 1);
235269
}
236270
else {
237271
setRelateType("");
238-
setKey(originKey);
272+
k = originKey;
239273
}
274+
275+
if (k != null && (k.contains("&") || k.contains("|"))) {
276+
throw new UnsupportedOperationException(joinType + "/.../" + table + "/" + originKey + " 中字符 " + k + " 不合法!与或非逻辑符仅支持 '!' 非逻辑符 !");
277+
}
278+
279+
Logic l = new Logic(k);
280+
setLogic(l);
281+
282+
if (StringUtil.isName(l.getKey()) == false) {
283+
throw new IllegalArgumentException(joinType + "/.../" + table + "/" + originKey + " 中字符 " + l.getKey() + " 不合法!必须符合字段命名格式!");
284+
}
285+
286+
setKey(l.getKey());
240287
}
288+
241289

242290
}
243291

0 commit comments

Comments
 (0)