From 5b29c96691df01b5a06da51fe58c8d490c9ce439 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 20 Feb 2022 20:45:08 +0800 Subject: [PATCH 001/619] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E6=BC=94=E7=A4=BA=E8=AF=B4=E6=98=8E=20GIF=20=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 7fea8ebd9..2a1d989a5 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,7 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这

![](https://oscimg.oschina.net/oscnet/up-bbbec4fc5edc472be127c02a4f3cd8f4ec2.JPEG) +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_associate.gif)

@@ -119,6 +120,8 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这

![](https://oscimg.oschina.net/oscnet/up-e21240ef3770326ee6015e052226d0da184.JPEG) +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_summary.gif) +

From 60f6bbe73f183f08b9b6f02ebd7ea2626f750c61 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 20 Feb 2022 21:01:28 +0800 Subject: [PATCH 002/619] =?UTF-8?q?=E6=96=87=E6=A1=A3=EF=BC=9A=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=8A=9F=E8=83=BD=E6=BC=94=E7=A4=BA=E5=8F=8A=E8=AF=B4?= =?UTF-8?q?=E6=98=8E=E7=9A=84=20GIF=20=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Document.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Document.md b/Document.md index 73f262b15..fdcc24238 100644 --- a/Document.md +++ b/Document.md @@ -52,6 +52,8 @@ https://github.com/Tencent/APIJSON } +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_single.gif) +
#### 获取用户列表 @@ -95,6 +97,8 @@ https://github.com/Tencent/APIJSON } +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_array.gif) +
#### 获取动态及发布者用户 @@ -134,6 +138,8 @@ https://github.com/Tencent/APIJSON "msg":"success" } + +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_associate.gif)
@@ -254,6 +260,29 @@ https://github.com/Tencent/APIJSON } +
+ +

+ APIJSON 各种 JOIN:< LEFT, > RIGHT, & INNER 等 +

+ +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_join.gif) + +
+ +

+ APIJSON 各种子查询:@from@ 数据源, key@ =, key{}@ IN, key<>@ CONTAINS, key}{@ EXISTS 等 +

+ +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_subquery.gif) + +
+ +

+ APIJSON 部分功能演示集合,由浅入深、由简单到复杂 +

+ +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_summary.gif)
From 7214c8d66ce48bfd3d48f4dc4dbf2c30213183f8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 20 Feb 2022 21:54:31 +0800 Subject: [PATCH 003/619] =?UTF-8?q?=E9=80=9A=E7=94=A8=E6=96=87=E6=A1=A3?= =?UTF-8?q?=EF=BC=9A=E5=AE=8C=E5=96=84=E5=8A=9F=E8=83=BD=E6=BC=94=E7=A4=BA?= =?UTF-8?q?=E5=8F=8A=E8=AF=B4=E6=98=8E=E7=9A=84=20GIF=20=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Document.md | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/Document.md b/Document.md index fdcc24238..9187c4a43 100644 --- a/Document.md +++ b/Document.md @@ -52,6 +52,10 @@ https://github.com/Tencent/APIJSON } +

+ APIJSON 各种单表对象查询:简单查询、统计、分组、排序、聚合、比较、筛选字段、字段别名 等 +

+ ![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_single.gif)
@@ -97,6 +101,10 @@ https://github.com/Tencent/APIJSON } +

+ APIJSON 各种单表数组查询:简单查询、统计、分组、排序、聚合、分页、比较、搜索、正则、条件组合 等 +

+ ![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_array.gif)
@@ -139,8 +147,6 @@ https://github.com/Tencent/APIJSON } -![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_associate.gif) -
#### 获取类似微信朋友圈的动态列表 @@ -260,20 +266,26 @@ https://github.com/Tencent/APIJSON } +

+ APIJSON 各种多表关联查询:一对一、一对多、多对一、各种条件 等 +

+ +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_associate.gif) +

- APIJSON 各种 JOIN:< LEFT, > RIGHT, & INNER 等 + APIJSON 各种 JOIN:< LEFT JOIN, & INNER JOIN 等

- + ![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_join.gif)

- APIJSON 各种子查询:@from@ 数据源, key@ =, key{}@ IN, key<>@ CONTAINS, key}{@ EXISTS 等 + APIJSON 各种子查询:@from@ FROM, key@ =, key>@ >, key{}@ IN, key}{@ EXISTS 等

- + ![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_subquery.gif)
@@ -281,7 +293,7 @@ https://github.com/Tencent/APIJSON

APIJSON 部分功能演示集合,由浅入深、由简单到复杂

- + ![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_summary.gif)
From cc34a54e27f467af5b82651d16f15fea6c24ec19 Mon Sep 17 00:00:00 2001 From: fanpocha <289484900@qq.com> Date: Tue, 22 Feb 2022 10:34:27 +0800 Subject: [PATCH 004/619] Update README.md add caizu --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2a1d989a5..61a28a7cf 100644 --- a/README.md +++ b/README.md @@ -244,6 +244,7 @@ https://github.com/Tencent/APIJSON/issues/187 + 珠海采筑电子商务有限公司
* [腾讯科技有限公司](https://www.tencent.com) From 24e5c0b264fbd858bb4af9ab7ece71e548d225cf Mon Sep 17 00:00:00 2001 From: fanpocha <289484900@qq.com> Date: Tue, 22 Feb 2022 10:37:11 +0800 Subject: [PATCH 005/619] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 61a28a7cf..a7057b70a 100644 --- a/README.md +++ b/README.md @@ -244,7 +244,7 @@ https://github.com/Tencent/APIJSON/issues/187 - 珠海采筑电子商务有限公司 +
* [腾讯科技有限公司](https://www.tencent.com) From b2059445ab16e94d5a39227c29444fba67a76c06 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Feb 2022 23:43:51 +0800 Subject: [PATCH 006/619] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=99=BB=E8=AE=B0?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E7=8F=A0=E6=B5=B7=E9=87=87=E7=AD=91?= =?UTF-8?q?=E7=94=B5=E5=AD=90=E5=95=86=E5=8A=A1=E6=9C=89=E9=99=90=E5=85=AC?= =?UTF-8?q?=E5=8F=B8=EF=BC=8C=E6=96=B0=E5=A2=9E=20=E4=B9=90=E6=8B=BC?= =?UTF-8?q?=E7=94=A8=E8=BD=A6=20=E7=9A=84=20Logo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON#%E4%BD%BF%E7%94%A8%E7%99%BB%E8%AE%B0 --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a7057b70a..22390da85 100644 --- a/README.md +++ b/README.md @@ -226,7 +226,7 @@ https://github.com/Tencent/APIJSON/issues/36 如果您在使用 APIJSON,请让我们知道,您的使用对我们非常重要(按登记顺序排列):
https://github.com/Tencent/APIJSON/issues/187
- +
@@ -244,6 +244,7 @@ https://github.com/Tencent/APIJSON/issues/187 +
@@ -255,6 +256,7 @@ https://github.com/Tencent/APIJSON/issues/187 * [投投科技](https://www.toutou.com.cn) * [圆通速递](https://www.yto.net.cn) * [乐拼科技](https://www.lepinyongche.com) + * [珠海采筑电子商务有限公司](https://www.aupup.com) ### 贡献者们 主项目 APIJSON 的贡献者们(6 个腾讯工程师、1 个知乎基础研发架构师、1 个圆通工程师 等):
From dda1120c5d0e16344d4ac905ed79ea7abf947537 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Feb 2022 02:31:16 +0800 Subject: [PATCH 007/619] =?UTF-8?q?JOIN=20=E6=94=AF=E6=8C=81=E5=A4=9A?= =?UTF-8?q?=E4=B8=AA=E5=AD=97=E6=AE=B5=E5=85=B3=E8=81=94=E5=8F=8A=E5=BC=95?= =?UTF-8?q?=E7=94=A8=E8=B5=8B=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractParser.java | 350 ++++++++++-------- .../java/apijson/orm/AbstractSQLConfig.java | 62 ++-- .../java/apijson/orm/AbstractSQLExecutor.java | 48 ++- .../src/main/java/apijson/orm/Entry.java | 7 +- .../src/main/java/apijson/orm/Join.java | 204 +++++----- 5 files changed, 384 insertions(+), 287 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 529f4025f..3e3703468 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -1307,8 +1308,25 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // return response; } - /**多表同时筛选 - * @param join "&/User/id@, JOIN_COPY_KEY_LIST; + static { // TODO 不全 + JOIN_COPY_KEY_LIST = new ArrayList(); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_ROLE); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_DATABASE); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_SCHEMA); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_DATASOURCE); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COLUMN); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COMBINE); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_GROUP); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_HAVING); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_ORDER); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_RAW); + } + + /**JOIN 多表同时筛选 + * @param join "&/User, joinList = new ArrayList<>(); - - JSONObject tableObj; - String targetPath; - - JSONObject targetObj; - String targetTable; - String targetKey; - - String path; - - // List onList = new ArrayList<>(); - for (Entry e : set) {//User/id@ - if (e.getValue() instanceof JSONObject == false) { + for (Entry e : set) { // { &/User:{}, ( ) <> () * // if (StringUtil.isEmpty(joinType, true)) { @@ -1373,192 +1380,223 @@ else if (join != null){ path = path.substring(index + 1); index = path.indexOf("/"); - String tableKey = index < 0 ? null : path.substring(0, index); //User:owner + String tableKey = index < 0 ? path : path.substring(0, index); // User:owner apijson.orm.Entry entry = Pair.parseEntry(tableKey, true); - String table = entry.getKey(); //User + String table = entry.getKey(); // User if (StringUtil.isName(table) == false) { throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 Table 值 " + table + " 不合法!" - + "必须为 &/Table0/key0,> tableSet = tableObj.entrySet(); + // 取出所有 join 条件 + JSONObject requestObj = new JSONObject(true); // (JSONObject) obj.clone(); + + boolean matchSingle = false; + for (Entry tableEntry : tableSet) { + String k = tableEntry.getKey(); + Object v = k == null ? null : tableEntry.getValue(); + if (v == null) { + continue; + } - // 主表不允许别名 - // apijson.orm.Entry targetEntry = Pair.parseEntry(targetTableKey, true); - // targetTable = targetEntry.getKey(); //User - // if (StringUtil.isName(targetTable) == false) { - // throw new IllegalArgumentException("/" + path + ":'/targetTable/targetKey' 中 targetTable 值 " + targetTable + " 不合法!必须满足大写字母开头的表对象英文单词 key 格式!"); - // } - // - // String targetAlias = targetEntry.getValue(); //owner - // if (StringUtil.isNotEmpty(targetAlias, true) && StringUtil.isName(targetAlias) == false) { - // throw new IllegalArgumentException("/" + path + ":'/targetTable:targetAlias/targetKey' 中 targetAlias 值 " + targetAlias + " 不合法!必须满足英文单词变量名格式!"); - // } + matchSingle = matchSingle == false && k.equals(key); + if (matchSingle) { + continue; + } - targetTable = targetTableKey; // 主表不允许别名 - if (StringUtil.isName(targetTable) == false) { - throw new IllegalArgumentException("/" + path + ":'/targetTable/targetKey' 中 targetTable 值 " + targetTable + " 不合法!必须满足大写字母开头的表对象英文单词 key 格式!"); - } + if (k.length() > 1 && k.indexOf("@") == k.length() - 1 && v instanceof String) { + String sv = (String) v; + int ind = sv.endsWith("@") ? -1 : sv.indexOf("/"); + if (ind == 0 && key == null) { // 指定了某个就只允许一个 ON 条件 + String p = sv.substring(1); + int ind2 = p.indexOf("/"); + String tk = ind2 < 0 ? null : p.substring(0, ind2); - //对引用的JSONObject添加条件 - try { - targetObj = request.getJSONObject(targetTableKey); - } - catch (Exception e2) { - throw new IllegalArgumentException("/" + path + ":'/targetTable/targetKey' 中路径对应的 '" + targetTableKey + "':value 中 value 类型不合法!必须是 {} 这种 JSONObject 格式!" + e2.getMessage()); - } + apijson.orm.Entry te = tk == null || p.substring(ind2 + 1).indexOf("/") >= 0 ? null : Pair.parseEntry(tk, true); - if (targetObj == null) { - throw new IllegalArgumentException("/" + path + ":'/targetTable/targetKey' 中路径对应的对象 '" + targetTableKey + "':{} 不存在或值为 null !必须是 {} 这种 JSONObject 格式!"); - } + if (te != null && JSONRequest.isTableKey(te.getKey()) && request.get(tk) instanceof JSONObject) { + refObj.put(k, v); + continue; + } + } - // 保证和 SQLExcecutor 缓存的 Config 里 where 顺序一致,生成的 SQL 也就一致 <<<<<<<<< - // AbstractSQLConfig.newSQLConfig 中强制把 id, id{}, userId, userId{} 放到了最前面 tableObj.put(key, tableObj.remove(key)); + Object rv = getValueByPath(sv); + if (rv != null && rv.equals(sv) == false) { + requestObj.put(k.substring(0, k.length() - 1), rv); + continue; + } - if (tableObj.size() > 1) { // 把 key 强制放最前,AbstractSQLExcecutor 中 config.putWhere 也是放尽可能最前 - JSONObject newTableObj = new JSONObject(tableObj.size(), true); - newTableObj.put(key, tableObj.remove(key)); - newTableObj.putAll(tableObj); + throw new UnsupportedOperationException(table + "/" + k + " 不合法!" + JSONRequest.KEY_JOIN + " 关联的 Table 中," + + "join: ?/Table/key 时只能有 1 个 key@:value;join: ?/Table 时所有 key@:value 要么是符合 join 格式,要么能直接解析成具体值!"); // TODO 支持 join on + } - tableObj = newTableObj; - request.put(tableKey, tableObj); + if (k.startsWith("@")) { + if (JOIN_COPY_KEY_LIST.contains(k)) { + requestObj.put(k, v); // 保留 + } + } + else { + if (k.endsWith("@")) { + throw new UnsupportedOperationException(table + "/" + k + " 不合法!" + JSONRequest.KEY_JOIN + " 关联的 Table 中," + + "join: ?/Table/key 时只能有 1 个 key@:value;join: ?/Table 时所有 key@:value 要么是符合 join 格式,要么能直接解析成具体值!"); // TODO 支持 join on + } -// tableObj.clear(); -// tableObj.putAll(newTableObj); + if (k.contains("()") == false) { // 不需要远程函数 + requestObj.put(k, v); // 保留 + } + } + } + + Set> refSet = refObj.entrySet(); + if (refSet.isEmpty()) { + throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 alias 值 " + alias + " 不合法!" + + "必须为 &/Table0,>>>>>>>> Join j = new Join(); - j.setPath(path); - j.setOriginKey(key); - j.setOriginValue(targetPath); + j.setPath(e.getKey()); j.setJoinType(joinType); j.setTable(table); j.setAlias(alias); - j.setTargetTable(targetTable); - // j.setTargetAlias(targetAlias); - j.setTargetKey(targetKey); - j.setKeyAndType(key); - j.setRequest(getJoinObject(table, tableObj, key)); - j.setOuter((JSONObject) e.getValue()); - - if (StringUtil.isName(j.getKey()) == false) { - throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 key@ 中 key 值 " + j.getKey() + " 不合法!必须满足英文单词变量名格式!"); - } + j.setOuter((JSONObject) outer); + j.setRequest(requestObj); - joinList.add(j); - - // onList.add(table + "." + key + " = " + targetTable + "." + targetKey); // ON User.id = Moment.userId + List onList = new ArrayList<>(); + for (Entry refEntry : refSet) { + String originKey = refEntry.getKey(); - } - - - //拼接多个 SQLConfig 的SQL语句,然后执行,再把结果分别缓存(Moment, User等)到 SQLExecutor 的 cacheMap - // AbstractSQLConfig config0 = null; - // String sql = "SELECT " + config0.getColumnString() + " FROM " + config0.getTable() + " INNER JOIN " + targetTable + " ON " - // + onList.get(0) + config0.getGroupString() + config0.getHavingString() + config0.getOrderString(); - - - return joinList; - } + String targetPath = (String) refEntry.getValue(); + if (StringUtil.isEmpty(targetPath, true)) { + throw new IllegalArgumentException(e.getKey() + ":value 中 value 值 " + targetPath + " 不合法!必须为引用赋值的路径 '/targetTable/targetKey' !"); + } + // 取出引用赋值路径 targetPath 对应的 Table 和 key + index = targetPath.lastIndexOf("/"); + String targetKey = index < 0 ? null : targetPath.substring(index + 1); + if (StringUtil.isName(targetKey) == false) { + throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中 targetKey 值 " + targetKey + " 不合法!必须满足英文单词变量名格式!"); + } + targetPath = targetPath.substring(0, index); + index = targetPath.lastIndexOf("/"); + String targetTableKey = index < 0 ? targetPath : targetPath.substring(index + 1); - private static final List JOIN_COPY_KEY_LIST; - static { // TODO 不全 - JOIN_COPY_KEY_LIST = new ArrayList(); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_ROLE); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_DATABASE); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_SCHEMA); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_DATASOURCE); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COLUMN); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COMBINE); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_GROUP); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_HAVING); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_ORDER); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_RAW); - } + // 主表允许别名 + apijson.orm.Entry targetEntry = Pair.parseEntry(targetTableKey, true); + String targetTable = targetEntry.getKey(); //User + if (StringUtil.isName(targetTable) == false) { + throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中 targetTable 值 " + targetTable + " 不合法!必须满足大写字母开头的表对象英文单词 key 格式!"); + } - /**取指定 JSON 对象的 id 集合 - * @param table - * @param key - * @param obj - * @return null ? 全部 : 有限的数组 - */ - private JSONObject getJoinObject(String table, JSONObject obj, String key) { - if (obj == null || obj.isEmpty()) { - Log.e(TAG, "getIdList obj == null || obj.isEmpty() >> return null;"); - return null; - } - if (StringUtil.isEmpty(key, true)) { - Log.e(TAG, "getIdList StringUtil.isEmpty(key, true) >> return null;"); - return null; - } + String targetAlias = targetEntry.getValue(); //owner + if (StringUtil.isNotEmpty(targetAlias, true) && StringUtil.isName(targetAlias) == false) { + throw new IllegalArgumentException(e.getKey() + ":'/targetTable:targetAlias/targetKey' 中 targetAlias 值 " + targetAlias + " 不合法!必须满足英文单词变量名格式!"); + } - // 取出所有 join 条件 - JSONObject requestObj = new JSONObject(true); // (JSONObject) obj.clone(); - Set set = new LinkedHashSet<>(obj.keySet()); - for (String k : set) { - if (StringUtil.isEmpty(k, true)) { - continue; - } + targetTable = targetTableKey; // 主表允许别名 + if (StringUtil.isName(targetTable) == false) { + throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中 targetTable 值 " + targetTable + " 不合法!必须满足大写字母开头的表对象英文单词 key 格式!"); + } - if (k.startsWith("@")) { - if (JOIN_COPY_KEY_LIST.contains(k)) { - requestObj.put(k, obj.get(k)); //保留 + //对引用的JSONObject添加条件 + JSONObject targetObj; + try { + targetObj = request.getJSONObject(targetTableKey); } - } - else { - if (k.endsWith("@")) { - if (k.equals(key)) { - continue; - } - throw new UnsupportedOperationException(table + "." + k + " 不合法!" + JSONRequest.KEY_JOIN - + " 关联的Table中只能有1个 key@:value !"); // TODO 支持 join on + catch (Exception e2) { + throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中路径对应的 '" + targetTableKey + "':value 中 value 类型不合法!必须是 {} 这种 JSONObject 格式!" + e2.getMessage()); } - if (k.contains("()") == false) { //不需要远程函数 - // requestObj.put(k, obj.remove(k)); //remove是为了避免重复查询副表 - requestObj.put(k, obj.get(k)); //remove是为了避免重复查询副表 + if (targetObj == null) { + throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中路径对应的对象 '" + targetTableKey + "':{} 不存在或值为 null !必须是 {} 这种 JSONObject 格式!"); } + + Join.On on = new Join.On(); + on.setKeyAndType(j.getJoinType(), j.getTable(), originKey); + if (StringUtil.isName(on.getKey()) == false) { + throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 key@ 中 key 值 " + on.getKey() + " 不合法!必须满足英文单词变量名格式!"); + } + + on.setOriginKey(originKey); + on.setOriginValue((String) refEntry.getValue()); + on.setTargetTable(targetTable); + on.setTargetAlias(targetAlias); + on.setTargetKey(targetKey); + + onList.add(on); } + + j.setOnList(onList); + + joinList.add(j); + // onList.add(table + "." + key + " = " + targetTable + "." + targetKey); // ON User.id = Moment.userId + + // 保证和 SQLExcecutor 缓存的 Config 里 where 顺序一致,生成的 SQL 也就一致 <<<<<<<<< + // AbstractSQLConfig.newSQLConfig 中强制把 id, id{}, userId, userId{} 放到了最前面 tableObj.put(key, tableObj.remove(key)); + + if (refObj.size() != tableObj.size()) { // 把 key 强制放最前,AbstractSQLExcecutor 中 config.putWhere 也是放尽可能最前 + refObj.putAll(tableObj); + request.put(tableKey, refObj); + +// tableObj.clear(); +// tableObj.putAll(refObj); + } + // 保证和 SQLExcecutor 缓存的 Config 里 where 顺序一致,生成的 SQL 也就一致 >>>>>>>>> } + //拼接多个 SQLConfig 的SQL语句,然后执行,再把结果分别缓存(Moment, User等)到 SQLExecutor 的 cacheMap + // AbstractSQLConfig config0 = null; + // String sql = "SELECT " + config0.getColumnString() + " FROM " + config0.getTable() + " INNER JOIN " + targetTable + " ON " + // + onList.get(0) + config0.getGroupString() + config0.getHavingString() + config0.getOrderString(); - return requestObj; + return joinList; } + + + @Override public int getDefaultQueryCount() { return DEFAULT_QUERY_COUNT; diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index c28d07ad1..eb78e1613 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -56,6 +56,7 @@ import apijson.RequestMethod; import apijson.SQL; import apijson.StringUtil; +import apijson.orm.Join.On; import apijson.orm.exception.NotExistException; import apijson.orm.model.Access; import apijson.orm.model.Column; @@ -83,7 +84,6 @@ public abstract class AbstractSQLConfig implements SQLConfig { // * 和 / 不能同时出现,防止 /* */ 段注释! # 和 -- 不能出现,防止行注释! ; 不能出现,防止隔断SQL语句!空格不能出现,防止 CRUD,DROP,SHOW TABLES等语句! private static final Pattern PATTERN_RANGE; private static final Pattern PATTERN_FUNCTION; - private static final Pattern PATTERN_STRING; /** * 表名映射,隐藏真实表名,对安全要求很高的表可以这么做 @@ -97,10 +97,9 @@ public abstract class AbstractSQLConfig implements SQLConfig { // 允许调用的 SQL 函数:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL public static final Map SQL_FUNCTION_MAP; - static { // 凡是 SQL 边界符、分隔符、注释符 都不允许,例如 ' " ` ( ) ; # -- ,以免拼接 SQL 时被注入意外可执行指令 + static { // 凡是 SQL 边界符、分隔符、注释符 都不允许,例如 ' " ` ( ) ; # -- /**/ ,以免拼接 SQL 时被注入意外可执行指令 PATTERN_RANGE = Pattern.compile("^[0-9%,!=\\<\\>/\\.\\+\\-\\*\\^]+$"); // ^[a-zA-Z0-9_*%!=<>(),"]+$ 导致 exists(select*from(Comment)) 通过! PATTERN_FUNCTION = Pattern.compile("^[A-Za-z0-9%,:_@&~`!=\\<\\>\\|\\[\\]\\{\\} /\\.\\+\\-\\*\\^\\?\\(\\)\\$]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值 - PATTERN_STRING = Pattern.compile("^[,#;\"`]+$"); TABLE_KEY_MAP = new HashMap(); TABLE_KEY_MAP.put(Table.class.getSimpleName(), Table.TABLE_NAME); @@ -3378,10 +3377,6 @@ public String getJoinString() throws Exception { List pvl = new ArrayList<>(); boolean changed = false; - String sql = null; - SQLConfig jc; - String jt; - String tt; // 主表不用别名 String ta; for (Join j : joinList) { if (j.isAppJoin()) { // APP JOIN,只是作为一个标记,执行完主表的查询后自动执行副表的查询 User.id IN($commentIdList) @@ -3391,18 +3386,20 @@ public String getJoinString() throws Exception { //LEFT JOIN sys.apijson_user AS User ON User.id = Moment.userId, 都是用 = ,通过relateType处理缓存 // <"INNER JOIN User ON User.id = Moment.userId", UserConfig> TODO AS 放 getSQLTable 内 - jc = j.getJoinConfig(); + SQLConfig jc = j.getJoinConfig(); jc.setPrepared(isPrepared()); - jt = StringUtil.isEmpty(jc.getAlias(), true) ? jc.getTable() : jc.getAlias(); - tt = j.getTargetTable(); + String jt = StringUtil.isEmpty(jc.getAlias(), true) ? jc.getTable() : jc.getAlias(); + List onList = j.getOnList(); //如果要强制小写,则可在子类重写这个方法再 toLowerCase // if (DATABASE_POSTGRESQL.equals(getDatabase())) { // jt = jt.toLowerCase(); // tn = tn.toLowerCase(); // } - + + String sql; + switch (type) { //前面已跳过 case "@": // APP JOIN // continue; @@ -3413,9 +3410,17 @@ public String getJoinString() throws Exception { case ">": // RIGHT JOIN jc.setMain(true).setKeyPrefix(false); sql = ( "<".equals(type) ? " LEFT" : (">".equals(type) ? " RIGHT" : " CROSS") ) - + " JOIN ( " + jc.getSQL(isPrepared()) + " ) AS " - + quote + jt + quote + " ON " + quote + jt + quote + "." + quote + j.getKey() + quote + " = " - + quote + tt + quote + "." + quote + j.getTargetKey() + quote; + + " JOIN ( " + jc.getSQL(isPrepared()) + " ) AS " + quote + jt + quote; + + if (onList != null) { + boolean first = true; + for (On on : onList) { + sql += (first ? " ON " : " AND ") + quote + jt + quote + "." + quote + on.getKey() + quote + " = " + + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + first = false; + } + } + jc.setMain(false).setKeyPrefix(true); pvl.addAll(jc.getPreparedValueList()); @@ -3429,8 +3434,15 @@ public String getJoinString() throws Exception { case "^": // SIDE JOIN: ! (A & B) case "(": // ANTI JOIN: A & ! B case ")": // FOREIGN JOIN: B & ! A - sql = " INNER JOIN " + jc.getTablePath() - + " ON " + quote + jt + quote + "." + quote + j.getKey() + quote + " = " + quote + tt + quote + "." + quote + j.getTargetKey() + quote; + sql = " INNER JOIN " + jc.getTablePath(); + if (onList != null) { + boolean first = true; + for (On on : onList) { + sql += (first ? " ON " : " AND ") + quote + jt + quote + "." + quote + on.getKey() + quote + " = " + + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + first = false; + } + } break; default: throw new UnsupportedOperationException( @@ -3451,7 +3463,7 @@ public String getJoinString() throws Exception { } - return joinOns; + return StringUtil.isEmpty(joinOns, true) ? "" : joinOns + " \n"; } protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { @@ -3924,12 +3936,20 @@ else if (joinConfig.getDatabase().equals(config.getDatabase()) == false) { /* SELECT count(*) AS count FROM sys.Moment AS Moment LEFT JOIN ( SELECT count(*) AS count FROM sys.Comment ) AS Comment ON Comment.momentId = Moment.id LIMIT 1 OFFSET 0 */ if (RequestMethod.isHeadMethod(method, true)) { - joinConfig.setMethod(GET); //子查询不能为 SELECT count(*) ,而应该是 SELECT momentId - joinConfig.setColumn(Arrays.asList(j.getKey())); //优化性能,不取非必要的字段 + List onList = j.getOnList(); + List column = onList == null ? null : new ArrayList<>(onList.size()); + if (column != null) { + for (On on : onList) { + column.add(on.getKey()); + } + } + + joinConfig.setMethod(GET); // 子查询不能为 SELECT count(*) ,而应该是 SELECT momentId + joinConfig.setColumn(column); // 优化性能,不取非必要的字段 if (cacheConfig != null) { - cacheConfig.setMethod(GET); //子查询不能为 SELECT count(*) ,而应该是 SELECT momentId - cacheConfig.setColumn(Arrays.asList(j.getKey())); //优化性能,不取非必要的字段 + cacheConfig.setMethod(GET); // 子查询不能为 SELECT count(*) ,而应该是 SELECT momentId + cacheConfig.setColumn(column); // 优化性能,不取非必要的字段 } } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 85a2fc146..51705dca4 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -37,6 +38,7 @@ import apijson.NotNull; import apijson.RequestMethod; import apijson.StringUtil; +import apijson.orm.Join.On; /**executor for query(read) or update(write) MySQL database * @author Lemon @@ -531,7 +533,14 @@ else if (config.isClickHouse() && (sqlTable.startsWith("`") || sqlTable.startsWi if (curJoin != prevJoin) { // 前后字段不在同一个表对象,即便后面出现 null,也不该是主表数据,而是逻辑 bug 导致 SQLConfig viceConfig = curJoin != null && curJoin.isSQLJoin() ? curJoin.getCacheConfig() : null; if (viceConfig != null) { //FIXME 只有和主表关联才能用 item,否则应该从 childMap 查其它副表数据 - viceConfig.putWhere(curJoin.getKey(), item.get(curJoin.getTargetKey()), true); + List onList = curJoin.getOnList(); + if (onList != null) { + for (On on : onList) { + if (on != null) { + viceConfig.putWhere(on.getKey(), item.get(on.getTargetKey()), true); + } + } + } } String viceSql = viceConfig == null ? null : viceConfig.getSQL(false); //TODO 在 SQLConfig 缓存 SQL,减少大量的重复生成 @@ -660,25 +669,27 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map jc = join.getJoinConfig(); - //取出 "id@": "@/User/userId" 中所有 userId 的值 - List targetValueList = new ArrayList<>(); - JSONObject mainTable; - Object targetValue; + List onList = join.getOnList(); + if (onList != null) { + for (On on : onList) { + //取出 "id@": "@/User/userId" 中所有 userId 的值 + List targetValueList = new ArrayList<>(); - for (int i = 0; i < resultList.size(); i++) { - mainTable = resultList.get(i); - targetValue = mainTable == null ? null : mainTable.get(join.getTargetKey()); + for (int i = 0; i < resultList.size(); i++) { + JSONObject mainTable = resultList.get(i); + Object targetValue = mainTable == null ? null : mainTable.get(on.getTargetKey()); + + if (targetValue != null && targetValueList.contains(targetValue) == false) { + targetValueList.add(targetValue); + } + } - if (targetValue != null && targetValueList.contains(targetValue) == false) { - targetValueList.add(targetValue); + //替换为 "id{}": [userId1, userId2, userId3...] + jc.putWhere(on.getOriginKey(), null, false); // remove orginKey + jc.putWhere(on.getKey() + "{}", targetValueList, true); // add orginKey{} } } - - //替换为 "id{}": [userId1, userId2, userId3...] - jc.putWhere(join.getOriginKey(), null, false); // remove orginKey - jc.putWhere(join.getKey() + "{}", targetValueList, true); // add orginKey{} - jc.setMain(true).setPreparedValueList(new ArrayList<>()); boolean prepared = jc.isPrepared(); @@ -721,7 +732,6 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map result = new JSONObject(true); for (int i = 1; i <= length; i++) { - result = onPutColumn(jc, rs, rsmd, index, result, i, null, null); } @@ -731,7 +741,11 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n"); //缓存到 childMap - cc.putWhere(join.getKey(), result.get(join.getKey()), true); + if (onList != null) { + for (On on : onList) { + cc.putWhere(on.getKey(), result.get(on.getKey()), true); + } + } cacheSql = cc.getSQL(false); childMap.put(cacheSql, result); diff --git a/APIJSONORM/src/main/java/apijson/orm/Entry.java b/APIJSONORM/src/main/java/apijson/orm/Entry.java index 93e9f0418..a8aaf7bfd 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Entry.java +++ b/APIJSONORM/src/main/java/apijson/orm/Entry.java @@ -5,6 +5,8 @@ package apijson.orm; +import java.util.Map; + /**自定义Entry * *java.util.Map.Entry是interface,new Entry(...)不好用,其它的Entry也不好用 * @author Lemon @@ -13,7 +15,7 @@ * @use new Entry(...) * @warn K,V都需要基本类型时不建议使用,判空麻烦,不如新建一个Model */ -public class Entry { +public class Entry implements Map.Entry { public K key; public V value; @@ -39,8 +41,9 @@ public void setKey(K key) { public V getValue() { return value; } - public void setValue(V value) { + public V setValue(V value) { this.value = value; + return value; } public boolean isEmpty() { diff --git a/APIJSONORM/src/main/java/apijson/orm/Join.java b/APIJSONORM/src/main/java/apijson/orm/Join.java index 7d8707393..24ad36ec8 100644 --- a/APIJSONORM/src/main/java/apijson/orm/Join.java +++ b/APIJSONORM/src/main/java/apijson/orm/Join.java @@ -5,6 +5,8 @@ package apijson.orm; +import java.util.List; + import com.alibaba.fastjson.JSONObject; import apijson.NotNull; @@ -13,27 +15,21 @@ * @author Lemon */ public class Join { - + private String path; - private String originKey; - private String originValue; - - private String joinType; // "@" - APP, "<" - LEFT, ">" - RIGHT, "*" - CROSS, "&" - INNER, "|" - FULL, "!" - OUTER, "^" - SIDE, "(" - ANTI, ")" - FOREIGN - private String relateType; // "" - 一对一, "{}" - 一对多, "<>" - 多对一 - private JSONObject request; // { "id@":"/Moment/userId" } - private String table; //User - private String alias; //owner - private String key; //id - private String targetTable; // Moment - private String targetAlias; //main - private String targetKey; // userId - - private JSONObject outter; + private String joinType; // "@" - APP, "<" - LEFT, ">" - RIGHT, "*" - CROSS, "&" - INNER, "|" - FULL, "!" - OUTER, "^" - SIDE, "(" - ANTI, ")" - FOREIGN + private String table; // User + private String alias; // owner + private List onList; // ON User.id = Moment.userId AND ... + + private JSONObject request; // { "id@":"/Moment/userId" } + private JSONObject outer; // "join": { " getOnList() { + return onList; + } + public void setOnList(List onList) { + this.onList = onList; + } + public JSONObject getRequest() { return request; } public void setRequest(JSONObject request) { this.request = request; } - public String getKey() { - return key; - } - public void setKey(String key) { - this.key = key; - } - public void setTargetTable(String targetTable) { - this.targetTable = targetTable; - } - public String getTargetTable() { - return targetTable; - } - public void setTargetAlias(String targetAlias) { - this.targetAlias = targetAlias; - } - public String getTargetAlias() { - return targetAlias; - } - public String getTargetKey() { - return targetKey; - } - public void setTargetKey(String targetKey) { - this.targetKey = targetKey; - } - public JSONObject getOuter() { - return outter; + return outer; } - public void setOuter(JSONObject outter) { - this.outter = outter; + public void setOuter(JSONObject outer) { + this.outer = outer; } public SQLConfig getJoinConfig() { @@ -130,35 +89,11 @@ public SQLConfig getCacheConfig() { public void setCacheConfig(SQLConfig cacheConfig) { this.cacheConfig = cacheConfig; } - public SQLConfig getOuterConfig() { - return outterConfig; + return outerConfig; } - public void setOuterConfig(SQLConfig outterConfig) { - this.outterConfig = outterConfig; - } - - - public void setKeyAndType(@NotNull String originKey) throws Exception { //id, id@, id{}@, contactIdList<>@ ... - if (originKey.endsWith("@")) { - originKey = originKey.substring(0, originKey.length() - 1); - } - else { //TODO 暂时只允许 User.id = Moment.userId 字段关联,不允许 User.id = 82001 这种 - throw new IllegalArgumentException(joinType + "/.../" + table + "/" + originKey + " 不合法!join:'.../refKey'" + " 中 refKey 必须以 @ 结尾!"); - } - - if (originKey.endsWith("{}")) { - setRelateType("{}"); - setKey(originKey.substring(0, originKey.length() - 2)); - } - else if (originKey.endsWith("<>")) { - setRelateType("<>"); - setKey(originKey.substring(0, originKey.length() - 2)); - } - else { - setRelateType(""); - setKey(originKey); - } + public void setOuterConfig(SQLConfig outerConfig) { + this.outerConfig = outerConfig; } @@ -221,5 +156,92 @@ public static boolean isLeftOrRightJoin(Join j) { return j != null && j.isLeftOrRightJoin(); } + + + public static class On { + + private String originKey; + private String originValue; + + private String relateType; // "" - 一对一, "{}" - 一对多, "<>" - 多对一 + private String key; // id + private String targetTable; // Moment + private String targetAlias; // main + private String targetKey; // userId + + public String getOriginKey() { + return originKey; + } + public void setOriginKey(String originKey) { + this.originKey = originKey; + } + public String getOriginValue() { + return originValue; + } + public void setOriginValue(String originValue) { + this.originValue = originValue; + } + + + public String getRelateType() { + return relateType; + } + public void setRelateType(String relateType) { + this.relateType = relateType; + } + + + public String getKey() { + return key; + } + public void setKey(String key) { + this.key = key; + } + public void setTargetTable(String targetTable) { + this.targetTable = targetTable; + } + public String getTargetTable() { + return targetTable; + } + public void setTargetAlias(String targetAlias) { + this.targetAlias = targetAlias; + } + public String getTargetAlias() { + return targetAlias; + } + public String getTargetKey() { + return targetKey; + } + public void setTargetKey(String targetKey) { + this.targetKey = targetKey; + } + + + public void setKeyAndType(String joinType, String table, @NotNull String originKey) throws Exception { //id, id@, id{}@, contactIdList<>@ ... + if (originKey.endsWith("@")) { + originKey = originKey.substring(0, originKey.length() - 1); + } + else { //TODO 暂时只允许 User.id = Moment.userId 字段关联,不允许 User.id = 82001 这种 + throw new IllegalArgumentException(joinType + "/.../" + table + "/" + originKey + " 不合法!join:'.../refKey'" + " 中 refKey 必须以 @ 结尾!"); + } + + if (originKey.endsWith("{}")) { + setRelateType("{}"); + setKey(originKey.substring(0, originKey.length() - 2)); + } + else if (originKey.endsWith("<>")) { + setRelateType("<>"); + setKey(originKey.substring(0, originKey.length() - 2)); + } + else { + setRelateType(""); + setKey(originKey); + } + } + + } + + + } From 5e709edcff434e8dd115dde1c0e7e77bf8aa0405 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Feb 2022 03:05:08 +0800 Subject: [PATCH 008/619] =?UTF-8?q?JOIN=20ON=20=E6=94=AF=E6=8C=81=E5=B8=A6?= =?UTF-8?q?=E9=9D=9E=E5=BC=95=E7=94=A8=E8=B5=8B=E5=80=BC=E5=85=B3=E8=81=94?= =?UTF-8?q?=E7=9A=84=E6=99=AE=E9=80=9A=E6=9D=A1=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractSQLConfig.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index eb78e1613..ed95a0682 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -3451,8 +3451,20 @@ public String getJoinString() throws Exception { + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" ); } + + SQLConfig oc = j.getOuterConfig(); + String ow = null; + if (oc != null) { + oc.setPrepared(isPrepared()); + oc.setPreparedValueList(new ArrayList<>()); + oc.setMain(false).setKeyPrefix(true); + ow = oc.getWhereString(false); + + pvl.addAll(oc.getPreparedValueList()); + changed = true; + } - joinOns += " \n " + sql; + joinOns += " \n " + sql + (StringUtil.isEmpty(ow, true) ? "" : " AND ( " + ow + " ) "); } @@ -3925,7 +3937,7 @@ else if (joinConfig.getDatabase().equals(config.getDatabase()) == false) { joinConfig.setMain(false).setKeyPrefix(true); - if (j.isLeftOrRightJoin()) { + if (j.getOuter() != null) { SQLConfig outterConfig = newSQLConfig(method, table, alias, j.getOuter(), null, false, callback); outterConfig.setMain(false).setKeyPrefix(true).setDatabase(joinConfig.getDatabase()).setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 j.setOuterConfig(outterConfig); From f7b82fd90907b954b9ef90a4bfb8b8f05c72f513 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Feb 2022 04:17:05 +0800 Subject: [PATCH 009/619] =?UTF-8?q?&=20INNER=20JOIN=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=8D=95=E7=8B=AC=E8=AE=BE=E7=BD=AE=20JOIN=20=E8=AF=AD?= =?UTF-8?q?=E5=8F=A5=E4=B8=AD=E7=9A=84=E5=AD=97=E6=AE=B5=E3=80=81=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6=E3=80=81=E5=88=86=E7=BB=84=E3=80=81=E8=81=9A=E5=90=88?= =?UTF-8?q?=E3=80=81=E6=8E=92=E5=BA=8F=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 和 < LEFT JOIN, > RIGHT JOIN 一样,例如 "join": { "&/User/id": { "id>": 82001, "@order": "id+" } } --- .../java/apijson/orm/AbstractSQLConfig.java | 111 +++++++++--------- 1 file changed, 57 insertions(+), 54 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index ed95a0682..8e4cac06c 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1036,25 +1036,27 @@ public String getGroupString(boolean hasPrefix) { //加上子表的 group String joinGroup = ""; if (joinList != null) { - SQLConfig cfg; - String c; boolean first = true; for (Join j : joinList) { if (j.isAppJoin()) { continue; } - cfg = j.isLeftOrRightJoin() ? j.getOuterConfig() : j.getJoinConfig(); - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } + SQLConfig ocfg = j.getOuterConfig(); + SQLConfig cfg = (ocfg != null && ocfg.getGroup() != null) || j.isLeftOrRightJoin() ? ocfg : j.getJoinConfig(); - c = ((AbstractSQLConfig) cfg).getGroupString(false); - if (StringUtil.isEmpty(c, true) == false) { - joinGroup += (first ? "" : ", ") + c; - first = false; - } + if (cfg != null) { + cfg.setMain(false).setKeyPrefix(true); + if (StringUtil.isEmpty(cfg.getAlias(), true)) { + cfg.setAlias(cfg.getTable()); + } + String c = ((AbstractSQLConfig) cfg).getGroupString(false); + if (StringUtil.isEmpty(c, true) == false) { + joinGroup += (first ? "" : ", ") + c; + first = false; + } + } } } @@ -1098,25 +1100,27 @@ public String getHavingString(boolean hasPrefix) { //加上子表的 having String joinHaving = ""; if (joinList != null) { - SQLConfig cfg; - String c; boolean first = true; for (Join j : joinList) { if (j.isAppJoin()) { continue; } - cfg = j.isLeftOrRightJoin() ? j.getOuterConfig() : j.getJoinConfig(); - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } + SQLConfig ocfg = j.getOuterConfig(); + SQLConfig cfg = (ocfg != null && ocfg.getHaving() != null) || j.isLeftOrRightJoin() ? ocfg : j.getJoinConfig(); + + if (cfg != null) { + cfg.setMain(false).setKeyPrefix(true); + if (StringUtil.isEmpty(cfg.getAlias(), true)) { + cfg.setAlias(cfg.getTable()); + } + String c = ((AbstractSQLConfig) cfg).getHavingString(false); - c = ((AbstractSQLConfig) cfg).getHavingString(false); - if (StringUtil.isEmpty(c, true) == false) { - joinHaving += (first ? "" : ", ") + c; - first = false; + if (StringUtil.isEmpty(c, true) == false) { + joinHaving += (first ? "" : ", ") + c; + first = false; + } } - } } @@ -1255,25 +1259,27 @@ public String getOrderString(boolean hasPrefix) { //加上子表的 order String joinOrder = ""; if (joinList != null) { - SQLConfig cfg; - String c; boolean first = true; for (Join j : joinList) { if (j.isAppJoin()) { continue; } - cfg = j.isLeftOrRightJoin() ? j.getOuterConfig() : j.getJoinConfig(); - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } + SQLConfig ocfg = j.getOuterConfig(); + SQLConfig cfg = (ocfg != null && ocfg.getOrder() != null) || j.isLeftOrRightJoin() ? ocfg : j.getJoinConfig(); - c = ((AbstractSQLConfig) cfg).getOrderString(false); - if (StringUtil.isEmpty(c, true) == false) { - joinOrder += (first ? "" : ", ") + c; - first = false; - } + if (cfg != null) { + cfg.setMain(false).setKeyPrefix(true); + if (StringUtil.isEmpty(cfg.getAlias(), true)) { + cfg.setAlias(cfg.getTable()); + } + String c = ((AbstractSQLConfig) cfg).getOrderString(false); + if (StringUtil.isEmpty(c, true) == false) { + joinOrder += (first ? "" : ", ") + c; + first = false; + } + } } } @@ -1499,41 +1505,38 @@ public String getColumnString(boolean inSQLJoin) throws Exception { case GETS: String joinColumn = ""; if (joinList != null) { - SQLConfig ecfg; - SQLConfig cfg; - String c; boolean first = true; for (Join j : joinList) { if (j.isAppJoin()) { continue; } - if (j.isLeftOrRightJoin()) { + SQLConfig ocfg = j.getOuterConfig(); + boolean isEmpty = ocfg == null || ocfg.getColumn() == null; + boolean isLeftOrRightJoin = j.isLeftOrRightJoin(); + + if (isEmpty && isLeftOrRightJoin) { // 改为 SELECT ViceTable.* 解决 SELECT sum(ViceTable.id) LEFT/RIGHT JOIN (SELECT sum(id) FROM ViceTable...) AS ViceTable // 不仅导致 SQL 函数重复计算,还有时导致 SQL 报错或对应字段未返回 String quote = getQuote(); joinColumn += (first ? "" : ", ") + quote + (StringUtil.isEmpty(j.getAlias(), true) ? j.getTable() : j.getAlias()) + quote + ".*"; first = false; } else { - ecfg = j.getOuterConfig(); - if (ecfg != null && ecfg.getColumn() != null) { //优先级更高 - cfg = ecfg; - } - else { - cfg = j.getJoinConfig(); - } - - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } - - c = ((AbstractSQLConfig) cfg).getColumnString(true); - if (StringUtil.isEmpty(c, true) == false) { - joinColumn += (first ? "" : ", ") + c; - first = false; + SQLConfig cfg = isLeftOrRightJoin == false && isEmpty ? j.getJoinConfig() : ocfg; + if (cfg != null) { + cfg.setMain(false).setKeyPrefix(true); + if (StringUtil.isEmpty(cfg.getAlias(), true)) { + cfg.setAlias(cfg.getTable()); + } + + String c = ((AbstractSQLConfig) cfg).getColumnString(true); + if (StringUtil.isEmpty(c, true) == false) { + joinColumn += (first ? "" : ", ") + c; + first = false; + } } } - + inSQLJoin = true; } } From 9d2c95e0653dd4aec7aef022f50b5c7e432a6e25 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 1 Mar 2022 20:30:24 +0800 Subject: [PATCH 010/619] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=8F=90=E9=97=AE?= =?UTF-8?q?=E6=B3=A8=E6=84=8F=E4=BA=8B=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractParser.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 3e3703468..020406326 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -649,7 +649,11 @@ public static JSONObject newResult(int code, String msg, boolean isRoot) { public static JSONObject extendResult(JSONObject object, int code, String msg, boolean isRoot) { int index = Log.DEBUG == false || isRoot == false || msg == null ? -1 : msg.lastIndexOf(Log.KEY_SYSTEM_INFO_DIVIDER); String debug = Log.DEBUG == false || isRoot == false ? null : (index >= 0 ? msg.substring(index + Log.KEY_SYSTEM_INFO_DIVIDER.length()).trim() - : " \n **环境信息** " + : " \n 提 bug 请发请求和响应的【完整截屏】,没图的自行解决!" + + " \n 开发者有限的时间和精力主要放在【维护项目源码和文档】上!" + + " \n 【描述不详细】 或 【文档/常见问题 已有答案】 的问题可能会被忽略!!" + + " \n 【态度 不文明/不友善】的可能会被踢出群,问题也可能不予解答!!!" + + " \n\n **环境信息** " + " \n 系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version") + " \n 数据库: DEFAULT_DATABASE = " + AbstractSQLConfig.DEFAULT_DATABASE + " \n JDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch") From 028093d1fb43d6bc802d3b9aa00846313776f7ab Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 1 Mar 2022 20:34:52 +0800 Subject: [PATCH 011/619] Update Document.md --- Document.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Document.md b/Document.md index 9187c4a43..46c090233 100644 --- a/Document.md +++ b/Document.md @@ -1,5 +1,5 @@ # APIJSON 通用文档 -本文是通用文档,只和 APIJSON 协议有关,和 C#, Go, Java, JavaScript, Python, PHP 等开发语言无关。
+本文是通用文档,只和 APIJSON 协议有关,和 C#, Go, Java, JavaScript, PHP, Python, TypeScript 等开发语言无关。
具体开发语言相关的 配置、运行、部署 等文档见各个相关项目的文档,可以在首页点击对应语言的入口来查看。
https://github.com/Tencent/APIJSON ![image](https://user-images.githubusercontent.com/5738175/134520081-a63d3817-321c-4e7b-9e03-73c6827a19c1.png) @@ -53,7 +53,7 @@ https://github.com/Tencent/APIJSON

- APIJSON 各种单表对象查询:简单查询、统计、分组、排序、聚合、比较、筛选字段、字段别名 等 + [GIF] APIJSON 各种单表对象查询:简单查询、统计、分组、排序、聚合、比较、筛选字段、字段别名 等

![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_single.gif) @@ -102,7 +102,7 @@ https://github.com/Tencent/APIJSON

- APIJSON 各种单表数组查询:简单查询、统计、分组、排序、聚合、分页、比较、搜索、正则、条件组合 等 + [GIF] APIJSON 各种单表数组查询:简单查询、统计、分组、排序、聚合、分页、比较、搜索、正则、条件组合 等

![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_array.gif) @@ -267,7 +267,7 @@ https://github.com/Tencent/APIJSON

- APIJSON 各种多表关联查询:一对一、一对多、多对一、各种条件 等 + [GIF] APIJSON 各种多表关联查询:一对一、一对多、多对一、各种条件 等

![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_associate.gif) @@ -275,7 +275,7 @@ https://github.com/Tencent/APIJSON

- APIJSON 各种 JOIN:< LEFT JOIN, & INNER JOIN 等 + [GIF] APIJSON 各种 JOIN:< LEFT JOIN, & INNER JOIN 等

![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_join.gif) @@ -283,7 +283,7 @@ https://github.com/Tencent/APIJSON

- APIJSON 各种子查询:@from@ FROM, key@ =, key>@ >, key{}@ IN, key}{@ EXISTS 等 + [GIF] APIJSON 各种子查询:@from@ FROM, key@ =, key>@ >, key{}@ IN, key}{@ EXISTS 等

![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_subquery.gif) @@ -291,7 +291,7 @@ https://github.com/Tencent/APIJSON

- APIJSON 部分功能演示集合,由浅入深、由简单到复杂 + [GIF] APIJSON 部分功能演示集合,由浅入深、由简单到复杂

![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_summary.gif) From 5328809c2d4cc247f8c9cb6211c6fb509eb742be Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 2 Mar 2022 00:47:55 +0800 Subject: [PATCH 012/619] Update README.md --- README.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 22390da85..8e8f72995 100644 --- a/README.md +++ b/README.md @@ -226,26 +226,26 @@ https://github.com/Tencent/APIJSON/issues/36 如果您在使用 APIJSON,请让我们知道,您的使用对我们非常重要(按登记顺序排列):
https://github.com/Tencent/APIJSON/issues/187
- - - + + +
- - - - - - - - - - - - - - - + + + + + + + + + + + + + + +
* [腾讯科技有限公司](https://www.tencent.com) From 36a5612f86e9b9b557a62f85a0f1487cc8f51ac0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 5 Mar 2022 21:11:23 +0800 Subject: [PATCH 013/619] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81=20?= =?UTF-8?q?NULL=20=E5=80=BC=20@null:"tag"=20=E5=92=8C=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E8=BD=AC=E6=8D=A2=20@cast:"date:DATE"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/JSONObject.java | 37 +- APIJSONORM/src/main/java/apijson/SQL.java | 7 +- .../main/java/apijson/orm/AbstractParser.java | 4 +- .../java/apijson/orm/AbstractSQLConfig.java | 431 ++++++++++++------ .../src/main/java/apijson/orm/SQLConfig.java | 21 +- 5 files changed, 337 insertions(+), 163 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/JSONObject.java b/APIJSONORM/src/main/java/apijson/JSONObject.java index 925735bb9..8000e231b 100755 --- a/APIJSONORM/src/main/java/apijson/JSONObject.java +++ b/APIJSONORM/src/main/java/apijson/JSONObject.java @@ -134,6 +134,7 @@ public JSONObject setUserIdIn(List list) { // public static final String KEY_KEEP = "@keep"; //一定会返回,为 null 或 空对象时,会使用默认值(非空),解决其它对象因为不关联的第一个对为空导致也不返回 public static final String KEY_DEFULT = "@default"; //TODO 自定义默认值 { "@default":true },@default 可完全替代 @keep public static final String KEY_NULL = "@null"; //TODO 值为 null 的键值对 "@null":"tag,pictureList",允许 is NULL 条件判断, SET tag = NULL 修改值为 NULL 等 + public static final String KEY_CAST = "@cast"; //TODO 类型转换 cast(date AS DATE) public static final String KEY_ROLE = "@role"; //角色,拥有对某些数据的某些操作的权限 public static final String KEY_DATABASE = "@database"; //数据库类型,默认为MySQL @@ -161,6 +162,8 @@ public JSONObject setUserIdIn(List list) { TABLE_KEY_LIST.add(KEY_CACHE); TABLE_KEY_LIST.add(KEY_COLUMN); TABLE_KEY_LIST.add(KEY_FROM); + TABLE_KEY_LIST.add(KEY_NULL); + TABLE_KEY_LIST.add(KEY_CAST); TABLE_KEY_LIST.add(KEY_COMBINE); TABLE_KEY_LIST.add(KEY_GROUP); TABLE_KEY_LIST.add(KEY_HAVING); @@ -275,15 +278,45 @@ public JSONObject setColumn(String keys) { return puts(KEY_COLUMN, keys); } + /**set keys whose value is null + * @param keys key0, key1, key2 ... + * @return {@link #setNull(String)} + */ + public JSONObject setNull(String... keys) { + return setNull(StringUtil.getString(keys, true)); + } + /**set keys whose value is null + * @param keys "key0,key1,key2..." + * @return + */ + public JSONObject setNull(String keys) { + return puts(KEY_NULL, keys); + } + + /**set keys and types whose value should be cast to type, cast(value AS DATE) + * @param keyTypes key0:type0, key1:type1, key2:type2 ... + * @return {@link #setCast(String)} + */ + public JSONObject setCast(String... keyTypes) { + return setCast(StringUtil.getString(keyTypes, true)); + } + /**set keys and types whose value should be cast to type, cast(value AS DATE) + * @param keyTypes "key0:type0,key1:type1,key2:type2..." + * @return + */ + public JSONObject setCast(String keyTypes) { + return puts(KEY_CAST, keyTypes); + } + /**set combination of keys for conditions - * @param keys key0,&key1,|key2,!kye3 ... + * @param keys key0,&key1,|key2,!key3 ... TODO or key0> | (key1{} & !key2)... * @return {@link #setColumn(String)} */ public JSONObject setCombine(String... keys) { return setCombine(StringUtil.getString(keys, true)); } /**set combination of keys for conditions - * @param keys key0,&key1,|key2,!kye3 ... + * @param keys key0,&key1,|key2,!key3 ... TODO or key0> | (key1{} & !key2)... * @return */ public JSONObject setCombine(String keys) { diff --git a/APIJSONORM/src/main/java/apijson/SQL.java b/APIJSONORM/src/main/java/apijson/SQL.java index ce55eab29..397c2915f 100755 --- a/APIJSONORM/src/main/java/apijson/SQL.java +++ b/APIJSONORM/src/main/java/apijson/SQL.java @@ -14,8 +14,11 @@ public class SQL { public static final String AND = " AND "; public static final String NOT = " NOT "; public static final String AS = " AS "; - public static final String IS = " is "; - public static final String NULL = " null "; + public static final String IS = " IS "; + public static final String NULL = " NULL "; + public static final String IS_NOT = " IS NOT "; + public static final String IS_NULL = " IS NULL "; + public static final String IS_NOT_NULL = " IS NOT NULL "; //括号必须紧跟函数名! count (...) 报错! public static final String COUNT = "count"; diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 020406326..5bfa7da93 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1322,6 +1322,8 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_SCHEMA); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_DATASOURCE); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COLUMN); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_NULL); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_CAST); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COMBINE); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_GROUP); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_HAVING); @@ -1330,7 +1332,7 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // } /**JOIN 多表同时筛选 - * @param join "&/User,0"} * @param request * @return * @throws Exception diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 8e4cac06c..3f0aafcdd 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -8,6 +8,8 @@ import static apijson.JSONObject.KEY_CACHE; import static apijson.JSONObject.KEY_COLUMN; import static apijson.JSONObject.KEY_COMBINE; +import static apijson.JSONObject.KEY_NULL; +import static apijson.JSONObject.KEY_CAST; import static apijson.JSONObject.KEY_DATABASE; import static apijson.JSONObject.KEY_DATASOURCE; import static apijson.JSONObject.KEY_EXPLAIN; @@ -32,6 +34,11 @@ import static apijson.SQL.NOT; import static apijson.SQL.OR; +import java.math.BigDecimal; +import java.sql.Array; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -738,6 +745,8 @@ public String getUserIdKey() { private Subquery from; //子查询临时表 private List column; //表内字段名(或函数名,仅查询操作可用)的字符串数组,','分隔 private List> values; //对应表内字段的值的字符串数组,','分隔 + private List nulls; + private Map cast; private Map content; //Request内容,key:value形式,column = content.keySet(),values = content.values() private Map where; //筛选条件,key:value形式 private Map> combine; //条件组合,{ "&":[key], "|":[key], "!":[key] } @@ -1376,6 +1385,10 @@ public SQLConfig setRaw(List raw) { */ @Override public String getRawSQL(String key, Object value) throws Exception { + if (value == null) { + return null; + } + List rawList = getRaw(); boolean containRaw = rawList != null && rawList.contains(key); if (containRaw && value instanceof String == false) { @@ -1582,7 +1595,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { throw new UnsupportedOperationException("@column:value 的 value 中字符串 " + expression + " 不合法!" + "不允许传超过 100 个字符的函数或表达式!请用 @raw 简化传参!"); } - keys[i] = getColumnPrase(expression, containRaw); + keys[i] = parseColumn(expression, containRaw); } String c = StringUtil.getString(keys); @@ -1602,7 +1615,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { * @param expression * @return */ - public String getColumnPrase(String expression, boolean containRaw) { + public String parseColumn(String expression, boolean containRaw) { String quote = getQuote(); int start = expression.indexOf('('); if (start < 0) { @@ -2106,17 +2119,29 @@ public static String getLimitString(int page, int count, boolean isTSQL, boolean return " LIMIT " + count + (offset <= 0 ? "" : " OFFSET " + offset); // DELETE, UPDATE 不支持 OFFSET } + + @Override + public List getNull() { + return nulls; + } + @Override + public SQLConfig setNull(List nulls) { + this.nulls = nulls; + return this; + } - //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @Override - public Map getWhere() { - return where; + public Map getCast() { + return cast; } @Override - public AbstractSQLConfig setWhere(Map where) { - this.where = where; + public SQLConfig setCast(Map cast) { + this.cast = cast; return this; } + + //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + @NotNull @Override public Map> getCombine() { @@ -2135,6 +2160,17 @@ public AbstractSQLConfig setCombine(Map> combine) { this.combine = combine; return this; } + + @Override + public Map getWhere() { + return where; + } + @Override + public AbstractSQLConfig setWhere(Map where) { + this.where = where; + return this; + } + /** * noFunctionChar = false * @param key @@ -2307,7 +2343,6 @@ else if ("!".equals(ce.getKey())) { logic = Logic.TYPE_AND; } - isItemFirst = true; cs = ""; for (String key : keyList) { @@ -2477,9 +2512,8 @@ else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) protected String getWhereItem(String key, Object value, RequestMethod method, boolean verifyName) throws Exception { Log.d(TAG, "getWhereItem key = " + key); //避免筛选到全部 value = key == null ? null : where.get(key); - if (key == null || value == null || key.endsWith("()") || key.startsWith("@")) { //关键字||方法, +或-直接报错 - Log.d(TAG, "getWhereItem key == null || value == null" - + " || key.startsWith(@) || key.endsWith(()) >> continue;"); + if (key == null || key.endsWith("()") || key.startsWith("@")) { //关键字||方法, +或-直接报错 + Log.d(TAG, "getWhereItem key == null || key.endsWith(()) || key.startsWith(@) >> continue;"); return null; } if (key.endsWith("@")) {//引用 @@ -2524,63 +2558,64 @@ else if (key.endsWith("<")) { keyType = 0; } - key = getRealKey(method, key, false, true, verifyName); + String column = getRealKey(method, key, false, true, verifyName); switch (keyType) { case 1: - return getSearchString(key, value, rawSQL); + return getSearchString(key, column, value, rawSQL); case -2: case 2: - return getRegExpString(key, value, keyType < 0, rawSQL); + return getRegExpString(key, column, value, keyType < 0, rawSQL); case 3: - return getBetweenString(key, value, rawSQL); + return getBetweenString(key, column, value, rawSQL); case 4: - return getRangeString(key, value, rawSQL); + return getRangeString(key, column, value, rawSQL); case 5: - return getExistsString(key, value, rawSQL); + return getExistsString(key, column, value, rawSQL); case 6: - return getContainString(key, value, rawSQL); + return getContainString(key, column, value, rawSQL); case 7: - return getCompareString(key, value, ">=", rawSQL); + return getCompareString(key, column, value, ">=", rawSQL); case 8: - return getCompareString(key, value, "<=", rawSQL); + return getCompareString(key, column, value, "<=", rawSQL); case 9: - return getCompareString(key, value, ">", rawSQL); + return getCompareString(key, column, value, ">", rawSQL); case 10: - return getCompareString(key, value, "<", rawSQL); + return getCompareString(key, column, value, "<", rawSQL); default: // TODO MySQL JSON类型的字段对比 key='[]' 会无结果! key LIKE '[1, 2, 3]' //TODO MySQL , 后面有空格! - return getEqualString(key, value, rawSQL); + return getEqualString(key, column, value, rawSQL); } } @JSONField(serialize = false) - public String getEqualString(String key, Object value, String rawSQL) throws Exception { - if (JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { + public String getEqualString(String key, String column, Object value, String rawSQL) throws Exception { + if (value != null && JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { throw new IllegalArgumentException(key + ":value 中value不合法!非PUT请求只支持 [Boolean, Number, String] 内的类型 !"); } - boolean not = key.endsWith("!"); // & | 没有任何意义,写法多了不好控制 + boolean not = column.endsWith("!"); // & | 没有任何意义,写法多了不好控制 if (not) { - key = key.substring(0, key.length() - 1); + column = column.substring(0, column.length() - 1); } - if (StringUtil.isName(key) == false) { + if (StringUtil.isName(column) == false) { throw new IllegalArgumentException(key + ":value 中key不合法!不支持 ! 以外的逻辑符 !"); } - - return getKey(key) + (not ? " != " : " = ") + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(value))); + + String logic = value == null && rawSQL == null ? (not ? SQL.IS_NOT : SQL.IS) : (not ? " != " : " = "); + return getKey(column) + logic + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(key, column, value))); } @JSONField(serialize = false) - public String getCompareString(String key, Object value, String type, String rawSQL) throws Exception { - if (JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { - throw new IllegalArgumentException(key + type + ":value 中value不合法!比较运算 [>, <, >=, <=] 只支持 [Boolean, Number, String] 内的类型 !"); + public String getCompareString(String key, String column, Object value, String type, String rawSQL) throws Exception { + if (value != null && JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { + throw new IllegalArgumentException(key + ":value 中 value 不合法!比较运算 [>, <, >=, <=] 只支持 [Boolean, Number, String] 内的类型 !"); } - if (StringUtil.isName(key) == false) { - throw new IllegalArgumentException(key + type + ":value 中key不合法!比较运算 [>, <, >=, <=] 不支持 [&, !, |] 中任何逻辑运算符 !"); + if (StringUtil.isName(column) == false) { + throw new IllegalArgumentException(key + ":value 中 key 不合法!比较运算 [>, <, >=, <=] 不支持 [&, !, |] 中任何逻辑运算符 !"); } - return getKey(key) + " " + type + " " + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(value))); + return getKey(column) + " " + type + " " + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(key, column, value))); } public String getKey(String key) { @@ -2601,17 +2636,56 @@ public String getSQLKey(String key) { /** * 使用prepareStatement预编译,值为 ? ,后续动态set进去 */ - protected List preparedValueList = new ArrayList<>(); protected Object getValue(@NotNull Object value) { + return getValue(null, null, value); + } + + protected List preparedValueList = new ArrayList<>(); + protected Object getValue(String key, String column, Object value) { if (isPrepared()) { + if (value == null) { + return null; + } + + Map castMap = getCast(); + String type = key == null || castMap == null ? null : castMap.get(key); + +// if ("DATE".equalsIgnoreCase(type) && value instanceof Date == false) { +// value = value instanceof Number ? new Date(((Number) value).longValue()) : Date.valueOf((String) value); +// } +// else if ("TIME".equalsIgnoreCase(type) && value instanceof Time == false) { +// value = value instanceof Number ? new Time(((Number) value).longValue()) : Time.valueOf((String) value); +// } +// else if ("TIMESTAMP".equalsIgnoreCase(type) && value instanceof Timestamp == false) { +// value = value instanceof Number ? new Timestamp(((Number) value).longValue()) : Timestamp.valueOf((String) value); +// } +// else if ("ARRAY".equalsIgnoreCase(type) && value instanceof Array == false) { +// value = ((Collection) value).toArray(); +// } +// else if (StringUtil.isEmpty(type, true) == false) { +// preparedValueList.add(value); +// return "cast(?" + SQL.AS + type + ")"; +// } + preparedValueList.add(value); - return "?"; + return StringUtil.isEmpty(type, true) ? "?" : "cast(?" + SQL.AS + type + ")"; } - return getSQLValue(value); + + return key == null ? getSQLValue(value) : getSQLValue(key, column, value); + } + + public Object getSQLValue(String key, String column, @NotNull Object value) { + Map castMap = getCast(); + String type = key == null || castMap == null ? null : castMap.get(key); + Object val = getSQLValue(value); + return StringUtil.isEmpty(type, true) ? val : "cast(" + val + SQL.AS + type + ")"; } public Object getSQLValue(@NotNull Object value) { + if (value == null) { + return SQL.NULL; + } // return (value instanceof Number || value instanceof Boolean) && DATABASE_POSTGRESQL.equals(getDatabase()) ? value : "'" + value + "'"; - return (value instanceof Number || value instanceof Boolean) ? value : "'" + value + "'"; //MySQL 隐式转换用不了索引 + return (value instanceof Number || value instanceof Boolean) ? value : "'" + value.toString().replaceAll("'", "\\'") + "'"; //MySQL 隐式转换用不了索引 } @Override @@ -2631,7 +2705,7 @@ public AbstractSQLConfig setPreparedValueList(List preparedValueList) { * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getSearchString(String key, Object value, String rawSQL) throws IllegalArgumentException { + public String getSearchString(String key, String column, Object value, String rawSQL) throws IllegalArgumentException { if (rawSQL != null) { throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key$ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); } @@ -2639,15 +2713,15 @@ public String getSearchString(String key, Object value, String rawSQL) throws Il return ""; } - Logic logic = new Logic(key); - key = logic.getKey(); - Log.i(TAG, "getSearchString key = " + key); + Logic logic = new Logic(column); + column = logic.getKey(); + Log.i(TAG, "getSearchString column = " + column); JSONArray arr = newJSONArray(value); if (arr.isEmpty()) { return ""; } - return getSearchString(key, arr.toArray(), logic.getType()); + return getSearchString(key, column, arr.toArray(), logic.getType()); } /**search key match values * @param in @@ -2655,7 +2729,7 @@ public String getSearchString(String key, Object value, String rawSQL) throws Il * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getSearchString(String key, Object[] values, int type) throws IllegalArgumentException { + public String getSearchString(String key, String column, Object[] values, int type) throws IllegalArgumentException { if (values == null || values.length <= 0) { return ""; } @@ -2664,16 +2738,16 @@ public String getSearchString(String key, Object[] values, int type) throws Ille for (int i = 0; i < values.length; i++) { Object v = values[i]; if (v instanceof String == false) { - throw new IllegalArgumentException(key + "$:value 中 value 的类型只能为 String 或 String[]!"); + throw new IllegalArgumentException(key + ":value 中 value 的类型只能为 String 或 String[]!"); } if (((String) v).isEmpty()) { // 允许查空格 StringUtil.isEmpty((String) v, true) - throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + "是空字符串,没有意义,不允许这样传!"); + throw new IllegalArgumentException(key + ":value 中 value 值 " + v + "是空字符串,没有意义,不允许这样传!"); } // if (((String) v).contains("%%")) { // 需要通过 %\%% 来模糊搜索 % // throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + " 中包含 %% !不允许有连续的 % !"); // } - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getLikeString(key, v); + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getLikeString(key, column, v); } return getCondition(Logic.isNot(type), condition); @@ -2681,12 +2755,13 @@ public String getSearchString(String key, Object[] values, int type) throws Ille /**WHERE key LIKE 'value' * @param key + * @param column * @param value * @return key LIKE 'value' */ @JSONField(serialize = false) - public String getLikeString(String key, Object value) { - return getKey(key) + " LIKE " + getValue(value); + public String getLikeString(String key, String column, Object value) { + return getKey(column) + " LIKE " + getValue(key, column, value); } //$ search >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -2696,13 +2771,14 @@ public String getLikeString(String key, Object value) { //~ regexp <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /**search key match RegExp values * @param key + * @param column * @param value * @param ignoreCase * @return {@link #getRegExpString(String, Object[], int, boolean)} * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getRegExpString(String key, Object value, boolean ignoreCase, String rawSQL) throws IllegalArgumentException { + public String getRegExpString(String key, String column, Object value, boolean ignoreCase, String rawSQL) throws IllegalArgumentException { if (rawSQL != null) { throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key~ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); } @@ -2710,15 +2786,15 @@ public String getRegExpString(String key, Object value, boolean ignoreCase, Stri return ""; } - Logic logic = new Logic(key); - key = logic.getKey(); - Log.i(TAG, "getRegExpString key = " + key); + Logic logic = new Logic(column); + column = logic.getKey(); + Log.i(TAG, "getRegExpString column = " + column); JSONArray arr = newJSONArray(value); if (arr.isEmpty()) { return ""; } - return getRegExpString(key, arr.toArray(), logic.getType(), ignoreCase); + return getRegExpString(key, column, arr.toArray(), logic.getType(), ignoreCase); } /**search key match RegExp values * @param key @@ -2729,7 +2805,7 @@ public String getRegExpString(String key, Object value, boolean ignoreCase, Stri * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getRegExpString(String key, Object[] values, int type, boolean ignoreCase) throws IllegalArgumentException { + public String getRegExpString(String key, String column, Object[] values, int type, boolean ignoreCase) throws IllegalArgumentException { if (values == null || values.length <= 0) { return ""; } @@ -2737,9 +2813,9 @@ public String getRegExpString(String key, Object[] values, int type, boolean ign String condition = ""; for (int i = 0; i < values.length; i++) { if (values[i] instanceof String == false) { - throw new IllegalArgumentException(key + "$:value 中value的类型只能为String或String[]!"); + throw new IllegalArgumentException(key + ":value 中value的类型只能为String或String[]!"); } - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getRegExpString(key, (String) values[i], ignoreCase); + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getRegExpString(key, column, (String) values[i], ignoreCase); } return getCondition(Logic.isNot(type), condition); @@ -2752,20 +2828,22 @@ public String getRegExpString(String key, Object[] values, int type, boolean ign * @return key REGEXP 'value' */ @JSONField(serialize = false) - public String getRegExpString(String key, String value, boolean ignoreCase) { + public String getRegExpString(String key, String column, String value, boolean ignoreCase) { if (isPostgreSQL()) { - return getKey(key) + " ~" + (ignoreCase ? "* " : " ") + getValue(value); + return getKey(column) + " ~" + (ignoreCase ? "* " : " ") + getValue(key, column, value); } if (isOracle()) { - return "regexp_like(" + getKey(key) + ", " + getValue(value) + (ignoreCase ? ", 'i'" : ", 'c'") + ")"; + return "regexp_like(" + getKey(column) + ", " + getValue(key, column, value) + (ignoreCase ? ", 'i'" : ", 'c'") + ")"; } if (isClickHouse()) { - return "match(" + (ignoreCase ? "lower(" : "") + getKey(key) + (ignoreCase ? ")" : "") + ", " + (ignoreCase ? "lower(" : "") + getValue(value) + (ignoreCase ? ")" : "") + ")"; + return "match(" + (ignoreCase ? "lower(" : "") + getKey(column) + (ignoreCase ? ")" : "") + + ", " + (ignoreCase ? "lower(" : "") + getValue(key, column, value) + (ignoreCase ? ")" : "") + ")"; } if (isHive()) { - return (ignoreCase ? "lower(" : "") + getKey(key) + (ignoreCase ? ")" : "") + " REGEXP " + (ignoreCase ? "lower(" : "") + getValue(value) + (ignoreCase ? ")" : ""); + return (ignoreCase ? "lower(" : "") + getKey(column) + (ignoreCase ? ")" : "") + + " REGEXP " + (ignoreCase ? "lower(" : "") + getValue(key, column, value) + (ignoreCase ? ")" : ""); } - return getKey(key) + " REGEXP " + (ignoreCase ? "" : "BINARY ") + getValue(value); + return getKey(column) + " REGEXP " + (ignoreCase ? "" : "BINARY ") + getValue(key, column, value); } //~ regexp >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -2780,7 +2858,7 @@ public String getRegExpString(String key, String value, boolean ignoreCase) { * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getBetweenString(String key, Object value, String rawSQL) throws IllegalArgumentException { + public String getBetweenString(String key, String column, Object value, String rawSQL) throws IllegalArgumentException { if (rawSQL != null) { throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key% 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); } @@ -2788,15 +2866,15 @@ public String getBetweenString(String key, Object value, String rawSQL) throws I return ""; } - Logic logic = new Logic(key); - key = logic.getKey(); - Log.i(TAG, "getBetweenString key = " + key); + Logic logic = new Logic(column); + column = logic.getKey(); + Log.i(TAG, "getBetweenString column = " + column); JSONArray arr = newJSONArray(value); if (arr.isEmpty()) { return ""; } - return getBetweenString(key, arr.toArray(), logic.getType()); + return getBetweenString(key, column, arr.toArray(), logic.getType()); } /**WHERE key BETWEEN 'start' AND 'end' @@ -2806,7 +2884,7 @@ public String getBetweenString(String key, Object value, String rawSQL) throws I * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getBetweenString(String key, Object[] values, int type) throws IllegalArgumentException { + public String getBetweenString(String key, String column, Object[] values, int type) throws IllegalArgumentException { if (values == null || values.length <= 0) { return ""; } @@ -2815,15 +2893,15 @@ public String getBetweenString(String key, Object[] values, int type) throws Ill String[] vs; for (int i = 0; i < values.length; i++) { if (values[i] instanceof String == false) { - throw new IllegalArgumentException(key + "%:value 中 value 的类型只能为 String 或 String[] !"); + throw new IllegalArgumentException(key + ":value 中 value 的类型只能为 String 或 String[] !"); } vs = StringUtil.split((String) values[i]); if (vs == null || vs.length != 2) { - throw new IllegalArgumentException(key + "%:value 中 value 不合法!类型为 String 时必须包括1个逗号 , 且左右两侧都有值!类型为 String[] 里面每个元素要符合前面类型为 String 的规则 !"); + throw new IllegalArgumentException(key + ":value 中 value 不合法!类型为 String 时必须包括1个逗号 , 且左右两侧都有值!类型为 String[] 里面每个元素要符合前面类型为 String 的规则 !"); } - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + "(" + getBetweenString(key, (Object) vs[0], (Object) vs[1]) + ")"; + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + "(" + getBetweenString(key, column, (Object) vs[0], (Object) vs[1]) + ")"; } return getCondition(Logic.isNot(type), condition); @@ -2836,11 +2914,11 @@ public String getBetweenString(String key, Object[] values, int type) throws Ill * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getBetweenString(String key, Object start, Object end) throws IllegalArgumentException { + public String getBetweenString(String key, String column, Object start, Object end) throws IllegalArgumentException { if (JSON.isBooleanOrNumberOrString(start) == false || JSON.isBooleanOrNumberOrString(end) == false) { - throw new IllegalArgumentException(key + "%:value 中 value 不合法!类型为 String 时必须包括1个逗号 , 且左右两侧都有值!类型为 String[] 里面每个元素要符合前面类型为 String 的规则 !"); + throw new IllegalArgumentException(key + ":value 中 value 不合法!类型为 String 时必须包括1个逗号 , 且左右两侧都有值!类型为 String[] 里面每个元素要符合前面类型为 String 的规则 !"); } - return getKey(key) + " BETWEEN " + getValue(start) + AND + getValue(end); + return getKey(column) + " BETWEEN " + getValue(key, column, start) + AND + getValue(key, column, end); } @@ -2858,20 +2936,19 @@ public String getBetweenString(String key, Object start, Object end) throws Ille * @throws Exception */ @JSONField(serialize = false) - public String getRangeString(String key, Object range, String rawSQL) throws Exception { - Log.i(TAG, "getRangeString key = " + key); + public String getRangeString(String key, String column, Object range, String rawSQL) throws Exception { + Log.i(TAG, "getRangeString column = " + column); if (range == null) {//依赖的对象都没有给出有效值,这个存在无意义。如果是客户端传的,那就能在客户端确定了。 - throw new NotExistException(TAG + "getRangeString(" + key + ", " + range - + ") range == null"); + throw new NotExistException(TAG + "getRangeString(" + column + ", " + range + ") range == null"); } - Logic logic = new Logic(key); + Logic logic = new Logic(column); String k = logic.getKey(); Log.i(TAG, "getRangeString k = " + k); if (range instanceof List) { if (rawSQL != null) { - throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + "{} 不合法!" + throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" + "Raw SQL 不支持 key{}:[] 这种键值对!"); } @@ -2880,9 +2957,9 @@ public String getRangeString(String key, Object range, String rawSQL) throws Exc if (logic.isNot() && l.isEmpty()) { return ""; // key!{}: [] 这个条件无效,加到 SQL 语句中 key IN() 会报错,getInString 里不好处理 } - return getKey(k) + getInString(k, l.toArray(), logic.isNot()); + return getKey(k) + getInString(k, column, l.toArray(), logic.isNot()); } - throw new IllegalArgumentException(key + "{}\":[] 中 {} 前面的逻辑运算符错误!只能用'|','!'中的一种 !"); + throw new IllegalArgumentException(key + ":[] 中 {} 前面的逻辑运算符错误!只能用'|','!'中的一种 !"); } else if (range instanceof String) {//非Number类型需要客户端拼接成 < 'value0', >= 'value1'这种 String condition = ""; @@ -2929,7 +3006,7 @@ else if ("!=null".equals(c)) { c = SQL.isNull(false); } else if (isPrepared() && (c.contains("--") || PATTERN_RANGE.matcher(c).matches() == false)) { - throw new UnsupportedOperationException(key + "{}:value 的 value 中 " + c + " 不合法!" + throw new UnsupportedOperationException(key + ":value 的 value 中 " + c + " 不合法!" + "预编译模式下 key{}:\"condition\" 中 condition 必须 为 =null 或 !=null 或 符合正则表达式 " + PATTERN_RANGE + " !不允许连续减号 -- !不允许空格!"); } @@ -2949,7 +3026,7 @@ else if (range instanceof Subquery) { //如果在 Parser 解析成 SQL 字符串 return getKey(k) + (logic.isNot() ? NOT : "") + " IN " + getSubqueryString((Subquery) range); } - throw new IllegalArgumentException(key + "{}:range 类型为" + range.getClass().getSimpleName() + throw new IllegalArgumentException(key + ":range 类型为" + range.getClass().getSimpleName() + "!range 只能是 用','分隔条件的字符串 或者 可取选项JSONArray!"); } /**WHERE key IN ('key0', 'key1', ... ) @@ -2958,16 +3035,15 @@ else if (range instanceof Subquery) { //如果在 Parser 解析成 SQL 字符串 * @throws NotExistException */ @JSONField(serialize = false) - public String getInString(String key, Object[] in, boolean not) throws NotExistException { + public String getInString(String key, String column, Object[] in, boolean not) throws NotExistException { String condition = ""; if (in != null) {//返回 "" 会导致 id:[] 空值时效果和没有筛选id一样! for (int i = 0; i < in.length; i++) { - condition += ((i > 0 ? "," : "") + getValue(in[i])); + condition += ((i > 0 ? "," : "") + getValue(key, column, in[i])); } } if (condition.isEmpty()) {//条件如果存在必须执行,不能忽略。条件为空会导致出错,又很难保证条件不为空(@:条件),所以还是这样好 - throw new NotExistException(TAG + ".getInString(" + key + ", [], " + not - + ") >> condition.isEmpty() >> IN()"); + throw new NotExistException(TAG + ".getInString(" + key + "," + column + ", [], " + not + ") >> condition.isEmpty() >> IN()"); } return (not ? NOT : "") + " IN (" + condition + ")"; } @@ -2983,7 +3059,7 @@ public String getInString(String key, Object[] in, boolean not) throws NotExistE * @throws NotExistException */ @JSONField(serialize = false) - public String getExistsString(String key, Object value, String rawSQL) throws Exception { + public String getExistsString(String key, String column, Object value, String rawSQL) throws Exception { if (rawSQL != null) { throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key}{ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); } @@ -2991,13 +3067,13 @@ public String getExistsString(String key, Object value, String rawSQL) throws Ex return ""; } if (value instanceof Subquery == false) { - throw new IllegalArgumentException(key + "}{:subquery 类型为" + value.getClass().getSimpleName() + throw new IllegalArgumentException(key + ":subquery 类型为" + value.getClass().getSimpleName() + "!subquery 只能是 子查询JSONObejct!"); } - Logic logic = new Logic(key); - key = logic.getKey(); - Log.i(TAG, "getExistsString key = " + key); + Logic logic = new Logic(column); + column = logic.getKey(); + Log.i(TAG, "getExistsString column = " + column); return (logic.isNot() ? NOT : "") + " EXISTS " + getSubqueryString((Subquery) value); } @@ -3011,21 +3087,18 @@ public String getExistsString(String key, Object value, String rawSQL) throws Ex * @throws NotExistException */ @JSONField(serialize = false) - public String getContainString(String key, Object value, String rawSQL) throws IllegalArgumentException { + public String getContainString(String key, String column, Object value, String rawSQL) throws IllegalArgumentException { if (rawSQL != null) { throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key<> 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); } - if (value == null) { - return ""; - } - Logic logic = new Logic(key); - key = logic.getKey(); - Log.i(TAG, "getContainString key = " + key); + Logic logic = new Logic(column); + column = logic.getKey(); + Log.i(TAG, "getContainString column = " + column); - return getContainString(key, newJSONArray(value).toArray(), logic.getType()); + return getContainString(key, column, newJSONArray(value).toArray(), logic.getType()); } - /**WHERE key contains childs + /**WHERE key contains childs TODO 支持 key<>: { "path":"$[0].name", "value": 82001 } 或者 key<$[0].name>:82001 或者 key$[0].name<>:82001 ? 还是前者好,key 一旦复杂了,包含 , ; : / [] 等就容易和 @combine 其它功能等冲突 * @param key * @param childs null ? "" : (empty ? no child : contains childs) * @param type |, &, ! @@ -3034,42 +3107,55 @@ public String getContainString(String key, Object value, String rawSQL) throws I * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getContainString(String key, Object[] childs, int type) throws IllegalArgumentException { + public String getContainString(String key, String column, Object[] childs, int type) throws IllegalArgumentException { boolean not = Logic.isNot(type); String condition = ""; if (childs != null) { for (int i = 0; i < childs.length; i++) { Object c = childs[i]; - if (c != null) { - if (c instanceof JSON) { - throw new IllegalArgumentException(key + "<>:value 中value类型不能为JSON!"); + if (c instanceof Collection) { + throw new IllegalArgumentException(key + ":value 中 value 类型不能为 [JSONArray, Collection] 中的任何一个 !"); + } + + Object path = ""; + if (c instanceof Map) { + path = ((Map) c).get("path"); + if (path != null && path instanceof String == false) { + throw new IllegalArgumentException(key + ":{ path:path, value:value } 中 path 类型错误,只能是 $, $.key1, $[0].key2 等符合 SQL 中 JSON 路径的 String !"); } - - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)); - if (isPostgreSQL()) { - condition += (getKey(key) + " @> " + getValue(newJSONArray(c))); //operator does not exist: jsonb @> character varying "[" + c + "]"); + + c = ((Map) c).get("value"); + if (c instanceof Collection || c instanceof Map) { + throw new IllegalArgumentException(key + ":{ path:path, value:value } 中 value 类型不能为 [JSONObject, JSONArray, Collection, Map] 中的任何一个 !"); } - else if (isOracle()) { - condition += ("json_textcontains(" + getKey(key) + ", '$', " + getValue(c.toString()) + ")"); + } + + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)); + if (isPostgreSQL()) { + condition += (getKey(column) + " @> " + getValue(key, column, newJSONArray(c))); //operator does not exist: jsonb @> character varying "[" + c + "]"); + } + else if (isOracle()) { + condition += ("json_textcontains(" + getKey(column) + ", " + (StringUtil.isEmpty(path, true) ? "'$'" : getValue(key, column, path)) + ", " + getValue(key, column, c == null ? null : c.toString()) + ")"); + } + else { + String v = c == null ? "null" : (c instanceof Boolean || c instanceof Number ? c.toString() : "\"" + c + "\""); + if (isClickHouse()) { + condition += (condition + "has(JSONExtractArrayRaw(assumeNotNull(" + getKey(column) + "))" + ", " + getValue(key, column, v) + (StringUtil.isEmpty(path, true) ? "" : ", " + getValue(key, column, path)) + ")"); } else { - boolean isNum = c instanceof Number; - String v = (isNum ? "" : "\"") + childs[i] + (isNum ? "" : "\""); - if (isClickHouse()) { - condition += condition + "has(JSONExtractArrayRaw(assumeNotNull(" + getKey(key) + "))" + ", " + getValue(v) + ")"; - } - else { - condition += ("json_contains(" + getKey(key) + ", " + getValue(v) + ")"); - } + condition += ("json_contains(" + getKey(column) + ", " + getValue(key, column, v) + (StringUtil.isEmpty(path, true) ? "" : ", " + getValue(key, column, path)) + ")"); } } } + if (condition.isEmpty()) { - condition = (getKey(key) + SQL.isNull(true) + OR + getLikeString(key, "[]")); // key = '[]' 无结果! - } else { - condition = (getKey(key) + SQL.isNull(false) + AND + "(" + condition + ")"); + condition = getKey(column) + SQL.isNull(true) + OR + getLikeString(key, column, "[]"); // key = '[]' 无结果! + } + else { + condition = getKey(column) + SQL.isNull(false) + AND + "(" + condition + ")"; } } + if (condition.isEmpty()) { return ""; } @@ -3083,6 +3169,10 @@ else if (isOracle()) { @Override public String getSubqueryString(Subquery subquery) throws Exception { + if (subquery == null) { + return ""; + } + String range = subquery.getRange(); SQLConfig cfg = subquery.getConfig(); @@ -3171,10 +3261,10 @@ public String getSetString(RequestMethod method, Map content, bo keyType = 0; //注意重置类型,不然不该加减的字段会跟着加减 } value = entry.getValue(); - key = getRealKey(method, key, false, true, verifyName); + String column = getRealKey(method, key, false, true, verifyName); - setString += (isFirst ? "" : ", ") + (getKey(key) + " = " + (keyType == 1 ? getAddString(key, value) : (keyType == 2 - ? getRemoveString(key, value) : getValue(value)) ) ); + setString += (isFirst ? "" : ", ") + (getKey(column) + " = " + (keyType == 1 ? getAddString(key, column, value) : (keyType == 2 + ? getRemoveString(key, column, value) : getValue(key, column, value)) ) ); isFirst = false; } @@ -3193,14 +3283,14 @@ public String getSetString(RequestMethod method, Map content, bo * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getAddString(String key, Object value) throws IllegalArgumentException { + public String getAddString(String key, String column, Object value) throws IllegalArgumentException { if (value instanceof Number) { - return getKey(key) + " + " + value; + return getKey(column) + " + " + value; } if (value instanceof String) { - return SQL.concat(getKey(key), (String) getValue(value)); + return SQL.concat(getKey(column), (String) getValue(key, column, value)); } - throw new IllegalArgumentException(key + "+ 对应的值 " + value + " 不是Number,String,Array中的任何一种!"); + throw new IllegalArgumentException(key + ":value 中 value 类型错误,必须是 Number,String,Array 中的任何一种!"); } /**SET key = replace(key, 'value', '') * @param key @@ -3209,14 +3299,14 @@ public String getAddString(String key, Object value) throws IllegalArgumentExcep * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getRemoveString(String key, Object value) throws IllegalArgumentException { + public String getRemoveString(String key, String column, Object value) throws IllegalArgumentException { if (value instanceof Number) { - return getKey(key) + " - " + value; + return getKey(column) + " - " + value; } if (value instanceof String) { - return SQL.replace(getKey(key), (String) getValue(value), "''");// " replace(" + key + ", '" + value + "', '') "; + return SQL.replace(getKey(column), (String) getValue(key, column, value), "''");// " replace(" + column + ", '" + value + "', '') "; } - throw new IllegalArgumentException(key + "- 对应的值 " + value + " 不是Number,String,Array中的任何一种!"); + throw new IllegalArgumentException(key + ":value 中 value 类型错误,必须是 Number,String,Array 中的任何一种!"); } //SET >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -3359,6 +3449,7 @@ private static String getConditionString(String column, String table, AbstractSQ // return table + " AS t0 INNER JOIN (SELECT id FROM " + condition + ") AS t1 ON t0.id = t1.id"; } + private boolean keyPrefix; @Override public boolean isKeyPrefix() { @@ -3371,7 +3462,6 @@ public AbstractSQLConfig setKeyPrefix(boolean keyPrefix) { } - public String getJoinString() throws Exception { String joinOns = ""; @@ -3604,9 +3694,11 @@ else if (id instanceof Subquery) {} String role = request.getString(KEY_ROLE); String cache = request.getString(KEY_CACHE); - String combine = request.getString(KEY_COMBINE); Subquery from = (Subquery) request.get(KEY_FROM); String column = request.getString(KEY_COLUMN); + String nulls = request.getString(KEY_NULL); + String cast = request.getString(KEY_CAST); + String combine = request.getString(KEY_COMBINE); String group = request.getString(KEY_GROUP); String having = request.getString(KEY_HAVING); String order = request.getString(KEY_ORDER); @@ -3624,14 +3716,52 @@ else if (id instanceof Subquery) {} request.remove(KEY_DATASOURCE); request.remove(KEY_DATABASE); request.remove(KEY_SCHEMA); - request.remove(KEY_COMBINE); request.remove(KEY_FROM); request.remove(KEY_COLUMN); + request.remove(KEY_NULL); + request.remove(KEY_CAST); + request.remove(KEY_COMBINE); request.remove(KEY_GROUP); request.remove(KEY_HAVING); request.remove(KEY_ORDER); request.remove(KEY_RAW); request.remove(KEY_JSON); + + String[] nullKeys = StringUtil.split(nulls); + if (nullKeys != null && nullKeys.length > 0) { + for (String nk : nullKeys) { + if (StringUtil.isEmpty(nk, true)) { + throw new IllegalArgumentException(table + ":{} 里的 @null: value 中的字符 '" + nk + "' 不合法!不允许为空!"); + } + if (request.get(nk) != null) { + throw new IllegalArgumentException(table + ":{} 里的 @null: value 中的字符 '" + nk + "' 已在当前对象有非 null 值!不允许对同一个 JSON key 设置不同值!"); + } + + request.put(nk, null); + } + } + + String[] casts = StringUtil.split(cast); + Map castMap = null; + if (casts != null && casts.length > 0) { + castMap = new HashMap<>(casts.length); + for (String c : casts) { + apijson.orm.Entry p = Pair.parseEntry(c); + + if (StringUtil.isEmpty(p.getKey(), true)) { + throw new IllegalArgumentException(table + ":{} 里的 @cast: 'key0:type0,key1:type1..' 中 '" + c + "' 对应的 key 的字符 '" + p.getKey() + "' 不合法!不允许为空!"); + } + if (StringUtil.isName(p.getValue()) == false) { + throw new IllegalArgumentException(table + ":{} 里的 @cast: 'key0:type0,key1:type1..' 中 '" + c + "' 对应的 type 的字符 '" + p.getValue() + "' 不合法!必须符合类型名称格式!"); + } + if (castMap.get(p.getKey()) != null) { + throw new IllegalArgumentException(table + ":{} 里的 @cast: 'key0:type0,key1:type1..' 中 '" + c + "' 对应的 key 的字符 '" + p.getKey() + "' 已存在!不允许重复设置类型!"); + } + + castMap.put(p.getKey(), p.getValue()); + } + } + String[] rawArr = StringUtil.split(raw); config.setRaw(rawArr == null || rawArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(rawArr))); @@ -3642,7 +3772,7 @@ else if (id instanceof Subquery) {} Set set = request.keySet(); //前面已经判断request是否为空 if (method == POST) { //POST操作 if (idIn != null) { - throw new IllegalArgumentException("POST 请求中不允许传 " + idInKey + " !"); + throw new IllegalArgumentException(table + ":{} 里的 " + idInKey + ": value 不合法!POST 请求中不允许传 " + idInKey + " !"); } if (set != null && set.isEmpty() == false) { //不能直接return,要走完下面的流程 @@ -3841,26 +3971,27 @@ else if (whereList != null && whereList.contains(key)) { } } } + config.setExplain(explain); config.setCache(getCache(cache)); - config.setFrom(from); config.setDistinct(distinct); config.setColumn(column == null ? null : cs); //解决总是 config.column != null,总是不能得到 * - config.setWhere(tableWhere); + config.setFrom(from); + config.setRole(role); config.setId(id); //在 tableWhere 第0个 config.setIdIn(idIn); - config.setRole(role); + config.setNull(nullKeys == null || nullKeys.length <= 0 ? null : new ArrayList<>(Arrays.asList(nullKeys))); + config.setCast(castMap); + config.setWhere(tableWhere); config.setGroup(group); config.setHaving(having); config.setOrder(order); - String[] jsonArr = StringUtil.split(json); - config.setJson(jsonArr == null || jsonArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(jsonArr))); - - //TODO 解析JOIN,包括 @column,@group 等要合并 + String[] jsons = StringUtil.split(json); + config.setJson(jsons == null || jsons.length <= 0 ? null : new ArrayList<>(Arrays.asList(jsons))); } finally {//后面还可能用到,要还原 @@ -3876,9 +4007,11 @@ else if (whereList != null && whereList.contains(key)) { request.put(KEY_CACHE, cache); request.put(KEY_DATASOURCE, datasource); request.put(KEY_SCHEMA, schema); - request.put(KEY_COMBINE, combine); request.put(KEY_FROM, from); request.put(KEY_COLUMN, column); + request.put(KEY_NULL, nulls); + request.put(KEY_CAST, cast); + request.put(KEY_COMBINE, combine); request.put(KEY_GROUP, group); request.put(KEY_HAVING, having); request.put(KEY_ORDER, order); diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index 83eddb301..aca544111 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -131,6 +131,9 @@ public interface SQLConfig { String getQuote(); + List getJson(); + SQLConfig setJson(List json); + /**请求传进来的Table名 * @return * @see {@link #getSQLTable()} @@ -150,7 +153,6 @@ public interface SQLConfig { List getRaw(); SQLConfig setRaw(List raw); - String getGroup(); SQLConfig setGroup(String group); @@ -160,9 +162,6 @@ public interface SQLConfig { String getOrder(); SQLConfig setOrder(String order); - List getJson(); - SQLConfig setJson(List json); - Subquery getFrom(); SQLConfig setFrom(Subquery from); @@ -175,13 +174,17 @@ public interface SQLConfig { Map getContent(); SQLConfig setContent(Map content); - Map getWhere(); - SQLConfig setWhere(Map where); - Map> getCombine(); SQLConfig setCombine(Map> combine); - - + + Map getCast(); + SQLConfig setCast(Map cast); + + List getNull(); + SQLConfig setNull(List nulls); + + Map getWhere(); + SQLConfig setWhere(Map where); /** * exactMatch = false From 2cc13dab41f658f729eb34684bd6c8f64d8fd0c2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Mar 2022 02:41:09 +0800 Subject: [PATCH 014/619] =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E7=BB=84=E5=90=88?= =?UTF-8?q?=EF=BC=9A=E8=A7=A3=E5=86=B3=20@combine:"name*~,tag&$"=20?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E5=BC=82=E5=B8=B8=EF=BC=8C=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=20@combine:"name*~=20|=20tag&$"=20=E8=BF=99=E7=A7=8D=E6=9C=80?= =?UTF-8?q?=E5=90=8E=E6=B2=A1=E6=9C=89=E6=8B=AC=E5=8F=B7=E7=9A=84=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E5=90=8E=E7=BC=BA=E5=B0=91=E6=9C=80=E5=90=8E=E7=9A=84?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 461 +++++++++++++++--- .../src/main/java/apijson/orm/SQLConfig.java | 3 + 2 files changed, 392 insertions(+), 72 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 3f0aafcdd..cb4b0a981 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -6,10 +6,9 @@ package apijson.orm; import static apijson.JSONObject.KEY_CACHE; +import static apijson.JSONObject.KEY_CAST; import static apijson.JSONObject.KEY_COLUMN; import static apijson.JSONObject.KEY_COMBINE; -import static apijson.JSONObject.KEY_NULL; -import static apijson.JSONObject.KEY_CAST; import static apijson.JSONObject.KEY_DATABASE; import static apijson.JSONObject.KEY_DATASOURCE; import static apijson.JSONObject.KEY_EXPLAIN; @@ -18,6 +17,7 @@ import static apijson.JSONObject.KEY_HAVING; import static apijson.JSONObject.KEY_ID; import static apijson.JSONObject.KEY_JSON; +import static apijson.JSONObject.KEY_NULL; import static apijson.JSONObject.KEY_ORDER; import static apijson.JSONObject.KEY_RAW; import static apijson.JSONObject.KEY_ROLE; @@ -34,16 +34,14 @@ import static apijson.SQL.NOT; import static apijson.SQL.OR; -import java.math.BigDecimal; -import java.sql.Array; -import java.sql.Date; -import java.sql.Time; -import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Deque; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -750,7 +748,7 @@ public String getUserIdKey() { private Map content; //Request内容,key:value形式,column = content.keySet(),values = content.values() private Map where; //筛选条件,key:value形式 private Map> combine; //条件组合,{ "&":[key], "|":[key], "!":[key] } - + private String combineExpression; //array item <<<<<<<<<< private int count; //Table数量 @@ -2142,6 +2140,16 @@ public SQLConfig setCast(Map cast) { //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + @Override + public String getCombineExpression() { + return combineExpression; + } + @Override + public AbstractSQLConfig setCombineExpression(String combineExpression) { + this.combineExpression = combineExpression; + return this; + } + @NotNull @Override public Map> getCombine() { @@ -2300,7 +2308,11 @@ else if (key.equals(userIdInKey)) { @JSONField(serialize = false) @Override public String getWhereString(boolean hasPrefix) throws Exception { - return getWhereString(hasPrefix, getMethod(), getWhere(), getCombine(), getJoinList(), ! isTest()); + String ce = getCombineExpression(); + if (StringUtil.isEmpty(ce, true)) { + return getWhereString(hasPrefix, getMethod(), getWhere(), getCombine(), getJoinList(), ! isTest()); + } + return getWhereString(hasPrefix, getMethod(), getWhere(), ce, getJoinList(), ! isTest()); } /**获取WHERE * @param method @@ -2309,6 +2321,301 @@ public String getWhereString(boolean hasPrefix) throws Exception { * @throws Exception */ @JSONField(serialize = false) + public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, String combine, List joinList, boolean verifyName) throws Exception { + String s = StringUtil.getString(combine); + if (s.startsWith(" ") || s.endsWith(" ") ) { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + + "' 不合法!不允许首尾有空格,也不允许连续空格!空格不能多也不能少!" + + "逻辑连接符 & | 左右必须各一个相邻空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!"); + } + + + String whereString = ""; + + int depth = 0; + int n = s.length(); + int i = 0; + + char lastLogic = 0; + char last = 0; + boolean first = true; + + String key = ""; + Set usedKeySet = new HashSet<>(where.size()); + while (i < n) { // "date> | (contactIdList<> & (name*~ | tag&$))" + char c = s.charAt(i); + boolean isLast = i >= n - 1; + boolean isBlankOrRightParenthesis = c == ' ' || c == ')'; + if (isLast || isBlankOrRightParenthesis) { + if (isBlankOrRightParenthesis == false) { + key += c; + } + + boolean isEmpty = StringUtil.isEmpty(key, true); + if (isEmpty && last != ')') { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) + + "' 不合法!" + (c == ' ' ? "空格 ' ' " : "右括号 ')'") + " 左边缺少条件 key !逻辑连接符 & | 左右必须各一个相邻空格!" + + "空格不能多也不能少!不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); + } + + if (isEmpty == false) { + if (first == false && lastLogic <= 0) { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i - key.length()) + "' 不合法!左边缺少 & | 其中一个逻辑连接符!"); + } + + Object value = where.get(key); + if (value == null) { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + key + "' 对应的条件键值对 " + key + ":value 不存在!"); + } + + String wi = getWhereItem(key, value, method, verifyName); + if (StringUtil.isEmpty(wi, true)) { // 转成 1=1 ? + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + key + "' 对应的 " + key + ":value 不是有效条件键值对!"); + } + + usedKeySet.add(key); + whereString += "( " + wi + " )"; + first = false; + } + + key = ""; + + if (isLast) { + break; + } + } + + if (c == ' ') { + } + else if (c == '&') { + if (last == ' ') { + if (i >= n || s.charAt(i + 1) != ' ') { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + + "' 不合法!逻辑连接符 & 右边缺少一个空格 !逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); + } + + whereString += SQL.AND; + lastLogic = c; + i ++; + } + else if (isLast == false) { + key += c; + } + } + else if (c == '|') { + if (last == ' ') { + if (i >= n || s.charAt(i + 1) != ' ') { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + + "' 不合法!逻辑连接符 | 右边缺少一个空格 !逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + + "不允许首尾有空格,也不允许连续空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!"); + } + + whereString += SQL.OR; + lastLogic = c; + i ++; + } + else if (isLast == false) { + key += c; + } + } + else if (c == '(') { + if (key.isEmpty() == false || (i > 0 && lastLogic <= 0)) { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) + "' 不合法!左边缺少 & | 逻辑连接符!"); + } + + depth ++; + whereString += c; + lastLogic = 0; + first = true; + } + else if (c == ')') { + depth --; + if (depth < 0) { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + "' 不合法!左括号 ( 比 右括号 ) 少!数量必须相等从而完整闭合 (...) !"); + } + + whereString += c; + lastLogic = 0; + } + else if (isLast == false) { + key += c; + } + + last = c; + i ++; + + if (i >= n) { + i = n - 1; + } + } + + if (depth != 0) { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!左括号 ( 比 右括号 ) 多!数量必须相等从而完整闭合 (...) !"); + } + + Set> set = where.entrySet(); + + String andWhere = ""; + boolean isItemFirst = true; + + for (Entry entry : set) { + key = entry == null ? null : entry.getKey(); + if (key == null || usedKeySet.contains(key)) { + continue; + } + + String wi = getWhereItem(key, where.get(key), method, verifyName); + if (StringUtil.isEmpty(wi, true)) {//避免SQL条件连接错误 + continue; + } + + andWhere += (isItemFirst ? "" : AND) + "(" + wi + ")"; + isItemFirst = false; + } + + if (StringUtil.isEmpty(whereString, true)) { + whereString = andWhere; + } + else if (StringUtil.isNotEmpty(andWhere, true)) { + whereString = andWhere + AND + "( " + whereString + " )"; + } + + if (joinList != null) { + + String newWs = ""; + String ws = whereString; + + List newPvl = new ArrayList<>(); + List pvl = new ArrayList<>(preparedValueList); + + SQLConfig jc; + String js; + + boolean changed = false; + //各种 JOIN 没办法统一用 & | !连接,只能按优先级,和 @combine 一样? + for (Join j : joinList) { + String jt = j.getJoinType(); + + switch (jt) { + case "*": // CROSS JOIN + case "@": // APP JOIN + case "<": // LEFT JOIN + case ">": // RIGHT JOIN + break; + + case "&": // INNER JOIN: A & B + case "": // FULL JOIN: A | B + case "|": // FULL JOIN: A | B + case "!": // OUTER JOIN: ! (A | B) + case "^": // SIDE JOIN: ! (A & B) + case "(": // ANTI JOIN: A & ! B + case ")": // FOREIGN JOIN: B & ! A + jc = j.getJoinConfig(); + boolean isMain = jc.isMain(); + jc.setMain(false).setPrepared(isPrepared()).setPreparedValueList(new ArrayList()); + js = jc.getWhereString(false); + jc.setMain(isMain); + + boolean isOuterJoin = "!".equals(jt); + boolean isSideJoin = "^".equals(jt); + boolean isAntiJoin = "(".equals(jt); + boolean isForeignJoin = ")".equals(jt); + boolean isWsEmpty = StringUtil.isEmpty(ws, true); + + if (isWsEmpty) { + if (isOuterJoin) { // ! OUTER JOIN: ! (A | B) + throw new NotExistException("no result for ! OUTER JOIN( ! (A | B) ) when A or B is empty!"); + } + if (isForeignJoin) { // ) FOREIGN JOIN: B & ! A + throw new NotExistException("no result for ) FOREIGN JOIN( B & ! A ) when A is empty!"); + } + } + + if (StringUtil.isEmpty(js, true)) { + if (isOuterJoin) { // ! OUTER JOIN: ! (A | B) + throw new NotExistException("no result for ! OUTER JOIN( ! (A | B) ) when A or B is empty!"); + } + if (isAntiJoin) { // ( ANTI JOIN: A & ! B + throw new NotExistException("no result for ( ANTI JOIN( A & ! B ) when B is empty!"); + } + + if (isWsEmpty) { + if (isSideJoin) { + throw new NotExistException("no result for ^ SIDE JOIN( ! (A & B) ) when both A and B are empty!"); + } + } + else { + if (isSideJoin || isForeignJoin) { + newWs += " ( " + getCondition(true, ws) + " ) "; + + newPvl.addAll(pvl); + newPvl.addAll(jc.getPreparedValueList()); + changed = true; + } + } + + continue; + } + + if (StringUtil.isEmpty(newWs, true) == false) { + newWs += AND; + } + + if (isAntiJoin) { // ( ANTI JOIN: A & ! B + newWs += " ( " + ( isWsEmpty ? "" : ws + AND ) + NOT + " ( " + js + " ) " + " ) "; + } + else if (isForeignJoin) { // ) FOREIGN JOIN: (! A) & B // preparedValueList.add 不好反过来 B & ! A + newWs += " ( " + NOT + " ( " + ws + " ) ) " + AND + " ( " + js + " ) "; + } + else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) + //MySQL 因为 NULL 值处理问题,(A & ! B) | (B & ! A) 与 ! (A & B) 返回结果不一样,后者往往更多 + newWs += " ( " + getCondition( + true, + ( isWsEmpty ? "" : ws + AND ) + " ( " + js + " ) " + ) + " ) "; + } + else { // & INNER JOIN: A & B; | FULL JOIN: A | B; OUTER JOIN: ! (A | B) + int logic = Logic.getType(jt); + newWs += " ( " + + getCondition( + Logic.isNot(logic), + ws + + ( isWsEmpty ? "" : (Logic.isAnd(logic) ? AND : OR) ) + + " ( " + js + " ) " + ) + + " ) "; + } + + newPvl.addAll(pvl); + newPvl.addAll(jc.getPreparedValueList()); + + changed = true; + break; + default: + throw new UnsupportedOperationException( + "join:value 中 value 里的 " + jt + "/" + j.getPath() + + "错误!不支持 " + jt + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" + + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" + ); + } + } + + if (changed) { + whereString = newWs; + preparedValueList = newPvl; + } + } + + String result = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; + + if (result.isEmpty() && RequestMethod.isQueryMethod(method) == false) { + throw new UnsupportedOperationException("写操作请求必须带条件!!!"); + } + + return result; + } + public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, Map> combine, List joinList, boolean verifyName) throws Exception { Set>> combineSet = combine == null ? null : combine.entrySet(); if (combineSet == null || combineSet.isEmpty()) { @@ -2353,7 +2660,6 @@ else if ("!".equals(ce.getKey())) { } cs += (isItemFirst ? "" : (Logic.isAnd(logic) ? AND : OR)) + "(" + c + ")"; - isItemFirst = false; } @@ -3813,74 +4119,80 @@ else if (id instanceof Subquery) {} //条件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< List whereList = null; - Map> combineMap = new LinkedHashMap<>(); - List andList = new ArrayList<>(); - List orList = new ArrayList<>(); - List notList = new ArrayList<>(); - - //强制作为条件且放在最前面优化性能 - if (id != null) { - tableWhere.put(idKey, id); - andList.add(idKey); - } - if (idIn != null) { - tableWhere.put(idInKey, idIn); - andList.add(idInKey); - } - String[] ws = StringUtil.split(combine); - if (ws != null) { - if (method == DELETE || method == GETS || method == HEADS) { - throw new IllegalArgumentException("DELETE,GETS,HEADS 请求不允许传 @combine:value !"); + String combineExpression = ws == null || ws.length != 1 ? null : ws[0]; + + Map> combineMap = StringUtil.isNotEmpty(combineExpression, true) ? null : new LinkedHashMap<>(); + List andList = combineMap == null ? null : new ArrayList<>(); + List orList = combineMap == null ? null : new ArrayList<>(); + List notList = combineMap == null ? null : new ArrayList<>(); + + if (combineMap != null) { + //强制作为条件且放在最前面优化性能 + if (id != null) { + tableWhere.put(idKey, id); + andList.add(idKey); } - whereList = new ArrayList<>(); - - String w; - for (int i = 0; i < ws.length; i++) { //去除 &,|,! 前缀 - w = ws[i]; - if (w != null) { - if (w.startsWith("&")) { - w = w.substring(1); - andList.add(w); - } - else if (w.startsWith("|")) { - if (method == PUT) { - throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" - + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); + if (idIn != null) { + tableWhere.put(idInKey, idIn); + andList.add(idInKey); + } + + + if (ws != null) { + if (method == DELETE || method == GETS || method == HEADS) { + throw new IllegalArgumentException("DELETE,GETS,HEADS 请求不允许传 @combine:value !"); + } + whereList = new ArrayList<>(); + + String w; + for (int i = 0; i < ws.length; i++) { //去除 &,|,! 前缀 + w = ws[i]; + if (w != null) { + if (w.startsWith("&")) { + w = w.substring(1); + andList.add(w); } - w = w.substring(1); - orList.add(w); - } - else if (w.startsWith("!")) { - if (method == PUT) { - throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" - + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); + else if (w.startsWith("|")) { + if (method == PUT) { + throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" + + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); + } + w = w.substring(1); + orList.add(w); + } + else if (w.startsWith("!")) { + if (method == PUT) { + throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" + + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); + } + w = w.substring(1); + notList.add(w); + } + else { + orList.add(w); } - w = w.substring(1); - notList.add(w); - } - else { - orList.add(w); - } - if (w.isEmpty()) { - throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!不允许为空值!"); - } - else { - if (idKey.equals(w) || idInKey.equals(w) || userIdKey.equals(w) || userIdInKey.equals(w)) { - throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 不合法!" - + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); + if (w.isEmpty()) { + throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!不允许为空值!"); + } + else { + if (idKey.equals(w) || idInKey.equals(w) || userIdKey.equals(w) || userIdInKey.equals(w)) { + throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 不合法!" + + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); + } } + + whereList.add(w); } - whereList.add(w); + // 可重写回调方法自定义处理 // 动态设置的场景似乎很少,而且去掉后不方便用户排错!//去掉判断,有时候不在没关系,如果是对增删改等非开放请求强制要求传对应的条件,可以用 Operation.NECESSARY + if (request.containsKey(w) == false) { //和 request.get(w) == null 没区别,前面 Parser 已经过滤了 null + // throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 对应的 " + w + " 不在它里面!"); + callback.onMissingKey4Combine(table, request, combine, ws[i], w); + } } - // 可重写回调方法自定义处理 // 动态设置的场景似乎很少,而且去掉后不方便用户排错!//去掉判断,有时候不在没关系,如果是对增删改等非开放请求强制要求传对应的条件,可以用 Operation.NECESSARY - if (request.containsKey(w) == false) { //和 request.get(w) == null 没区别,前面 Parser 已经过滤了 null - // throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 对应的 " + w + " 不在它里面!"); - callback.onMissingKey4Combine(table, request, combine, ws[i], w); - } } } @@ -3900,7 +4212,9 @@ else if (w.startsWith("!")) { if (isWhere || (StringUtil.isName(key.replaceFirst("[+-]$", "")) == false)) { tableWhere.put(key, value); if (whereList == null || whereList.contains(key) == false) { - andList.add(key); + if (andList != null) { + andList.add(key); + } } } else if (whereList != null && whereList.contains(key)) { @@ -3911,10 +4225,13 @@ else if (whereList != null && whereList.contains(key)) { } } - combineMap.put("&", andList); - combineMap.put("|", orList); - combineMap.put("!", notList); + if (combineMap != null) { + combineMap.put("&", andList); + combineMap.put("|", orList); + combineMap.put("!", notList); + } config.setCombine(combineMap); + config.setCombineExpression(combineExpression); config.setContent(tableContent); } diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index aca544111..259788770 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -176,6 +176,9 @@ public interface SQLConfig { Map> getCombine(); SQLConfig setCombine(Map> combine); + + String getCombineExpression(); + SQLConfig setCombineExpression(String combineExpression); Map getCast(); SQLConfig setCast(Map cast); From d29d079ab867843f4fbedb73dea737663167a024 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Mar 2022 03:27:39 +0800 Subject: [PATCH 015/619] =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E7=BB=84=E5=90=88?= =?UTF-8?q?=EF=BC=9A=E8=A7=A3=E5=86=B3=20@combine:"(date>=20|=20tag&$)=20&?= =?UTF-8?q?=20name*~"=20=E8=A7=A3=E6=9E=90=E5=BC=82=E5=B8=B8=EF=BC=8C?= =?UTF-8?q?=E8=A7=A3=E5=86=B3=20@combine:"id=20|=20userId{}"=20=E5=8F=AF?= =?UTF-8?q?=E7=BB=95=E8=BF=87=E6=9D=83=E9=99=90=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 62 +++++++++++-------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index cb4b0a981..608676769 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2342,25 +2342,22 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map usedKeySet = new HashSet<>(where.size()); - while (i < n) { // "date> | (contactIdList<> & (name*~ | tag&$))" - char c = s.charAt(i); - boolean isLast = i >= n - 1; + while (i <= n) { // "date> | (contactIdList<> & (name*~ | tag&$))" + boolean isOver = i >= n; + char c = isOver ? 0 : s.charAt(i); boolean isBlankOrRightParenthesis = c == ' ' || c == ')'; - if (isLast || isBlankOrRightParenthesis) { - if (isBlankOrRightParenthesis == false) { - key += c; - } - + if (isOver || isBlankOrRightParenthesis) { boolean isEmpty = StringUtil.isEmpty(key, true); if (isEmpty && last != ')') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + (isOver ? s : s.substring(i)) + "' 不合法!" + (c == ' ' ? "空格 ' ' " : "右括号 ')'") + " 左边缺少条件 key !逻辑连接符 & | 左右必须各一个相邻空格!" + "空格不能多也不能少!不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); } if (isEmpty == false) { if (first == false && lastLogic <= 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i - key.length()) + "' 不合法!左边缺少 & | 其中一个逻辑连接符!"); + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i - key.length() - (isOver ? 1 : 0)) + + "' 不合法!左边缺少 & | 其中一个逻辑连接符!"); } Object value = where.get(key); @@ -2380,7 +2377,7 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map= n || s.charAt(i + 1) != ' ') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, 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)) + "' 不合法!逻辑连接符 & 右边缺少一个空格 !逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); } @@ -2399,14 +2396,14 @@ else if (c == '&') { lastLogic = c; i ++; } - else if (isLast == false) { + else { key += c; } } else if (c == '|') { if (last == ' ') { - if (i >= n || s.charAt(i + 1) != ' ') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, 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)) + "' 不合法!逻辑连接符 | 右边缺少一个空格 !逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + "不允许首尾有空格,也不允许连续空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!"); } @@ -2415,7 +2412,7 @@ else if (c == '|') { lastLogic = c; i ++; } - else if (isLast == false) { + else { key += c; } } @@ -2438,16 +2435,12 @@ else if (c == ')') { whereString += c; lastLogic = 0; } - else if (isLast == false) { + else { key += c; } last = c; i ++; - - if (i >= n) { - i = n - 1; - } } if (depth != 0) { @@ -2477,8 +2470,8 @@ else if (isLast == false) { if (StringUtil.isEmpty(whereString, true)) { whereString = andWhere; } - else if (StringUtil.isNotEmpty(andWhere, true)) { - whereString = andWhere + AND + "( " + whereString + " )"; + else if (StringUtil.isNotEmpty(andWhere, true)) { // andWhere 必须放后面,否则 prepared 值顺序错误 + whereString = "( " + whereString + " )" + AND + andWhere; } if (joinList != null) { @@ -4127,7 +4120,24 @@ else if (id instanceof Subquery) {} List orList = combineMap == null ? null : new ArrayList<>(); List notList = combineMap == null ? null : new ArrayList<>(); - if (combineMap != null) { + if (combineMap == null) { + if (StringUtil.isNotEmpty(combineExpression, true)) { + List banKeyList = Arrays.asList(idKey, idInKey, userIdKey, userIdInKey); + + for (String key : banKeyList) { + int index = combineExpression.indexOf(key); + if (index >= 0) { + char left = index <= 0 ? ' ' : combineExpression.charAt(index - 1); + char right = index >= combineExpression.length() - key.length() ? ' ' : combineExpression.charAt(index + key.length()); + if ((left == ' ' || left == '(') && (right == ' ' || right == ')')) { + throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的 value 里 " + key + " 不合法!" + + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); + } + } + } + } + } + else { //强制作为条件且放在最前面优化性能 if (id != null) { tableWhere.put(idKey, id); @@ -4178,7 +4188,7 @@ else if (w.startsWith("!")) { } else { if (idKey.equals(w) || idInKey.equals(w) || userIdKey.equals(w) || userIdInKey.equals(w)) { - throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 不合法!" + throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的 value 里 " + ws[i] + " 不合法!" + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); } } From 795c8e9ccb0e4ccc82f3bd5e01d1c865b879c8a1 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Mar 2022 04:38:35 +0800 Subject: [PATCH 016/619] =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E7=BB=84=E5=90=88?= =?UTF-8?q?=EF=BC=9A=E9=99=90=E5=88=B6=20@combine:value=20=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=20value=20=E7=9A=84=E6=8B=AC=E5=8F=B7=E5=B5=8C?= =?UTF-8?q?=E5=A5=97=E6=B7=B1=E5=BA=A6=E3=80=81key=20=E6=95=B0=E9=87=8F?= =?UTF-8?q?=E3=80=81key=20=E9=87=8D=E5=A4=8D=E6=AC=A1=E6=95=B0=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 77 +++++++++++++++++-- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 608676769..1541a6be1 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -37,11 +37,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Deque; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -81,7 +78,13 @@ */ public abstract class AbstractSQLConfig implements SQLConfig { private static final String TAG = "AbstractSQLConfig"; - + + public static int MAX_COMBINE_DEPTH = 2; + public static int MAX_WHERE_COUNT = 3; + public static int MAX_COMBINE_COUNT = 5; + public static int MAX_COMBINE_KEY_COUNT = 2; + public static float MAX_COMBINE_RATIO = 1.0f; + public static String DEFAULT_DATABASE = DATABASE_MYSQL; public static String DEFAULT_SCHEMA = "sys"; public static String PREFFIX_DISTINCT = "DISTINCT "; @@ -102,6 +105,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { // 允许调用的 SQL 函数:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL public static final Map SQL_FUNCTION_MAP; + static { // 凡是 SQL 边界符、分隔符、注释符 都不允许,例如 ' " ` ( ) ; # -- /**/ ,以免拼接 SQL 时被注入意外可执行指令 PATTERN_RANGE = Pattern.compile("^[0-9%,!=\\<\\>/\\.\\+\\-\\*\\^]+$"); // ^[a-zA-Z0-9_*%!=<>(),"]+$ 导致 exists(select*from(Comment)) 通过! PATTERN_FUNCTION = Pattern.compile("^[A-Za-z0-9%,:_@&~`!=\\<\\>\\|\\[\\]\\{\\} /\\.\\+\\-\\*\\^\\?\\(\\)\\$]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值 @@ -2140,6 +2144,23 @@ public SQLConfig setCast(Map cast) { //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + protected int getMaxWhereCount() { + return MAX_WHERE_COUNT; + } + protected int getMaxCombineDepth() { + return MAX_COMBINE_DEPTH; + } + protected int getMaxCombineCount() { + return MAX_COMBINE_COUNT; + } + protected int getMaxCombineKeyCount() { + return MAX_COMBINE_KEY_COUNT; + } + protected float getMaxCombineRatio() { + return MAX_COMBINE_RATIO; + } + + @Override public String getCombineExpression() { return combineExpression; @@ -2329,10 +2350,26 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map(); + } + int whereSize = where.size(); + + int maxWhereCount = getMaxWhereCount(); + if (maxWhereCount > 0 && whereSize > maxWhereCount) { + throw new IllegalArgumentException(table + ":{ key0:value0, key1:value1... } 中条件 key:value 数量 " + whereSize + " 已超过最大数量,必须在 0-" + maxWhereCount + " 内!"); + } String whereString = ""; + int maxDepth = getMaxCombineDepth(); + int maxCombineCount = getMaxCombineCount(); + int maxCombineKeyCount = getMaxCombineKeyCount(); + float maxCombineRatio = getMaxCombineRatio(); + int depth = 0; + int allCount = 0; + int n = s.length(); int i = 0; @@ -2341,7 +2378,7 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map usedKeySet = new HashSet<>(where.size()); + Map usedKeyCountMap = new HashMap<>(whereSize); while (i <= n) { // "date> | (contactIdList<> & (name*~ | tag&$))" boolean isOver = i >= n; char c = isOver ? 0 : s.charAt(i); @@ -2359,6 +2396,17 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map maxCombineCount && maxCombineCount > 0) { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!" + + "其中 key 数量 " + allCount + " 已超过最大值,必须在条件键值对数量 0-" + maxCombineCount + " 内!"); + } + if (1.0f*allCount/whereSize > maxCombineRatio && maxCombineRatio > 0) { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!" + + "其中 key 数量 " + allCount + " / 条件键值对数量 " + whereSize + " = " + (1.0f*allCount/whereSize) + + " 已超过 最大倍数,必须在条件键值对数量 0-" + maxCombineRatio + " 倍内!"); + } Object value = where.get(key); if (value == null) { @@ -2370,7 +2418,16 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map maxCombineKeyCount && maxCombineKeyCount > 0) { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!其中 '" + key + + "' 重复引用,次数 " + count + " 已超过最大值,必须在 0-" + maxCombineKeyCount + " 内!"); + } + + usedKeyCountMap.put(key, count); + + whereString += "( " + wi + " )"; first = false; } @@ -2422,6 +2479,10 @@ else if (c == '(') { } depth ++; + if (depth > maxDepth && maxDepth > 0) { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + "' 不合法!括号 (()) 嵌套层级 " + depth + " 已超过最大值,必须在 0-" + maxDepth + " 内!"); + } + whereString += c; lastLogic = 0; first = true; @@ -2454,7 +2515,7 @@ else if (c == ')') { for (Entry entry : set) { key = entry == null ? null : entry.getKey(); - if (key == null || usedKeySet.contains(key)) { + if (key == null || usedKeyCountMap.containsKey(key)) { continue; } @@ -2609,6 +2670,8 @@ else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) return result; } + + public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, Map> combine, List joinList, boolean verifyName) throws Exception { Set>> combineSet = combine == null ? null : combine.entrySet(); if (combineSet == null || combineSet.isEmpty()) { From 66000f747f4bc10e8053b9267e5394eb11f7a050 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Mar 2022 04:42:04 +0800 Subject: [PATCH 017/619] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E9=94=AE=E5=80=BC=E5=AF=B9=E7=9A=84=E9=BB=98=E8=AE=A4=E6=9C=80?= =?UTF-8?q?=E5=A4=A7=E6=95=B0=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 1541a6be1..5aa4c9f8e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -80,7 +80,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { private static final String TAG = "AbstractSQLConfig"; public static int MAX_COMBINE_DEPTH = 2; - public static int MAX_WHERE_COUNT = 3; + public static int MAX_WHERE_COUNT = 10; public static int MAX_COMBINE_COUNT = 5; public static int MAX_COMBINE_KEY_COUNT = 2; public static float MAX_COMBINE_RATIO = 1.0f; From 4cf7d985a56f93fc6f9766ae138fca159ae63810 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Mar 2022 17:23:10 +0800 Subject: [PATCH 018/619] =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E7=BB=84=E5=90=88?= =?UTF-8?q?=EF=BC=9A@combine:value=20=E4=B8=AD=E7=9A=84=20value=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E9=9D=9E=E9=80=BB=E8=BE=91=E7=AC=A6=20=20!?= =?UTF-8?q?=20=EF=BC=8C=E8=A7=A3=E5=86=B3=E4=B8=8D=E5=85=81=E8=AE=B8?= =?UTF-8?q?=E8=BF=9E=E7=BB=AD=E5=B7=A6=E6=8B=AC=E5=8F=B7=20((?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 42 +++++++++++++------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 5aa4c9f8e..4332b3069 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2376,6 +2376,7 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map usedKeyCountMap = new HashMap<>(whereSize); @@ -2394,7 +2395,7 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map maxCombineKeyCount && maxCombineKeyCount > 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!其中 '" + key + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!其中 '" + column + "' 重复引用,次数 " + count + " 已超过最大值,必须在 0-" + maxCombineKeyCount + " 内!"); } + usedKeyCountMap.put(column, count); - usedKeyCountMap.put(key, count); - - - whereString += "( " + wi + " )"; + whereString += "( " + getCondition(isNot, wi) + " )"; + isNot = false; first = false; } @@ -2473,8 +2475,22 @@ else if (c == '|') { key += c; } } + else if (c == '!') { + last = i < 1 ? 0 : s.charAt(i - 1); // & | 后面跳过了空格 + if (i < n - 1 && s.charAt(i + 1) == '(') { + whereString += SQL.NOT; + lastLogic = c; + } + else if (last <= 0 || last == ' ' || last == '(') { + isNot = true; +// lastLogic = c; + } + else { + key += c; + } + } else if (c == '(') { - if (key.isEmpty() == false || (i > 0 && lastLogic <= 0)) { + if (key.isEmpty() == false || (i > 0 && lastLogic <= 0 && last != '(')) { throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) + "' 不合法!左边缺少 & | 逻辑连接符!"); } @@ -3555,7 +3571,7 @@ public String getSubqueryString(Subquery subquery) throws Exception { * @param condition * @return */ - private static String getCondition(boolean not, String condition) { + public static String getCondition(boolean not, String condition) { return not ? NOT + "(" + condition + ")" : condition; } From bff0d44c35c91754d7768c32d8683d6f8696f116 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Mar 2022 19:41:25 +0800 Subject: [PATCH 019/619] =?UTF-8?q?@combine:value=20=E5=A4=8D=E6=9D=82?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E7=BB=84=E5=90=88=EF=BC=9A=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E6=9C=80=E7=BB=88=E6=9D=A1=E4=BB=B6=E4=B8=A2=E5=A4=B1=20id,=20?= =?UTF-8?q?id{}=EF=BC=8C=E8=A7=A3=E5=86=B3=E5=8F=AF=E4=BB=A5=E9=80=9A?= =?UTF-8?q?=E8=BF=87=20!id,=20!id{}=20=E7=BB=95=E8=BF=87=E6=9D=83=E9=99=90?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 90 +++++++++++++------ 1 file changed, 62 insertions(+), 28 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 4332b3069..5a4675471 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2357,7 +2357,8 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map 0 && whereSize > maxWhereCount) { - throw new IllegalArgumentException(table + ":{ key0:value0, key1:value1... } 中条件 key:value 数量 " + whereSize + " 已超过最大数量,必须在 0-" + maxWhereCount + " 内!"); + throw new IllegalArgumentException(table + ":{ key0:value0, key1:value1... } 中条件 key:value 数量 " + whereSize + + " 已超过最大数量,必须在 0-" + maxWhereCount + " 内!"); } String whereString = ""; @@ -2367,6 +2368,9 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map prepreadValues = getPreparedValueList(); + setPreparedValueList(new ArrayList<>()); + int depth = 0; int allCount = 0; @@ -2394,8 +2398,8 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map maxCombineKeyCount && maxCombineKeyCount > 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!其中 '" + column - + "' 重复引用,次数 " + count + " 已超过最大值,必须在 0-" + maxCombineKeyCount + " 内!"); + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!" + + "其中 '" + column + "' 重复引用,次数 " + count + " 已超过最大值,必须在 0-" + maxCombineKeyCount + " 内!"); } usedKeyCountMap.put(column, count); @@ -2476,8 +2482,24 @@ else if (c == '|') { } } else if (c == '!') { - last = i < 1 ? 0 : s.charAt(i - 1); // & | 后面跳过了空格 - if (i < n - 1 && s.charAt(i + 1) == '(') { + char next = i >= n - 1 ? 0 : s.charAt(i + 1); + if (next == ' ') { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + + "' 不合法!非逻辑符 '!' 右边多了一个空格 ' ' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); + } + if (next == ')' || next == '&' || next == '!') { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + + "' 不合法!非逻辑符 '!' 右边多了一个字符 '" + next + "' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); + } + + last = i <= 0 ? 0 : s.charAt(i - 1); // & | 后面跳过了空格 + if (i > 0 && lastLogic <= 0 && last != '(') { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) + + "' 不合法!左边缺少 & | 逻辑连接符!逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); + } + + if (next == '(') { whereString += SQL.NOT; lastLogic = c; } @@ -2491,12 +2513,15 @@ else if (last <= 0 || last == ' ' || last == '(') { } else if (c == '(') { if (key.isEmpty() == false || (i > 0 && lastLogic <= 0 && last != '(')) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) + "' 不合法!左边缺少 & | 逻辑连接符!"); + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) + + "' 不合法!左边缺少 & | 逻辑连接符!逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); } depth ++; if (depth > maxDepth && maxDepth > 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + "' 不合法!括号 (()) 嵌套层级 " + depth + " 已超过最大值,必须在 0-" + maxDepth + " 内!"); + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + + "' 不合法!括号 (()) 嵌套层级 " + depth + " 已超过最大值,必须在 0-" + maxDepth + " 内!"); } whereString += c; @@ -2506,7 +2531,8 @@ else if (c == '(') { else if (c == ')') { depth --; if (depth < 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + "' 不合法!左括号 ( 比 右括号 ) 少!数量必须相等从而完整闭合 (...) !"); + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + + "' 不合法!左括号 ( 比 右括号 ) 少!数量必须相等从而完整闭合 (...) !"); } whereString += c; @@ -2521,7 +2547,8 @@ else if (c == ')') { } if (depth != 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!左括号 ( 比 右括号 ) 多!数量必须相等从而完整闭合 (...) !"); + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + + "' 不合法!左括号 ( 比 右括号 ) 多!数量必须相等从而完整闭合 (...) !"); } Set> set = where.entrySet(); @@ -2548,7 +2575,11 @@ else if (c == ')') { whereString = andWhere; } else if (StringUtil.isNotEmpty(andWhere, true)) { // andWhere 必须放后面,否则 prepared 值顺序错误 - whereString = "( " + whereString + " )" + AND + andWhere; +// whereString = "( " + whereString + " )" + AND + andWhere; + + whereString = andWhere + AND + "( " + whereString + " )"; // 先暂存之前的 prepared 值,然后反向整合 + prepreadValues.addAll(getPreparedValueList()); + setPreparedValueList(prepreadValues); } if (joinList != null) { @@ -2557,7 +2588,7 @@ else if (StringUtil.isNotEmpty(andWhere, true)) { // andWhere 必须放后面 String ws = whereString; List newPvl = new ArrayList<>(); - List pvl = new ArrayList<>(preparedValueList); + List pvl = new ArrayList<>(getPreparedValueList()); SQLConfig jc; String js; @@ -2673,7 +2704,7 @@ else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) if (changed) { whereString = newWs; - preparedValueList = newPvl; + setPreparedValueList(newPvl); } } @@ -4199,6 +4230,20 @@ else if (id instanceof Subquery) {} List orList = combineMap == null ? null : new ArrayList<>(); List notList = combineMap == null ? null : new ArrayList<>(); + //强制作为条件且放在最前面优化性能 + if (id != null) { + tableWhere.put(idKey, id); + if (andList != null) { + andList.add(idKey); + } + } + if (idIn != null) { + tableWhere.put(idInKey, idIn); + if (andList != null) { + andList.add(idInKey); + } + } + if (combineMap == null) { if (StringUtil.isNotEmpty(combineExpression, true)) { List banKeyList = Arrays.asList(idKey, idInKey, userIdKey, userIdInKey); @@ -4208,7 +4253,7 @@ else if (id instanceof Subquery) {} if (index >= 0) { char left = index <= 0 ? ' ' : combineExpression.charAt(index - 1); char right = index >= combineExpression.length() - key.length() ? ' ' : combineExpression.charAt(index + key.length()); - if ((left == ' ' || left == '(') && (right == ' ' || right == ')')) { + if ((left == ' ' || left == '(' || left == '&' || left == '|' || left == '!') && (right == ' ' || right == ')')) { throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的 value 里 " + key + " 不合法!" + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); } @@ -4217,17 +4262,6 @@ else if (id instanceof Subquery) {} } } else { - //强制作为条件且放在最前面优化性能 - if (id != null) { - tableWhere.put(idKey, id); - andList.add(idKey); - } - if (idIn != null) { - tableWhere.put(idInKey, idIn); - andList.add(idInKey); - } - - if (ws != null) { if (method == DELETE || method == GETS || method == HEADS) { throw new IllegalArgumentException("DELETE,GETS,HEADS 请求不允许传 @combine:value !"); From cf7bdd74e4b25bed7b19eff65c1ec4a5051b86d2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Mar 2022 20:04:26 +0800 Subject: [PATCH 020/619] =?UTF-8?q?@combine:value=20=E5=A4=8D=E6=9D=82?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E7=BB=84=E5=90=88=EF=BC=9A=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=20key!=20=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 5a4675471..2bed0dda0 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2441,6 +2441,7 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map= n - 1 ? 0 : s.charAt(i + 1); - if (next == ' ') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) - + "' 不合法!非逻辑符 '!' 右边多了一个空格 ' ' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); - } - if (next == ')' || next == '&' || next == '!') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) - + "' 不合法!非逻辑符 '!' 右边多了一个字符 '" + next + "' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); - } - last = i <= 0 ? 0 : s.charAt(i - 1); // & | 后面跳过了空格 - if (i > 0 && lastLogic <= 0 && last != '(') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) - + "' 不合法!左边缺少 & | 逻辑连接符!逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" - + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); + + char next = i >= n - 1 ? 0 : s.charAt(i + 1); + if (last == ' ' || last == '(') { + if (next == ' ') { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + + "' 不合法!非逻辑符 '!' 右边多了一个空格 ' ' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); + } + if (next == ')' || next == '&' || next == '!') { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + + "' 不合法!非逻辑符 '!' 右边多了一个字符 '" + next + "' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); + } + if (i > 0 && lastLogic <= 0 && last != '(') { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) + + "' 不合法!左边缺少 & | 逻辑连接符!逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); + } } if (next == '(') { From 3ea3e6121552965b21e3e79b4bde1bf0f0b1ae2f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Mar 2022 20:53:29 +0800 Subject: [PATCH 021/619] =?UTF-8?q?=E4=BC=98=E5=8C=96=20=20where=20?= =?UTF-8?q?=E5=92=8C=20JOIN=20=E8=A7=A3=E6=9E=90=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 157 +++--------------- 1 file changed, 20 insertions(+), 137 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 2bed0dda0..83e0d85c1 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2585,132 +2585,8 @@ else if (StringUtil.isNotEmpty(andWhere, true)) { // andWhere 必须放后面 setPreparedValueList(prepreadValues); } - if (joinList != null) { - - String newWs = ""; - String ws = whereString; - - List newPvl = new ArrayList<>(); - List pvl = new ArrayList<>(getPreparedValueList()); - - SQLConfig jc; - String js; - - boolean changed = false; - //各种 JOIN 没办法统一用 & | !连接,只能按优先级,和 @combine 一样? - for (Join j : joinList) { - String jt = j.getJoinType(); - - switch (jt) { - case "*": // CROSS JOIN - case "@": // APP JOIN - case "<": // LEFT JOIN - case ">": // RIGHT JOIN - break; - - case "&": // INNER JOIN: A & B - case "": // FULL JOIN: A | B - case "|": // FULL JOIN: A | B - case "!": // OUTER JOIN: ! (A | B) - case "^": // SIDE JOIN: ! (A & B) - case "(": // ANTI JOIN: A & ! B - case ")": // FOREIGN JOIN: B & ! A - jc = j.getJoinConfig(); - boolean isMain = jc.isMain(); - jc.setMain(false).setPrepared(isPrepared()).setPreparedValueList(new ArrayList()); - js = jc.getWhereString(false); - jc.setMain(isMain); - - boolean isOuterJoin = "!".equals(jt); - boolean isSideJoin = "^".equals(jt); - boolean isAntiJoin = "(".equals(jt); - boolean isForeignJoin = ")".equals(jt); - boolean isWsEmpty = StringUtil.isEmpty(ws, true); - - if (isWsEmpty) { - if (isOuterJoin) { // ! OUTER JOIN: ! (A | B) - throw new NotExistException("no result for ! OUTER JOIN( ! (A | B) ) when A or B is empty!"); - } - if (isForeignJoin) { // ) FOREIGN JOIN: B & ! A - throw new NotExistException("no result for ) FOREIGN JOIN( B & ! A ) when A is empty!"); - } - } - - if (StringUtil.isEmpty(js, true)) { - if (isOuterJoin) { // ! OUTER JOIN: ! (A | B) - throw new NotExistException("no result for ! OUTER JOIN( ! (A | B) ) when A or B is empty!"); - } - if (isAntiJoin) { // ( ANTI JOIN: A & ! B - throw new NotExistException("no result for ( ANTI JOIN( A & ! B ) when B is empty!"); - } - - if (isWsEmpty) { - if (isSideJoin) { - throw new NotExistException("no result for ^ SIDE JOIN( ! (A & B) ) when both A and B are empty!"); - } - } - else { - if (isSideJoin || isForeignJoin) { - newWs += " ( " + getCondition(true, ws) + " ) "; - - newPvl.addAll(pvl); - newPvl.addAll(jc.getPreparedValueList()); - changed = true; - } - } - - continue; - } - - if (StringUtil.isEmpty(newWs, true) == false) { - newWs += AND; - } - - if (isAntiJoin) { // ( ANTI JOIN: A & ! B - newWs += " ( " + ( isWsEmpty ? "" : ws + AND ) + NOT + " ( " + js + " ) " + " ) "; - } - else if (isForeignJoin) { // ) FOREIGN JOIN: (! A) & B // preparedValueList.add 不好反过来 B & ! A - newWs += " ( " + NOT + " ( " + ws + " ) ) " + AND + " ( " + js + " ) "; - } - else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) - //MySQL 因为 NULL 值处理问题,(A & ! B) | (B & ! A) 与 ! (A & B) 返回结果不一样,后者往往更多 - newWs += " ( " + getCondition( - true, - ( isWsEmpty ? "" : ws + AND ) + " ( " + js + " ) " - ) + " ) "; - } - else { // & INNER JOIN: A & B; | FULL JOIN: A | B; OUTER JOIN: ! (A | B) - int logic = Logic.getType(jt); - newWs += " ( " - + getCondition( - Logic.isNot(logic), - ws - + ( isWsEmpty ? "" : (Logic.isAnd(logic) ? AND : OR) ) - + " ( " + js + " ) " - ) - + " ) "; - } - - newPvl.addAll(pvl); - newPvl.addAll(jc.getPreparedValueList()); - - changed = true; - break; - default: - throw new UnsupportedOperationException( - "join:value 中 value 里的 " + jt + "/" + j.getPath() - + "错误!不支持 " + jt + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" - + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" - ); - } - } - - if (changed) { - whereString = newWs; - setPreparedValueList(newPvl); - } - } - + whereString = concatJoinWhereString(whereString); + String result = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; if (result.isEmpty() && RequestMethod.isQueryMethod(method) == false) { @@ -2721,7 +2597,6 @@ else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) } - public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, Map> combine, List joinList, boolean verifyName) throws Exception { Set>> combineSet = combine == null ? null : combine.entrySet(); if (combineSet == null || combineSet.isEmpty()) { @@ -2776,15 +2651,28 @@ else if ("!".equals(ce.getKey())) { whereString += (isCombineFirst ? "" : AND) + (Logic.isNot(logic) ? NOT : "") + " ( " + cs + " ) "; isCombineFirst = false; } + + whereString = concatJoinWhereString(whereString); + String s = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; + if (s.isEmpty() && RequestMethod.isQueryMethod(method) == false) { + throw new UnsupportedOperationException("写操作请求必须带条件!!!"); + } + + return s; + } + + + protected String concatJoinWhereString(String whereString) throws Exception { + List joinList = getJoinList(); if (joinList != null) { String newWs = ""; String ws = whereString; List newPvl = new ArrayList<>(); - List pvl = new ArrayList<>(preparedValueList); + List pvl = new ArrayList<>(getPreparedValueList()); SQLConfig jc; String js; @@ -2873,7 +2761,7 @@ else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) ) + " ) "; } else { // & INNER JOIN: A & B; | FULL JOIN: A | B; OUTER JOIN: ! (A | B) - logic = Logic.getType(jt); + int logic = Logic.getType(jt); newWs += " ( " + getCondition( Logic.isNot(logic), @@ -2900,19 +2788,14 @@ else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) if (changed) { whereString = newWs; - preparedValueList = newPvl; + setPreparedValueList(newPvl); } } - String s = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; - - if (s.isEmpty() && RequestMethod.isQueryMethod(method) == false) { - throw new UnsupportedOperationException("写操作请求必须带条件!!!"); - } - - return s; + return whereString; } + /** * @param key * @param value From 0063721354b60ae62244b36429ddaf5c3674b389 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Mar 2022 21:57:26 +0800 Subject: [PATCH 022/619] =?UTF-8?q?JOIN=20ON=20=E6=96=B0=E5=A2=9E=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20{}=20IN=20=E5=92=8C=20<>=20json=5Fcontains=20?= =?UTF-8?q?=E4=B8=A4=E7=A7=8D=E5=85=B3=E8=81=94=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/SQL.java | 2 + .../java/apijson/orm/AbstractSQLConfig.java | 44 +++++++++++++++++-- .../java/apijson/orm/AbstractSQLExecutor.java | 6 ++- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/SQL.java b/APIJSONORM/src/main/java/apijson/SQL.java index 397c2915f..e8b7bda6f 100755 --- a/APIJSONORM/src/main/java/apijson/SQL.java +++ b/APIJSONORM/src/main/java/apijson/SQL.java @@ -10,6 +10,8 @@ */ public class SQL { + public static final String JOIN = " JOIN "; + public static final String ON = " ON "; public static final String OR = " OR "; public static final String AND = " AND "; public static final String NOT = " NOT "; diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 83e0d85c1..94b65a484 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -33,6 +33,7 @@ import static apijson.SQL.AND; import static apijson.SQL.NOT; import static apijson.SQL.OR; +import static apijson.SQL.ON; import java.util.ArrayList; import java.util.Arrays; @@ -3803,8 +3804,25 @@ public String getJoinString() throws Exception { if (onList != null) { boolean first = true; for (On on : onList) { - sql += (first ? " ON " : " AND ") + quote + jt + quote + "." + quote + on.getKey() + quote + " = " - + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + String rt = on.getRelateType(); + if (StringUtil.isEmpty(rt, false)) { + sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + " = " + + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + } + else if ("{}".equals(rt)) { + sql += (first ? ON : AND) + "json_contains(" + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + // + ", concat('\\'', " + quote + jt + quote + "." + quote + on.getKey() + quote + ", '\\''), '$')"; + + ", cast(" + quote + jt + quote + "." + quote + on.getKey() + quote + " AS CHAR), '$')"; + } + else if ("<>".equals(rt)) { + sql += (first ? ON : AND) + "json_contains(" + quote + jt + quote + "." + quote + on.getKey() + quote + // + ", concat('\\'', " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + ", '\\''), '$')"; + + ", cast(" + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + " AS CHAR), '$')"; + } + else { + throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath() + + " 中 JOIN ON 条件关联类型 " + rt + " 不合法!只支持 =, {}, <> 这几种!"); + } first = false; } } @@ -3826,8 +3844,26 @@ public String getJoinString() throws Exception { if (onList != null) { boolean first = true; for (On on : onList) { - sql += (first ? " ON " : " AND ") + quote + jt + quote + "." + quote + on.getKey() + quote + " = " - + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + String rt = on.getRelateType(); + if (StringUtil.isEmpty(rt, false)) { + sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + " = " + + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + } + else if ("{}".equals(rt)) { + sql += (first ? ON : AND) + "json_contains(" + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + // + ", concat('\\'', " + quote + jt + quote + "." + quote + on.getKey() + quote + ", '\\''), '$')"; + + ", cast(" + quote + jt + quote + "." + quote + on.getKey() + quote + " AS CHAR), '$')"; + } + else if ("<>".equals(rt)) { + sql += (first ? ON : AND) + "json_contains(" + quote + jt + quote + "." + quote + on.getKey() + quote + // + ", concat('\\'', " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + ", '\\''), '$')"; + + ", cast(" + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + " AS CHAR), '$')"; + } + else { + throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath() + + " 中 JOIN ON 条件关联类型 " + rt + " 不合法!只支持 =, {}, <> 这几种!"); + } + first = false; } } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 51705dca4..13edbfc26 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -23,7 +23,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -537,7 +536,8 @@ else if (config.isClickHouse() && (sqlTable.startsWith("`") || sqlTable.startsWi if (onList != null) { for (On on : onList) { if (on != null) { - viceConfig.putWhere(on.getKey(), item.get(on.getTargetKey()), true); + String ok = on.getOriginKey(); + viceConfig.putWhere(ok.substring(0, ok.length() - 1), item.get(on.getTargetKey()), true); } } } @@ -743,6 +743,8 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map //缓存到 childMap if (onList != null) { for (On on : onList) { + String ok = on.getOriginKey(); + String vk = ok.substring(0, ok.length() - 1); cc.putWhere(on.getKey(), result.get(on.getKey()), true); } } From 0dc96b4681a74ebeb35435ac131f5a2978a5e4ed Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 7 Mar 2022 00:48:54 +0800 Subject: [PATCH 023/619] =?UTF-8?q?JOIN=20ON=20=E6=96=B0=E5=A2=9E=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=AF=94=E8=BE=83=E8=BF=90=E7=AE=97=E7=AC=A6=20>,=20=3D,=20<=3D=20=E5=92=8C=E5=AD=97=E7=AC=A6=E5=8C=B9?= =?UTF-8?q?=E9=85=8D=20$=20LIKE,=20~=20REGEXP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/SQL.java | 11 +- .../java/apijson/orm/AbstractSQLConfig.java | 203 +++++++++++++----- .../java/apijson/orm/AbstractSQLExecutor.java | 1 + .../src/main/java/apijson/orm/Join.java | 60 +++++- .../src/main/java/apijson/orm/Logic.java | 5 + 5 files changed, 219 insertions(+), 61 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/SQL.java b/APIJSONORM/src/main/java/apijson/SQL.java index e8b7bda6f..4f2a1ccb4 100755 --- a/APIJSONORM/src/main/java/apijson/SQL.java +++ b/APIJSONORM/src/main/java/apijson/SQL.java @@ -388,7 +388,16 @@ public static String search(String s, int type, boolean ignoreCase) { return "%" + s + "%"; } } - + //search>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + public static boolean isBooleanOrNumber(String type) { + type = StringUtil.toUpperCase(type, true); + return type.isEmpty() || (type.endsWith("INT") && type.endsWith("POINT") == false) + || type.endsWith("BOOLEAN") || type.endsWith("ENUM") + || type.endsWith("FLOAT") || type.endsWith("DOUBLE") || type.endsWith("DECIMAL"); + } + + } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 94b65a484..9dfc7d9eb 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -430,6 +430,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { SQL_FUNCTION_MAP.put("nullif", ""); // NULLIF(expr1, expr2) 比较两个字符串,如果字符串 expr1 与 expr2 相等 返回 NULL,否则返回 expr1 SQL_FUNCTION_MAP.put("group_concat", ""); // GROUP_CONCAT([DISTINCT], s1, s2...) 聚合拼接字符串 SQL_FUNCTION_MAP.put("match", ""); // MATCH (name,tag) AGAINST ('a b' IN NATURAL LANGUAGE MODE) 全文检索 + SQL_FUNCTION_MAP.put("any_value", ""); // any_value(userId) 解决 ONLY_FULL_GROUP_BY 报错 @@ -3439,7 +3440,7 @@ else if (isOracle()) { condition += (condition + "has(JSONExtractArrayRaw(assumeNotNull(" + getKey(column) + "))" + ", " + getValue(key, column, v) + (StringUtil.isEmpty(path, true) ? "" : ", " + getValue(key, column, path)) + ")"); } else { - condition += ("json_contains(" + getKey(column) + ", " + getValue(key, column, v) + (StringUtil.isEmpty(path, true) ? "" : ", " + getValue(key, column, path)) + ")"); + condition += ("json_contains(" + getKey(column) + ", " + getValue(key, column, v) + (StringUtil.isEmpty(path, true) ? "" : ", " + getValue(key, column, path)) + ")"); } } } @@ -3490,7 +3491,17 @@ public String getSubqueryString(Subquery subquery) throws Exception { * @return */ public static String getCondition(boolean not, String condition) { - return not ? NOT + "(" + condition + ")" : condition; + return getCondition(not, condition, false); + } + /**拼接条件 + * @param not + * @param condition + * @param outerBreaket + * @return + */ + public static String getCondition(boolean not, String condition, boolean addOuterBracket) { + String s = not ? NOT + "(" + condition + ")" : condition; + return addOuterBracket ? "( " + s + " )" : s; } @@ -3800,32 +3811,7 @@ public String getJoinString() throws Exception { jc.setMain(true).setKeyPrefix(false); sql = ( "<".equals(type) ? " LEFT" : (">".equals(type) ? " RIGHT" : " CROSS") ) + " JOIN ( " + jc.getSQL(isPrepared()) + " ) AS " + quote + jt + quote; - - if (onList != null) { - boolean first = true; - for (On on : onList) { - String rt = on.getRelateType(); - if (StringUtil.isEmpty(rt, false)) { - sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + " = " - + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; - } - else if ("{}".equals(rt)) { - sql += (first ? ON : AND) + "json_contains(" + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote - // + ", concat('\\'', " + quote + jt + quote + "." + quote + on.getKey() + quote + ", '\\''), '$')"; - + ", cast(" + quote + jt + quote + "." + quote + on.getKey() + quote + " AS CHAR), '$')"; - } - else if ("<>".equals(rt)) { - sql += (first ? ON : AND) + "json_contains(" + quote + jt + quote + "." + quote + on.getKey() + quote - // + ", concat('\\'', " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + ", '\\''), '$')"; - + ", cast(" + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + " AS CHAR), '$')"; - } - else { - throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath() - + " 中 JOIN ON 条件关联类型 " + rt + " 不合法!只支持 =, {}, <> 这几种!"); - } - first = false; - } - } + sql = concatJoinOn(sql, quote, j, jt, onList); jc.setMain(false).setKeyPrefix(true); @@ -3841,32 +3827,7 @@ else if ("<>".equals(rt)) { case "(": // ANTI JOIN: A & ! B case ")": // FOREIGN JOIN: B & ! A sql = " INNER JOIN " + jc.getTablePath(); - if (onList != null) { - boolean first = true; - for (On on : onList) { - String rt = on.getRelateType(); - if (StringUtil.isEmpty(rt, false)) { - sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + " = " - + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; - } - else if ("{}".equals(rt)) { - sql += (first ? ON : AND) + "json_contains(" + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote - // + ", concat('\\'', " + quote + jt + quote + "." + quote + on.getKey() + quote + ", '\\''), '$')"; - + ", cast(" + quote + jt + quote + "." + quote + on.getKey() + quote + " AS CHAR), '$')"; - } - else if ("<>".equals(rt)) { - sql += (first ? ON : AND) + "json_contains(" + quote + jt + quote + "." + quote + on.getKey() + quote - // + ", concat('\\'', " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + ", '\\''), '$')"; - + ", cast(" + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + " AS CHAR), '$')"; - } - else { - throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath() - + " 中 JOIN ON 条件关联类型 " + rt + " 不合法!只支持 =, {}, <> 这几种!"); - } - - first = false; - } - } + sql = concatJoinOn(sql, quote, j, jt, onList); break; default: throw new UnsupportedOperationException( @@ -3902,6 +3863,140 @@ else if ("<>".equals(rt)) { return StringUtil.isEmpty(joinOns, true) ? "" : joinOns + " \n"; } + + protected String concatJoinOn(@NotNull String sql, @NotNull String quote, @NotNull Join j, @NotNull String jt, List onList) { + if (onList != null) { + boolean first = true; + for (On on : onList) { + Logic logic = on.getLogic(); + boolean isNot = logic == null ? false : logic.isNot(); + if (isNot) { + onJoinNotRelation(sql, quote, j, jt, onList, on); + } + + String rt = on.getRelateType(); + if (StringUtil.isEmpty(rt, false)) { + sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + (isNot ? " != " : " = ") + + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + } + else { + onJoinComplextRelation(sql, quote, j, jt, onList, on); + + if (">=".equals(rt) || "<=".equals(rt) || ">".equals(rt) || "<".equals(rt)) { + if (isNot) { + throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath() + + " 中 JOIN ON 条件关联逻辑符 " + rt + " 不合法! >, <, >=, <= 不支持与或非逻辑符 & | ! !"); + } + + sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + " " + rt + " " + + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + } + else if ("$".equals(rt)) { + sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + (isNot ? NOT : "") + + " LIKE concat('%', " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + ", '%')"; + } + else if (rt.endsWith("~")) { + boolean ignoreCase = "*~".equals(rt); + if (isPostgreSQL()) { + sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + (isNot ? NOT : "") + + " ~" + (ignoreCase ? "* " : " ") + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + } + else if (isOracle()) { + sql += (first ? ON : AND) + "regexp_like(" + quote + jt + quote + "." + quote + on.getKey() + quote + + ", " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + (ignoreCase ? ", 'i'" : ", 'c'") + ")"; + } + else if (isClickHouse()) { + sql += (first ? ON : AND) + "match(" + (ignoreCase ? "lower(" : "") + quote + jt + quote + "." + quote + on.getKey() + quote + (ignoreCase ? ")" : "") + + ", " + (ignoreCase ? "lower(" : "") + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + (ignoreCase ? ")" : "") + ")"; + } + else if (isHive()) { + sql += (first ? ON : AND) + (ignoreCase ? "lower(" : "") + quote + jt + quote + "." + quote + on.getKey() + quote + (ignoreCase ? ")" : "") + + " REGEXP " + (ignoreCase ? "lower(" : "") + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + (ignoreCase ? ")" : ""); + } + else { + sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + (isNot ? NOT : "") + + " REGEXP " + (ignoreCase ? "" : "BINARY ") + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + } + } + else if ("{}".equals(rt) || "<>".equals(rt)) { + String tt = on.getTargetTable(); + String ta = on.getTargetAlias(); + + Map cast = null; + if (tt.equals(getTable()) && ((ta == null && getAlias() == null) || ta.equals(getAlias()))) { + cast = getCast(); + } + else { + boolean find = false; + for (Join jn : joinList) { + if (tt.equals(jn.getTable()) && ((ta == null && jn.getAlias() == null) || ta.equals(jn.getAlias()))) { + cast = getCast(); + find = true; + break; + } + } + + if (find == false) { + throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath() + + " 中 JOIN ON 条件中找不到对应的 " + rt + " 不合法!只支持 =, {}, <> 这几种!"); + } + } + + boolean isBoolOrNum = SQL.isBooleanOrNumber(cast == null ? null : cast.get(on.getTargetKey())); + + String arrKeyPath; + String itemKeyPath; + if ("{}".equals(rt)) { + arrKeyPath = quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + itemKeyPath = quote + jt + quote + "." + quote + on.getKey() + quote; + } + else { + arrKeyPath = quote + jt + quote + "." + quote + on.getKey() + quote; + itemKeyPath = quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + } + + if (isPostgreSQL()) { //operator does not exist: jsonb @> character varying "[" + c + "]"); + sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath + + " IS NOT NULL AND " + arrKeyPath + " @> " + itemKeyPath) + (isNot ? ") " : ""); + } + else if (isOracle()) { + sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath + + " IS NOT NULL AND json_textcontains(" + arrKeyPath + + ", '$', " + itemKeyPath + ")") + (isNot ? ") " : ""); + } + else if (isClickHouse()) { + sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath + + " IS NOT NULL AND has(JSONExtractArrayRaw(assumeNotNull(" + arrKeyPath + "))" + + ", " + itemKeyPath + ")") + (isNot ? ") " : ""); + } + else { + sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath + + " IS NOT NULL AND json_contains(" + arrKeyPath + + (isBoolOrNum ? ", cast(" + itemKeyPath + " AS CHAR), '$')" + : ", concat('\"', " + itemKeyPath + ", '\"'), '$')" + ) + ) + (isNot ? ") " : ""); + } + } + else { + throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath() + + " 中 JOIN ON 条件关联类型 " + rt + " 不合法!只支持 =, >, <, >=, <=, !=, $, ~, {}, <> 这几种!"); + } + } + + first = false; + } + } + + return sql; + } + + protected void onJoinNotRelation(String sql, String quote, Join j, String jt, List onList, On on) { +// throw new UnsupportedOperationException("JOIN 已禁用 '!' 非逻辑连接符 !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); + } + protected void onJoinComplextRelation(String sql, String quote, Join j, String jt, List onList, On on) { +// throw new UnsupportedOperationException("JOIN 已禁用 {} 和 <> 等复杂关联 !性能很差、需求极少,默认只允许等价关联,如要取消禁用可在后端重写相关方法!"); + } protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { throw new UnsupportedOperationException("已禁用 * CROSS JOIN !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 13edbfc26..bd5c0cf00 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -745,6 +745,7 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map for (On on : onList) { String ok = on.getOriginKey(); String vk = ok.substring(0, ok.length() - 1); + //TODO 兼容复杂关联 cc.putWhere(on.getKey(), result.get(on.getKey()), true); } } diff --git a/APIJSONORM/src/main/java/apijson/orm/Join.java b/APIJSONORM/src/main/java/apijson/orm/Join.java index 24ad36ec8..bd10ec8d7 100644 --- a/APIJSONORM/src/main/java/apijson/orm/Join.java +++ b/APIJSONORM/src/main/java/apijson/orm/Join.java @@ -10,6 +10,7 @@ import com.alibaba.fastjson.JSONObject; import apijson.NotNull; +import apijson.StringUtil; /**连表 配置 * @author Lemon @@ -163,7 +164,8 @@ public static class On { private String originKey; private String originValue; - private String relateType; // "" - 一对一, "{}" - 一对多, "<>" - 多对一 + private Logic logic; // & | ! + private String relateType; // "" - 一对一, "{}" - 一对多, "<>" - 多对一, > , <= , != private String key; // id private String targetTable; // Moment private String targetAlias; // main @@ -183,6 +185,12 @@ public void setOriginValue(String originValue) { } + public Logic getLogic() { + return logic; + } + public void setLogic(Logic logic) { + this.logic = logic; + } public String getRelateType() { return relateType; } @@ -190,7 +198,6 @@ public void setRelateType(String relateType) { this.relateType = relateType; } - public String getKey() { return key; } @@ -222,22 +229,63 @@ public void setKeyAndType(String joinType, String table, @NotNull String originK originKey = originKey.substring(0, originKey.length() - 1); } else { //TODO 暂时只允许 User.id = Moment.userId 字段关联,不允许 User.id = 82001 这种 - throw new IllegalArgumentException(joinType + "/.../" + table + "/" + originKey + " 不合法!join:'.../refKey'" + " 中 refKey 必须以 @ 结尾!"); + throw new IllegalArgumentException(joinType + "/.../" + table + "/" + originKey + " 中字符 " + originKey + " 不合法!join:'.../refKey'" + " 中 refKey 必须以 @ 结尾!"); } + String k; + if (originKey.endsWith("{}")) { setRelateType("{}"); - setKey(originKey.substring(0, originKey.length() - 2)); + k = originKey.substring(0, originKey.length() - 2); } else if (originKey.endsWith("<>")) { setRelateType("<>"); - setKey(originKey.substring(0, originKey.length() - 2)); + k = originKey.substring(0, originKey.length() - 2); + } + else if (originKey.endsWith("$")) { + setRelateType("$"); + k = originKey.substring(0, originKey.length() - 1); + } + else if (originKey.endsWith("~")) { + boolean ignoreCase = originKey.endsWith("*~"); + setRelateType(ignoreCase ? "*~" : "~"); + k = originKey.substring(0, originKey.length() - (ignoreCase ? 2 : 1)); + } + else if (originKey.endsWith(">=")) { + setRelateType(">="); + k = originKey.substring(0, originKey.length() - 2); + } + else if (originKey.endsWith("<=")) { + setRelateType("<="); + k = originKey.substring(0, originKey.length() - 2); + } + else if (originKey.endsWith(">")) { + setRelateType(">"); + k = originKey.substring(0, originKey.length() - 1); + } + else if (originKey.endsWith("<")) { + setRelateType("<"); + k = originKey.substring(0, originKey.length() - 1); } else { setRelateType(""); - setKey(originKey); + k = originKey; } + + if (k != null && (k.contains("&") || k.contains("|"))) { + throw new UnsupportedOperationException(joinType + "/.../" + table + "/" + originKey + " 中字符 " + k + " 不合法!与或非逻辑符仅支持 '!' 非逻辑符 !"); + } + + Logic l = new Logic(k); + setLogic(l); + + if (StringUtil.isName(l.getKey()) == false) { + throw new IllegalArgumentException(joinType + "/.../" + table + "/" + originKey + " 中字符 " + l.getKey() + " 不合法!必须符合字段命名格式!"); + } + + setKey(l.getKey()); } + } diff --git a/APIJSONORM/src/main/java/apijson/orm/Logic.java b/APIJSONORM/src/main/java/apijson/orm/Logic.java index b795ae961..cfc08d016 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Logic.java +++ b/APIJSONORM/src/main/java/apijson/orm/Logic.java @@ -30,6 +30,7 @@ public class Logic { private int type; private String key; + private String originKey; public Logic() { super(); @@ -40,6 +41,7 @@ public Logic(int type) { this.type = type; } public Logic(String key) { + this.originKey = key; key = StringUtil.getString(key); int type = getType(key.isEmpty() ? "" : key.substring(key.length() - 1)); @@ -71,6 +73,9 @@ public String getKey() { public void setKey(String key) { this.key = key; } + public String getOriginKey() { + return originKey; + } public boolean isOr() { From 895917ba987e656d7866dd2f9a35b218e43758fb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 7 Mar 2022 00:51:24 +0800 Subject: [PATCH 024/619] =?UTF-8?q?JOIN=20=E9=BB=98=E8=AE=A4=E7=A6=81?= =?UTF-8?q?=E7=94=A8=20!=20=E9=9D=9E=E9=80=BB=E8=BE=91=E7=AC=A6=E5=92=8C?= =?UTF-8?q?=E5=A4=8D=E6=9D=82=E5=85=B3=E8=81=94=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 9dfc7d9eb..8dfcc425e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -3992,10 +3992,10 @@ else if (isClickHouse()) { } protected void onJoinNotRelation(String sql, String quote, Join j, String jt, List onList, On on) { -// throw new UnsupportedOperationException("JOIN 已禁用 '!' 非逻辑连接符 !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); + throw new UnsupportedOperationException("JOIN 已禁用 '!' 非逻辑连接符 !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); } protected void onJoinComplextRelation(String sql, String quote, Join j, String jt, List onList, On on) { -// throw new UnsupportedOperationException("JOIN 已禁用 {} 和 <> 等复杂关联 !性能很差、需求极少,默认只允许等价关联,如要取消禁用可在后端重写相关方法!"); + throw new UnsupportedOperationException("JOIN 已禁用 $, ~, {}, <>, >, <, >=, <= 等复杂关联 !性能很差、需求极少,默认只允许等价关联,如要取消禁用可在后端重写相关方法!"); } protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { throw new UnsupportedOperationException("已禁用 * CROSS JOIN !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); From 3c8058ee27113963c56aba968c9815d9e32affd9 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 13 Mar 2022 15:52:29 +0800 Subject: [PATCH 025/619] Update README.md --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8e8f72995..e0d4110cf 100644 --- a/README.md +++ b/README.md @@ -155,22 +155,21 @@ https://www.bilibili.com/video/BV1yv411p7Y4 前后端 关于接口的 开发、文档、联调 等 10 大痛点解析
https://github.com/Tencent/APIJSON/wiki -* **解决十大痛点** (APIJSON 可帮助用户 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等) -* **开发提速很大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) +* **解决十大痛点** (可帮前后端开发大幅提振开发效率、强力杜绝联调扯皮、巧妙规避文档缺陷、非常节省流量带宽等) +* **开发提速很大** (CRUD 零代码热更新全自动,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * **社区影响力大** (GitHub 1W+ Star 在 350W Java 项目中排名前 120,远超 FLAG, BAT 等国内外绝大部分开源项目) * **多样用户案例** (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等) * **适用场景广泛** (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) * **周边生态丰富** (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) * **文档视频齐全** (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) -* **功能丰富强大** (增删改查、分页排序、分组聚合、各种 JOIN、各种子查询、跨库跨表、性能分析 等零代码实现) -* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动控制权限、自动校验参数、自动防SQL注入等) +* **功能丰富强大** (增删改查、分页排序、分组聚合、各种条件、各种 JOIN、各种子查询、跨库连表 等零代码实现) +* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动控制权限、自动校验参数、自动防 SQL 注入等) * **灵活定制业务** (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) * **高质可靠代码** (代码严谨规范,商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 Bug 率低至 0.15%) * **兼容各种项目** (协议不限 HTTP,与其它库无冲突,对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的示例) * **工程轻量小巧** (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1) -* **多年持续迭代** (自 2016 年开源至今已连续维护 5 年,累计 2000+ Commits、80+ Releases,不断更新迭代中...) - +* **多年持续迭代** (自 2016 年开源至今已连续维护 5 年多,累计 2000+ Commits、80+ Releases,不断更新迭代中...) ### 常见问题 #### 1.如何定制业务逻辑? From 29d8d1ef1f0848bb49dd73a2dc7d5fcf5daa97e5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 13 Mar 2022 21:50:57 +0800 Subject: [PATCH 026/619] =?UTF-8?q?JOIN=20ON=20=E5=8F=8A=E6=99=AE=E9=80=9A?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81=E5=9C=A8?= =?UTF-8?q?=20key$:value=20=E7=9A=84=20key=20=E4=B8=AD=E5=AE=9A=E5=88=B6?= =?UTF-8?q?=E5=8D=A0=E4=BD=8D=E7=AC=A6=20%,=20=5F=20=E4=B8=8E=20value=20?= =?UTF-8?q?=E7=9A=84=E6=8B=BC=E6=8E=A5=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 80 +++++++++++++++++-- .../src/main/java/apijson/orm/Join.java | 27 ++++++- 2 files changed, 99 insertions(+), 8 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 8dfcc425e..9e4a328bd 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -3891,9 +3891,55 @@ protected String concatJoinOn(@NotNull String sql, @NotNull String quote, @NotNu sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + " " + rt + " " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; } - else if ("$".equals(rt)) { - sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + (isNot ? NOT : "") - + " LIKE concat('%', " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + ", '%')"; + else if (rt.endsWith("$")) { + String t = rt.substring(0, rt.length() - 1); + char r = t.isEmpty() ? 0 : t.charAt(t.length() - 1); + + char l; + if (r == '%' || r == '_' || r == '?') { + t = t.substring(0, t.length() - 1); + + if (t.isEmpty()) { + if (r == '?') { + throw new IllegalArgumentException(on.getOriginKey() + ":value 中字符 " + on.getOriginKey() + " 不合法!key$:value 中不允许只有单独的 '?',必须和 '%', '_' 之一配合使用 !"); + } + + l = r; + } + else { + l = t.charAt(t.length() - 1); + if (l == '%' || l == '_' || l == '?') { + if (l == r) { + throw new IllegalArgumentException(on.getOriginKey() + ":value 中字符 " + t + " 不合法!key$:value 中不允许 key 中有连续相同的占位符!"); + } + + t = t.substring(0, t.length() - 1); + } + else if (l > 0 && StringUtil.isName(String.valueOf(l))) { + l = r; + } + } + + if (l == '?') { + l = 0; + } + if (r == '?') { + r = 0; + } + } + else { + l = r = 0; + } + + if (l <= 0 && r <= 0) { + sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + (isNot ? NOT : "") + + " LIKE " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + } + else { + sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + (isNot ? NOT : "") + + (l <= 0 ? " LIKE concat(" : " LIKE concat('" + l + "', ") + quote + on.getTargetTable() + quote + + "." + quote + on.getTargetKey() + quote + (r <= 0 ? ")" : ", '" + r + "')"); + } } else if (rt.endsWith("~")) { boolean ignoreCase = "*~".equals(rt); @@ -3992,10 +4038,10 @@ else if (isClickHouse()) { } protected void onJoinNotRelation(String sql, String quote, Join j, String jt, List onList, On on) { - throw new UnsupportedOperationException("JOIN 已禁用 '!' 非逻辑连接符 !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); +// throw new UnsupportedOperationException("JOIN 已禁用 '!' 非逻辑连接符 !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); } protected void onJoinComplextRelation(String sql, String quote, Join j, String jt, List onList, On on) { - throw new UnsupportedOperationException("JOIN 已禁用 $, ~, {}, <>, >, <, >=, <= 等复杂关联 !性能很差、需求极少,默认只允许等价关联,如要取消禁用可在后端重写相关方法!"); +// throw new UnsupportedOperationException("JOIN 已禁用 $, ~, {}, <>, >, <, >=, <= 等复杂关联 !性能很差、需求极少,默认只允许等价关联,如要取消禁用可在后端重写相关方法!"); } protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { throw new UnsupportedOperationException("已禁用 * CROSS JOIN !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); @@ -4599,7 +4645,27 @@ public static String getRealKey(RequestMethod method, String originKey String key = new String(originKey); if (key.endsWith("$")) {//搜索 LIKE,查询时处理 - key = key.substring(0, key.length() - 1); + String k = key.substring(0, key.length() - 1); + // key%$:"a" -> key LIKE '%a%'; key?%$:"a" -> key LIKE 'a%'; key_?$:"a" -> key LIKE '_a'; key_%$:"a" -> key LIKE '_a%' + char c = k.isEmpty() ? 0 : k.charAt(k.length() - 1); + + if (c == '%' || c == '_' || c == '?') { + k = k.substring(0, k.length() - 1); + + char c2 = k.isEmpty() ? 0 : k.charAt(k.length() - 1); + if (c2 == '%' || c2 == '_' || c2 == '?') { + if (c2 == c) { + throw new IllegalArgumentException(originKey + ":value 中字符 " + k + " 不合法!key$:value 中不允许 key 中有连续相同的占位符!"); + } + + k = k.substring(0, k.length() - 1); + } + else if (c == '?') { + throw new IllegalArgumentException(originKey + ":value 中字符 " + originKey + " 不合法!key$:value 中不允许只有单独的 '?',必须和 '%', '_' 之一配合使用 !"); + } + } + + key = k; } else if (key.endsWith("~")) {//匹配正则表达式 REGEXP,查询时处理 key = key.substring(0, key.length() - 1); @@ -4648,6 +4714,8 @@ else if (key.endsWith("-")) {//缩减,PUT查询时处理 } } + //TODO if (key.endsWith("-")) { // 表示 key 和 value 顺序反过来: value LIKE key + String last = null;//不用Logic优化代码,否则 key 可能变为 key| 导致 key=value 变成 key|=value 而出错 if (RequestMethod.isQueryMethod(method)) {//逻辑运算符仅供GET,HEAD方法使用 last = key.isEmpty() ? "" : key.substring(key.length() - 1); diff --git a/APIJSONORM/src/main/java/apijson/orm/Join.java b/APIJSONORM/src/main/java/apijson/orm/Join.java index bd10ec8d7..4fb34b0a9 100644 --- a/APIJSONORM/src/main/java/apijson/orm/Join.java +++ b/APIJSONORM/src/main/java/apijson/orm/Join.java @@ -242,9 +242,30 @@ else if (originKey.endsWith("<>")) { setRelateType("<>"); k = originKey.substring(0, originKey.length() - 2); } - else if (originKey.endsWith("$")) { - setRelateType("$"); + else if (originKey.endsWith("$")) { // key%$:"a" -> key LIKE '%a%'; key?%$:"a" -> key LIKE 'a%'; key_?$:"a" -> key LIKE '_a'; key_%$:"a" -> key LIKE '_a%' k = originKey.substring(0, originKey.length() - 1); + char c = k.isEmpty() ? 0 : k.charAt(k.length() - 1); + + String t = "$"; + if (c == '%' || c == '_' || c == '?') { + t = c + t; + k = k.substring(0, k.length() - 1); + + char c2 = k.isEmpty() ? 0 : k.charAt(k.length() - 1); + if (c2 == '%' || c2 == '_' || c2 == '?') { + if (c2 == c) { + throw new IllegalArgumentException(originKey + ":value 中字符 " + k + " 不合法!key$:value 中不允许 key 中有连续相同的占位符!"); + } + + t = c2 + t; + k = k.substring(0, k.length() - 1); + } + else if (c == '?') { + throw new IllegalArgumentException(originKey + ":value 中字符 " + originKey + " 不合法!key$:value 中不允许只有单独的 '?',必须和 '%', '_' 之一配合使用 !"); + } + } + + setRelateType(t); } else if (originKey.endsWith("~")) { boolean ignoreCase = originKey.endsWith("*~"); @@ -276,6 +297,8 @@ else if (originKey.endsWith("<")) { throw new UnsupportedOperationException(joinType + "/.../" + table + "/" + originKey + " 中字符 " + k + " 不合法!与或非逻辑符仅支持 '!' 非逻辑符 !"); } + //TODO if (c3 == '-') { // 表示 key 和 value 顺序反过来: value LIKE key + Logic l = new Logic(k); setLogic(l); From 96ee9dd23c5b0fdec988c377fdcc3f260718b3f7 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 13 Mar 2022 23:52:29 +0800 Subject: [PATCH 027/619] =?UTF-8?q?LIKE:=20=E6=94=AF=E6=8C=81=E9=9D=9E=20J?= =?UTF-8?q?OIN=20ON=20=E5=BC=95=E7=94=A8=E8=B5=8B=E5=80=BC=E4=B9=9F?= =?UTF-8?q?=E8=83=BD=E7=94=A8=20key%$:value=20=E6=A0=BC=E5=BC=8F=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E4=B8=94=E7=BB=99=20key%$:"%"=20=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E7=89=B9=E6=AE=8A=E7=AC=A6=E5=8F=B7=E8=BD=AC=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 52 +++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 9e4a328bd..5598c9685 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2982,7 +2982,7 @@ public Object getSQLValue(@NotNull Object value) { return SQL.NULL; } // return (value instanceof Number || value instanceof Boolean) && DATABASE_POSTGRESQL.equals(getDatabase()) ? value : "'" + value + "'"; - return (value instanceof Number || value instanceof Boolean) ? value : "'" + value.toString().replaceAll("'", "\\'") + "'"; //MySQL 隐式转换用不了索引 + return (value instanceof Number || value instanceof Boolean) ? value : "'" + value.toString().replaceAll("\\'", "\\\\'") + "'"; //MySQL 隐式转换用不了索引 } @Override @@ -3044,7 +3044,7 @@ public String getSearchString(String key, String column, Object[] values, int ty // throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + " 中包含 %% !不允许有连续的 % !"); // } - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getLikeString(key, column, v); + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getLikeString(key, column, (String) v); } return getCondition(Logic.isNot(type), condition); @@ -3057,7 +3057,53 @@ public String getSearchString(String key, String column, Object[] values, int ty * @return key LIKE 'value' */ @JSONField(serialize = false) - public String getLikeString(String key, String column, Object value) { + public String getLikeString(@NotNull String key, @NotNull String column, String value) { + String k = key.substring(0, key.length() - 1); + char r = k.charAt(k.length() - 1); + + char l; + if (r == '%' || r == '_' || r == '?') { + k = k.substring(0, k.length() - 1); + + l = k.charAt(k.length() - 1); + if (l == '%' || l == '_' || l == '?') { + if (l == r) { + throw new IllegalArgumentException(key + ":value 中字符 " + k + " 不合法!key$:value 中不允许 key 中有连续相同的占位符!"); + } + + k = k.substring(0, k.length() - 1); + } + else if (l > 0 && StringUtil.isName(String.valueOf(l))) { + l = r; + } + + if (l == '?') { + l = 0; + } + if (r == '?') { + r = 0; + } + } + else { + l = r = 0; + } + + if (l > 0 || r > 0) { + if (value == null) { + throw new IllegalArgumentException(key + ":value 中 value 为 null!key$:value 中 value 不能为 null,且类型必须是 String !"); + } + + value = value.replaceAll("\\\\", "\\\\\\\\"); + value = value.replaceAll("\\%", "\\\\%"); + value = value.replaceAll("\\_", "\\\\_"); + if (l > 0) { + value = l + value; + } + if (r > 0) { + value = value + r; + } + } + return getKey(column) + " LIKE " + getValue(key, column, value); } From 38c19975ea67efb07c1ea71a52b4556614e242e5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 14 Mar 2022 00:07:07 +0800 Subject: [PATCH 028/619] =?UTF-8?q?*=20CROSS=20JOIN=20=E5=85=81=E8=AE=B8?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=20JOIN=20ON=20=E5=BC=95=E7=94=A8=E8=B5=8B?= =?UTF-8?q?=E5=80=BC=E5=85=B3=E8=81=94=E6=9D=A1=E4=BB=B6=EF=BC=9B=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E7=A6=81=E7=94=A8=20JOIN=20ON=20=E5=A4=8D=E6=9D=82?= =?UTF-8?q?=E5=85=B3=E8=81=94=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractParser.java | 2 +- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 5bfa7da93..733414049 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1494,7 +1494,7 @@ else if (join != null){ } Set> refSet = refObj.entrySet(); - if (refSet.isEmpty()) { + if (refSet.isEmpty() && "*".equals(joinType) == false) { throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 alias 值 " + alias + " 不合法!" + "必须为 &/Table0, onList, On on) { -// throw new UnsupportedOperationException("JOIN 已禁用 '!' 非逻辑连接符 !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); + throw new UnsupportedOperationException("JOIN 已禁用 '!' 非逻辑连接符 !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); } protected void onJoinComplextRelation(String sql, String quote, Join j, String jt, List onList, On on) { -// throw new UnsupportedOperationException("JOIN 已禁用 $, ~, {}, <>, >, <, >=, <= 等复杂关联 !性能很差、需求极少,默认只允许等价关联,如要取消禁用可在后端重写相关方法!"); + throw new UnsupportedOperationException("JOIN 已禁用 $, ~, {}, <>, >, <, >=, <= 等复杂关联 !性能很差、需求极少,默认只允许 = 等价关联,如要取消禁用可在后端重写相关方法!"); } protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { throw new UnsupportedOperationException("已禁用 * CROSS JOIN !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); From c39cd1ec7c5b568cfbeac19e82e4688a9c26a679 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 14 Mar 2022 01:55:32 +0800 Subject: [PATCH 029/619] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=95=B0=E7=BB=84?= =?UTF-8?q?=E5=85=B3=E9=94=AE=E8=AF=8D=20compat=20=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E5=AF=B9=E8=81=9A=E5=90=88=E5=87=BD=E6=95=B0=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E9=80=9A=E8=BF=87=20query:2=20=E5=88=86=E9=A1=B5=E6=9F=A5?= =?UTF-8?q?=E6=80=BB=E6=95=B0=E8=BF=94=E5=9B=9E=E5=80=BC=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/JSONRequest.java | 2 + APIJSONORM/src/main/java/apijson/SQL.java | 33 ++++---- .../main/java/apijson/orm/AbstractParser.java | 35 +++++++- .../java/apijson/orm/AbstractSQLConfig.java | 81 +++++++++++++++---- .../src/main/java/apijson/orm/SQLConfig.java | 3 + 5 files changed, 120 insertions(+), 34 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/JSONRequest.java b/APIJSONORM/src/main/java/apijson/JSONRequest.java index 5b49f608e..8707cc2c0 100755 --- a/APIJSONORM/src/main/java/apijson/JSONRequest.java +++ b/APIJSONORM/src/main/java/apijson/JSONRequest.java @@ -87,6 +87,7 @@ public JSONRequest setFormat(Boolean format) { public static final String SUBQUERY_RANGE_ANY = "ANY"; public static final String KEY_QUERY = "query"; + public static final String KEY_COMPAT = "compat"; public static final String KEY_COUNT = "count"; public static final String KEY_PAGE = "page"; public static final String KEY_JOIN = "join"; @@ -97,6 +98,7 @@ public JSONRequest setFormat(Boolean format) { static { ARRAY_KEY_LIST = new ArrayList(); ARRAY_KEY_LIST.add(KEY_QUERY); + ARRAY_KEY_LIST.add(KEY_COMPAT); ARRAY_KEY_LIST.add(KEY_COUNT); ARRAY_KEY_LIST.add(KEY_PAGE); ARRAY_KEY_LIST.add(KEY_JOIN); diff --git a/APIJSONORM/src/main/java/apijson/SQL.java b/APIJSONORM/src/main/java/apijson/SQL.java index 4f2a1ccb4..391d5db48 100755 --- a/APIJSONORM/src/main/java/apijson/SQL.java +++ b/APIJSONORM/src/main/java/apijson/SQL.java @@ -21,7 +21,7 @@ public class SQL { public static final String IS_NOT = " IS NOT "; public static final String IS_NULL = " IS NULL "; public static final String IS_NOT_NULL = " IS NOT NULL "; - + //括号必须紧跟函数名! count (...) 报错! public static final String COUNT = "count"; public static final String SUM = "sum"; @@ -191,7 +191,7 @@ public static String indexOf(String s, String c) { public static String replace(String s, String c1, String c2) { return "replace(" + s + ", " + c1 + ", " + c2 + ")"; } - + /** * @param s1 * @param s2 @@ -225,11 +225,11 @@ public static String toLowerCase(String s) { return "lower(" + s + ")"; } - - + + //column and function<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - + /**字段 * @param column * @return column.isEmpty() ? "*" : column; @@ -245,15 +245,16 @@ public static String column(String column) { public static String columnAs(String column) { return count(column) + AS; } - + /**函数 * @param column if (StringUtil.isEmpty(column, true) || column.contains(",")) -> column = null; * @return " " + fun + "(" + {@link #column(String)} + ") "; */ public static String function(String fun, String column) { - if (StringUtil.isEmpty(column, true) || column.contains(",")) { - column = null; //解决 count(id,name) 这种多个字段导致的SQL异常 - } + // 支持 fun(col1,col2..) + // if (StringUtil.isEmpty(column, true) || column.contains(",")) { + // column = null; //解决 count(id,name) 这种多个字段导致的SQL异常 + // } return " " + fun + "(" + column(column) + ") "; } /**有别名的函数 @@ -263,7 +264,7 @@ public static String function(String fun, String column) { public static String functionAs(String fun, String column) { return function(fun, column) + AS + fun + " "; } - + /**计数 * column = null * @return {@link #count(String)} @@ -313,9 +314,9 @@ public static String avg(String column) { } //column and function>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - + + + //search<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< public static final int SEARCH_TYPE_CONTAIN_FULL = 0; @@ -391,13 +392,13 @@ public static String search(String s, int type, boolean ignoreCase) { //search>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - + public static boolean isBooleanOrNumber(String type) { type = StringUtil.toUpperCase(type, true); return type.isEmpty() || (type.endsWith("INT") && type.endsWith("POINT") == false) || type.endsWith("BOOLEAN") || type.endsWith("ENUM") || type.endsWith("FLOAT") || type.endsWith("DOUBLE") || type.endsWith("DECIMAL"); } - - + + } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 733414049..2d0f7748c 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -42,6 +42,7 @@ import apijson.Log; import apijson.NotNull; import apijson.RequestMethod; +import apijson.SQL; import apijson.StringUtil; import apijson.orm.exception.ConditionErrorException; import apijson.orm.exception.ConflictException; @@ -1051,9 +1052,33 @@ public JSONObject onObjectParse(final JSONObject request //total 这里不能用arrayConfig.getType(),因为在createObjectParser.onChildParse传到onObjectParse时已被改掉 if (type == SQLConfig.TYPE_ITEM_CHILD_0 && query != JSONRequest.QUERY_TABLE && position == 0) { - RequestMethod method = op.getMethod(); - JSONObject rp = op.setMethod(RequestMethod.HEAD).setSQLConfig().executeSQL().getSqlReponse(); - op.setMethod(method); + JSONObject rp; + Boolean compat = arrayConfig.getCompat(); + if (compat != null && compat) { + // 解决对聚合函数字段通过 query:2 分页查总数返回值错误 + // 这里可能改变了内部的一些数据,下方通过 arrayConfig 还原 + SQLConfig cfg = op.setSQLConfig(0, 0, 0).getSQLConfig(); + boolean isExplain = cfg.isExplain(); + cfg.setExplain(false); + + Subquery subq = new Subquery(); + subq.setFrom(cfg.getTable()); + subq.setConfig(cfg); + + SQLConfig countSQLCfg = createSQLConfig(); + countSQLCfg.setColumn(Arrays.asList("count(*):count")); + countSQLCfg.setFrom(subq); + + rp = executeSQL(countSQLCfg, false); + + cfg.setExplain(isExplain); + } + else { + // 对聚合函数字段通过 query:2 分页查总数返回值错误 + RequestMethod method = op.getMethod(); + rp = op.setMethod(RequestMethod.HEAD).setSQLConfig().executeSQL().getSqlReponse(); + op.setMethod(method); + } if (rp != null) { int index = parentPath.lastIndexOf("]/"); @@ -1147,6 +1172,7 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name //不能改变,因为后面可能继续用到,导致1以上都改变 []:{0:{Comment[]:{0:{Comment:{}},1:{...},...}},1:{...},...} final String query = request.getString(JSONRequest.KEY_QUERY); + final Boolean compat = request.getBoolean(JSONRequest.KEY_COMPAT); final Integer count = request.getInteger(JSONRequest.KEY_COUNT); //TODO 如果不想用默认数量可以改成 getIntValue(JSONRequest.KEY_COUNT); final Integer page = request.getInteger(JSONRequest.KEY_PAGE); final Object join = request.get(JSONRequest.KEY_JOIN); @@ -1189,6 +1215,7 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name } request.remove(JSONRequest.KEY_QUERY); + request.remove(JSONRequest.KEY_COMPAT); request.remove(JSONRequest.KEY_COUNT); request.remove(JSONRequest.KEY_PAGE); request.remove(JSONRequest.KEY_JOIN); @@ -1227,6 +1254,7 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // .setCount(size) .setPage(page2) .setQuery(query2) + .setCompat(compat) .setTable(arrTableKey) .setJoinList(onJoinParse(join, request)); @@ -1301,6 +1329,7 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // } finally { //后面还可能用到,要还原 request.put(JSONRequest.KEY_QUERY, query); + request.put(JSONRequest.KEY_COMPAT, compat); request.put(JSONRequest.KEY_COUNT, count); request.put(JSONRequest.KEY_PAGE, page); request.put(JSONRequest.KEY_JOIN, join); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 15482a889..2b5f1760f 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -104,6 +104,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { // 自定义原始 SQL 片段 Map:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL public static final Map RAW_MAP; // 允许调用的 SQL 函数:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL + public static final Map SQL_AGGREGATE_FUNCTION_MAP; public static final Map SQL_FUNCTION_MAP; @@ -242,6 +243,13 @@ public abstract class AbstractSQLConfig implements SQLConfig { + SQL_AGGREGATE_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 + SQL_AGGREGATE_FUNCTION_MAP.put("max", ""); + SQL_AGGREGATE_FUNCTION_MAP.put("min", ""); + SQL_AGGREGATE_FUNCTION_MAP.put("avg", ""); + SQL_AGGREGATE_FUNCTION_MAP.put("count", ""); + SQL_AGGREGATE_FUNCTION_MAP.put("sum", ""); + SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 //窗口函数 @@ -761,6 +769,7 @@ public String getUserIdKey() { private int page; //Table所在页码 private int position; //Table在[]中的位置 private int query; //JSONRequest.query + private Boolean compat; //JSONRequest.compat query total private int type; //ObjectParser.type private int cache; private boolean explain; @@ -1458,14 +1467,9 @@ public String getColumnString(boolean inSQLJoin) throws Exception { case HEAD: case HEADS: //StringUtil.isEmpty(column, true) || column.contains(",") 时SQL.count(column)会return "*" if (isPrepared() && column != null) { - List raw = getRaw(); boolean containRaw = raw != null && raw.contains(KEY_COLUMN); - - String origin; - String alias; - int index; - + for (String c : column) { if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 @@ -1476,9 +1480,9 @@ public String getColumnString(boolean inSQLJoin) throws Exception { } } - index = c.lastIndexOf(":"); //StringUtil.split返回数组中,子项不会有null - origin = index < 0 ? c : c.substring(0, index); - alias = index < 0 ? null : c.substring(index + 1); + int index = c.lastIndexOf(":"); //StringUtil.split返回数组中,子项不会有null + String origin = index < 0 ? c : c.substring(0, index); + String alias = index < 0 ? null : c.substring(index + 1); if (alias != null && StringUtil.isName(alias) == false) { throw new IllegalArgumentException("HEAD请求: 字符 " + alias + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" @@ -1499,8 +1503,41 @@ public String getColumnString(boolean inSQLJoin) throws Exception { } } } + + boolean onlyOne = column != null && column.size() == 1; + String c0 = onlyOne ? column.get(0) : null; + + if (onlyOne) { + int index = c0 == null ? -1 : c0.lastIndexOf(":"); + if (index > 0) { + c0 = c0.substring(0, index); + } + + int start = c0 == null ? -1 : c0.indexOf("("); + int end = start <= 0 ? -1 : c0.lastIndexOf(")"); + if (start > 0 && end > start) { + String fun = c0.substring(0, start); + + // Invalid use of group function SELECT count(max(`id`)) AS count FROM `sys`.`Comment` + if (SQL_AGGREGATE_FUNCTION_MAP.containsKey(fun)) { + String group = getGroup(); // TODO 唯一 100% 兼容的可能只有 SELECT count(*) FROM (原语句) AS table + return StringUtil.isEmpty(group, true) ? "1" : "count(DISTINCT " + group + ")"; + } + + String[] args = start == end - 1 ? null : StringUtil.split(c0.substring(start + 1, end)); + if (args == null || args.length <= 0) { + return SQL.count(c0); + } - return SQL.count(column != null && column.size() == 1 && StringUtil.isName(column.get(0)) ? getKey(column.get(0)) : "*"); + List raw = getRaw(); + boolean containRaw = raw != null && raw.contains(KEY_COLUMN); + + return SQL.count(parseColumn(c0, containRaw)); + } + } + + return SQL.count(onlyOne ? getKey(c0) : "*"); + // return SQL.count(onlyOne && StringUtil.isName(column.get(0)) ? getKey(column.get(0)) : "*"); case POST: if (column == null || column.isEmpty()) { throw new IllegalArgumentException("POST 请求必须在Table内设置要保存的 key:value !"); @@ -1997,6 +2034,16 @@ public AbstractSQLConfig setQuery(int query) { this.query = query; return this; } + @Override + public Boolean getCompat() { + return compat; + } + @Override + public AbstractSQLConfig setCompat(Boolean compat) { + this.compat = compat; + return this; + } + @Override public int getType() { return type; @@ -3753,14 +3800,13 @@ private static String getConditionString(String column, String table, AbstractSQ //根据方法不同,聚合语句不同。GROUP BY 和 HAVING 可以加在 HEAD 上, HAVING 可以加在 PUT, DELETE 上,GET 全加,POST 全都不加 String aggregation = ""; - if (RequestMethod.isGetMethod(config.getMethod(), true)){ - aggregation = config.getGroupString(true) + config.getHavingString(true) + - config.getOrderString(true); + if (RequestMethod.isGetMethod(config.getMethod(), true)) { + aggregation = config.getGroupString(true) + config.getHavingString(true) + config.getOrderString(true); } - if (RequestMethod.isHeadMethod(config.getMethod(), true)){ + if (RequestMethod.isHeadMethod(config.getMethod(), true)) { // TODO 加参数 isPagenation 判断是 GET 内分页 query:2 查总数,不用加这些条件 aggregation = config.getGroupString(true) + config.getHavingString(true) ; } - if (config.getMethod() == PUT || config.getMethod() == DELETE){ + if (config.getMethod() == PUT || config.getMethod() == DELETE) { aggregation = config.getHavingString(true) ; } @@ -3825,6 +3871,8 @@ public String getJoinString() throws Exception { // 主表不用别名 String ta; for (Join j : joinList) { + onGetJoinString(j); + if (j.isAppJoin()) { // APP JOIN,只是作为一个标记,执行完主表的查询后自动执行副表的查询 User.id IN($commentIdList) continue; } @@ -4089,6 +4137,9 @@ protected void onJoinNotRelation(String sql, String quote, Join j, String jt, Li protected void onJoinComplextRelation(String sql, String quote, Join j, String jt, List onList, On on) { throw new UnsupportedOperationException("JOIN 已禁用 $, ~, {}, <>, >, <, >=, <= 等复杂关联 !性能很差、需求极少,默认只允许 = 等价关联,如要取消禁用可在后端重写相关方法!"); } + + protected void onGetJoinString(Join j) throws UnsupportedOperationException { + } protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { throw new UnsupportedOperationException("已禁用 * CROSS JOIN !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); } diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index 259788770..51a8df36b 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -98,6 +98,9 @@ public interface SQLConfig { int getQuery(); SQLConfig setQuery(int query); + Boolean getCompat(); + SQLConfig setCompat(Boolean compat); + int getPosition(); SQLConfig setPosition(int position); From fca75602b01a8deb5b3a88f6e39e96ba9806c296 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 14 Mar 2022 01:59:47 +0800 Subject: [PATCH 030/619] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=85=B3=E4=BA=8E?= =?UTF-8?q?=E5=88=86=E9=A1=B5=E6=9F=A5=E6=80=BB=E6=95=B0=E6=97=B6=20@colum?= =?UTF-8?q?n:"max(id)"=20=E8=BF=99=E7=A7=8D=E8=81=9A=E5=90=88=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E7=9A=84=E4=BC=98=E5=8C=96=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractParser.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 2d0f7748c..5e49d5c02 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1052,6 +1052,8 @@ public JSONObject onObjectParse(final JSONObject request //total 这里不能用arrayConfig.getType(),因为在createObjectParser.onChildParse传到onObjectParse时已被改掉 if (type == SQLConfig.TYPE_ITEM_CHILD_0 && query != JSONRequest.QUERY_TABLE && position == 0) { + //TODO 应在这里判断 @column 中是否有聚合函数,而不是 AbstractSQLConfig.getColumnString + JSONObject rp; Boolean compat = arrayConfig.getCompat(); if (compat != null && compat) { From a3d9c90a8dc2ba2949fa824f08087ea67390952d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 21 Mar 2022 00:26:22 +0800 Subject: [PATCH 031/619] =?UTF-8?q?=E5=8C=85=E5=90=AB=E9=80=89=E9=A1=B9?= =?UTF-8?q?=E8=8C=83=E5=9B=B4=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81=E4=BC=A0?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=EF=BC=8C=E4=BE=8B=E5=A6=82=20key<>:{=20path:?= =?UTF-8?q?=20"$",=20value:82001=20}?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractObjectParser.java | 2 +- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 590e016dc..003aee026 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -255,7 +255,7 @@ public AbstractObjectParser parse(String name, boolean isReuse) throws Exception key = entry.getKey(); try { - if (key.startsWith("@") || key.endsWith("@")) { + if (key.startsWith("@") || key.endsWith("@") || (key.endsWith("<>") && value instanceof JSONObject)) { if (onParse(key, value) == false) { invalidate(); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 2b5f1760f..8cd68bacd 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -4487,8 +4487,8 @@ else if (w.startsWith("!")) { for (String key : set) { value = request.get(key); - if (value instanceof Map) {//只允许常规Object - throw new IllegalArgumentException("不允许 " + key + " 等任何key的value类型为 {JSONObject} !"); + if (key.endsWith("<>") == false && value instanceof Map) {//只允许常规Object + throw new IllegalArgumentException(table + ":{ " + key + ":value } 中 value 类型错误!除了 key<>:{} 外,不允许 " + key + " 等其它任何 key 对应 value 的类型为 JSONObject {} !"); } //解决AccessVerifier新增userId没有作为条件,而是作为内容,导致PUT,DELETE出错 From 9776408d63bc1d768cdd97d910f6b2243b2a94a8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 21 Mar 2022 00:26:48 +0800 Subject: [PATCH 032/619] =?UTF-8?q?@having=20=E6=94=AF=E6=8C=81=E5=A4=8D?= =?UTF-8?q?=E6=9D=82=E6=9D=A1=E4=BB=B6=E7=BB=84=E5=90=88=EF=BC=8C=E4=B8=94?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20@having&=20=E7=AE=80=E5=8C=96=20AND=20?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E7=9A=84=E5=86=99=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/JSONObject.java | 11 +- .../main/java/apijson/orm/AbstractParser.java | 9 +- .../java/apijson/orm/AbstractSQLConfig.java | 749 +++++++++++------- .../src/main/java/apijson/orm/SQLConfig.java | 7 +- 4 files changed, 468 insertions(+), 308 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/JSONObject.java b/APIJSONORM/src/main/java/apijson/JSONObject.java index 8000e231b..6825a02de 100755 --- a/APIJSONORM/src/main/java/apijson/JSONObject.java +++ b/APIJSONORM/src/main/java/apijson/JSONObject.java @@ -147,6 +147,7 @@ public JSONObject setUserIdIn(List list) { public static final String KEY_COMBINE = "@combine"; //条件组合,每个条件key前面可以放&,|,!逻辑关系 "id!{},&sex,!name&$" public static final String KEY_GROUP = "@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_RAW = "@raw"; // 自定义原始 SQL 片段 public static final String KEY_JSON = "@json"; //SQL Server 把字段转为 JSON 输出 @@ -167,6 +168,7 @@ public JSONObject setUserIdIn(List list) { TABLE_KEY_LIST.add(KEY_COMBINE); TABLE_KEY_LIST.add(KEY_GROUP); TABLE_KEY_LIST.add(KEY_HAVING); + TABLE_KEY_LIST.add(KEY_HAVING_AND); TABLE_KEY_LIST.add(KEY_ORDER); TABLE_KEY_LIST.add(KEY_RAW); TABLE_KEY_LIST.add(KEY_JSON); @@ -350,7 +352,14 @@ public JSONObject setHaving(String... keys) { * @return */ 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 diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 5e49d5c02..6e407144e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1063,13 +1063,13 @@ public JSONObject onObjectParse(final JSONObject request boolean isExplain = cfg.isExplain(); cfg.setExplain(false); - Subquery subq = new Subquery(); - subq.setFrom(cfg.getTable()); - subq.setConfig(cfg); + Subquery subqy = new Subquery(); + subqy.setFrom(cfg.getTable()); + subqy.setConfig(cfg); SQLConfig countSQLCfg = createSQLConfig(); countSQLCfg.setColumn(Arrays.asList("count(*):count")); - countSQLCfg.setFrom(subq); + countSQLCfg.setFrom(subqy); rp = executeSQL(countSQLCfg, false); @@ -1358,6 +1358,7 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COMBINE); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_GROUP); 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_RAW); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 8cd68bacd..c9c64b61e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -15,6 +15,7 @@ import static apijson.JSONObject.KEY_FROM; import static apijson.JSONObject.KEY_GROUP; import static apijson.JSONObject.KEY_HAVING; +import static apijson.JSONObject.KEY_HAVING_AND; import static apijson.JSONObject.KEY_ID; import static apijson.JSONObject.KEY_JSON; import static apijson.JSONObject.KEY_NULL; @@ -32,8 +33,8 @@ import static apijson.RequestMethod.PUT; import static apijson.SQL.AND; import static apijson.SQL.NOT; -import static apijson.SQL.OR; import static apijson.SQL.ON; +import static apijson.SQL.OR; import java.util.ArrayList; import java.util.Arrays; @@ -80,8 +81,20 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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_COMBINE_DEPTH = 2; public static int MAX_COMBINE_COUNT = 5; public static int MAX_COMBINE_KEY_COUNT = 2; public static float MAX_COMBINE_RATIO = 1.0f; @@ -750,7 +763,8 @@ public String getUserIdKey() { private String table; //表名 private String alias; //表别名 private String group; //分组方式的字符串数组,','分隔 - private String having; //聚合函数的字符串数组,','分隔 + private String havingCombine; //聚合函数的字符串数组,','分隔 + private Map having; //聚合函数的字符串数组,','分隔 private String order; //排序方式的字符串数组,','分隔 private List raw; //需要保留原始 SQL 的字段,','分隔 private List json; //需要转为 JSON 的字段,','分隔 @@ -1101,24 +1115,36 @@ public String getGroupString(boolean hasPrefix) { return (hasPrefix ? " GROUP BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinGroup, ", "); } + + @Override + public String getHavingCombine() { + return havingCombine; + } + @Override + public SQLConfig setHavingCombine(String havingCombine) { + this.havingCombine = havingCombine; + return this; + } @Override - public String getHaving() { + public Map getHaving() { return having; } - public AbstractSQLConfig setHaving(String... conditions) { - return setHaving(StringUtil.getString(conditions)); - } @Override - public AbstractSQLConfig setHaving(String having) { + public SQLConfig setHaving(Map having) { this.having = having; return this; } + public AbstractSQLConfig setHaving(String... conditions) { + return setHaving(StringUtil.getString(conditions)); + } + /**TODO @having 改为默认 | 或连接,且支持 @having: { "key1>": 1, "key{}": "length(key2)>0", "@combine": "key1,key2" } * @return HAVING conditoin0 AND condition1 OR condition2 ... + * @throws Exception */ @JSONField(serialize = false) - public String getHavingString(boolean hasPrefix) { + public String getHavingString(boolean hasPrefix) throws Exception { //加上子表的 having String joinHaving = ""; if (joinList != null) { @@ -1146,122 +1172,120 @@ public String getHavingString(boolean hasPrefix) { } } - String[] keys = StringUtil.split(getHaving(), ";"); - if (keys == null || keys.length <= 0) { + Map map = getHaving(); + Set> set = map == null ? null : map.entrySet(); + if (set == null || set.isEmpty()) { return StringUtil.isEmpty(joinHaving, true) ? "" : (hasPrefix ? " HAVING " : "") + joinHaving; } - String quote = getQuote(); - String tableAlias = getAliasWithQuote(); - List 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); - - String expression; - String method; - //暂时不允许 String prefix; - String suffix; + + // 直接把 having 类型从 Map 定改为 Map,避免额外拷贝 + // Map newMap = new LinkedHashMap<>(map.size()); + // for (Entry entry : set) { + // newMap.put(entry.getKey(), entry.getValue()); + // } //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); - //fun(arg0,arg1,...) - expression = keys[i]; - if (containRaw) { - try { - String rawSQL = getRawSQL(KEY_HAVING, expression); - if (rawSQL != null) { - keys[i] = rawSQL; - continue; - } - } catch (Exception e) { - Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { " - + " String rawSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, fk); ... " - + "} catch (Exception e) = " + e.getMessage()); + 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,...) + if (containRaw) { + try { + String rawSQL = getRawSQL(KEY_HAVING, expression); + if (rawSQL != null) { + return rawSQL; } + } catch (Exception e) { + Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { " + + " String rawSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, fk); ... " + + "} catch (Exception e) = " + e.getMessage()); } + } - if (expression.length() > 50) { - throw new UnsupportedOperationException("@having:value 的 value 中字符串 " + expression + " 不合法!" - + "不允许传超过 50 个字符的函数或表达式!请用 @raw 简化传参!"); - } + if (expression.length() > 100) { + throw new UnsupportedOperationException("@having:value 的 value 中字符串 " + expression + " 不合法!" + + "不允许传超过 100 个字符的函数或表达式!请用 @raw 简化传参!"); + } - int start = expression.indexOf("("); - if (start < 0) { - if (isPrepared() && PATTERN_FUNCTION.matcher(expression).matches() == false) { - throw new UnsupportedOperationException("字符串 " + expression + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 column?value 必须符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许空格!"); - } - continue; + int start = expression.indexOf("("); + if (start < 0) { + if (isPrepared() && PATTERN_FUNCTION.matcher(expression).matches() == false) { + throw new UnsupportedOperationException("字符串 " + expression + " 不合法!" + + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中 column?value 必须符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许空格!"); } + return expression; + } - int end = expression.lastIndexOf(")"); - if (start >= end) { - throw new IllegalArgumentException("字符 " + expression + " 不合法!" - + "@having:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); - } + int end = expression.lastIndexOf(")"); + if (start >= end) { + throw new IllegalArgumentException("字符 " + expression + " 不合法!" + + "@having:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); + } - method = expression.substring(0, start); - if (method.isEmpty() == false) { - if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { - if (StringUtil.isName(method) == false) { - throw new IllegalArgumentException("字符 " + method + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); - } - } - else if (SQL_FUNCTION_MAP.containsKey(method) == false) { + String method = expression.substring(0, start); + if (method.isEmpty() == false) { + if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { + if (StringUtil.isName(method) == false) { throw new IllegalArgumentException("字符 " + method + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); + + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); } } - - suffix = expression.substring(end + 1, expression.length()); - - if (isPrepared() && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { - throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); + else if (SQL_FUNCTION_MAP.containsKey(method) == false) { + throw new IllegalArgumentException("字符 " + method + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); } + } + + String suffix = expression.substring(end + 1, expression.length()); - String[] ckeys = StringUtil.split(expression.substring(start + 1, end)); + if (isPrepared() && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { + throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" + + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); + } - if (ckeys != null) { - for (int j = 0; j < ckeys.length; j++) { - String origin = ckeys[j]; + String[] ckeys = StringUtil.split(expression.substring(start + 1, end)); - if (isPrepared()) { - if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { - throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中所有 column, arg 都必须是1个不以 _ 开头的单词 或者 符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许多余的空格!"); - } - } + if (ckeys != null) { + for (int j = 0; j < ckeys.length; j++) { + String origin = ckeys[j]; - //JOIN 副表不再在外层加副表名前缀 userId AS `Commet.userId`, 而是直接 userId AS `userId` - boolean isName = false; - if (StringUtil.isNumer(origin)) { - //do nothing - } - else if (StringUtil.isName(origin)) { - origin = quote + origin + quote; - isName = true; - } - else { - origin = getValue(origin).toString(); + if (isPrepared()) { + if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { + throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" + + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中所有 column, arg 都必须是1个不以 _ 开头的单词 或者 符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许多余的空格!"); } + } - ckeys[j] = (isName && isKeyPrefix() ? tableAlias + "." : "") + origin; + //JOIN 副表不再在外层加副表名前缀 userId AS `Commet.userId`, 而是直接 userId AS `userId` + boolean isName = false; + if (StringUtil.isNumer(origin)) { + //do nothing + } + else if (StringUtil.isName(origin)) { + origin = quote + origin + quote; + isName = true; + } + else { + origin = getValue(origin).toString(); } - } - keys[i] = method + "(" + StringUtil.getString(ckeys) + ")" + suffix; + ckeys[j] = (isName && isKeyPrefix() ? alias + "." : "") + origin; + } } - //TODO 支持 OR, NOT 参考 @combine:"&key0,|key1,!key2" - return (hasPrefix ? " HAVING " : "") + StringUtil.concat(StringUtil.getString(keys, AND), joinHaving, AND); + return method + "(" + StringUtil.getString(ckeys) + ")" + suffix; } @Override @@ -2193,6 +2217,9 @@ public SQLConfig setCast(Map cast) { //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + protected int getMaxHavingCount() { + return MAX_HAVING_COUNT; + } protected int getMaxWhereCount() { return MAX_WHERE_COUNT; } @@ -2392,260 +2419,274 @@ public String getWhereString(boolean hasPrefix) throws Exception { */ @JSONField(serialize = false) public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, String combine, List 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 conditioinMap, String combine, boolean verifyName, boolean containRaw, boolean isHaving) throws Exception { + + String errPrefix = table + (isHaving ? ":{ @having:{ " : ":{ ") + "@combine:'" + combine + (isHaving ? "' } }" : "' }"); String s = StringUtil.getString(combine); if (s.startsWith(" ") || s.endsWith(" ") ) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s + "' 不合法!不允许首尾有空格,也不允许连续空格!空格不能多也不能少!" + "逻辑连接符 & | 左右必须各一个相邻空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!"); } - if (where == null) { - where = new HashMap<>(); + if (conditioinMap == null) { + conditioinMap = new HashMap<>(); } - int whereSize = where.size(); + int size = conditioinMap.size(); - int maxWhereCount = getMaxWhereCount(); - if (maxWhereCount > 0 && whereSize > maxWhereCount) { - throw new IllegalArgumentException(table + ":{ key0:value0, key1:value1... } 中条件 key:value 数量 " + whereSize - + " 已超过最大数量,必须在 0-" + maxWhereCount + " 内!"); + int maxCount = isHaving ? getMaxHavingCount() : getMaxWhereCount(); + if (maxCount > 0 && size > maxCount) { + throw new IllegalArgumentException(table + (isHaving ? ":{ @having:{ " : ":{ ") + "key0:value0, key1:value1... " + combine + + (isHaving ? " } }" : " }") + " 中条件 key:value 数量 " + size + " 已超过最大数量,必须在 0-" + maxCount + " 内!"); } - - String whereString = ""; - - int maxDepth = getMaxCombineDepth(); - int maxCombineCount = getMaxCombineCount(); - int maxCombineKeyCount = getMaxCombineKeyCount(); - float maxCombineRatio = getMaxCombineRatio(); + String result = ""; + List prepreadValues = getPreparedValueList(); - setPreparedValueList(new ArrayList<>()); - - int depth = 0; - int allCount = 0; + Map usedKeyCountMap = new HashMap<>(size); + int n = s.length(); - int i = 0; - - char lastLogic = 0; - char last = 0; - boolean first = true; - boolean isNot = false; - - String key = ""; - Map usedKeyCountMap = new HashMap<>(whereSize); - while (i <= n) { // "date> | (contactIdList<> & (name*~ | tag&$))" - boolean isOver = i >= n; - char c = isOver ? 0 : s.charAt(i); - boolean isBlankOrRightParenthesis = c == ' ' || c == ')'; - if (isOver || isBlankOrRightParenthesis) { - boolean isEmpty = StringUtil.isEmpty(key, true); - if (isEmpty && last != ')') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + (isOver ? s : s.substring(i)) - + "' 不合法!" + (c == ' ' ? "空格 ' ' " : "右括号 ')'") + " 左边缺少条件 key !逻辑连接符 & | 左右必须各一个相邻空格!" - + "空格不能多也不能少!不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); - } - - if (isEmpty == false) { - if (first == false && lastLogic <= 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 " - + "'" + s.substring(i - key.length() - (isOver ? 1 : 0)) + "' 不合法!左边缺少 & | 其中一个逻辑连接符!"); - } - - allCount ++; - if (allCount > maxCombineCount && maxCombineCount > 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!" - + "其中 key 数量 " + allCount + " 已超过最大值,必须在条件键值对数量 0-" + maxCombineCount + " 内!"); - } - if (1.0f*allCount/whereSize > maxCombineRatio && maxCombineRatio > 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!" - + "其中 key 数量 " + allCount + " / 条件键值对数量 " + whereSize + " = " + (1.0f*allCount/whereSize) - + " 已超过 最大倍数,必须在条件键值对数量 0-" + maxCombineRatio + " 倍内!"); - } - - String column = key; - - Object value = where.get(column); - if (value == null) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + key - + "' 对应的条件键值对 " + column + ":value 不存在!"); + if (n > 0) { + setPreparedValueList(new ArrayList<>()); + + int maxDepth = getMaxCombineDepth(); + int maxCombineCount = getMaxCombineCount(); + int maxCombineKeyCount = getMaxCombineKeyCount(); + float maxCombineRatio = getMaxCombineRatio(); + + int depth = 0; + int allCount = 0; + + int i = 0; + + char lastLogic = 0; + char last = 0; + boolean first = true; + boolean isNot = false; + + String key = ""; + while (i <= n) { // "date> | (contactIdList<> & (name*~ | tag&$))" + boolean isOver = i >= n; + char c = isOver ? 0 : s.charAt(i); + boolean isBlankOrRightParenthesis = c == ' ' || c == ')'; + if (isOver || isBlankOrRightParenthesis) { + boolean isEmpty = StringUtil.isEmpty(key, true); + if (isEmpty && last != ')') { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + (isOver ? s : s.substring(i)) + + "' 不合法!" + (c == ' ' ? "空格 ' ' " : "右括号 ')'") + " 左边缺少条件 key !逻辑连接符 & | 左右必须各一个相邻空格!" + + "空格不能多也不能少!不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); } - - String wi = getWhereItem(column, value, method, verifyName); - if (StringUtil.isEmpty(wi, true)) { // 转成 1=1 ? - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + key - + "' 对应的 " + column + ":value 不是有效条件键值对!"); + + if (isEmpty == false) { + if (first == false && lastLogic <= 0) { + throw new IllegalArgumentException(errPrefix + " 中字符 " + + "'" + s.substring(i - key.length() - (isOver ? 1 : 0)) + "' 不合法!左边缺少 & | 其中一个逻辑连接符!"); + } + + allCount ++; + if (allCount > maxCombineCount && maxCombineCount > 0) { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s + "' 不合法!" + + "其中 key 数量 " + allCount + " 已超过最大值,必须在条件键值对数量 0-" + maxCombineCount + " 内!"); + } + if (1.0f*allCount/size > maxCombineRatio && maxCombineRatio > 0) { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s + "' 不合法!" + + "其中 key 数量 " + allCount + " / 条件键值对数量 " + size + " = " + (1.0f*allCount/size) + + " 已超过 最大倍数,必须在条件键值对数量 0-" + maxCombineRatio + " 倍内!"); + } + + String column = key; + + Object value = conditioinMap.get(column); + if (value == null) { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + key + + "' 对应的条件键值对 " + column + ":value 不存在!"); + } + + String wi = isHaving ? getHavingItem(quote, table, alias, column, (String) value, containRaw) : getWhereItem(column, value, method, verifyName); + if (StringUtil.isEmpty(wi, true)) { // 转成 1=1 ? + throw new IllegalArgumentException(errPrefix + " 中字符 '" + key + + "' 对应的 " + column + ":value 不是有效条件键值对!"); + } + + Integer count = usedKeyCountMap.get(column); + count = count == null ? 1 : count + 1; + if (count > maxCombineKeyCount && maxCombineKeyCount > 0) { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s + "' 不合法!" + + "其中 '" + column + "' 重复引用,次数 " + count + " 已超过最大值,必须在 0-" + maxCombineKeyCount + " 内!"); + } + usedKeyCountMap.put(column, count); + + result += "( " + getCondition(isNot, wi) + " )"; + isNot = false; + first = false; } - - Integer count = usedKeyCountMap.get(column); - count = count == null ? 1 : count + 1; - if (count > maxCombineKeyCount && maxCombineKeyCount > 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!" - + "其中 '" + column + "' 重复引用,次数 " + count + " 已超过最大值,必须在 0-" + maxCombineKeyCount + " 内!"); + + key = ""; + lastLogic = 0; + + if (isOver) { + break; } - usedKeyCountMap.put(column, count); - - whereString += "( " + getCondition(isNot, wi) + " )"; - isNot = false; - first = false; } - - key = ""; - lastLogic = 0; - - if (isOver) { - break; + + if (c == ' ') { } - } - - if (c == ' ') { - } - else if (c == '&') { - if (last == ' ') { - if (i >= n - 1 || s.charAt(i + 1) != ' ') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + (i >= n - 1 ? s : s.substring(0, i + 1)) - + "' 不合法!逻辑连接符 & 右边缺少一个空格 !逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" - + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); + else if (c == '&') { + if (last == ' ') { + if (i >= n - 1 || s.charAt(i + 1) != ' ') { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + (i >= n - 1 ? s : s.substring(0, i + 1)) + + "' 不合法!逻辑连接符 & 右边缺少一个空格 !逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); + } + + result += SQL.AND; + lastLogic = c; + i ++; + } + else { + key += c; } - - whereString += SQL.AND; - lastLogic = c; - i ++; - } - else { - key += c; } - } - else if (c == '|') { - if (last == ' ') { - if (i >= n - 1 || s.charAt(i + 1) != ' ') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + (i >= n - 1 ? s : s.substring(0, i + 1)) - + "' 不合法!逻辑连接符 | 右边缺少一个空格 !逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" - + "不允许首尾有空格,也不允许连续空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!"); + else if (c == '|') { + if (last == ' ') { + if (i >= n - 1 || s.charAt(i + 1) != ' ') { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + (i >= n - 1 ? s : s.substring(0, i + 1)) + + "' 不合法!逻辑连接符 | 右边缺少一个空格 !逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + + "不允许首尾有空格,也不允许连续空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!"); + } + + result += SQL.OR; + lastLogic = c; + i ++; + } + else { + key += c; } - - whereString += SQL.OR; - lastLogic = c; - i ++; - } - else { - key += c; } - } - else if (c == '!') { - last = i <= 0 ? 0 : s.charAt(i - 1); // & | 后面跳过了空格 - - char next = i >= n - 1 ? 0 : s.charAt(i + 1); - if (last == ' ' || last == '(') { - if (next == ' ') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) - + "' 不合法!非逻辑符 '!' 右边多了一个空格 ' ' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); + else if (c == '!') { + last = i <= 0 ? 0 : s.charAt(i - 1); // & | 后面跳过了空格 + + char next = i >= n - 1 ? 0 : s.charAt(i + 1); + if (last == ' ' || last == '(') { + if (next == ' ') { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(0, i + 1) + + "' 不合法!非逻辑符 '!' 右边多了一个空格 ' ' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); + } + if (next == ')' || next == '&' || next == '!') { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(0, i + 1) + + "' 不合法!非逻辑符 '!' 右边多了一个字符 '" + next + "' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); + } + if (i > 0 && lastLogic <= 0 && last != '(') { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(i) + + "' 不合法!左边缺少 & | 逻辑连接符!逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); + } + } + + if (next == '(') { + result += SQL.NOT; + lastLogic = c; } - if (next == ')' || next == '&' || next == '!') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) - + "' 不合法!非逻辑符 '!' 右边多了一个字符 '" + next + "' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); + else if (last <= 0 || last == ' ' || last == '(') { + isNot = true; + // lastLogic = c; } - if (i > 0 && lastLogic <= 0 && last != '(') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) + else { + key += c; + } + } + else if (c == '(') { + if (key.isEmpty() == false || (i > 0 && lastLogic <= 0 && last != '(')) { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(i) + "' 不合法!左边缺少 & | 逻辑连接符!逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); } + + depth ++; + if (depth > maxDepth && maxDepth > 0) { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(0, i + 1) + + "' 不合法!括号 (()) 嵌套层级 " + depth + " 已超过最大值,必须在 0-" + maxDepth + " 内!"); + } + + result += c; + lastLogic = 0; + first = true; } - - if (next == '(') { - whereString += SQL.NOT; - lastLogic = c; - } - else if (last <= 0 || last == ' ' || last == '(') { - isNot = true; -// lastLogic = c; - } + else if (c == ')') { + depth --; + if (depth < 0) { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(0, i + 1) + + "' 不合法!左括号 ( 比 右括号 ) 少!数量必须相等从而完整闭合 (...) !"); + } + + result += c; + lastLogic = 0; + } else { key += c; } + + last = c; + i ++; } - else if (c == '(') { - if (key.isEmpty() == false || (i > 0 && lastLogic <= 0 && last != '(')) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) - + "' 不合法!左边缺少 & | 逻辑连接符!逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" - + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); - } - - depth ++; - if (depth > maxDepth && maxDepth > 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) - + "' 不合法!括号 (()) 嵌套层级 " + depth + " 已超过最大值,必须在 0-" + maxDepth + " 内!"); - } - - whereString += c; - lastLogic = 0; - first = true; - } - else if (c == ')') { - depth --; - if (depth < 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) - + "' 不合法!左括号 ( 比 右括号 ) 少!数量必须相等从而完整闭合 (...) !"); - } - - whereString += c; - lastLogic = 0; - } - else { - key += c; + + if (depth != 0) { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s + + "' 不合法!左括号 ( 比 右括号 ) 多!数量必须相等从而完整闭合 (...) !"); } - - last = c; - i ++; } - if (depth != 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s - + "' 不合法!左括号 ( 比 右括号 ) 多!数量必须相等从而完整闭合 (...) !"); - } - - Set> set = where.entrySet(); + Set> set = conditioinMap.entrySet(); - String andWhere = ""; + String andCond = ""; boolean isItemFirst = true; for (Entry entry : set) { - key = entry == null ? null : entry.getKey(); + String key = entry == null ? null : entry.getKey(); if (key == null || usedKeyCountMap.containsKey(key)) { 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条件连接错误 continue; } - andWhere += (isItemFirst ? "" : AND) + "(" + wi + ")"; + andCond += (isItemFirst ? "" : AND) + "(" + wi + ")"; isItemFirst = false; } - if (StringUtil.isEmpty(whereString, true)) { - whereString = andWhere; - } - else if (StringUtil.isNotEmpty(andWhere, true)) { // andWhere 必须放后面,否则 prepared 值顺序错误 -// whereString = "( " + whereString + " )" + AND + andWhere; - - whereString = andWhere + AND + "( " + whereString + " )"; // 先暂存之前的 prepared 值,然后反向整合 - prepreadValues.addAll(getPreparedValueList()); - setPreparedValueList(prepreadValues); + if (StringUtil.isEmpty(result, true)) { + result = andCond; + } + else if (StringUtil.isNotEmpty(andCond, true)) { // andWhere 必须放后面,否则 prepared 值顺序错误 + // result = "( " + result + " )" + AND + andCond; + result = andCond + AND + "( " + result + " )"; // 先暂存之前的 prepared 值,然后反向整合 + if (n > 0) { + prepreadValues.addAll(getPreparedValueList()); + 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; } - public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, Map> combine, List joinList, boolean verifyName) throws Exception { Set>> combineSet = combine == null ? null : combine.entrySet(); if (combineSet == null || combineSet.isEmpty()) { @@ -4269,7 +4310,8 @@ else if (id instanceof Subquery) {} String cast = request.getString(KEY_CAST); String combine = request.getString(KEY_COMBINE); 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 raw = request.getString(KEY_RAW); String json = request.getString(KEY_JSON); @@ -4292,24 +4334,29 @@ else if (id instanceof Subquery) {} request.remove(KEY_COMBINE); request.remove(KEY_GROUP); request.remove(KEY_HAVING); + request.remove(KEY_HAVING_AND); request.remove(KEY_ORDER); request.remove(KEY_RAW); request.remove(KEY_JSON); + + // @null <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< String[] nullKeys = StringUtil.split(nulls); if (nullKeys != null && nullKeys.length > 0) { for (String nk : nullKeys) { if (StringUtil.isEmpty(nk, true)) { - throw new IllegalArgumentException(table + ":{} 里的 @null: value 中的字符 '" + nk + "' 不合法!不允许为空!"); + throw new IllegalArgumentException(table + ":{ @null: value } 中的字符 '" + nk + "' 不合法!不允许为空!"); } 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); } } + // @null >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + // @cast <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< String[] casts = StringUtil.split(cast); Map castMap = null; if (casts != null && casts.length > 0) { @@ -4330,8 +4377,9 @@ else if (id instanceof Subquery) {} castMap.put(p.getKey(), p.getValue()); } } + // @cast >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - + String[] rawArr = StringUtil.split(raw); config.setRaw(rawArr == null || rawArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(rawArr))); @@ -4523,8 +4571,15 @@ else if (whereList != null && whereList.contains(key)) { List cs = new ArrayList<>(); List 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; if (containColumnRaw) { try { @@ -4572,6 +4627,96 @@ else if (whereList != null && whereList.contains(key)) { } } + + // @having, @haivng& <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + Object newHaving = having; + boolean isHavingAnd = false; + + Map 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> havingSet = havingObj.entrySet(); + for (Entry 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.setCache(getCache(cache)); @@ -4587,7 +4732,8 @@ else if (whereList != null && whereList.contains(key)) { config.setCast(castMap); config.setWhere(tableWhere); config.setGroup(group); - config.setHaving(having); + config.setHaving(havingMap); + config.setHavingCombine(havingCombine); config.setOrder(order); String[] jsons = StringUtil.split(json); @@ -4614,6 +4760,7 @@ else if (whereList != null && whereList.contains(key)) { request.put(KEY_COMBINE, combine); request.put(KEY_GROUP, group); request.put(KEY_HAVING, having); + request.put(KEY_HAVING_AND, havingAnd); request.put(KEY_ORDER, order); request.put(KEY_RAW, raw); request.put(KEY_JSON, json); diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index 51a8df36b..22acf1461 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -159,8 +159,11 @@ public interface SQLConfig { String getGroup(); SQLConfig setGroup(String group); - String getHaving(); - SQLConfig setHaving(String having); + Map getHaving(); + SQLConfig setHaving(Map having); + + String getHavingCombine(); + SQLConfig setHavingCombine(String havingCombine); String getOrder(); SQLConfig setOrder(String order); From 12738bfb6bafb4bdcd91afd58016119f37bd5598 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 21 Mar 2022 00:46:20 +0800 Subject: [PATCH 033/619] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20JOIN=20ON=20?= =?UTF-8?q?=E4=B8=AD=E7=94=A8=20@combine=20=E7=AD=89=E6=83=85=E5=86=B5?= =?UTF-8?q?=E4=B8=8B=E9=A2=84=E7=BC=96=E8=AF=91=E5=80=BC=E4=B8=8E=20SQL=20?= =?UTF-8?q?=E4=B8=AD=20=3F=20=E5=8D=A0=E4=BD=8D=E7=AC=A6=E9=A1=BA=E5=BA=8F?= =?UTF-8?q?=E5=AF=B9=E4=B8=8D=E4=B8=8A=E5=AF=BC=E8=87=B4=E7=9A=84=E5=BC=82?= =?UTF-8?q?=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index c9c64b61e..fd9631c2e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2463,8 +2463,10 @@ protected String parseCombineExpression(RequestMethod method, String quote, Stri int n = s.length(); if (n > 0) { - setPreparedValueList(new ArrayList<>()); - + if (isHaving == false) { + setPreparedValueList(new ArrayList<>()); // 必须反过来,否则 JOIN ON 内部 @combine 拼接后顺序错误 + } + int maxDepth = getMaxCombineDepth(); int maxCombineCount = getMaxCombineCount(); int maxCombineKeyCount = getMaxCombineKeyCount(); @@ -2675,13 +2677,17 @@ else if (c == ')') { if (StringUtil.isEmpty(result, true)) { result = andCond; } - else if (StringUtil.isNotEmpty(andCond, true)) { // andWhere 必须放后面,否则 prepared 值顺序错误 - // result = "( " + result + " )" + AND + andCond; - result = andCond + AND + "( " + result + " )"; // 先暂存之前的 prepared 值,然后反向整合 - if (n > 0) { - prepreadValues.addAll(getPreparedValueList()); - setPreparedValueList(prepreadValues); - } + else if (StringUtil.isNotEmpty(andCond, true)) { // andCond 必须放后面,否则 prepared 值顺序错误 + if (isHaving) { // HAVING 前 WHERE 已经有条件 ? 占位,不能反过来,想优化 AND 连接在最前,需要多遍历一次内部的 key,也可以 newSQLConfig 时存到 andList + result = "( " + result + " )" + AND + andCond; + } + else { + result = andCond + AND + "( " + result + " )"; // 先暂存之前的 prepared 值,然后反向整合 + if (n > 0) { + prepreadValues.addAll(getPreparedValueList()); + setPreparedValueList(prepreadValues); + } + } } return result; From be92b894b5beb6f2b1cdd8ffe71f9fbef11ae79a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 26 Mar 2022 16:26:27 +0800 Subject: [PATCH 034/619] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e0d4110cf..1aca30303 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON -

零代码、热更新、全自动 ORM 库
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构

+

零代码、全自动、强安全 ORM 库
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构

From b248c698887728bd826febd77c4404dd1faecf06 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 26 Mar 2022 23:56:23 +0800 Subject: [PATCH 035/619] =?UTF-8?q?=E4=BC=98=E5=8C=96=20@combine=20?= =?UTF-8?q?=E5=AF=B9=E5=BA=94=E7=9A=84=E4=BB=A3=E7=A0=81=EF=BC=8C=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=20combine=20=E4=B8=BA=E9=80=BB=E8=BE=91=E8=BF=90?= =?UTF-8?q?=E7=AE=97=E6=A8=A1=E6=9D=BF=EF=BC=8C=E5=8E=9F=E6=9D=A5=E7=9A=84?= =?UTF-8?q?=20combine=20=E9=87=8D=E5=91=BD=E5=90=8D=E4=B8=BA=20combineMap?= =?UTF-8?q?=EF=BC=8CcombineExpression=20=E9=87=8D=E5=91=BD=E5=90=8D?= =?UTF-8?q?=E4=B8=BA=20combine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 83 ++++++++++++------- .../java/apijson/orm/AbstractSQLExecutor.java | 2 +- .../src/main/java/apijson/orm/SQLConfig.java | 32 +++---- 3 files changed, 70 insertions(+), 47 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index fd9631c2e..17aebb59a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -775,8 +775,8 @@ public String getUserIdKey() { private Map cast; private Map content; //Request内容,key:value形式,column = content.keySet(),values = content.values() private Map where; //筛选条件,key:value形式 - private Map> combine; //条件组合,{ "&":[key], "|":[key], "!":[key] } - private String combineExpression; + private String combine; //条件组合, a | (b & c & !(d | !e)) + private Map> combineMap; //条件组合,{ "&":[key], "|":[key], "!":[key] } //array item <<<<<<<<<< private int count; //Table数量 @@ -2238,31 +2238,31 @@ protected float getMaxCombineRatio() { @Override - public String getCombineExpression() { - return combineExpression; + public String getCombine() { + return combine; } @Override - public AbstractSQLConfig setCombineExpression(String combineExpression) { - this.combineExpression = combineExpression; + public AbstractSQLConfig setCombine(String combine) { + this.combine = combine; return this; } @NotNull @Override - public Map> getCombine() { - List andList = combine == null ? null : combine.get("&"); + public Map> getCombineMap() { + List andList = combineMap == null ? null : combineMap.get("&"); if (andList == null) { andList = where == null ? new ArrayList() : new ArrayList(where.keySet()); - if (combine == null) { - combine = new HashMap<>(); + if (combineMap == null) { + combineMap = new HashMap<>(); } - combine.put("&", andList); + combineMap.put("&", andList); } - return combine; + return combineMap; } @Override - public AbstractSQLConfig setCombine(Map> combine) { - this.combine = combine; + public AbstractSQLConfig setCombineMap(Map> combineMap) { + this.combineMap = combineMap; return this; } @@ -2329,8 +2329,8 @@ public AbstractSQLConfig putWhere(String key, Object value, boolean prior) { where.put(key, value); } - combine = getCombine(); - List andList = combine.get("&"); + Map> combineMap = getCombineMap(); + List andList = combineMap.get("&"); if (value == null) { if (andList != null) { andList.remove(key); @@ -2392,7 +2392,7 @@ else if (key.equals(userIdInKey)) { andList.add(key); //AbstractSQLExecutor.onPutColumn里getSQL,要保证缓存的SQL和查询的SQL里 where 的 key:value 顺序一致 } } - combine.put("&", andList); + combineMap.put("&", andList); } return this; @@ -2405,11 +2405,11 @@ else if (key.equals(userIdInKey)) { @JSONField(serialize = false) @Override public String getWhereString(boolean hasPrefix) throws Exception { - String ce = getCombineExpression(); - if (StringUtil.isEmpty(ce, true)) { - return getWhereString(hasPrefix, getMethod(), getWhere(), getCombine(), getJoinList(), ! isTest()); + String combineExpr = getCombine(); + if (StringUtil.isEmpty(combineExpr, true)) { + return getWhereString(hasPrefix, getMethod(), getWhere(), getCombineMap(), getJoinList(), ! isTest()); } - return getWhereString(hasPrefix, getMethod(), getWhere(), ce, getJoinList(), ! isTest()); + return getWhereString(hasPrefix, getMethod(), getWhere(), combineExpr, getJoinList(), ! isTest()); } /**获取WHERE * @param method @@ -2432,7 +2432,19 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map conditioinMap, String combine, boolean verifyName, boolean containRaw, boolean isHaving) throws Exception { @@ -2693,6 +2705,17 @@ else if (StringUtil.isNotEmpty(andCond, true)) { // andCond 必须放后面, return result; } + /**已废弃,最快 6.0 删除,请尽快把前端/客户端 @combine:"a,b" 这种旧方式改为 @combine:"a | b" 这种新方式 + * @param hasPrefix + * @param method + * @param where + * @param combine + * @param joinList + * @param verifyName + * @return + * @throws Exception + */ + @Deprecated public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, Map> combine, List joinList, boolean verifyName) throws Exception { Set>> combineSet = combine == null ? null : combine.entrySet(); if (combineSet == null || combineSet.isEmpty()) { @@ -4437,9 +4460,9 @@ else if (id instanceof Subquery) {} List whereList = null; String[] ws = StringUtil.split(combine); - String combineExpression = ws == null || ws.length != 1 ? null : ws[0]; + String combineExpr = ws == null || ws.length != 1 ? null : ws[0]; - Map> combineMap = StringUtil.isNotEmpty(combineExpression, true) ? null : new LinkedHashMap<>(); + Map> combineMap = StringUtil.isNotEmpty(combineExpr, true) ? null : new LinkedHashMap<>(); List andList = combineMap == null ? null : new ArrayList<>(); List orList = combineMap == null ? null : new ArrayList<>(); List notList = combineMap == null ? null : new ArrayList<>(); @@ -4459,14 +4482,14 @@ else if (id instanceof Subquery) {} } if (combineMap == null) { - if (StringUtil.isNotEmpty(combineExpression, true)) { + if (StringUtil.isNotEmpty(combineExpr, true)) { List banKeyList = Arrays.asList(idKey, idInKey, userIdKey, userIdInKey); for (String key : banKeyList) { - int index = combineExpression.indexOf(key); + int index = combineExpr.indexOf(key); if (index >= 0) { - char left = index <= 0 ? ' ' : combineExpression.charAt(index - 1); - char right = index >= combineExpression.length() - key.length() ? ' ' : combineExpression.charAt(index + key.length()); + char left = index <= 0 ? ' ' : combineExpr.charAt(index - 1); + char right = index >= combineExpr.length() - key.length() ? ' ' : combineExpr.charAt(index + key.length()); if ((left == ' ' || left == '(' || left == '&' || left == '|' || left == '!') && (right == ' ' || right == ')')) { throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的 value 里 " + key + " 不合法!" + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); @@ -4567,8 +4590,8 @@ else if (whereList != null && whereList.contains(key)) { combineMap.put("|", orList); combineMap.put("!", notList); } - config.setCombine(combineMap); - config.setCombineExpression(combineExpression); + config.setCombineMap(combineMap); + config.setCombine(combineExpr); config.setContent(tableContent); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index bd5c0cf00..c99f1e5eb 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -335,7 +335,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws capacity = config.getCount() <= 0 ? Parser.MAX_QUERY_COUNT : config.getCount(); if (capacity > 100) { // 有条件,条件越多过滤数据越多 - Map> combine = config.getCombine(); + Map> combine = config.getCombineMap(); List andList = combine == null ? null : combine.get("&"); int andCondCount = andList == null ? (config.getWhere() == null ? 0 : config.getWhere().size()) : andList.size(); diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index 22acf1461..43561fdd8 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -156,18 +156,6 @@ public interface SQLConfig { List getRaw(); SQLConfig setRaw(List raw); - String getGroup(); - SQLConfig setGroup(String group); - - Map getHaving(); - SQLConfig setHaving(Map having); - - String getHavingCombine(); - SQLConfig setHavingCombine(String havingCombine); - - String getOrder(); - SQLConfig setOrder(String order); - Subquery getFrom(); SQLConfig setFrom(Subquery from); @@ -180,11 +168,11 @@ public interface SQLConfig { Map getContent(); SQLConfig setContent(Map content); - Map> getCombine(); - SQLConfig setCombine(Map> combine); + Map> getCombineMap(); + SQLConfig setCombineMap(Map> combineMap); - String getCombineExpression(); - SQLConfig setCombineExpression(String combineExpression); + String getCombine(); + SQLConfig setCombine(String combine); Map getCast(); SQLConfig setCast(Map cast); @@ -195,6 +183,18 @@ public interface SQLConfig { Map getWhere(); SQLConfig setWhere(Map where); + String getGroup(); + SQLConfig setGroup(String group); + + Map getHaving(); + SQLConfig setHaving(Map having); + + String getHavingCombine(); + SQLConfig setHavingCombine(String havingCombine); + + String getOrder(); + SQLConfig setOrder(String order); + /** * exactMatch = false * @param key From b881541295e32f8cfce8c05391e7d70892e2bcd5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 26 Mar 2022 23:56:50 +0800 Subject: [PATCH 036/619] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=8C=E5=8E=BB=E9=99=A4=E4=B8=8D=E5=BF=85=E8=A6=81=E7=9A=84?= =?UTF-8?q?=20synchonized?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractParser.java | 2 +- .../java/apijson/orm/AbstractSQLConfig.java | 18 ++++++++---------- .../java/apijson/orm/AbstractSQLExecutor.java | 18 +++++++++++------- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 6e407144e..4e615ce08 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1769,7 +1769,7 @@ public static String replaceArrayChildPath(String parentPath, String valuePath) * @param result 需要被关联的object */ @Override - public synchronized void putQueryResult(String path, Object result) { + public void putQueryResult(String path, Object result) { Log.i(TAG, "\n putQueryResult valuePath = " + path + "; result = " + result + "\n <<<<<<<<<<<<<<<<<<<<<<<"); // if (queryResultMap.containsKey(valuePath)) {//只保存被关联的value Log.d(TAG, "putQueryResult queryResultMap.containsKey(valuePath) >> queryResultMap.put(path, result);"); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 17aebb59a..27689a5cb 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2303,18 +2303,16 @@ public Object getWhere(String key, boolean exactMatch) { if (key == null || where == null){ return null; } - synchronized (where) { - if (where != null) { - int index; - for (Entry entry : where.entrySet()) { - String k = entry.getKey(); - index = k.indexOf(key); - if (index >= 0 && StringUtil.isName(k.substring(index)) == false) { - return entry.getValue(); - } - } + + int index; + for (Entry entry : where.entrySet()) { + String k = entry.getKey(); + index = k.indexOf(key); + if (index >= 0 && StringUtil.isName(k.substring(index, index + 1)) == false) { + return entry.getValue(); } } + return null; } @Override diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index c99f1e5eb..2c36b35e6 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -1120,18 +1120,22 @@ public void close() { @Override public ResultSet executeQuery(@NotNull SQLConfig config) throws Exception { - PreparedStatement s = getStatement(config); -// 不准,getStatement 有时比 execute sql 更耗时 executedSQLStartTime = System.currentTimeMillis(); - ResultSet rs = s.executeQuery(); //PreparedStatement 不用传 SQL -// executedSQLEndTime = System.currentTimeMillis(); + PreparedStatement stt = getStatement(config); + // 不准,getStatement 有时比 execute sql 更耗时 executedSQLStartTime = System.currentTimeMillis(); + ResultSet rs = stt.executeQuery(); //PreparedStatement 不用传 SQL + // executedSQLEndTime = System.currentTimeMillis(); + // if (config.isExplain() && (config.isSQLServer() || config.isOracle())) { + // FIXME 返回的是 boolean 值 rs = stt.getMoreResults(Statement.CLOSE_CURRENT_RESULT); + // } + return rs; } @Override public int executeUpdate(@NotNull SQLConfig config) throws Exception { - PreparedStatement s = getStatement(config); + PreparedStatement stt = getStatement(config); // 不准,getStatement 有时比 execute sql 更耗时 executedSQLStartTime = System.currentTimeMillis(); - int count = s.executeUpdate(); // PreparedStatement 不用传 SQL + int count = stt.executeUpdate(); // PreparedStatement 不用传 SQL // executedSQLEndTime = System.currentTimeMillis(); if (count <= 0 && config.isHive()) { @@ -1139,7 +1143,7 @@ public int executeUpdate(@NotNull SQLConfig config) throws Exception { } if (config.getId() == null && config.getMethod() == RequestMethod.POST) { // 自增id - ResultSet rs = s.getGeneratedKeys(); + ResultSet rs = stt.getGeneratedKeys(); if (rs != null && rs.next()) { config.setId(rs.getLong(1)); //返回插入的主键id FIXME Oracle 拿不到 } From 2433f3b50fc9c84d54b85ca5023a6f132f5a8637 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Mar 2022 02:38:47 +0800 Subject: [PATCH 037/619] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=AF=B9=20id,=20id{?= =?UTF-8?q?},=20userId,=20userId{}=20=E7=9A=84=E6=9D=A1=E4=BB=B6=E5=BC=BA?= =?UTF-8?q?=E5=88=B6=E5=89=8D=E7=BD=AE=20AND=20=E5=A4=84=E7=90=86=E4=BB=A5?= =?UTF-8?q?=E5=8F=8A=20@combine=20=E4=B8=8D=E5=85=81=E8=AE=B8=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E7=9A=84=E6=A0=A1=E9=AA=8C=EF=BC=8C=E9=81=BF=E5=85=8D?= =?UTF-8?q?=20id!=20|=20id=20=E8=BF=99=E7=A7=8D=E9=87=8D=E5=A4=8D=E5=AD=97?= =?UTF-8?q?=E7=AC=A6=E7=BB=95=E8=BF=87=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 309 ++++++++++++------ 1 file changed, 209 insertions(+), 100 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 27689a5cb..851ec4e7c 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -748,10 +748,15 @@ public String getUserIdKey() { } - private Object id; //Table的id private RequestMethod method; //操作方法 private boolean prepared = true; //预编译 private boolean main = true; + + private Object id; // Table 的 id + private Object idIn; // User Table 的 id IN + private Object userId; // Table 的 userId + private Object userIdIn; // Table 的 userId IN + /** * TODO 被关联的表通过就忽略关联的表?(这个不行 User:{"sex@":"/Comment/toId"}) */ @@ -857,6 +862,37 @@ public AbstractSQLConfig setId(Object id) { this.id = id; return this; } + + @Override + public Object getIdIn() { + return idIn; + } + @Override + public AbstractSQLConfig setIdIn(Object idIn) { + this.idIn = idIn; + return this; + } + + + @Override + public Object getUserId() { + return userId; + } + @Override + public AbstractSQLConfig setUserId(Object userId) { + this.userId = userId; + return this; + } + + @Override + public Object getUserIdIn() { + return userIdIn; + } + @Override + public AbstractSQLConfig setUserIdIn(Object userIdIn) { + this.userIdIn = userIdIn; + return this; + } @Override public String getRole() { @@ -2348,18 +2384,22 @@ else if (prior && andList.isEmpty() == false) { int lastIndex; if (key.equals(idKey)) { + setId(value); lastIndex = -1; } else if (key.equals(idInKey)) { + setIdIn(value); lastIndex = andList.lastIndexOf(idKey); } else if (key.equals(userIdKey)) { + setUserId(value); lastIndex = andList.lastIndexOf(idInKey); if (lastIndex < 0) { lastIndex = andList.lastIndexOf(idKey); } } else if (key.equals(userIdInKey)) { + setUserIdIn(value); lastIndex = andList.lastIndexOf(userIdKey); if (lastIndex < 0) { lastIndex = andList.lastIndexOf(idInKey); @@ -2404,7 +2444,7 @@ else if (key.equals(userIdInKey)) { @Override public String getWhereString(boolean hasPrefix) throws Exception { String combineExpr = getCombine(); - if (StringUtil.isEmpty(combineExpr, true)) { + if (StringUtil.isEmpty(combineExpr, false)) { return getWhereString(hasPrefix, getMethod(), getWhere(), getCombineMap(), getJoinList(), ! isTest()); } return getWhereString(hasPrefix, getMethod(), getWhere(), combineExpr, getJoinList(), ! isTest()); @@ -2417,10 +2457,9 @@ public String getWhereString(boolean hasPrefix) throws Exception { */ @JSONField(serialize = false) public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, String combine, List 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) { @@ -4256,23 +4295,22 @@ public static SQLConfig newSQLConfig(RequestMethod method, String table, String return config; //request.remove(key); 前都可以直接return,之后必须保证 put 回去 } + //对 id, id{}, userId, userId{} 处理,这些只要不为 null 就一定会作为 AND 条件 <<<<<<<<<<<<<<<<<<<<<<<<< String idKey = callback.getIdKey(datasource, database, schema, table); String idInKey = idKey + "{}"; String userIdKey = callback.getUserIdKey(datasource, database, schema, table); String userIdInKey = userIdKey + "{}"; - //对id和id{}处理,这两个一定会作为条件 - Object idIn = request.get(idInKey); //可能是 id{}:">0" - if (idIn instanceof List) { // 排除掉 0, 负数, 空字符串 等无效 id 值 - List ids = ((List) idIn); + if (idIn instanceof Collection) { // 排除掉 0, 负数, 空字符串 等无效 id 值 + Collection ids = (Collection) idIn; List newIdIn = new ArrayList<>(); - Object d; - for (int i = 0; i < ids.size(); i++) { //不用 idIn.contains(id) 因为 idIn 里存到很可能是 Integer,id 又是 Long! - d = ids.get(i); + for (Object d : ids) { //不用 idIn.contains(id) 因为 idIn 里存到很可能是 Integer,id 又是 Long! if ((d instanceof Number && ((Number) d).longValue() > 0) || (d instanceof String && StringUtil.isNotEmpty(d, true))) { - newIdIn.add(d); + if (newIdIn.contains(d) == false) { + newIdIn.add(d); + } } } if (newIdIn.isEmpty()) { @@ -4286,8 +4324,7 @@ public static SQLConfig newSQLConfig(RequestMethod method, String table, String } Object id = request.get(idKey); - boolean hasId = id != null; - if (method == POST && hasId == false) { + if (id == null && method == POST) { id = callback.newId(method, database, schema, table); // null 表示数据库自增 id } @@ -4307,12 +4344,10 @@ else if (id instanceof Subquery) {} throw new IllegalArgumentException(idKey + ":value 中 value 的类型只能是 Long , String 或 Subquery !"); } - if (idIn instanceof List) { //共用idIn场景少性能差 + if (idIn instanceof Collection) { //共用idIn场景少性能差 boolean contains = false; - List ids = ((List) idIn); - Object d; - for (int i = 0; i < ids.size(); i++) { //不用 idIn.contains(id) 因为 idIn 里存到很可能是 Integer,id 又是 Long! - d = ids.get(i); + Collection idList = ((Collection) idIn); + for (Object d : idList) { //不用 idIn.contains(id) 因为 idIn 里存到很可能是 Integer,id 又是 Long! if (d != null && id.toString().equals(d.toString())) { contains = true; break; @@ -4328,7 +4363,59 @@ else if (id instanceof Subquery) {} } } - + //对 userId 和 userId{} 处理,这两个一定会作为条件 + Object userIdIn = request.get(userIdInKey); //可能是 userId{}:">0" + if (userIdIn instanceof Collection) { // 排除掉 0, 负数, 空字符串 等无效 userId 值 + Collection userIds = (Collection) userIdIn; + List newUserIdIn = new ArrayList<>(); + for (Object d : userIds) { //不用 userIdIn.contains(userId) 因为 userIdIn 里存到很可能是 Integer,userId 又是 Long! + if ((d instanceof Number && ((Number) d).longValue() > 0) || (d instanceof String && StringUtil.isNotEmpty(d, true))) { + if (newUserIdIn.contains(d) == false) { + newUserIdIn.add(d); + } + } + } + if (newUserIdIn.isEmpty()) { + throw new NotExistException(TAG + ": newSQLConfig userIdIn instanceof List >> 去掉无效 userId 后 newIdIn.isEmpty()"); + } + userIdIn = newUserIdIn; + } + + Object userId = request.get(userIdKey); + if (userId != null) { //null无效 + if (userId instanceof Number) { + if (((Number) userId).longValue() <= 0) { //一定没有值 + throw new NotExistException(TAG + ": newSQLConfig " + table + ".userId <= 0"); + } + } + else if (userId instanceof String) { + if (StringUtil.isEmpty(userId, true)) { //一定没有值 + throw new NotExistException(TAG + ": newSQLConfig StringUtil.isEmpty(" + table + ".userId, true)"); + } + } + else if (userId instanceof Subquery) {} + else { + throw new IllegalArgumentException(userIdKey + ":value 中 value 的类型只能是 Long , String 或 Subquery !"); + } + + if (userIdIn instanceof Collection) { //共用userIdIn场景少性能差 + boolean contains = false; + Collection userIds = (Collection) userIdIn; + for (Object d : userIds) { //不用 userIdIn.contains(userId) 因为 userIdIn 里存到很可能是 Integer,userId 又是 Long! + if (d != null && userId.toString().equals(d.toString())) { + contains = true; + break; + } + } + if (contains == false) {//empty有效 BaseModel.isEmpty(userIdIn) == false) { + throw new NotExistException(TAG + ": newSQLConfig userIdIn != null && (((List) userIdIn).contains(userId) == false"); + } + } + } + + //对 id, id{}, userId, userId{} 处理,这些只要不为 null 就一定会作为 AND 条件 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + String role = request.getString(KEY_ROLE); String cache = request.getString(KEY_CACHE); Subquery from = (Subquery) request.get(KEY_FROM); @@ -4347,6 +4434,8 @@ else if (id instanceof Subquery) {} //强制作为条件且放在最前面优化性能 request.remove(idKey); request.remove(idInKey); + request.remove(userIdKey); + request.remove(userIdInKey); //关键词 request.remove(KEY_ROLE); request.remove(KEY_EXPLAIN); @@ -4452,107 +4541,116 @@ else if (id instanceof Subquery) {} } } else { //非POST操作 - final boolean isWhere = method != PUT;//除了POST,PUT,其它全是条件!!! + final boolean isWhere = method != PUT; //除了POST,PUT,其它全是条件!!! //条件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< List whereList = null; String[] ws = StringUtil.split(combine); + if (ws != null && (method == DELETE || method == GETS || method == HEADS)) { + throw new IllegalArgumentException(table + ":{} 里的 @combine:value 不合法!DELETE,GETS,HEADS 请求不允许传 @combine:value !"); + } + String combineExpr = ws == null || ws.length != 1 ? null : ws[0]; - Map> combineMap = StringUtil.isNotEmpty(combineExpr, true) ? null : new LinkedHashMap<>(); - List andList = combineMap == null ? null : new ArrayList<>(); - List orList = combineMap == null ? null : new ArrayList<>(); - List notList = combineMap == null ? null : new ArrayList<>(); + Map> combineMap = new LinkedHashMap<>(); + List andList = new ArrayList<>(); + List orList = new ArrayList<>(); + List notList = new ArrayList<>(); //强制作为条件且放在最前面优化性能 if (id != null) { tableWhere.put(idKey, id); - if (andList != null) { - andList.add(idKey); - } + andList.add(idKey); } if (idIn != null) { tableWhere.put(idInKey, idIn); - if (andList != null) { - andList.add(idInKey); - } + andList.add(idInKey); } - - if (combineMap == null) { - if (StringUtil.isNotEmpty(combineExpr, true)) { - List banKeyList = Arrays.asList(idKey, idInKey, userIdKey, userIdInKey); - - for (String key : banKeyList) { - int index = combineExpr.indexOf(key); - if (index >= 0) { - char left = index <= 0 ? ' ' : combineExpr.charAt(index - 1); - char right = index >= combineExpr.length() - key.length() ? ' ' : combineExpr.charAt(index + key.length()); - if ((left == ' ' || left == '(' || left == '&' || left == '|' || left == '!') && (right == ' ' || right == ')')) { - throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的 value 里 " + key + " 不合法!" - + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); - } + if (userId != null) { + tableWhere.put(userIdKey, userId); + andList.add(userIdKey); + } + if (userIdIn != null) { + tableWhere.put(userIdInKey, userIdIn); + andList.add(userIdInKey); + } + + if (StringUtil.isNotEmpty(combineExpr, true)) { + List banKeyList = Arrays.asList(idKey, idInKey, userIdKey, userIdInKey); + for (String key : banKeyList) { + String str = combineExpr; + while (str.isEmpty() == false) { + int index = str.indexOf(key); + if (index < 0) { + break; + } + + char left = index <= 0 ? ' ' : str.charAt(index - 1); + char right = index >= str.length() - key.length() ? ' ' : str.charAt(index + key.length()); + if ((left == ' ' || left == '(' || left == '&' || left == '|' || left == '!') && (right == ' ' || right == ')')) { + throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的 value 里 " + key + " 不合法!" + + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); } + + int newIndex = index + key.length() + 1; + if (str.length() <= newIndex) { + break; + } + str = str.substring(newIndex); } } - } - else { - if (ws != null) { - if (method == DELETE || method == GETS || method == HEADS) { - throw new IllegalArgumentException("DELETE,GETS,HEADS 请求不允许传 @combine:value !"); - } - whereList = new ArrayList<>(); - - String w; - for (int i = 0; i < ws.length; i++) { //去除 &,|,! 前缀 - w = ws[i]; - if (w != null) { - if (w.startsWith("&")) { - w = w.substring(1); - andList.add(w); - } - else if (w.startsWith("|")) { - if (method == PUT) { - throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" - + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); - } - w = w.substring(1); - orList.add(w); - } - else if (w.startsWith("!")) { - if (method == PUT) { - throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" - + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); - } - w = w.substring(1); - notList.add(w); + } + else if (ws != null) { + whereList = new ArrayList<>(); + + String w; + for (int i = 0; i < ws.length; i++) { //去除 &,|,! 前缀 + w = ws[i]; + if (w != null) { + if (w.startsWith("&")) { + w = w.substring(1); + andList.add(w); + } + else if (w.startsWith("|")) { + if (method == PUT) { + throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" + + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); } - else { - orList.add(w); + w = w.substring(1); + orList.add(w); + } + else if (w.startsWith("!")) { + if (method == PUT) { + throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" + + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); } + w = w.substring(1); + notList.add(w); + } + else { + orList.add(w); + } - if (w.isEmpty()) { - throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!不允许为空值!"); - } - else { - if (idKey.equals(w) || idInKey.equals(w) || userIdKey.equals(w) || userIdInKey.equals(w)) { - throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的 value 里 " + ws[i] + " 不合法!" - + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); - } + if (w.isEmpty()) { + throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!不允许为空值!"); + } + else { + if (idKey.equals(w) || idInKey.equals(w) || userIdKey.equals(w) || userIdInKey.equals(w)) { + throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的 value 里 " + ws[i] + " 不合法!" + + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); } - - whereList.add(w); } - // 可重写回调方法自定义处理 // 动态设置的场景似乎很少,而且去掉后不方便用户排错!//去掉判断,有时候不在没关系,如果是对增删改等非开放请求强制要求传对应的条件,可以用 Operation.NECESSARY - if (request.containsKey(w) == false) { //和 request.get(w) == null 没区别,前面 Parser 已经过滤了 null - // throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 对应的 " + w + " 不在它里面!"); - callback.onMissingKey4Combine(table, request, combine, ws[i], w); - } + whereList.add(w); } + // 可重写回调方法自定义处理 // 动态设置的场景似乎很少,而且去掉后不方便用户排错!//去掉判断,有时候不在没关系,如果是对增删改等非开放请求强制要求传对应的条件,可以用 Operation.NECESSARY + if (request.containsKey(w) == false) { //和 request.get(w) == null 没区别,前面 Parser 已经过滤了 null + // throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 对应的 " + w + " 不在它里面!"); + callback.onMissingKey4Combine(table, request, combine, ws[i], w); + } } - } //条件>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -4753,7 +4851,9 @@ else if (newHaving != null) { config.setRole(role); config.setId(id); - //在 tableWhere 第0个 config.setIdIn(idIn); + config.setIdIn(idIn); + config.setUserId(userId); + config.setUserIdIn(userIdIn); config.setNull(nullKeys == null || nullKeys.length <= 0 ? null : new ArrayList<>(Arrays.asList(nullKeys))); config.setCast(castMap); @@ -4767,13 +4867,22 @@ else if (newHaving != null) { config.setJson(jsons == null || jsons.length <= 0 ? null : new ArrayList<>(Arrays.asList(jsons))); } - finally {//后面还可能用到,要还原 - //id或id{}条件 - if (hasId) { + finally { // 后面还可能用到,要还原 + // id, id{}, userId, userIdIn 条件 + if (id != null) { request.put(idKey, id); } - request.put(idInKey, idIn); - //关键词 + if (idIn != null) { + request.put(idInKey, idIn); + } + if (userId != null) { + request.put(userIdKey, userId); + } + if (userIdIn != null) { + request.put(userIdInKey, userIdIn); + } + + // 关键词 request.put(KEY_DATABASE, database); request.put(KEY_ROLE, role); request.put(KEY_EXPLAIN, explain); From 544a8694165bde0f858d67678dcc784d24f04167 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Mar 2022 02:39:36 +0800 Subject: [PATCH 038/619] =?UTF-8?q?=E9=A2=84=E4=BC=B0=E5=AE=B9=E9=87=8F?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=AF=B9=20HAVING=20=E8=81=9A=E5=90=88?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E7=9A=84=E5=A4=84=E7=90=86=EF=BC=9B=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/StringUtil.java | 56 ++++++++++++++++--- .../main/java/apijson/orm/AbstractParser.java | 3 - .../java/apijson/orm/AbstractSQLExecutor.java | 32 +++++++---- .../src/main/java/apijson/orm/SQLConfig.java | 9 +++ 4 files changed, 79 insertions(+), 21 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index 0d473a6b0..ecc64a083 100755 --- a/APIJSONORM/src/main/java/apijson/StringUtil.java +++ b/APIJSONORM/src/main/java/apijson/StringUtil.java @@ -225,13 +225,27 @@ public static int getLength(String s, boolean trim) { //判断字符是否为空 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + /**判断字符是否为空 trim = true + * @param obj + * @return + */ + public static boolean isEmpty(Object obj) { + return isEmpty(obj, true); + } /**判断字符是否为空 - * @param object + * @param obj * @param trim * @return */ - public static boolean isEmpty(Object object, boolean trim) { - return isEmpty(getString(object), trim); + public static boolean isEmpty(Object obj, boolean trim) { + return isEmpty(getString(obj), trim); + } + /**判断字符是否为空 trim = true + * @param cs + * @return + */ + public static boolean isEmpty(CharSequence cs) { + return isEmpty(cs, true); } /**判断字符是否为空 * @param cs @@ -241,6 +255,13 @@ public static boolean isEmpty(Object object, boolean trim) { public static boolean isEmpty(CharSequence cs, boolean trim) { return isEmpty(getString(cs), trim); } + /**判断字符是否为空 trim = true + * @param s + * @return + */ + public static boolean isEmpty(String s) { + return isEmpty(s, true); + } /**判断字符是否为空 * @param s * @param trim @@ -267,13 +288,27 @@ public static boolean isEmpty(String s, boolean trim) { //判断字符是否非空 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - /**判断字符是否非空 + /**判断字符是否非空 trim = true * @param object + * @return + */ + public static boolean isNotEmpty(Object obj) { + return ! isEmpty(obj); + } + /**判断字符是否非空 + * @param obj * @param trim * @return */ - public static boolean isNotEmpty(Object object, boolean trim) { - return isNotEmpty(getString(object), trim); + public static boolean isNotEmpty(Object obj, boolean trim) { + return ! isEmpty(obj, trim); + } + /**判断字符是否非空 trim = true + * @param cs + * @return + */ + public static boolean isNotEmpty(CharSequence cs) { + return ! isEmpty(cs); } /**判断字符是否非空 * @param cs @@ -281,7 +316,14 @@ public static boolean isNotEmpty(Object object, boolean trim) { * @return */ public static boolean isNotEmpty(CharSequence cs, boolean trim) { - return isNotEmpty(getString(cs), trim); + return ! isEmpty(cs, trim); + } + /**判断字符是否非空 trim = true + * @param s + * @return + */ + public static boolean isNotEmpty(String s) { + return ! isEmpty(s); } /**判断字符是否非空 * @param s diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 4e615ce08..25e851bf0 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -18,8 +18,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -42,7 +40,6 @@ import apijson.Log; import apijson.NotNull; import apijson.RequestMethod; -import apijson.SQL; import apijson.StringUtil; import apijson.orm.exception.ConditionErrorException; import apijson.orm.exception.ConflictException; diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 2c36b35e6..3606c5043 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -264,11 +264,15 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws //id,id{}至少一个会有,一定会返回,不用抛异常来阻止关联写操作时前面错误导致后面无条件执行! result.put(JSONResponse.KEY_COUNT, updateCount);//返回修改的记录数 + + String idKey = config.getIdKey(); if (config.getId() != null) { - result.put(config.getIdKey(), config.getId()); - } else { - result.put(config.getIdKey() + "[]", config.getWhere(config.getIdKey() + "{}", true)); + result.put(idKey, config.getId()); + } + if (config.getIdIn() != null) { + result.put(idKey + "[]", config.getIdIn()); } + return result; case GET: @@ -327,14 +331,14 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws capacity = 1; } else { - Object idIn = config.getWhere(config.getIdKey() + "{}", true); + Object idIn = config.getIdIn(); if (idIn instanceof Collection) { // id{}:[] 一定是 AND 条件,最终返回数据最多就这么多 capacity = ((Collection) idIn).size(); } else { // 预估容量 capacity = config.getCount() <= 0 ? Parser.MAX_QUERY_COUNT : config.getCount(); if (capacity > 100) { - // 有条件,条件越多过滤数据越多 + // 有 WHERE 条件,条件越多过滤数据越多,暂时不考虑 @combine:"a | (b & !c)" 里面 | OR 和 ! NOT 条件,太复杂也不是很必要 Map> combine = config.getCombineMap(); List andList = combine == null ? null : combine.get("&"); @@ -346,23 +350,29 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws List notList = combine == null ? null : combine.get("|"); int notCondCount = notList == null ? 0 : notList.size(); - - // 有分组,分组字段越少过滤数据越多 + // 有 GROUP BY 分组,字段越少过滤数据越多 String[] group = StringUtil.split(config.getGroup()); int groupCount = group == null ? 0 : group.length; if (groupCount > 0 && Arrays.asList(group).contains(config.getIdKey())) { groupCount = 0; } - capacity /= Math.pow(1.5, Math.log10(capacity) + andCondCount - + (orCondCount <= 0 ? 0 : 2/orCondCount) // 1: 2.3, 2: 1.5, 3: 1.3, 4: 1.23, 5: 1.18 - + (notCondCount/5) // 1: 1.08, 2: 1.18, 3: 1.28, 4: 1.38, 1.50 - + (groupCount <= 0 ? 0 : 10/groupCount) // 1: 57.7, 7.6, 3: 3.9, 4: 2.8, 5: 2.3 + // 有 HAVING 聚合函数,字段越多过滤数据越多,暂时不考虑 @combine:"a | (b & !c)" 里面 | OR 和 ! NOT 条件,太复杂也不是很必要 + Map having = config.getHaving(); + int havingCount = having == null ? 0 : having.size(); + + capacity /= Math.pow(1.5, Math.log10(capacity) + + andCondCount + + ((orCondCount <= 0 ? 0 : 2.0d/orCondCount) // 1: 2.3, 2: 1.5, 3: 1.3, 4: 1.23, 5: 1.18 + + (notCondCount/5.0d) // 1: 1.08, 2: 1.18, 3: 1.28, 4: 1.38, 1.50 + + (groupCount <= 0 ? 0 : 10.0d/groupCount)) // 1: 57.7, 7.6, 3: 3.9, 4: 2.8, 5: 2.3 + + havingCount ); capacity += 1; // 避免正好比需要容量少一点点导致多一次扩容,大量数据 System.arrayCopy } } } + resultList = new ArrayList<>(capacity); } diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index 43561fdd8..57e282d51 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -116,6 +116,15 @@ public interface SQLConfig { Object getId(); SQLConfig setId(Object id); + + Object getIdIn(); + SQLConfig setIdIn(Object idIn); + + Object getUserId(); + SQLConfig setUserId(Object userId); + + Object getUserIdIn(); + SQLConfig setUserIdIn(Object userIdIn); String getRole(); SQLConfig setRole(String role); From 46ccadec084742ea5bc7beaf061e2e1560115faf Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Mar 2022 02:42:33 +0800 Subject: [PATCH 039/619] =?UTF-8?q?=E6=9D=83=E9=99=90=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=EF=BC=9A=E5=88=86=E6=8B=86=E5=AF=B9=E8=A7=92=E8=89=B2=E7=9A=84?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E7=9A=84=E4=BB=A3=E7=A0=81=E4=B8=BA=E5=A4=9A?= =?UTF-8?q?=E4=B8=AA=E6=96=B9=E6=B3=95=EF=BC=8C=E6=96=B9=E4=BE=BF=E7=81=B5?= =?UTF-8?q?=E6=B4=BB=E9=87=8D=E5=86=99=E9=83=A8=E5=88=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractVerifier.java | 98 ++++++++++++------- .../src/main/java/apijson/orm/Verifier.java | 7 +- 2 files changed, 67 insertions(+), 38 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 1f366d0c0..feda82ac1 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -240,6 +240,7 @@ public AbstractVerifier setVisitor(Visitor visitor) { * @return * @throws Exception */ + @Override public boolean verifyAccess(SQLConfig config) throws Exception { String table = config == null ? null : config.getTable(); if (table == null) { @@ -249,7 +250,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception { String role = config.getRole(); if (role == null) { role = UNKNOWN; - } + } else { if (ROLE_MAP.containsKey(role) == false) { Set NAMES = ROLE_MAP.keySet(); @@ -262,14 +263,72 @@ public boolean verifyAccess(SQLConfig config) throws Exception { } RequestMethod method = config.getMethod(); + verifyRole(config, table, method, role); + + return true; + } + + @Override + public void verifyRole(SQLConfig config, String table, RequestMethod method, String role) throws Exception { + verifyAllowRole(config, table, method, role); //验证允许的角色 + verifyUseRole(config, table, method, role); //验证使用的角色 + } - verifyRole(table, method, role);//验证允许的角色 + /**允许请求使用的所以可能角色 + * @param config + * @param table + * @param method + * @param role + * @return + * @throws Exception + * @see {@link apijson.JSONObject#KEY_ROLE} + */ + public void verifyAllowRole(SQLConfig config, String table, RequestMethod method, String role) throws Exception { + Log.d(TAG, "verifyAllowRole table = " + table + "; method = " + method + "; role = " + role); + if (table == null) { + table = config == null ? null : config.getTable(); + } + + if (table != null) { + if (method == null) { + method = config == null ? GET : config.getMethod(); + } + if (role == null) { + role = config == null ? UNKNOWN : config.getRole(); + } + + Map map = ACCESS_MAP.get(table); + if (map == null || Arrays.asList(map.get(method)).contains(role) == false) { + throw new IllegalAccessException(table + " 不允许 " + role + " 用户的 " + method.name() + " 请求!"); + } + } + } + /**校验请求使用的角色,角色不好判断,让访问者发过来角色名,OWNER,CONTACT,ADMIN等 + * @param config + * @param table + * @param method + * @param role + * @return + * @throws Exception + * @see {@link apijson.JSONObject#KEY_ROLE} + */ + public void verifyUseRole(SQLConfig config, String table, RequestMethod method, String role) throws Exception { + Log.d(TAG, "verifyUseRole table = " + table + "; method = " + method + "; role = " + role); //验证角色,假定真实强制匹配<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< String visitorIdKey = getVisitorIdKey(config); - + if (table == null) { + table = config == null ? null : config.getTable(); + } + if (method == null) { + method = config == null ? GET : config.getMethod(); + } + if (role == null) { + role = config == null ? UNKNOWN : config.getRole(); + } + Object requestId; switch (role) { case LOGIN://verifyRole通过就行 @@ -366,39 +425,6 @@ public boolean verifyAccess(SQLConfig config) throws Exception { } //验证角色,假定真实强制匹配>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - return true; - } - - - - - - /**允许请求,角色不好判断,让访问者发过来角色名,OWNER,CONTACT,ADMIN等 - * @param table - * @param method - * @param role - * @return - * @throws Exception - * @see {@link apijson.JSONObject#KEY_ROLE} - */ - public void verifyRole(String table, RequestMethod method, String role) throws Exception { - Log.d(TAG, "verifyRole table = " + table + "; method = " + method + "; role = " + role); - if (table != null) { - if (method == null) { - method = GET; - } - if (role == null) { - role = UNKNOWN; - } - - Map map = ACCESS_MAP.get(table); - - if (map == null || Arrays.asList(map.get(method)).contains(role) == false) { - throw new IllegalAccessException(table + " 不允许 " + role + " 用户的 " + method.name() + " 请求!"); - } - } } diff --git a/APIJSONORM/src/main/java/apijson/orm/Verifier.java b/APIJSONORM/src/main/java/apijson/orm/Verifier.java index 903b69530..76f142fc5 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Verifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/Verifier.java @@ -24,7 +24,9 @@ public interface Verifier { */ boolean verifyAccess(SQLConfig config) throws Exception; - /**允许请求,角色不好判断,让访问者发过来角色名,OWNER,CONTACT,ADMIN等 + + /**校验请求使用的角色,角色不好判断,让访问者发过来角色名,OWNER,CONTACT,ADMIN等 + * @param config * @param table * @param method * @param role @@ -32,7 +34,7 @@ public interface Verifier { * @throws Exception * @see {@link apijson.JSONObject#KEY_ROLE} */ - void verifyRole(String table, RequestMethod method, String role) throws Exception; + void verifyRole(SQLConfig config, String table, RequestMethod method, String role) throws Exception; /**登录校验 * @param config @@ -94,4 +96,5 @@ JSONObject verifyResponse(RequestMethod method, String name, JSONObject target, String getVisitorIdKey(SQLConfig config); + } From 5340749bea823820cf201200a61c6deaece48e0a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Mar 2022 05:47:00 +0800 Subject: [PATCH 040/619] =?UTF-8?q?=E5=AF=B9=20@having:"=E8=A1=A8=E8=BE=BE?= =?UTF-8?q?=E5=BC=8F"=20=E5=92=8C=20key{}:"=E8=A1=A8=E8=BE=BE=E5=BC=8F"=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81=E5=8D=95=E5=BC=95=E5=8F=B7?= =?UTF-8?q?=E3=80=81=E5=8F=8D=E5=BC=95=E5=8F=B7=E3=80=81=E5=90=84=E7=A7=8D?= =?UTF-8?q?=E5=85=B3=E9=94=AE=E8=AF=8D=E7=AD=89=EF=BC=9B=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E5=BD=93=20idKey=20=E5=92=8C=20idInKey=20=E4=B8=80=E6=A0=B7?= =?UTF-8?q?=E6=97=B6=E9=87=8D=E5=A4=8D=E6=8B=BC=E6=8E=A5=E6=9D=A1=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 684 +++++++++--------- 1 file changed, 362 insertions(+), 322 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 851ec4e7c..21fe2efce 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -80,7 +80,7 @@ */ public abstract class AbstractSQLConfig implements SQLConfig { private static final String TAG = "AbstractSQLConfig"; - + /** * 为 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 连接 @@ -91,14 +91,14 @@ public abstract class AbstractSQLConfig implements SQLConfig { * 否则按 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_COMBINE_DEPTH = 2; public static int MAX_COMBINE_COUNT = 5; public static int MAX_COMBINE_KEY_COUNT = 2; public static float MAX_COMBINE_RATIO = 1.0f; - + public static String DEFAULT_DATABASE = DATABASE_MYSQL; public static String DEFAULT_SCHEMA = "sys"; public static String PREFFIX_DISTINCT = "DISTINCT "; @@ -153,7 +153,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 - + RAW_MAP.put("+", ""); RAW_MAP.put("-", ""); RAW_MAP.put("*", ""); @@ -200,8 +200,8 @@ public abstract class AbstractSQLConfig implements SQLConfig { RAW_MAP.put("MONTH", ""); RAW_MAP.put("QUARTER", ""); RAW_MAP.put("YEAR", ""); -// RAW_MAP.put("json", ""); -// RAW_MAP.put("unit", ""); + // RAW_MAP.put("json", ""); + // RAW_MAP.put("unit", ""); //MYSQL 数据类型 BINARY,CHAR,DATETIME,TIME,DECIMAL,SIGNED,UNSIGNED RAW_MAP.put("BINARY", ""); @@ -254,7 +254,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { RAW_MAP.put("LANGUAGE", ""); RAW_MAP.put("MODE", ""); - + SQL_AGGREGATE_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 SQL_AGGREGATE_FUNCTION_MAP.put("max", ""); @@ -262,7 +262,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { SQL_AGGREGATE_FUNCTION_MAP.put("avg", ""); SQL_AGGREGATE_FUNCTION_MAP.put("count", ""); SQL_AGGREGATE_FUNCTION_MAP.put("sum", ""); - + SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 //窗口函数 @@ -756,7 +756,7 @@ public String getUserIdKey() { private Object idIn; // User Table 的 id IN private Object userId; // Table 的 userId private Object userIdIn; // Table 的 userId IN - + /** * TODO 被关联的表通过就忽略关联的表?(这个不行 User:{"sex@":"/Comment/toId"}) */ @@ -862,7 +862,7 @@ public AbstractSQLConfig setId(Object id) { this.id = id; return this; } - + @Override public Object getIdIn() { return idIn; @@ -872,8 +872,8 @@ public AbstractSQLConfig setIdIn(Object idIn) { this.idIn = idIn; return this; } - - + + @Override public Object getUserId() { return userId; @@ -1151,7 +1151,7 @@ public String getGroupString(boolean hasPrefix) { return (hasPrefix ? " GROUP BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinGroup, ", "); } - + @Override public String getHavingCombine() { return havingCombine; @@ -1174,7 +1174,7 @@ public SQLConfig setHaving(Map having) { public AbstractSQLConfig setHaving(String... conditions) { return setHaving(StringUtil.getString(conditions)); } - + /**TODO @having 改为默认 | 或连接,且支持 @having: { "key1>": 1, "key{}": "length(key2)>0", "@combine": "key1,key2" } * @return HAVING conditoin0 AND condition1 OR condition2 ... * @throws Exception @@ -1192,7 +1192,7 @@ public String getHavingString(boolean hasPrefix) throws Exception { SQLConfig ocfg = j.getOuterConfig(); SQLConfig cfg = (ocfg != null && ocfg.getHaving() != null) || j.isLeftOrRightJoin() ? ocfg : j.getJoinConfig(); - + if (cfg != null) { cfg.setMain(false).setKeyPrefix(true); if (StringUtil.isEmpty(cfg.getAlias(), true)) { @@ -1217,7 +1217,7 @@ public String getHavingString(boolean hasPrefix) throws Exception { List 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); - + // 直接把 having 类型从 Map 定改为 Map,避免额外拷贝 // Map newMap = new LinkedHashMap<>(map.size()); // for (Entry entry : set) { @@ -1282,46 +1282,48 @@ else if (SQL_FUNCTION_MAP.containsKey(method) == false) { } } - String suffix = expression.substring(end + 1, expression.length()); - - if (isPrepared() && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { - throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); - } - - String[] ckeys = StringUtil.split(expression.substring(start + 1, end)); - - if (ckeys != null) { - for (int j = 0; j < ckeys.length; j++) { - String origin = ckeys[j]; - - if (isPrepared()) { - if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { - throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中所有 column, arg 都必须是1个不以 _ 开头的单词 或者 符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许多余的空格!"); - } - } - - //JOIN 副表不再在外层加副表名前缀 userId AS `Commet.userId`, 而是直接 userId AS `userId` - boolean isName = false; - if (StringUtil.isNumer(origin)) { - //do nothing - } - else if (StringUtil.isName(origin)) { - origin = quote + origin + quote; - isName = true; - } - else { - origin = getValue(origin).toString(); - } - - ckeys[j] = (isName && isKeyPrefix() ? alias + "." : "") + origin; - } - } + // String suffix = expression.substring(end + 1, expression.length()); + // + // if (isPrepared() && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { + // throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" + // + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + // + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); + // } + // + // String[] ckeys = StringUtil.split(expression.substring(start + 1, end)); + // + // if (ckeys != null) { + // for (int j = 0; j < ckeys.length; j++) { + // String origin = ckeys[j]; + // + // if (isPrepared()) { + // if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { + // throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" + // + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + // + " 中所有 column, arg 都必须是1个不以 _ 开头的单词 或者 符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许多余的空格!"); + // } + // } + // + // //JOIN 副表不再在外层加副表名前缀 userId AS `Commet.userId`, 而是直接 userId AS `userId` + // boolean isName = false; + // if (StringUtil.isNumer(origin)) { + // //do nothing + // } + // else if (StringUtil.isName(origin)) { + // origin = quote + origin + quote; + // isName = true; + // } + // else { + // origin = getValue(origin).toString(); + // } + // + // ckeys[j] = (isName && isKeyPrefix() ? alias + "." : "") + origin; + // } + // } + // + // return method + "(" + StringUtil.getString(ckeys) + ")" + suffix; - return method + "(" + StringUtil.getString(ckeys) + ")" + suffix; + return method + parseSQLExpression(KEY_HAVING, expression.substring(start), containRaw, false, null); } @Override @@ -1461,7 +1463,7 @@ public String getRawSQL(String key, Object value) throws Exception { if (value == null) { return null; } - + List rawList = getRaw(); boolean containRaw = rawList != null && rawList.contains(key); if (containRaw && value instanceof String == false) { @@ -1529,7 +1531,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { if (isPrepared() && column != null) { List raw = getRaw(); boolean containRaw = raw != null && raw.contains(KEY_COLUMN); - + for (String c : column) { if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 @@ -1563,10 +1565,10 @@ public String getColumnString(boolean inSQLJoin) throws Exception { } } } - + boolean onlyOne = column != null && column.size() == 1; String c0 = onlyOne ? column.get(0) : null; - + if (onlyOne) { int index = c0 == null ? -1 : c0.lastIndexOf(":"); if (index > 0) { @@ -1577,13 +1579,13 @@ public String getColumnString(boolean inSQLJoin) throws Exception { int end = start <= 0 ? -1 : c0.lastIndexOf(")"); if (start > 0 && end > start) { String fun = c0.substring(0, start); - + // Invalid use of group function SELECT count(max(`id`)) AS count FROM `sys`.`Comment` if (SQL_AGGREGATE_FUNCTION_MAP.containsKey(fun)) { String group = getGroup(); // TODO 唯一 100% 兼容的可能只有 SELECT count(*) FROM (原语句) AS table return StringUtil.isEmpty(group, true) ? "1" : "count(DISTINCT " + group + ")"; } - + String[] args = start == end - 1 ? null : StringUtil.split(c0.substring(start + 1, end)); if (args == null || args.length <= 0) { return SQL.count(c0); @@ -1592,7 +1594,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { List raw = getRaw(); boolean containRaw = raw != null && raw.contains(KEY_COLUMN); - return SQL.count(parseColumn(c0, containRaw)); + return SQL.count(parseSQLExpression(KEY_COLUMN, c0, containRaw, false, null)); } } @@ -1624,11 +1626,11 @@ public String getColumnString(boolean inSQLJoin) throws Exception { if (j.isAppJoin()) { continue; } - + SQLConfig ocfg = j.getOuterConfig(); boolean isEmpty = ocfg == null || ocfg.getColumn() == null; boolean isLeftOrRightJoin = j.isLeftOrRightJoin(); - + if (isEmpty && isLeftOrRightJoin) { // 改为 SELECT ViceTable.* 解决 SELECT sum(ViceTable.id) LEFT/RIGHT JOIN (SELECT sum(id) FROM ViceTable...) AS ViceTable // 不仅导致 SQL 函数重复计算,还有时导致 SQL 报错或对应字段未返回 @@ -1650,7 +1652,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { } } } - + inSQLJoin = true; } } @@ -1696,7 +1698,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { throw new UnsupportedOperationException("@column:value 的 value 中字符串 " + expression + " 不合法!" + "不允许传超过 100 个字符的函数或表达式!请用 @raw 简化传参!"); } - keys[i] = parseColumn(expression, containRaw); + keys[i] = parseSQLExpression(KEY_COLUMN, expression, containRaw, true, "@column:\"column0,column1:alias1;function0(arg0,arg1,...);function1(...):alias2...\""); } String c = StringUtil.getString(keys); @@ -1710,23 +1712,49 @@ public String getColumnString(boolean inSQLJoin) throws Exception { } } - /** - * 解析@column 中以“;”分隔的表达式("@column":"expression1;expression2;expression2;....")中的expression - * + /**解析@column 中以“;”分隔的表达式("@column":"expression1;expression2;expression2;....")中的expression + * @param key * @param expression + * @param containRaw + * @param allowAlias + * @param columnPrefix * @return */ - public String parseColumn(String expression, boolean containRaw) { + public String parseSQLExpression(String key, String expression, boolean containRaw, boolean allowAlias) { + return parseSQLExpression(key, expression, containRaw, allowAlias, null); + } + /**解析@column 中以“;”分隔的表达式("@column":"expression1;expression2;expression2;....")中的expression + * @param key + * @param expression + * @param containRaw + * @param allowAlias + * @param columnPrefix + * @param example + * @return + */ + public String parseSQLExpression(String key, String expression, boolean containRaw, boolean allowAlias, String example) { String quote = getQuote(); int start = expression.indexOf('('); if (start < 0) { //没有函数 ,可能是字段,也可能是 DISTINCT xx - String[] cks = parseArgsSplitWithComma(expression, true, containRaw); + String[] cks = parseArgsSplitWithComma(expression, true, containRaw, allowAlias); expression = StringUtil.getString(cks); } else { // FIXME 用括号断开? 如果少的话,用关键词加括号断开,例如 )OVER( 和 )AGAINST( // 窗口函数 rank() OVER (PARTITION BY id ORDER BY userId ASC) // 全文索引 math(name,tag) AGAINST ('a b +c -d' IN NATURALE LANGUAGE MODE) // IN BOOLEAN MODE + if (StringUtil.isEmpty(example)) { + if (KEY_COLUMN.equals(key)) { + example = key + ":\"column0,column1:alias1;function0(arg0,arg1,...);function1(...):alias2...\""; + } + // 和 key{}:"" 一样 else if (KEY_HAVING.equals(key) || KEY_HAVING_AND.equals(key)) { + // exeptionExample = key + ":\"function0(arg0,arg1,...)>1;function1(...)%5<=3...\""; + // } + else { + example = key + ":\"column0!=0;column1+3*2<=10;function0(arg0,arg1,...)>1;function1(...)%5<=3...\""; + } + } + //有函数,但不是窗口函数 int overIndex = expression.indexOf(")OVER("); // 传参不传空格,拼接带空格 ") OVER ("); int againstIndex = expression.indexOf(")AGAINST("); // 传参不传空格,拼接带空格 ") AGAINST ("); @@ -1734,28 +1762,25 @@ public String parseColumn(String expression, boolean containRaw) { boolean containAgainst = againstIndex > 0 && againstIndex < expression.length() - ")AGAINST(".length(); if (containOver && containAgainst) { - throw new IllegalArgumentException("字符 " + expression + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + throw new IllegalArgumentException("字符 " + expression + " 不合法!预编译模式下 " + example + " 中 function 必须符合小写英文单词的 SQL 函数名格式!不能同时存在窗口函数关键词 OVER 和全文索引关键词 AGAINST!"); } if (containOver == false && containAgainst == false) { - int end = expression.lastIndexOf(")"); + int end = expression.lastIndexOf(')'); if (start >= end) { throw new IllegalArgumentException("字符 " + expression + " 不合法!" - + "@column:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); + + key + ":value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); } String fun = expression.substring(0, start); if (fun.isEmpty() == false) { if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { if (StringUtil.isName(fun) == false) { - throw new IllegalArgumentException("字符 " + fun + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + throw new IllegalArgumentException("字符 " + fun + " 不合法!预编译模式下 " + example + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); } } else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { - throw new IllegalArgumentException("字符 " + fun + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + throw new IllegalArgumentException("字符 " + fun + " 不合法!预编译模式下 " + example + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); } } @@ -1767,28 +1792,32 @@ public String parseColumn(String expression, boolean containRaw) { } // 解析函数内的参数 - String ckeys[] = parseArgsSplitWithComma(s, false, containRaw); + String ckeys[] = parseArgsSplitWithComma(s, false, containRaw, allowAlias); String suffix = expression.substring(end + 1, expression.length()); //:contactCount - int index = suffix.lastIndexOf(":"); - String alias = index < 0 ? "" : suffix.substring(index + 1); //contactCount - suffix = index < 0 ? suffix : suffix.substring(0, index); - if (alias.isEmpty() == false && StringUtil.isName(alias) == false) { - throw new IllegalArgumentException("字符串 " + alias + " 不合法!" - + "预编译模式下 @column:value 中 value里面用 ; 分割的每一项" - + " function(arg0,arg1,...):alias 中 alias 必须是1个单词!并且不要有多余的空格!"); + String alias = null; + if (allowAlias) { + int index = suffix.lastIndexOf(":"); + alias = index < 0 ? "" : suffix.substring(index + 1); //contactCount + suffix = index < 0 ? suffix : suffix.substring(0, index); + if (alias.isEmpty() == false && StringUtil.isName(alias) == false) { + throw new IllegalArgumentException("字符串 " + alias + " 不合法!预编译模式下 " + + key + ":value 中 value里面用 ; 分割的每一项" + + " function(arg0,arg1,...):alias 中 alias 必须是1个单词!并且不要有多余的空格!"); + } } - if (suffix.isEmpty() == false && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { - throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" - + "预编译模式下 @column:\"column?value;function(arg0,arg1,...)?value...\"" + if (suffix.isEmpty() == false && (((String) suffix).contains("--") || ((String) suffix).contains("/*") + || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { + throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!预编译模式下 " + key + + ":\"column?value;function(arg0,arg1,...)?value...\"" + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); } String origin = fun + "(" + (distinct ? PREFFIX_DISTINCT : "") + StringUtil.getString(ckeys) + ")" + suffix; expression = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); - - } else { + } + else { //是窗口函数 fun(arg0,agr1) OVER (agr0 agr1 ...) int keyIndex = containOver ? overIndex : againstIndex; String s1 = expression.substring(0, keyIndex + 1); // OVER 前半部分 @@ -1800,31 +1829,31 @@ public String parseColumn(String expression, boolean containRaw) { if (index1 >= end) { throw new IllegalArgumentException("字符 " + expression + " 不合法!" - + "@column:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); + + key + ":value 中 value 里的 SQL 函数必须为 function(arg0,arg1,...) 这种格式!"); } + if (fun.isEmpty() == false) { if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { if (StringUtil.isName(fun) == false) { - throw new IllegalArgumentException("字符 " + fun + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + throw new IllegalArgumentException("字符 " + fun + " 不合法!预编译模式下 " + example + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); } - } else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { - throw new IllegalArgumentException("字符 " + fun + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + } + else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { + throw new IllegalArgumentException("字符 " + fun + " 不合法!预编译模式下 " + example + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); } } // 获取前半部分函数的参数解析 fun(arg0,agr1) - String agrsString1[] = parseArgsSplitWithComma(s1.substring(index1 + 1, s1.lastIndexOf(")")), false, containRaw); + String agrsString1[] = parseArgsSplitWithComma(s1.substring(index1 + 1, s1.lastIndexOf(")")), false, containRaw, allowAlias); int index2 = s2.indexOf("("); // 后半部分 “(”的起始位置 String argString2 = s2.substring(index2 + 1, end); // 后半部分的参数 // 别名 - String alias = s2.lastIndexOf(":") < s2.lastIndexOf(")") ? null : s2.substring(s2.lastIndexOf(":") + 1); + String alias = allowAlias == false || s2.lastIndexOf(":") < s2.lastIndexOf(")") ? null : s2.substring(s2.lastIndexOf(":") + 1); // 获取后半部分的参数解析 (agr0 agr1 ...) - String argsString2[] = parseArgsSplitWithComma(argString2, false, containRaw); + String argsString2[] = parseArgsSplitWithComma(argString2, false, containRaw, allowAlias); expression = fun + "(" + StringUtil.getString(agrsString1) + (containOver ? ") OVER (" : ") AGAINST (") // 传参不传空格,拼接带空格 + StringUtil.getString(argsString2) + ")" + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); } } @@ -1832,25 +1861,27 @@ public String parseColumn(String expression, boolean containRaw) { return expression; } - /** - * 解析函数参数或者字段,此函数对于解析字段 和 函数内参数通用 - * + + /**解析函数参数或者字段,此函数对于解析字段 和 函数内参数通用 * @param param * @param isColumn true:不是函数参数。false:是函数参数 + * @param containRaw + * @param allowAlias * @return */ - private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean containRaw) { + private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean containRaw, boolean allowAlias) { // 以"," 分割参数 String quote = getQuote(); String tableAlias = getAliasWithQuote(); String ckeys[] = StringUtil.split(param); // 以","分割参数 if (ckeys != null && ckeys.length > 0) { - String origin; - String alias; - int index; + for (int i = 0; i < ckeys.length; i++) { String ck = ckeys[i]; + String origin; + String alias; + // 如果参数包含 "'" ,解析字符串 if (ck.startsWith("`") && ck.endsWith("`")) { origin = ck.substring(1, ck.length() - 1); @@ -1861,7 +1892,7 @@ private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean + " 中所有字符串 column 都必须必须为1个单词 !"); } - ckeys[i] = getKey(origin).toString(); + origin = getKey(origin).toString(); } else if (ck.startsWith("'") && ck.endsWith("'")) { origin = ck.substring(1, ck.length() - 1); @@ -1870,69 +1901,82 @@ else if (ck.startsWith("'") && ck.endsWith("'")) { + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中字符串参数不合法,必须以 ' 开头, ' 结尾,字符串中不能包含 ' "); } - + // 1.字符串不是字段也没有别名,所以不解析别名 2. 是字符串,进行预编译,使用getValue() ,对字符串进行截取 - ckeys[i] = getValue(origin).toString(); + origin = getValue(origin).toString(); } else { // 参数不包含",",即不是字符串 // 解析参数:1. 字段 ,2. 是以空格分隔的参数 eg: cast(now() as date) - index = isColumn ? ck.lastIndexOf(":") : -1; //StringUtil.split返回数组中,子项不会有null - origin = index < 0 ? ck : ck.substring(0, index); //获取 : 之前的 - alias = index < 0 ? null : ck.substring(index + 1); - if (isPrepared()) { - if (isColumn) { - if (StringUtil.isName(origin) == false || (alias != null && StringUtil.isName(alias) == false)) { - throw new IllegalArgumentException("字符 " + ck + " 不合法!" - + "预编译模式下 @column:value 中 value里面用 , 分割的每一项" - + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!" - + "关键字必须全大写,且以空格分隔的参数,空格必须只有 1 个!其它情况不允许空格!"); - } - } else { - if (origin.startsWith("_") || origin.contains("--")) { // || PATTERN_FUNCTION.matcher(origin).matches() == false) { - throw new IllegalArgumentException("字符 " + ck + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); + if ("=null".equals(ck)) { + origin = SQL.isNull(); + } + else if ("!=null".equals(ck)) { + origin = SQL.isNull(false); + } + else { + origin = ck; + alias = null; + if (allowAlias) { + int index = isColumn ? ck.lastIndexOf(":") : -1; //StringUtil.split返回数组中,子项不会有null + origin = index < 0 ? ck : ck.substring(0, index); //获取 : 之前的 + alias = index < 0 ? null : ck.substring(index + 1); + if (isPrepared()) { + if (isColumn) { + if (StringUtil.isName(origin) == false || (alias != null && StringUtil.isName(alias) == false)) { + throw new IllegalArgumentException("字符 " + ck + " 不合法!" + + "预编译模式下 @column:value 中 value里面用 , 分割的每一项" + + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!" + + "关键字必须全大写,且以空格分隔的参数,空格必须只有 1 个!其它情况不允许空格!"); + } + } else { + if (origin.startsWith("_") || origin.contains("--")) { // || PATTERN_FUNCTION.matcher(origin).matches() == false) { + throw new IllegalArgumentException("字符 " + ck + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); + } + } } } - } - // 以空格分割参数 - String[] mkes = containRaw ? StringUtil.split(ck, " ", true) : new String[]{ ck }; - //如果参数中含有空格(少数情况) 比如 fun(arg1 arg2 arg3 ,arg4) 中的 arg1 arg2 arg3,比如 DISTINCT id - if (mkes != null && mkes.length >= 2) { - ckeys[i] = praseArgsSplitWithSpace(mkes); - } else { - boolean isName = false; + // 以空格分割参数 + String[] mkes = containRaw ? StringUtil.split(ck, " ", true) : new String[]{ ck }; - String mk = RAW_MAP.get(origin); - if (mk != null) { // newSQLConfig 提前处理好的 - if (mk.length() > 0) { - origin = mk; - } - } else if (StringUtil.isNumer(origin)) { - //do nothing - } else if (StringUtil.isName(origin)) { - origin = quote + origin + quote; - isName = true; + //如果参数中含有空格(少数情况) 比如 fun(arg1 arg2 arg3 ,arg4) 中的 arg1 arg2 arg3,比如 DISTINCT id + if (mkes != null && mkes.length >= 2) { + origin = praseArgsSplitWithSpace(mkes); } else { - origin = getValue(origin).toString(); - } + boolean isName = false; - if (isName && isKeyPrefix()) { - origin = tableAlias + "." + origin; - } + String mk = RAW_MAP.get(origin); + if (mk != null) { // newSQLConfig 提前处理好的 + if (mk.length() > 0) { + origin = mk; + } + } else if (StringUtil.isNumer(origin)) { + //do nothing + } else if (StringUtil.isName(origin)) { + origin = quote + origin + quote; + isName = true; + } else { + origin = getValue(origin).toString(); + } - if (isColumn && StringUtil.isEmpty(alias, true) == false) { - origin += " AS " + quote + alias + quote; - } + if (isName && isKeyPrefix()) { + origin = tableAlias + "." + origin; + } - ckeys[i] = origin; + if (isColumn && StringUtil.isEmpty(alias, true) == false) { + origin += " AS " + quote + alias + quote; + } + } } - } + + ckeys[i] = origin; } } + return ckeys; } @@ -2103,7 +2147,7 @@ public AbstractSQLConfig setCompat(Boolean compat) { this.compat = compat; return this; } - + @Override public int getType() { return type; @@ -2230,7 +2274,7 @@ public static String getLimitString(int page, int count, boolean isTSQL, boolean return " LIMIT " + count + (offset <= 0 ? "" : " OFFSET " + offset); // DELETE, UPDATE 不支持 OFFSET } - + @Override public List getNull() { return nulls; @@ -2250,9 +2294,9 @@ public SQLConfig setCast(Map cast) { this.cast = cast; return this; } - + //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - + protected int getMaxHavingCount() { return MAX_HAVING_COUNT; } @@ -2282,7 +2326,7 @@ public AbstractSQLConfig setCombine(String combine) { this.combine = combine; return this; } - + @NotNull @Override public Map> getCombineMap() { @@ -2301,7 +2345,7 @@ public AbstractSQLConfig setCombineMap(Map> combineMap) { this.combineMap = combineMap; return this; } - + @Override public Map getWhere() { return where; @@ -2339,7 +2383,7 @@ public Object getWhere(String key, boolean exactMatch) { if (key == null || where == null){ return null; } - + int index; for (Entry entry : where.entrySet()) { String k = entry.getKey(); @@ -2348,7 +2392,7 @@ public Object getWhere(String key, boolean exactMatch) { return entry.getValue(); } } - + return null; } @Override @@ -2420,7 +2464,7 @@ else if (key.equals(userIdInKey)) { lastIndex = andList.lastIndexOf(idKey); } } - + i = lastIndex + 1; } @@ -2432,7 +2476,7 @@ else if (key.equals(userIdInKey)) { } combineMap.put("&", andList); } - + return this; } @@ -2457,7 +2501,7 @@ public String getWhereString(boolean hasPrefix) throws Exception { */ @JSONField(serialize = false) public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, String combine, List 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; @@ -2484,30 +2528,30 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map conditioinMap, String combine, boolean verifyName, boolean containRaw, boolean isHaving) throws Exception { - + String errPrefix = table + (isHaving ? ":{ @having:{ " : ":{ ") + "@combine:'" + combine + (isHaving ? "' } }" : "' }"); String s = StringUtil.getString(combine); if (s.startsWith(" ") || s.endsWith(" ") ) { throw new IllegalArgumentException(errPrefix + " 中字符 '" + s - + "' 不合法!不允许首尾有空格,也不允许连续空格!空格不能多也不能少!" - + "逻辑连接符 & | 左右必须各一个相邻空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!"); + + "' 不合法!不允许首尾有空格,也不允许连续空格!空格不能多也不能少!" + + "逻辑连接符 & | 左右必须各一个相邻空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!"); } - + if (conditioinMap == null) { conditioinMap = new HashMap<>(); } int size = conditioinMap.size(); - + int maxCount = isHaving ? getMaxHavingCount() : getMaxWhereCount(); if (maxCount > 0 && size > maxCount) { throw new IllegalArgumentException(table + (isHaving ? ":{ @having:{ " : ":{ ") + "key0:value0, key1:value1... " + combine + (isHaving ? " } }" : " }") + " 中条件 key:value 数量 " + size + " 已超过最大数量,必须在 0-" + maxCount + " 内!"); } - + String result = ""; List prepreadValues = getPreparedValueList(); - + Map usedKeyCountMap = new HashMap<>(size); int n = s.length(); @@ -2707,13 +2751,13 @@ else if (c == ')') { String andCond = ""; boolean isItemFirst = true; - + for (Entry entry : set) { String key = entry == null ? null : entry.getKey(); if (key == null || usedKeyCountMap.containsKey(key)) { continue; } - + String wi = isHaving ? getHavingItem(quote, table, alias, key, (String) entry.getValue(), containRaw) : getWhereItem(key, entry.getValue(), method, verifyName); if (StringUtil.isEmpty(wi, true)) {//避免SQL条件连接错误 continue; @@ -2722,23 +2766,23 @@ else if (c == ')') { andCond += (isItemFirst ? "" : AND) + "(" + wi + ")"; isItemFirst = false; } - + if (StringUtil.isEmpty(result, true)) { result = andCond; } else if (StringUtil.isNotEmpty(andCond, true)) { // andCond 必须放后面,否则 prepared 值顺序错误 - if (isHaving) { // HAVING 前 WHERE 已经有条件 ? 占位,不能反过来,想优化 AND 连接在最前,需要多遍历一次内部的 key,也可以 newSQLConfig 时存到 andList - result = "( " + result + " )" + AND + andCond; + if (isHaving) { // HAVING 前 WHERE 已经有条件 ? 占位,不能反过来,想优化 AND 连接在最前,需要多遍历一次内部的 key,也可以 newSQLConfig 时存到 andList + result = "( " + result + " )" + AND + andCond; } - else { - result = andCond + AND + "( " + result + " )"; // 先暂存之前的 prepared 值,然后反向整合 - if (n > 0) { - prepreadValues.addAll(getPreparedValueList()); - setPreparedValueList(prepreadValues); - } - } - } - + else { + result = andCond + AND + "( " + result + " )"; // 先暂存之前的 prepared 值,然后反向整合 + if (n > 0) { + prepreadValues.addAll(getPreparedValueList()); + setPreparedValueList(prepreadValues); + } + } + } + return result; } @@ -2807,7 +2851,7 @@ else if ("!".equals(ce.getKey())) { whereString += (isCombineFirst ? "" : AND) + (Logic.isNot(logic) ? NOT : "") + " ( " + cs + " ) "; isCombineFirst = false; } - + whereString = concatJoinWhereString(whereString); String s = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; @@ -2818,7 +2862,7 @@ else if ("!".equals(ce.getKey())) { return s; } - + protected String concatJoinWhereString(String whereString) throws Exception { List joinList = getJoinList(); @@ -3052,7 +3096,7 @@ public String getEqualString(String key, String column, Object value, String raw if (StringUtil.isName(column) == false) { throw new IllegalArgumentException(key + ":value 中key不合法!不支持 ! 以外的逻辑符 !"); } - + String logic = value == null && rawSQL == null ? (not ? SQL.IS_NOT : SQL.IS) : (not ? " != " : " = "); return getKey(column) + logic + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(key, column, value))); } @@ -3097,34 +3141,34 @@ protected Object getValue(String key, String column, Object value) { if (value == null) { return null; } - + Map castMap = getCast(); String type = key == null || castMap == null ? null : castMap.get(key); - -// if ("DATE".equalsIgnoreCase(type) && value instanceof Date == false) { -// value = value instanceof Number ? new Date(((Number) value).longValue()) : Date.valueOf((String) value); -// } -// else if ("TIME".equalsIgnoreCase(type) && value instanceof Time == false) { -// value = value instanceof Number ? new Time(((Number) value).longValue()) : Time.valueOf((String) value); -// } -// else if ("TIMESTAMP".equalsIgnoreCase(type) && value instanceof Timestamp == false) { -// value = value instanceof Number ? new Timestamp(((Number) value).longValue()) : Timestamp.valueOf((String) value); -// } -// else if ("ARRAY".equalsIgnoreCase(type) && value instanceof Array == false) { -// value = ((Collection) value).toArray(); -// } -// else if (StringUtil.isEmpty(type, true) == false) { -// preparedValueList.add(value); -// return "cast(?" + SQL.AS + type + ")"; -// } - + + // if ("DATE".equalsIgnoreCase(type) && value instanceof Date == false) { + // value = value instanceof Number ? new Date(((Number) value).longValue()) : Date.valueOf((String) value); + // } + // else if ("TIME".equalsIgnoreCase(type) && value instanceof Time == false) { + // value = value instanceof Number ? new Time(((Number) value).longValue()) : Time.valueOf((String) value); + // } + // else if ("TIMESTAMP".equalsIgnoreCase(type) && value instanceof Timestamp == false) { + // value = value instanceof Number ? new Timestamp(((Number) value).longValue()) : Timestamp.valueOf((String) value); + // } + // else if ("ARRAY".equalsIgnoreCase(type) && value instanceof Array == false) { + // value = ((Collection) value).toArray(); + // } + // else if (StringUtil.isEmpty(type, true) == false) { + // preparedValueList.add(value); + // return "cast(?" + SQL.AS + type + ")"; + // } + preparedValueList.add(value); return StringUtil.isEmpty(type, true) ? "?" : "cast(?" + SQL.AS + type + ")"; } - + return key == null ? getSQLValue(value) : getSQLValue(key, column, value); } - + public Object getSQLValue(String key, String column, @NotNull Object value) { Map castMap = getCast(); String type = key == null || castMap == null ? null : castMap.get(key); @@ -3214,7 +3258,7 @@ public String getSearchString(String key, String column, Object[] values, int ty public String getLikeString(@NotNull String key, @NotNull String column, String value) { String k = key.substring(0, key.length() - 1); char r = k.charAt(k.length() - 1); - + char l; if (r == '%' || r == '_' || r == '?') { k = k.substring(0, k.length() - 1); @@ -3230,7 +3274,7 @@ public String getLikeString(@NotNull String key, @NotNull String column, String else if (l > 0 && StringUtil.isName(String.valueOf(l))) { l = r; } - + if (l == '?') { l = 0; } @@ -3241,12 +3285,12 @@ else if (l > 0 && StringUtil.isName(String.valueOf(l))) { else { l = r = 0; } - + if (l > 0 || r > 0) { if (value == null) { throw new IllegalArgumentException(key + ":value 中 value 为 null!key$:value 中 value 不能为 null,且类型必须是 String !"); } - + value = value.replaceAll("\\\\", "\\\\\\\\"); value = value.replaceAll("\\%", "\\\\%"); value = value.replaceAll("\\_", "\\\\_"); @@ -3257,7 +3301,7 @@ else if (l > 0 && StringUtil.isName(String.valueOf(l))) { value = value + r; } } - + return getKey(column) + " LIKE " + getValue(key, column, value); } @@ -3460,59 +3504,56 @@ public String getRangeString(String key, String column, Object range, String raw } else if (range instanceof String) {//非Number类型需要客户端拼接成 < 'value0', >= 'value1'这种 String condition = ""; - String[] cs = rawSQL != null ? null : StringUtil.split((String) range, false); + String[] cs = rawSQL != null ? null : StringUtil.split((String) range, ";", false); if (rawSQL != null) { - int index = rawSQL == null ? -1 : rawSQL.indexOf("("); + int index = rawSQL.indexOf("("); condition = (index >= 0 && index < rawSQL.lastIndexOf(")") ? "" : getKey(k) + " ") + rawSQL; } - // 还是只支持整段为 Raw SQL 比较好 - // boolean appendRaw = false; - // if ("".equals(rawSQL)) { - // condition = rawSQL; - // cs = null; - // } - // else { - // if (rawSQL != null) { //先找出所有 rawSQL 的位置,然后去掉,再最后按原位置来拼接 - // String[] rs = StringUtil.split((String) range, rawSQL, false); - // - // if (rs != null && rs.length > 0) { - // String cond = ""; - // for (int i = 0; i < rs.length; i++) { - // cond += rs[i]; - // } - // range = cond; - // appendRaw = true; - // } - // } - // - // cs = StringUtil.split((String) range, false); - // } - if (cs != null) { - String c; - int index; + List raw = getRaw(); + boolean containRaw = raw == null ? false : raw.contains(key); + String lk = logic.isAnd() ? AND : OR; + for (int i = 0; i < cs.length; i++) {//对函数条件length(key)<=5这种不再在开头加key - c = cs[i]; - - if ("=null".equals(c)) { - c = SQL.isNull(); - } - else if ("!=null".equals(c)) { - c = SQL.isNull(false); + String expr = cs[i]; + + if (expr.length() > 100) { + throw new UnsupportedOperationException(key + ":value 的 value 中字符串 " + expr + " 不合法!" + + "不允许传超过 100 个字符的函数或表达式!请用 @raw 简化传参!"); } - else if (isPrepared() && (c.contains("--") || PATTERN_RANGE.matcher(c).matches() == false)) { - throw new UnsupportedOperationException(key + ":value 的 value 中 " + c + " 不合法!" - + "预编译模式下 key{}:\"condition\" 中 condition 必须 为 =null 或 !=null 或 符合正则表达式 " + PATTERN_RANGE + " !不允许连续减号 -- !不允许空格!"); + + int index = expr == null ? -1 : expr.indexOf("("); + if (index >= 0) { + expr = parseSQLExpression(key, expr, containRaw, false, key + ":\"!=null;+3*2<=10;function0(arg0,arg1,...)>1;function1(...)%5<=3...\""); + } + else { + String fk = getKey(k) + " "; + String[] ccs = StringUtil.split(expr, false); + expr = ""; + + for (int j = 0; j < ccs.length; j++) { + String c = ccs[j]; + if ("=null".equals(c)) { + c = SQL.isNull(); + } + else if ("!=null".equals(c)) { + c = SQL.isNull(false); + } + else if (isPrepared() && (c.contains("--") || PATTERN_RANGE.matcher(c).matches() == false)) { + throw new UnsupportedOperationException(key + ":value 的 value 中 " + c + " 不合法!" + + "预编译模式下 key{}:\"condition\" 中 condition 必须 为 =null 或 !=null 或 符合正则表达式 " + PATTERN_RANGE + " !不允许连续减号 -- !不允许空格!"); + } + + expr += (j <= 0 ? "" : lk) + fk + c; + } } - index = c == null ? -1 : c.indexOf("("); - condition += ((i <= 0 ? "" : (logic.isAnd() ? AND : OR)) //连接方式 - + (index >= 0 && index < c.lastIndexOf(")") ? "" : getKey(k) + " ") //函数和非函数条件 - + c); // 还是只支持整段为 Raw SQL 比较好 (appendRaw && index > 0 ? rawSQL : "") + c); //单个条件,如果有 Raw SQL 则按原来位置拼接 + condition += ((i <= 0 ? "" : lk) + expr); } } + if (condition.isEmpty()) { return ""; } @@ -3613,14 +3654,14 @@ public String getContainString(String key, String column, Object[] childs, int t if (c instanceof Collection) { throw new IllegalArgumentException(key + ":value 中 value 类型不能为 [JSONArray, Collection] 中的任何一个 !"); } - + Object path = ""; if (c instanceof Map) { path = ((Map) c).get("path"); if (path != null && path instanceof String == false) { throw new IllegalArgumentException(key + ":{ path:path, value:value } 中 path 类型错误,只能是 $, $.key1, $[0].key2 等符合 SQL 中 JSON 路径的 String !"); } - + c = ((Map) c).get("value"); if (c instanceof Collection || c instanceof Map) { throw new IllegalArgumentException(key + ":{ path:path, value:value } 中 value 类型不能为 [JSONObject, JSONArray, Collection, Map] 中的任何一个 !"); @@ -3644,7 +3685,7 @@ else if (isOracle()) { } } } - + if (condition.isEmpty()) { condition = getKey(column) + SQL.isNull(true) + OR + getLikeString(key, column, "[]"); // key = '[]' 无结果! } @@ -3652,7 +3693,7 @@ else if (isOracle()) { condition = getKey(column) + SQL.isNull(false) + AND + "(" + condition + ")"; } } - + if (condition.isEmpty()) { return ""; } @@ -3669,7 +3710,7 @@ public String getSubqueryString(Subquery subquery) throws Exception { if (subquery == null) { return ""; } - + String range = subquery.getRange(); SQLConfig cfg = subquery.getConfig(); @@ -3955,7 +3996,7 @@ private static String getConditionString(String column, String table, AbstractSQ // return table + " AS t0 INNER JOIN (SELECT id FROM " + condition + ") AS t1 ON t0.id = t1.id"; } - + private boolean keyPrefix; @Override public boolean isKeyPrefix() { @@ -3979,7 +4020,7 @@ public String getJoinString() throws Exception { // 主表不用别名 String ta; for (Join j : joinList) { onGetJoinString(j); - + if (j.isAppJoin()) { // APP JOIN,只是作为一个标记,执行完主表的查询后自动执行副表的查询 User.id IN($commentIdList) continue; } @@ -3998,9 +4039,9 @@ public String getJoinString() throws Exception { // jt = jt.toLowerCase(); // tn = tn.toLowerCase(); // } - + String sql; - + switch (type) { //前面已跳过 case "@": // APP JOIN // continue; @@ -4013,7 +4054,7 @@ public String getJoinString() throws Exception { sql = ( "<".equals(type) ? " LEFT" : (">".equals(type) ? " RIGHT" : " CROSS") ) + " JOIN ( " + jc.getSQL(isPrepared()) + " ) AS " + quote + jt + quote; sql = concatJoinOn(sql, quote, j, jt, onList); - + jc.setMain(false).setKeyPrefix(true); pvl.addAll(jc.getPreparedValueList()); @@ -4037,7 +4078,7 @@ public String getJoinString() throws Exception { + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" ); } - + SQLConfig oc = j.getOuterConfig(); String ow = null; if (oc != null) { @@ -4045,7 +4086,7 @@ public String getJoinString() throws Exception { oc.setPreparedValueList(new ArrayList<>()); oc.setMain(false).setKeyPrefix(true); ow = oc.getWhereString(false); - + pvl.addAll(oc.getPreparedValueList()); changed = true; } @@ -4064,7 +4105,7 @@ public String getJoinString() throws Exception { return StringUtil.isEmpty(joinOns, true) ? "" : joinOns + " \n"; } - + protected String concatJoinOn(@NotNull String sql, @NotNull String quote, @NotNull Join j, @NotNull String jt, List onList) { if (onList != null) { boolean first = true; @@ -4074,7 +4115,7 @@ protected String concatJoinOn(@NotNull String sql, @NotNull String quote, @NotNu if (isNot) { onJoinNotRelation(sql, quote, j, jt, onList, on); } - + String rt = on.getRelateType(); if (StringUtil.isEmpty(rt, false)) { sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + (isNot ? " != " : " = ") @@ -4082,29 +4123,29 @@ protected String concatJoinOn(@NotNull String sql, @NotNull String quote, @NotNu } else { onJoinComplextRelation(sql, quote, j, jt, onList, on); - + if (">=".equals(rt) || "<=".equals(rt) || ">".equals(rt) || "<".equals(rt)) { if (isNot) { throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath() + " 中 JOIN ON 条件关联逻辑符 " + rt + " 不合法! >, <, >=, <= 不支持与或非逻辑符 & | ! !"); } - + sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + " " + rt + " " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; } else if (rt.endsWith("$")) { String t = rt.substring(0, rt.length() - 1); char r = t.isEmpty() ? 0 : t.charAt(t.length() - 1); - + char l; if (r == '%' || r == '_' || r == '?') { t = t.substring(0, t.length() - 1); - + if (t.isEmpty()) { if (r == '?') { throw new IllegalArgumentException(on.getOriginKey() + ":value 中字符 " + on.getOriginKey() + " 不合法!key$:value 中不允许只有单独的 '?',必须和 '%', '_' 之一配合使用 !"); } - + l = r; } else { @@ -4120,7 +4161,7 @@ else if (l > 0 && StringUtil.isName(String.valueOf(l))) { l = r; } } - + if (l == '?') { l = 0; } @@ -4131,7 +4172,7 @@ else if (l > 0 && StringUtil.isName(String.valueOf(l))) { else { l = r = 0; } - + if (l <= 0 && r <= 0) { sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + (isNot ? NOT : "") + " LIKE " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; @@ -4201,7 +4242,7 @@ else if ("{}".equals(rt) || "<>".equals(rt)) { arrKeyPath = quote + jt + quote + "." + quote + on.getKey() + quote; itemKeyPath = quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; } - + if (isPostgreSQL()) { //operator does not exist: jsonb @> character varying "[" + c + "]"); sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath + " IS NOT NULL AND " + arrKeyPath + " @> " + itemKeyPath) + (isNot ? ") " : ""); @@ -4230,11 +4271,11 @@ else if (isClickHouse()) { + " 中 JOIN ON 条件关联类型 " + rt + " 不合法!只支持 =, >, <, >=, <=, !=, $, ~, {}, <> 这几种!"); } } - + first = false; } } - + return sql; } @@ -4244,7 +4285,7 @@ protected void onJoinNotRelation(String sql, String quote, Join j, String jt, Li protected void onJoinComplextRelation(String sql, String quote, Join j, String jt, List onList, On on) { throw new UnsupportedOperationException("JOIN 已禁用 $, ~, {}, <>, >, <, >=, <= 等复杂关联 !性能很差、需求极少,默认只允许 = 等价关联,如要取消禁用可在后端重写相关方法!"); } - + protected void onGetJoinString(Join j) throws UnsupportedOperationException { } protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { @@ -4364,7 +4405,7 @@ else if (id instanceof Subquery) {} } //对 userId 和 userId{} 处理,这两个一定会作为条件 - Object userIdIn = request.get(userIdInKey); //可能是 userId{}:">0" + Object userIdIn = userIdInKey.equals(idInKey) ? null : request.get(userIdInKey); //可能是 userId{}:">0" if (userIdIn instanceof Collection) { // 排除掉 0, 负数, 空字符串 等无效 userId 值 Collection userIds = (Collection) userIdIn; List newUserIdIn = new ArrayList<>(); @@ -4380,8 +4421,8 @@ else if (id instanceof Subquery) {} } userIdIn = newUserIdIn; } - - Object userId = request.get(userIdKey); + + Object userId = userIdKey.equals(idKey) ? null : request.get(userIdKey); if (userId != null) { //null无效 if (userId instanceof Number) { if (((Number) userId).longValue() <= 0) { //一定没有值 @@ -4397,7 +4438,7 @@ else if (userId instanceof Subquery) {} else { throw new IllegalArgumentException(userIdKey + ":value 中 value 的类型只能是 Long , String 或 Subquery !"); } - + if (userIdIn instanceof Collection) { //共用userIdIn场景少性能差 boolean contains = false; Collection userIds = (Collection) userIdIn; @@ -4412,10 +4453,10 @@ else if (userId instanceof Subquery) {} } } } - + //对 id, id{}, userId, userId{} 处理,这些只要不为 null 就一定会作为 AND 条件 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - + + String role = request.getString(KEY_ROLE); String cache = request.getString(KEY_CACHE); Subquery from = (Subquery) request.get(KEY_FROM); @@ -4454,8 +4495,8 @@ else if (userId instanceof Subquery) {} request.remove(KEY_ORDER); request.remove(KEY_RAW); request.remove(KEY_JSON); - - + + // @null <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< String[] nullKeys = StringUtil.split(nulls); if (nullKeys != null && nullKeys.length > 0) { @@ -4466,12 +4507,12 @@ else if (userId instanceof Subquery) {} if (request.get(nk) != null) { throw new IllegalArgumentException(table + ":{ @null: value } 中的字符 '" + nk + "' 已在当前对象有非 null 值!不允许对同一个 JSON key 设置不同值!"); } - + request.put(nk, null); } } // @null >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - + // @cast <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< String[] casts = StringUtil.split(cast); Map castMap = null; @@ -4479,7 +4520,7 @@ else if (userId instanceof Subquery) {} castMap = new HashMap<>(casts.length); for (String c : casts) { apijson.orm.Entry p = Pair.parseEntry(c); - + if (StringUtil.isEmpty(p.getKey(), true)) { throw new IllegalArgumentException(table + ":{} 里的 @cast: 'key0:type0,key1:type1..' 中 '" + c + "' 对应的 key 的字符 '" + p.getKey() + "' 不合法!不允许为空!"); } @@ -4489,13 +4530,13 @@ else if (userId instanceof Subquery) {} if (castMap.get(p.getKey()) != null) { throw new IllegalArgumentException(table + ":{} 里的 @cast: 'key0:type0,key1:type1..' 中 '" + c + "' 对应的 key 的字符 '" + p.getKey() + "' 已存在!不允许重复设置类型!"); } - + castMap.put(p.getKey(), p.getValue()); } } // @cast >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - + String[] rawArr = StringUtil.split(raw); config.setRaw(rawArr == null || rawArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(rawArr))); @@ -4544,36 +4585,40 @@ else if (userId instanceof Subquery) {} final boolean isWhere = method != PUT; //除了POST,PUT,其它全是条件!!! //条件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - List whereList = null; - String[] ws = StringUtil.split(combine); if (ws != null && (method == DELETE || method == GETS || method == HEADS)) { throw new IllegalArgumentException(table + ":{} 里的 @combine:value 不合法!DELETE,GETS,HEADS 请求不允许传 @combine:value !"); } - + String combineExpr = ws == null || ws.length != 1 ? null : ws[0]; - + Map> combineMap = new LinkedHashMap<>(); List andList = new ArrayList<>(); List orList = new ArrayList<>(); List notList = new ArrayList<>(); + List whereList = new ArrayList<>(); + //强制作为条件且放在最前面优化性能 if (id != null) { tableWhere.put(idKey, id); andList.add(idKey); + whereList.add(idKey); } if (idIn != null) { tableWhere.put(idInKey, idIn); andList.add(idInKey); + whereList.add(idInKey); } if (userId != null) { tableWhere.put(userIdKey, userId); andList.add(userIdKey); + whereList.add(userIdKey); } if (userIdIn != null) { tableWhere.put(userIdInKey, userIdIn); andList.add(userIdInKey); + whereList.add(userIdInKey); } if (StringUtil.isNotEmpty(combineExpr, true)) { @@ -4585,7 +4630,7 @@ else if (userId instanceof Subquery) {} if (index < 0) { break; } - + char left = index <= 0 ? ' ' : str.charAt(index - 1); char right = index >= str.length() - key.length() ? ' ' : str.charAt(index + key.length()); if ((left == ' ' || left == '(' || left == '&' || left == '|' || left == '!') && (right == ' ' || right == ')')) { @@ -4602,11 +4647,8 @@ else if (userId instanceof Subquery) {} } } else if (ws != null) { - whereList = new ArrayList<>(); - - String w; for (int i = 0; i < ws.length; i++) { //去除 &,|,! 前缀 - w = ws[i]; + String w = ws[i]; if (w != null) { if (w.startsWith("&")) { w = w.substring(1); @@ -4667,17 +4709,15 @@ else if (w.startsWith("!")) { //解决AccessVerifier新增userId没有作为条件,而是作为内容,导致PUT,DELETE出错 if (isWhere || (StringUtil.isName(key.replaceFirst("[+-]$", "")) == false)) { tableWhere.put(key, value); - if (whereList == null || whereList.contains(key) == false) { - if (andList != null) { - andList.add(key); - } + if (whereList.contains(key) == false) { + andList.add(key); } } - else if (whereList != null && whereList.contains(key)) { + else if (whereList.contains(key)) { tableWhere.put(key, value); } else { - tableContent.put(key, value);//一样 instanceof JSONArray ? JSON.toJSONString(value) : value); + tableContent.put(key, value); //一样 instanceof JSONArray ? JSON.toJSONString(value) : value); } } @@ -4751,19 +4791,19 @@ else if (whereList != null && whereList.contains(key)) { } } } - - + + // @having, @haivng& <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Object newHaving = having; boolean isHavingAnd = false; - + Map 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; } @@ -4776,7 +4816,7 @@ else if (whereList != null && whereList.contains(key)) { 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(")"); @@ -4784,7 +4824,7 @@ else if (whereList != null && whereList.contains(key)) { 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++) { @@ -4841,7 +4881,7 @@ else if (newHaving != null) { + "@having:value 中 value 只能是 String 或 JSONObject,@having&:value 中 value 只能是 String !"); } // @having, @haivng& >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - + config.setExplain(explain); config.setCache(getCache(cache)); @@ -4881,7 +4921,7 @@ else if (newHaving != null) { if (userIdIn != null) { request.put(userIdInKey, userIdIn); } - + // 关键词 request.put(KEY_DATABASE, database); request.put(KEY_ROLE, role); @@ -4974,7 +5014,7 @@ LEFT JOIN ( SELECT count(*) AS count FROM sys.Comment ) AS Comment ON Comment.m column.add(on.getKey()); } } - + joinConfig.setMethod(GET); // 子查询不能为 SELECT count(*) ,而应该是 SELECT momentId joinConfig.setColumn(column); // 优化性能,不取非必要的字段 @@ -5044,7 +5084,7 @@ else if (c == '?') { throw new IllegalArgumentException(originKey + ":value 中字符 " + originKey + " 不合法!key$:value 中不允许只有单独的 '?',必须和 '%', '_' 之一配合使用 !"); } } - + key = k; } else if (key.endsWith("~")) {//匹配正则表达式 REGEXP,查询时处理 @@ -5095,7 +5135,7 @@ else if (key.endsWith("-")) {//缩减,PUT查询时处理 } //TODO if (key.endsWith("-")) { // 表示 key 和 value 顺序反过来: value LIKE key - + String last = null;//不用Logic优化代码,否则 key 可能变为 key| 导致 key=value 变成 key|=value 而出错 if (RequestMethod.isQueryMethod(method)) {//逻辑运算符仅供GET,HEAD方法使用 last = key.isEmpty() ? "" : key.substring(key.length() - 1); From 1e5e587eaa48d5d356b80de1790fae3eef77d675 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Mar 2022 06:32:20 +0800 Subject: [PATCH 041/619] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81=20?= =?UTF-8?q?@having:"match(arg0..)AGAINST(..)%2=3D1"=20=E5=85=A8=E6=96=87?= =?UTF-8?q?=E6=A3=80=E7=B4=A2=E7=AD=89=E5=87=BD=E6=95=B0=E5=90=8E=E5=B8=A6?= =?UTF-8?q?=E6=95=B0=E5=AD=A6=E8=A1=A8=E8=BE=BE=E5=BC=8F=EF=BC=9B=E5=AF=B9?= =?UTF-8?q?=20key{}:">0;length(key)<=3D5"=20=E6=96=B0=E5=A2=9E=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E9=83=A8=E5=88=86=E4=B8=BA=20RAW=20SQL=EF=BC=9B?= =?UTF-8?q?=E7=A6=81=E6=AD=A2=20@having:"fun(arg0..):alias"=20=E8=BF=99?= =?UTF-8?q?=E6=A0=B7=E4=BD=BF=E7=94=A8=E5=88=AB=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 88 +++++++++++-------- .../src/main/java/apijson/orm/SQLConfig.java | 1 + 2 files changed, 50 insertions(+), 39 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 21fe2efce..310e75dc6 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1230,18 +1230,12 @@ public String getHavingString(boolean hasPrefix) throws Exception { return (hasPrefix ? " HAVING " : "") + StringUtil.concat(havingString, joinHaving, AND); } - protected String getHavingItem(String quote, String table, String alias, String key, String expression, boolean containRaw) { + protected String getHavingItem(String quote, String table, String alias, String key, String expression, boolean containRaw) throws Exception { //fun(arg0,arg1,...) if (containRaw) { - try { - String rawSQL = getRawSQL(KEY_HAVING, expression); - if (rawSQL != null) { - return rawSQL; - } - } catch (Exception e) { - Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { " - + " String rawSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, fk); ... " - + "} catch (Exception e) = " + e.getMessage()); + String rawSQL = getRawSQL(KEY_HAVING, expression); + if (rawSQL != null) { + return rawSQL; } } @@ -1460,6 +1454,17 @@ public SQLConfig setRaw(List raw) { */ @Override public String getRawSQL(String key, Object value) throws Exception { + return getRawSQL(key, value, false); + } + /**获取原始 SQL 片段 + * @param key + * @param value + * @param throwWhenMissing + * @return + * @throws Exception + */ + @Override + public String getRawSQL(String key, Object value, boolean throwWhenMissing) throws Exception { if (value == null) { return null; } @@ -1474,11 +1479,12 @@ public String getRawSQL(String key, Object value) throws Exception { String rawSQL = containRaw ? RAW_MAP.get(value) : null; if (containRaw) { if (rawSQL == null) { - throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" - + "对应的 " + key + ":value 中 value 值 " + value + " 未在后端 RAW_MAP 中配置 !"); + if (throwWhenMissing) { + throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" + + "对应的 " + key + ":value 中 value 值 " + value + " 未在后端 RAW_MAP 中配置 !"); + } } - - if (rawSQL.isEmpty()) { + else if (rawSQL.isEmpty()) { return (String) value; } } @@ -1824,14 +1830,14 @@ public String parseSQLExpression(String key, String expression, boolean containR String s2 = expression.substring(keyIndex + 1); // OVER 后半部分 int index1 = s1.indexOf("("); // 函数 "(" 的起始位置 - String fun = s1.substring(0, index1); // 函数名称 int end = s2.lastIndexOf(")"); // 后半部分 “)” 的位置 - if (index1 >= end) { + if (index1 >= end + s1.length()) { throw new IllegalArgumentException("字符 " + expression + " 不合法!" + key + ":value 中 value 里的 SQL 函数必须为 function(arg0,arg1,...) 这种格式!"); } + String fun = s1.substring(0, index1); // 函数名称 if (fun.isEmpty() == false) { if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { if (StringUtil.isName(fun) == false) { @@ -1851,11 +1857,26 @@ else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { int index2 = s2.indexOf("("); // 后半部分 “(”的起始位置 String argString2 = s2.substring(index2 + 1, end); // 后半部分的参数 // 别名 - String alias = allowAlias == false || s2.lastIndexOf(":") < s2.lastIndexOf(")") ? null : s2.substring(s2.lastIndexOf(":") + 1); + int aliasIndex = allowAlias == false ? -1 : s2.lastIndexOf(":"); + String alias = aliasIndex < 0 ? "" : s2.substring(aliasIndex + 1); + if (alias.isEmpty() == false && StringUtil.isName(alias) == false) { + throw new IllegalArgumentException("字符串 " + alias + " 不合法!预编译模式下 " + + key + ":value 中 value里面用 ; 分割的每一项" + + " function(arg0,arg1,...):alias 中 alias 必须是1个单词!并且不要有多余的空格!"); + } + + String suffix = s2.substring(end + 1, aliasIndex < 0 ? s2.length() : aliasIndex); + if (suffix.isEmpty() == false && (((String) suffix).contains("--") || ((String) suffix).contains("/*") + || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { + throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!预编译模式下 " + key + + ":\"column?value;function(arg0,arg1,...)?value...\"" + + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); + } + // 获取后半部分的参数解析 (agr0 agr1 ...) String argsString2[] = parseArgsSplitWithComma(argString2, false, containRaw, allowAlias); expression = fun + "(" + StringUtil.getString(agrsString1) + (containOver ? ") OVER (" : ") AGAINST (") // 传参不传空格,拼接带空格 - + StringUtil.getString(argsString2) + ")" + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); } + + StringUtil.getString(argsString2) + ")" + suffix + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); } } return expression; @@ -3016,8 +3037,6 @@ protected String getWhereItem(String key, Object value, RequestMethod method, bo throw new IllegalArgumentException(TAG + ".getWhereItem: 字符 " + key + " 不合法!"); } - // 原始 SQL 片段 - String rawSQL = getRawSQL(key, value); int keyType; if (key.endsWith("$")) { @@ -3054,6 +3073,9 @@ else if (key.endsWith("<")) { } String column = getRealKey(method, key, false, true, verifyName); + + // 原始 SQL 片段 + String rawSQL = getRawSQL(key, value, keyType != 4 || value instanceof String == false); switch (keyType) { case 1: @@ -4747,15 +4769,9 @@ else if (whereList.contains(key)) { boolean containColumnRaw = rawList != null && rawList.contains(KEY_COLUMN); String rawColumnSQL = null; if (containColumnRaw) { - try { - rawColumnSQL = config.getRawSQL(KEY_COLUMN, column); - if (rawColumnSQL != null) { - cs.add(rawColumnSQL); - } - } catch (Exception e) { - Log.e(TAG, "newSQLConfig config instanceof AbstractSQLConfig >> try { " - + " rawColumnSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, column); " - + "} catch (Exception e) = " + e.getMessage()); + rawColumnSQL = config.getRawSQL(KEY_COLUMN, column); + if (rawColumnSQL != null) { + cs.add(rawColumnSQL); } } @@ -4766,16 +4782,10 @@ else if (whereList.contains(key)) { String[] ks; for (String fk : fks) { if (containColumnRaw) { - try { - String rawSQL = config.getRawSQL(KEY_COLUMN, fk); - if (rawSQL != null) { - cs.add(rawSQL); - continue; - } - } catch (Exception e) { - Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { " - + " String rawSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, fk); ... " - + "} catch (Exception e) = " + e.getMessage()); + String rawSQL = config.getRawSQL(KEY_COLUMN, fk); + if (rawSQL != null) { + cs.add(rawSQL); + continue; } } diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index 57e282d51..4db5a7520 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -244,6 +244,7 @@ public interface SQLConfig { String getWhereString(boolean hasPrefix) throws Exception; String getRawSQL(String key, Object value) throws Exception; + String getRawSQL(String key, Object value, boolean throwWhenMissing) throws Exception; boolean isKeyPrefix(); From d7c311554042620a91faf8f4a63ebcf9a2fd0be2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Mar 2022 20:34:02 +0800 Subject: [PATCH 042/619] =?UTF-8?q?=E6=8B=BC=E9=94=99=E5=8D=95=E8=AF=8D=20?= =?UTF-8?q?globle=20=E7=BA=A0=E6=AD=A3=E4=B8=BA=20global?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apijson/orm/AbstractObjectParser.java | 20 ++-- .../main/java/apijson/orm/AbstractParser.java | 94 +++++++++---------- .../src/main/java/apijson/orm/Parser.java | 14 +-- .../src/main/java/apijson/orm/Verifier.java | 2 +- 4 files changed, 65 insertions(+), 65 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 003aee026..3f3a09a04 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -332,22 +332,22 @@ else if (sqlConfig.isClickHouse()) { } if (isTable) { - if (parser.getGlobleDatabase() != null && sqlRequest.get(JSONRequest.KEY_DATABASE) == null) { - sqlRequest.put(JSONRequest.KEY_DATABASE, parser.getGlobleDatabase()); + if (parser.getGlobalDatabase() != null && sqlRequest.get(JSONRequest.KEY_DATABASE) == null) { + sqlRequest.put(JSONRequest.KEY_DATABASE, parser.getGlobalDatabase()); } - if (parser.getGlobleSchema() != null && sqlRequest.get(JSONRequest.KEY_SCHEMA) == null) { - sqlRequest.put(JSONRequest.KEY_SCHEMA, parser.getGlobleSchema()); + if (parser.getGlobalSchema() != null && sqlRequest.get(JSONRequest.KEY_SCHEMA) == null) { + sqlRequest.put(JSONRequest.KEY_SCHEMA, parser.getGlobalSchema()); } - if (parser.getGlobleDatasource() != null && sqlRequest.get(JSONRequest.KEY_DATASOURCE) == null) { - sqlRequest.put(JSONRequest.KEY_DATASOURCE, parser.getGlobleDatasource()); + if (parser.getGlobalDatasource() != null && sqlRequest.get(JSONRequest.KEY_DATASOURCE) == null) { + sqlRequest.put(JSONRequest.KEY_DATASOURCE, parser.getGlobalDatasource()); } if (isSubquery == false) { //解决 SQL 语法报错,子查询不能 EXPLAIN - if (parser.getGlobleExplain() != null && sqlRequest.get(JSONRequest.KEY_EXPLAIN) == null) { - sqlRequest.put(JSONRequest.KEY_EXPLAIN, parser.getGlobleExplain()); + if (parser.getGlobalExplain() != null && sqlRequest.get(JSONRequest.KEY_EXPLAIN) == null) { + sqlRequest.put(JSONRequest.KEY_EXPLAIN, parser.getGlobalExplain()); } - if (parser.getGlobleCache() != null && sqlRequest.get(JSONRequest.KEY_CACHE) == null) { - sqlRequest.put(JSONRequest.KEY_CACHE, parser.getGlobleCache()); + if (parser.getGlobalCache() != null && sqlRequest.get(JSONRequest.KEY_CACHE) == null) { + sqlRequest.put(JSONRequest.KEY_CACHE, parser.getGlobalCache()); } } } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 25e851bf0..f56af8cc8 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -187,69 +187,69 @@ public AbstractParser setRequest(JSONObject request) { return this; } - protected Boolean globleFormat; - public AbstractParser setGlobleFormat(Boolean globleFormat) { - this.globleFormat = globleFormat; + protected Boolean globalFormat; + public AbstractParser setGlobalFormat(Boolean globalFormat) { + this.globalFormat = globalFormat; return this; } @Override - public Boolean getGlobleFormat() { - return globleFormat; + public Boolean getGlobalFormat() { + return globalFormat; } - protected String globleRole; - public AbstractParser setGlobleRole(String globleRole) { - this.globleRole = globleRole; + protected String globalRole; + public AbstractParser setGlobalRole(String globalRole) { + this.globalRole = globalRole; return this; } @Override - public String getGlobleRole() { - return globleRole; + public String getGlobalRole() { + return globalRole; } - protected String globleDatabase; - public AbstractParser setGlobleDatabase(String globleDatabase) { - this.globleDatabase = globleDatabase; + protected String globalDatabase; + public AbstractParser setGlobalDatabase(String globalDatabase) { + this.globalDatabase = globalDatabase; return this; } @Override - public String getGlobleDatabase() { - return globleDatabase; + public String getGlobalDatabase() { + return globalDatabase; } - protected String globleSchema; - public AbstractParser setGlobleSchema(String globleSchema) { - this.globleSchema = globleSchema; + protected String globalSchema; + public AbstractParser setGlobalSchema(String globalSchema) { + this.globalSchema = globalSchema; return this; } @Override - public String getGlobleSchema() { - return globleSchema; + public String getGlobalSchema() { + return globalSchema; } - protected String globleDatasource; + protected String globalDatasource; @Override - public String getGlobleDatasource() { - return globleDatasource; + public String getGlobalDatasource() { + return globalDatasource; } - public AbstractParser setGlobleDatasource(String globleDatasource) { - this.globleDatasource = globleDatasource; + public AbstractParser setGlobalDatasource(String globalDatasource) { + this.globalDatasource = globalDatasource; return this; } - protected Boolean globleExplain; - public AbstractParser setGlobleExplain(Boolean globleExplain) { - this.globleExplain = globleExplain; + protected Boolean globalExplain; + public AbstractParser setGlobalExplain(Boolean globalExplain) { + this.globalExplain = globalExplain; return this; } @Override - public Boolean getGlobleExplain() { - return globleExplain; + public Boolean getGlobalExplain() { + return globalExplain; } - protected String globleCache; - public AbstractParser setGlobleCache(String globleCache) { - this.globleCache = globleCache; + protected String globalCache; + public AbstractParser setGlobalCache(String globalCache) { + this.globalCache = globalCache; return this; } @Override - public String getGlobleCache() { - return globleCache; + public String getGlobalCache() { + return globalCache; } @Override @@ -383,9 +383,9 @@ public JSONObject parseResponse(JSONObject request) { } //必须在parseCorrectRequest后面,因为parseCorrectRequest可能会添加 @role - if (isNeedVerifyRole() && globleRole == null) { + if (isNeedVerifyRole() && globalRole == null) { try { - setGlobleRole(requestObject.getString(JSONRequest.KEY_ROLE)); + setGlobalRole(requestObject.getString(JSONRequest.KEY_ROLE)); requestObject.remove(JSONRequest.KEY_ROLE); } catch (Exception e) { return extendErrorResult(requestObject, e, requestMethod, getRequestURL(), isRoot); @@ -393,12 +393,12 @@ public JSONObject parseResponse(JSONObject request) { } try { - setGlobleFormat(requestObject.getBoolean(JSONRequest.KEY_FORMAT)); - setGlobleDatabase(requestObject.getString(JSONRequest.KEY_DATABASE)); - setGlobleSchema(requestObject.getString(JSONRequest.KEY_SCHEMA)); - setGlobleDatasource(requestObject.getString(JSONRequest.KEY_DATASOURCE)); - setGlobleExplain(requestObject.getBoolean(JSONRequest.KEY_EXPLAIN)); - setGlobleCache(requestObject.getString(JSONRequest.KEY_CACHE)); + setGlobalFormat(requestObject.getBoolean(JSONRequest.KEY_FORMAT)); + setGlobalDatabase(requestObject.getString(JSONRequest.KEY_DATABASE)); + setGlobalSchema(requestObject.getString(JSONRequest.KEY_SCHEMA)); + setGlobalDatasource(requestObject.getString(JSONRequest.KEY_DATASOURCE)); + setGlobalExplain(requestObject.getBoolean(JSONRequest.KEY_EXPLAIN)); + setGlobalCache(requestObject.getString(JSONRequest.KEY_CACHE)); requestObject.remove(JSONRequest.KEY_FORMAT); requestObject.remove(JSONRequest.KEY_DATABASE); @@ -435,7 +435,7 @@ public JSONObject parseResponse(JSONObject request) { requestObject = error == null ? extendSuccessResult(requestObject, isRoot) : extendErrorResult(requestObject, error, requestMethod, getRequestURL(), isRoot); - JSONObject res = (globleFormat != null && globleFormat) && JSONResponse.isSuccess(requestObject) ? new JSONResponse(requestObject) : requestObject; + JSONObject res = (globalFormat != null && globalFormat) && JSONResponse.isSuccess(requestObject) ? new JSONResponse(requestObject) : requestObject; long endTime = System.currentTimeMillis(); long duration = endTime - startTime; @@ -493,8 +493,8 @@ public void onVerifyRole(@NotNull SQLConfig config) throws Exception { if (isNeedVerifyRole()) { if (config.getRole() == null) { - if (globleRole != null) { - config.setRole(globleRole); + if (globalRole != null) { + config.setRole(globalRole); } else { config.setRole(getVisitor().getId() == null ? AbstractVerifier.UNKNOWN : AbstractVerifier.LOGIN); } @@ -548,7 +548,7 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers JSONObject target = wrapRequest(method, tag, object, true); //JSONObject clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {} - return getVerifier().verifyRequest(method, name, target, request, maxUpdateCount, getGlobleDatabase(), getGlobleSchema(), creator); + return getVerifier().verifyRequest(method, name, target, request, maxUpdateCount, getGlobalDatabase(), getGlobalSchema(), creator); } diff --git a/APIJSONORM/src/main/java/apijson/orm/Parser.java b/APIJSONORM/src/main/java/apijson/orm/Parser.java index 135dc1c7a..db98ea1f5 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Parser.java +++ b/APIJSONORM/src/main/java/apijson/orm/Parser.java @@ -118,13 +118,13 @@ JSONObject parseCorrectRequest(RequestMethod method, String tag, int version, St Verifier getVerifier(); - Boolean getGlobleFormat(); - String getGlobleRole(); - String getGlobleDatabase(); - String getGlobleSchema(); - String getGlobleDatasource(); - Boolean getGlobleExplain(); - String getGlobleCache(); + Boolean getGlobalFormat(); + String getGlobalRole(); + String getGlobalDatabase(); + String getGlobalSchema(); + String getGlobalDatasource(); + Boolean getGlobalExplain(); + String getGlobalCache(); int getTransactionIsolation(); diff --git a/APIJSONORM/src/main/java/apijson/orm/Verifier.java b/APIJSONORM/src/main/java/apijson/orm/Verifier.java index 76f142fc5..366adb070 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Verifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/Verifier.java @@ -74,7 +74,7 @@ public interface Verifier { * @throws Exception */ JSONObject verifyRequest(RequestMethod method, String name, JSONObject target, JSONObject request, - int maxUpdateCount, String globleDatabase, String globleSchema, SQLCreator creator) throws Exception; + int maxUpdateCount, String globalDatabase, String globalSchema, SQLCreator creator) throws Exception; /**验证返回结果的数据和结构 * @param table From d803766fc4880c59375218819356c20b3d4b86f7 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Mar 2022 22:50:21 +0800 Subject: [PATCH 043/619] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E8=87=AA=E8=BA=AB,?= =?UTF-8?q?=20fastjson=20=E7=89=88=E6=9C=AC=E5=88=86=E5=88=AB=E4=B8=BA=205?= =?UTF-8?q?.0.0,=201.2.79?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 5 ++--- APIJSONORM/src/main/java/apijson/Log.java | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 377ecc644..edd10dfb9 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.9.0 + 5.0.0 jar APIJSONORM @@ -18,11 +18,10 @@ - com.alibaba fastjson - 1.2.74 + 1.2.79 javax.activation diff --git a/APIJSONORM/src/main/java/apijson/Log.java b/APIJSONORM/src/main/java/apijson/Log.java index f97fae342..9c2c60a98 100755 --- a/APIJSONORM/src/main/java/apijson/Log.java +++ b/APIJSONORM/src/main/java/apijson/Log.java @@ -14,7 +14,7 @@ public class Log { public static boolean DEBUG = true; - public static final String VERSION = "4.9.0"; + public static final String VERSION = "5.0.0"; public static final String KEY_SYSTEM_INFO_DIVIDER = "---|-----APIJSON SYSTEM INFO-----|---"; //默认的时间格式 From e609c8682cedbb9b39c5cec6b252cda8cfc2cdb4 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 5 Apr 2022 16:56:48 +0800 Subject: [PATCH 044/619] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=85=B3=E9=97=AD?= =?UTF-8?q?=E6=9D=83=E9=99=90=E6=A0=A1=E9=AA=8C=E6=97=B6=20POST=20?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E4=BC=A0=20userId=20=E6=97=A0=E6=95=88?= =?UTF-8?q?=EF=BC=8C=E5=8A=A0=E5=BC=BA=E5=AF=B9=20POST=20=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E5=86=85=E5=AD=97=E6=AE=B5=E6=A0=BC=E5=BC=8F=E7=9A=84?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 310e75dc6..6698e4f4b 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -4568,10 +4568,20 @@ else if (userId instanceof Subquery) {} Set set = request.keySet(); //前面已经判断request是否为空 if (method == POST) { //POST操作 if (idIn != null) { - throw new IllegalArgumentException(table + ":{} 里的 " + idInKey + ": value 不合法!POST 请求中不允许传 " + idInKey + " !"); - } + throw new IllegalArgumentException(table + ":{" + idInKey + ": value} 里的 key 不合法!POST 请求中不允许传 " + idInKey + + " 这种非字段命名 key !必须为 英文字母 开头且只包含 英文字母、数字、下划线的 字段命名!"); } + if (userIdIn != null) { + throw new IllegalArgumentException(table + ":{" + userIdInKey + ": value} 里的 key 不合法!POST 请求中不允许传 " + userIdInKey + + " 这种非字段命名 key !必须为 英文字母 开头且只包含 英文字母、数字、下划线的 字段命名!"); } if (set != null && set.isEmpty() == false) { //不能直接return,要走完下面的流程 + for (String k : set) { + if (StringUtil.isName(k) == false) { + throw new IllegalArgumentException(table + ":{" + k + ": value} 里的 key 不合法!POST 请求中不允许传 " + k + + " 这种非字段命名 key !必须为 英文字母 开头且只包含 英文字母、数字、下划线的 字段命名!"); + } + } + String[] columns = set.toArray(new String[]{}); Collection valueCollection = request.values(); @@ -4581,24 +4591,25 @@ else if (userId instanceof Subquery) {} throw new Exception("服务器内部错误:\n" + TAG + " newSQLConfig values == null || values.length != columns.length !"); } - column = (id == null ? "" : idKey + ",") + StringUtil.getString(columns); //set已经判断过不为空 + + column = (id == null ? "" : idKey + ",") + (userId == null ? "" : userIdKey + ",") + StringUtil.getString(columns); //set已经判断过不为空 - List> valuess = new ArrayList<>(1); - List items; //(item0, item1, ...) - if (id == null) { //数据库自增 id - items = Arrays.asList(values); //FIXME 是否还需要进行 add 或 remove 操作?Arrays.ArrayList 不允许修改,会抛异常 - } - else { - int size = columns.length + (id == null ? 0 : 1); //以key数量为准 + int idCount = id == null ? (userId == null ? 0 : 1) : (userId == null ? 1 : 2); + int size = idCount + columns.length; // 以 key 数量为准 - items = new ArrayList<>(size); - items.add(id); //idList.get(i)); //第0个就是id + List items = new ArrayList<>(size); // VALUES(item0, item1, ...) + if (id != null) { + items.add(id); // idList.get(i)); // 第 0 个就是 id + } + if (userId != null) { + items.add(userId); // idList.get(i)); // 第 1 个就是 userId + } - for (int j = 1; j < size; j++) { - items.add(values[j-1]); //从第1个开始,允许"null" - } + for (int j = 0; j < values.length; j++) { + items.add(values[j]); // 从第 1 个开始,允许 "null" } + List> valuess = new ArrayList<>(1); valuess.add(items); config.setValues(valuess); } From eb3e5189f6c72b408902b0de25974d62f746fdb6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 5 Apr 2022 17:17:17 +0800 Subject: [PATCH 045/619] =?UTF-8?q?=E6=96=87=E6=A1=A3=EF=BC=9A=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=20@combine=20=E6=9D=A1=E4=BB=B6=E7=BB=84=E5=90=88?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E4=B8=BA=205.0+=20=E7=9A=84=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6=E4=BB=BB=E6=84=8F=E7=BB=84=E5=90=88=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 46c090233..09d88f847 100644 --- a/Document.md +++ b/Document.md @@ -418,6 +418,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-数据、总数和分页详情
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑬ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑫ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"key0 \| (key1 & (key2 \| !key3))...",条件组合方式,最终按
(其它key条件 AND 连接) AND (key0条件 OR (key1条件 AND (key2条件 OR (NOT key3条件))))
这种方式连接,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑬ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~ \| tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~%20%7C%20tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑫ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

① "tag":String,后面的 tag 是非 GET、HEAD 请求中匹配请求的 JSON 结构的标识,一般是要查询的 Table 的名称或该名称对应的数组 Table[] 或 Table:[],由后端 Request 表中指定。

② "version":Integer,接口版本,version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本,由后端 Request 表中指定。

③ "format":Boolean,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22version%22:1,%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})
From df979db60bbdff1137d4835187797f0195901a87 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 5 Apr 2022 17:25:25 +0800 Subject: [PATCH 046/619] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 09d88f847..12bac1aa6 100644 --- a/Document.md +++ b/Document.md @@ -418,6 +418,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-数据、总数和分页详情
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"key0 \| (key1 & (key2 \| !key3))...",条件组合方式,最终按
(其它key条件 AND 连接) AND (key0条件 OR (key1条件 AND (key2条件 OR (NOT key3条件))))
这种方式连接,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑬ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~ \| tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~%20%7C%20tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑫ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"key0 \| (key1 & (key2 \| !key3))...",条件组合方式,最终按
(其它key条件 AND 连接) AND (key0条件 OR (key1条件 AND (key2条件 OR (NOT key3条件))))
这种方式连接,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑬ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~ \| tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~%20%7C%20tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"(maxId)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"(maxId)>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑫ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

① "tag":String,后面的 tag 是非 GET、HEAD 请求中匹配请求的 JSON 结构的标识,一般是要查询的 Table 的名称或该名称对应的数组 Table[] 或 Table:[],由后端 Request 表中指定。

② "version":Integer,接口版本,version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本,由后端 Request 表中指定。

③ "format":Boolean,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22version%22:1,%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})
From a11e876026c6db8b71ca9f76d83232f3c21112d2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 5 Apr 2022 17:35:06 +0800 Subject: [PATCH 047/619] =?UTF-8?q?=E9=80=9A=E7=94=A8=E6=96=87=E6=A1=A3?= =?UTF-8?q?=EF=BC=9A=E6=9B=B4=E6=96=B0=205.0=20=E6=96=B0=E5=A2=9E=E7=9A=84?= =?UTF-8?q?=20@having&:"...",=20@having:{...}=20=E4=B8=A4=E7=A7=8D?= =?UTF-8?q?=E7=94=A8=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 12bac1aa6..4bcb6a319 100644 --- a/Document.md +++ b/Document.md @@ -418,6 +418,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-数据、总数和分页详情
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"key0 \| (key1 & (key2 \| !key3))...",条件组合方式,最终按
(其它key条件 AND 连接) AND (key0条件 OR (key1条件 AND (key2条件 OR (NOT key3条件))))
这种方式连接,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑬ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~ \| tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~%20%7C%20tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"(maxId)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"(maxId)>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑫ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"key0 \| (key1 & (key2 \| !key3))...",条件组合方式,最终按
(其它key条件 AND 连接) AND (key0条件 OR (key1条件 AND (key2条件 OR (NOT key3条件))))
这种方式连接,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // OR 连接,或
"@having&":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // AND 连接,或
"@having":{
   "h0":"function0(...)?value0",
   "h1":function1(...)?value1",
   "h2":function2(...)?value2...",
   "@combine":"h0 & (h1 \| !h2)" // 任意组合,非必传
}
SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑬ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~ \| tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~%20%7C%20tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"(maxId)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"(maxId)>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING (maxId)>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑫ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

① "tag":String,后面的 tag 是非 GET、HEAD 请求中匹配请求的 JSON 结构的标识,一般是要查询的 Table 的名称或该名称对应的数组 Table[] 或 Table:[],由后端 Request 表中指定。

② "version":Integer,接口版本,version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本,由后端 Request 表中指定。

③ "format":Boolean,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22version%22:1,%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})
From b22b6d96aa92b6c49ed539fdc64d5cd0774831f6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 5 Apr 2022 17:49:37 +0800 Subject: [PATCH 048/619] =?UTF-8?q?=E9=80=9A=E7=94=A8=E6=96=87=E6=A1=A3?= =?UTF-8?q?=EF=BC=9A=E5=AE=8C=E5=96=84=20JOIN=20=E7=9A=84=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E4=BB=A5=E5=8F=8A=20join:{...}=20=E8=BF=99=E7=A7=8D?= =?UTF-8?q?=E5=8F=AF=E5=B8=A6=20ON=20=E5=8F=8A=E5=8A=9F=E8=83=BD=E7=AC=A6?= =?UTF-8?q?=E7=9A=84=E5=86=99=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Document.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Document.md b/Document.md index 4bcb6a319..1c8094150 100644 --- a/Document.md +++ b/Document.md @@ -417,7 +417,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 减少 或 去除 | "key-":Object,与"key+"相反 | "balance-":100.00,对应SQL是`balance = balance - 100.00`,余额减少100.00,即花费了100元 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 - 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-数据、总数和分页详情
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"key0 \| (key1 & (key2 \| !key3))...",条件组合方式,最终按
(其它key条件 AND 连接) AND (key0条件 OR (key1条件 AND (key2条件 OR (NOT key3条件))))
这种方式连接,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // OR 连接,或
"@having&":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // AND 连接,或
"@having":{
   "h0":"function0(...)?value0",
   "h1":function1(...)?value1",
   "h2":function2(...)?value2...",
   "@combine":"h0 & (h1 \| !h2)" // 任意组合,非必传
}
SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑬ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~ \| tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~%20%7C%20tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"(maxId)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"(maxId)>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING (maxId)>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑫ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-数据、总数和分页详情
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\"join":{
   "&/Table0/key0@":{},
   "\      "key0":value0, // ON 条件
     "key1":value1,
     ...
     "@combine":"...", // ON 条件组合
     "@column":"...", // 外层 SELECT
     "@group":"...", // 外层 GROUP BY
     "@having":"..." // 外层 HAVING
   }
}
多表连接方式:
"@" - APP JOIN
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"*" - CROSS JOIN
"^" - SIDE JOIN
"(" - ANTI JOIN
")" - FOREIGN JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"key0 \| (key1 & (key2 \| !key3))...",条件组合方式,最终按
(其它key条件 AND 连接) AND (key0条件 OR (key1条件 AND (key2条件 OR (NOT key3条件))))
这种方式连接,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接。注意不要缺少或多余任何一个空格。

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // OR 连接,或
"@having&":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // AND 连接,或
"@having":{
   "h0":"function0(...)?value0",
   "h1":function1(...)?value1",
   "h2":function2(...)?value2...",
   "@combine":"h0 & (h1 \| !h2)" // 任意组合,非必传
}
SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑬ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~ \| tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~%20%7C%20tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"(maxId)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"(maxId)>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING (maxId)>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑫ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

① "tag":String,后面的 tag 是非 GET、HEAD 请求中匹配请求的 JSON 结构的标识,一般是要查询的 Table 的名称或该名称对应的数组 Table[] 或 Table:[],由后端 Request 表中指定。

② "version":Integer,接口版本,version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本,由后端 Request 表中指定。

③ "format":Boolean,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22version%22:1,%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})
From 8401596d3ebe598707694004aff895f7634a2c99 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 5 Apr 2022 18:00:05 +0800 Subject: [PATCH 049/619] =?UTF-8?q?=E9=80=9A=E7=94=A8=E6=96=87=E6=A1=A3?= =?UTF-8?q?=EF=BC=9A=E5=AE=8C=E5=96=84=20JOIN=20ON=20=E7=9A=84=E5=90=84?= =?UTF-8?q?=E7=A7=8D=E5=85=B3=E8=81=94=E6=96=B9=E5=BC=8F=E3=80=81=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=A4=9A=E5=AD=97=E6=AE=B5=E5=85=B3=E8=81=94=E3=80=81?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=85=B6=E5=AE=83=E6=9D=A1=E4=BB=B6=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 1c8094150..4dcf02437 100644 --- a/Document.md +++ b/Document.md @@ -417,7 +417,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 减少 或 去除 | "key-":Object,与"key+"相反 | "balance-":100.00,对应SQL是`balance = balance - 100.00`,余额减少100.00,即花费了100元 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 - 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-数据、总数和分页详情
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\"join":{
   "&/Table0/key0@":{},
   "\      "key0":value0, // ON 条件
     "key1":value1,
     ...
     "@combine":"...", // ON 条件组合
     "@column":"...", // 外层 SELECT
     "@group":"...", // 外层 GROUP BY
     "@having":"..." // 外层 HAVING
   }
}
多表连接方式:
"@" - APP JOIN
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"*" - CROSS JOIN
"^" - SIDE JOIN
"(" - ANTI JOIN
")" - FOREIGN JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) + 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-数据、总数和分页详情
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0@,\"join":{
   "&/Table0@":{} // 支持 ON 多个字段关联,
   "\      "key0":value0, // 其它ON条件
     "key1":value1,
     ...
     "@combine":"...", // 其它ON条件的组合方式
     "@column":"...", // 外层 SELECT
     "@group":"...", // 外层 GROUP BY
     "@having":"..." // 外层 HAVING
   }
}
多表连接方式:
"@" - APP JOIN
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"*" - CROSS JOIN
"^" - SIDE JOIN
"(" - ANTI JOIN
")" - FOREIGN JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey` AND 其它ON条件
除了 = 等价关联,也支持 ! 不等关联、\> \< \>= \<= 等比较关联和 $ ~ {} <> 等其它复杂关联方式

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"key0 \| (key1 & (key2 \| !key3))...",条件组合方式,最终按
(其它key条件 AND 连接) AND (key0条件 OR (key1条件 AND (key2条件 OR (NOT key3条件))))
这种方式连接,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接。注意不要缺少或多余任何一个空格。

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // OR 连接,或
"@having&":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // AND 连接,或
"@having":{
   "h0":"function0(...)?value0",
   "h1":function1(...)?value1",
   "h2":function2(...)?value2...",
   "@combine":"h0 & (h1 \| !h2)" // 任意组合,非必传
}
SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑬ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~ \| tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~%20%7C%20tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"(maxId)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"(maxId)>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING (maxId)>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑫ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

① "tag":String,后面的 tag 是非 GET、HEAD 请求中匹配请求的 JSON 结构的标识,一般是要查询的 Table 的名称或该名称对应的数组 Table[] 或 Table:[],由后端 Request 表中指定。

② "version":Integer,接口版本,version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本,由后端 Request 表中指定。

③ "format":Boolean,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22version%22:1,%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})
From 85ef5af251b1c1fbc3ec8a7d906315c138ba3da9 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 5 Apr 2022 18:14:28 +0800 Subject: [PATCH 050/619] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 4dcf02437..1c65d3039 100644 --- a/Document.md +++ b/Document.md @@ -417,7 +417,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 减少 或 去除 | "key-":Object,与"key+"相反 | "balance-":100.00,对应SQL是`balance = balance - 100.00`,余额减少100.00,即花费了100元 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 - 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-数据、总数和分页详情
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0@,\"join":{
   "&/Table0@":{} // 支持 ON 多个字段关联,
   "\      "key0":value0, // 其它ON条件
     "key1":value1,
     ...
     "@combine":"...", // 其它ON条件的组合方式
     "@column":"...", // 外层 SELECT
     "@group":"...", // 外层 GROUP BY
     "@having":"..." // 外层 HAVING
   }
}
多表连接方式:
"@" - APP JOIN
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"*" - CROSS JOIN
"^" - SIDE JOIN
"(" - ANTI JOIN
")" - FOREIGN JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey` AND 其它ON条件
除了 = 等价关联,也支持 ! 不等关联、\> \< \>= \<= 等比较关联和 $ ~ {} <> 等其它复杂关联方式

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) + 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-数据、总数和分页详情
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0,\"join":{
   "&/Table0":{}, // 支持 ON 多个字段关联,
   "\      "key0":value0, // 其它ON条件
     "key2":value2,
     ...
     "@combine":"...", // 其它ON条件的组合方式
     "@column":"...", // 外层 SELECT
     "@group":"...", // 外层 GROUP BY
     "@having":"..." // 外层 HAVING
   }
}
多表连接方式:
"@" - APP JOIN
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"*" - CROSS JOIN
"^" - SIDE JOIN
"(" - ANTI JOIN
")" - FOREIGN JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey` AND 其它ON条件
除了 = 等价关联,也支持 ! 不等关联、\> \< \>= \<= 等比较关联和 $ ~ {} <> 等其它复杂关联方式

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn/api/?type=JSON&url=http://apijson.cn:8080/get&json=%7B%22%5B%5D%22:%7B%22count%22:5,%22join%22:%22%26%2FUser%2Fid@,%3C%2FComment%22,%22Moment%22:%7B%22@column%22:%22id,userId,content%22,%22@group%22:%22id%22%7D,%22User%22:%7B%22name~%22:%22t%22,%22id@%22:%22%2FMoment%2FuserId%22,%22@column%22:%22id,name,head%22%7D,%22Comment%22:%7B%22momentId@%22:%22%2FMoment%2Fid%22,%22@column%22:%22id,momentId,content%22%7D%7D%7D)

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"key0 \| (key1 & (key2 \| !key3))...",条件组合方式,最终按
(其它key条件 AND 连接) AND (key0条件 OR (key1条件 AND (key2条件 OR (NOT key3条件))))
这种方式连接,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接。注意不要缺少或多余任何一个空格。

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // OR 连接,或
"@having&":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // AND 连接,或
"@having":{
   "h0":"function0(...)?value0",
   "h1":function1(...)?value1",
   "h2":function2(...)?value2...",
   "@combine":"h0 & (h1 \| !h2)" // 任意组合,非必传
}
SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑬ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~ \| tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~%20%7C%20tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"(maxId)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"(maxId)>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING (maxId)>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑫ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

① "tag":String,后面的 tag 是非 GET、HEAD 请求中匹配请求的 JSON 结构的标识,一般是要查询的 Table 的名称或该名称对应的数组 Table[] 或 Table:[],由后端 Request 表中指定。

② "version":Integer,接口版本,version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本,由后端 Request 表中指定。

③ "format":Boolean,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22version%22:1,%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})
From a74084589abdf524089a5aa19eb74c3dec7089de Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 9 Apr 2022 00:39:54 +0800 Subject: [PATCH 051/619] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1aca30303..1adbcfcda 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON -

零代码、全自动、强安全 ORM 库
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构

+

零代码、全功能、强安全 ORM 库
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构

@@ -66,7 +66,7 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 * 数据和结构完全定制,要啥有啥 * 看请求知结果,所求即所得 * 可一次获取任何数据、任何结构 -* 能去除重复数据,节省流量提高速度 +* 能去除多余数据,节省流量提高速度 #### 对于后端 * 提供通用接口,大部分 API 不用再写 From d868d8e6fd998591c0689fe80fb7dfb18d023a0b Mon Sep 17 00:00:00 2001 From: weiwei162 Date: Fri, 15 Apr 2022 18:12:32 +0800 Subject: [PATCH 052/619] fix #362 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 支持PUT请求修改json/jsonb类型字段 --- .../src/main/java/apijson/orm/AbstractObjectParser.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 3f3a09a04..c64236960 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -599,8 +599,8 @@ public void onPUTArrayParse(@NotNull String key, @NotNull JSONArray array) throw } else if (key.endsWith("-")) {//remove putType = 2; } else {//replace - // throw new IllegalAccessException("PUT " + path + ", PUT Array不允许 " + key + - // " 这种没有 + 或 - 结尾的key!不允许整个替换掉原来的Array!"); + sqlRequest.put(key, array); + return; } String realKey = AbstractSQLConfig.getRealKey(method, key, false, false); From e2e752caedc3a354566f7ae5bd9e61a9553701f9 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 16 Apr 2022 14:55:53 +0800 Subject: [PATCH 053/619] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 1c65d3039..fefcd5eae 100644 --- a/Document.md +++ b/Document.md @@ -405,7 +405,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 匹配条件范围 | "key{}":"条件0,条件1...",条件为SQL表达式字符串,可进行数字比较运算等 | ["id{}":"<=80000,\>90000"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"id{}":"<=80000,\>90000"}}}),对应SQL是`id<=80000 OR id>90000`,查询id符合id\<=80000 \| id>90000的一个User数组 包含选项范围 | "key<\>":Object => "key<\>":[Object],key对应值的类型必须为JSONArray,Object类型不能为JSON | ["contactIdList<\>":38710](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"contactIdList<\>":38710}}}),对应SQL是`json_contains(contactIdList,38710)`,查询contactIdList包含38710的一个User数组 判断是否存在 | "key}{@":{
   "from":"Table",
   "Table":{ ... }
}
其中:
}{ 表示 EXISTS;
key 用来标识是哪个判断;
@ 后面是 子查询 对象,具体见下方 子查询 的说明。 | ["id}{@":{
   "from":"Comment",
   "Comment":{
      "momentId":15
   }
}](http://apijson.cn:8080/get/{"User":{"id}{@":{"from":"Comment","Comment":{"momentId":15}}}})
WHERE EXISTS(SELECT * FROM Comment WHERE momentId=15) - 远程调用函数 | "key()":"函数表达式",函数表达式为 function(key0,key1...),会调用后端对应的函数 function(JSONObject request, String key0, String key1...),实现 参数校验、数值计算、数据同步、消息推送、字段拼接、结构变换 等特定的业务逻辑处理,
可使用 - 和 + 表示优先级,解析 key-() > 解析当前对象 > 解析 key() > 解析子对象 > 解析 key+() | ["isPraised()":"isContain(praiseUserIdList,userId)"](http://apijson.cn:8080/get/{"Moment":{"id":301,"isPraised()":"isContain(praiseUserIdList,userId)"}}),会调用远程函数 boolean isContain(JSONObject request, String array, String value) ,然后变为 "isPraised":true 这种(假设点赞用户id列表包含了userId,即这个User点了赞) + 远程调用函数 | "key()":"函数表达式",函数表达式为 function(key0,key1...),会调用后端对应的函数 function(JSONObject request, String key0, String key1...),实现 参数校验、数值计算、数据同步、消息推送、字段拼接、结构变换 等特定的业务逻辑处理,
可使用 - 和 + 表示优先级,解析 key-() > 解析当前对象 > 解析 key() > 解析子对象 > 解析 key+() | ["isPraised()":"isContain(praiseUserIdList,userId)"](http://apijson.cn:8080/get/{"Moment":{"id":301,"isPraised()":"isContain(praiseUserIdList,userId)"}}),会调用远程函数 [boolean isContain(JSONObject request, String array, String value)](https://github.com/APIJSON/apijson-framework/blob/master/src/main/java/apijson/framework/APIJSONFunctionParser.java#L361-L374) ,然后变为 "isPraised":true 这种(假设点赞用户id列表包含了userId,即这个User点了赞) 存储过程 | "@key()":"SQL函数表达式",函数表达式为
function(key0,key1...)
会调用后端数据库对应的存储过程 SQL函数
function(String key0, String key1...)
除了参数会提前赋值,其它和 远程函数 一致 | ["@limit":10,
"@offset":0,
"@procedure()":"getCommentByUserId(id,@limit,@offset)"](http://apijson.cn:8080/get/{"User":{"@limit":10,"@offset":0,"@procedure()":"getCommentByUserId(id,@limit,@offset)"}})
会转为
`getCommentByUserId(38710,10,0)`
来调用存储过程 SQL 函数
`getCommentByUserId(IN id bigint, IN limit int, IN offset int)`
然后变为
"procedure":{
   "count":-1,
   "update":false,
   "list":[]
}
其中 count 是指写操作影响记录行数,-1 表示不是写操作;update 是指是否为写操作(增删改);list 为返回结果集 引用赋值 | "key@":"key0/key1/.../refKey",引用路径为用/分隔的字符串。以/开头的是缺省引用路径,从声明key所处容器的父容器路径开始;其它是完整引用路径,从最外层开始。
被引用的refKey必须在声明key的上面。如果对refKey的容器指定了返回字段,则被引用的refKey必须写在@column对应的值内,例如 "@column":"refKey,key1,..." | ["Moment":{
   "userId":38710
},
"User":{
   "id@":"/Moment/userId"
}](http://apijson.cn:8080/get/{"Moment":{"userId":38710},"User":{"id@":"%252FMoment%252FuserId"}})
User内的id引用了与User同级的Moment内的userId,
即User.id = Moment.userId,请求完成后
"id@":"/Moment/userId" 会变成 "id":38710 子查询 | "key@":{
   "range":"ALL",
   "from":"Table",
   "Table":{ ... }
}
其中:
range 可为 ALL,ANY;
from 为目标表 Table 的名称;
@ 后面的对象类似数组对象,可使用 count 和 join 等功能。 | ["id@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id=(SELECT min(userId) FROM Comment) From 5073ee5ccb43f8f1cdd851b5961e4eafd190fe73 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 16 Apr 2022 15:04:07 +0800 Subject: [PATCH 054/619] Update README.md --- README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1adbcfcda..28bf5c25c 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,12 @@ This source code is licensed under the Apache License Version 2.0

零代码、全功能、强安全 ORM 库
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构

+

+ English +  通用文档 + 视频教程 + 在线体验 +

  @@ -28,17 +34,15 @@ This source code is licensed under the Apache License Version 2.0
 

+

+ +   +   +

    -

-

- English -  通用文档 - 视频教程 - 在线体验 -

From 06658dfd843513ad0a0ed3597808caf4a2fbe56d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 16 Apr 2022 15:06:01 +0800 Subject: [PATCH 055/619] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 28bf5c25c..3937a0806 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,9 @@ This source code is licensed under the Apache License Version 2.0

- -   -   + +   +  

From 3f5a1ae4737ff6d898bff0cb97862c940cef4044 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 16 Apr 2022 15:27:08 +0800 Subject: [PATCH 056/619] Update README-English.md --- README-English.md | 65 +++++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/README-English.md b/README-English.md index eeceb8783..c0d40e09c 100644 --- a/README-English.md +++ b/README-English.md @@ -6,35 +6,43 @@ This source code is licensed under the Apache License Version 2.0
APIJSON -

🚀 A JSON Transmission Protocol and an ORM Library for providing APIs and Documents automatically.

+

🏆 Tencent top 10 open source project, Achieved 5 awards inside & outside Tencent
🚀 A JSON Transmission Protocol and an ORM Library for providing APIs and Documents without writing any code.

+

+  中文版  +  Document  +  Video  +  Test  +

    - + + + + +

- + +   -   +     -

+

+ +   +   +

    -

-

-  中文版  -  Document  -  Video  -  Test  -

@@ -309,23 +317,26 @@ If you have any questions or suggestions, you can [create an issue](https://gith https://github.com/Tencent/APIJSON/issues/187

- - - + + +
- - - - - - - - - - - - + + + + + + + + + + + + + + +
[More APIJSON Users](https://github.com/Tencent/APIJSON/issues/73) From 9a70737eb832c614fa7f8fe0fc88792066969543 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 16 Apr 2022 15:31:02 +0800 Subject: [PATCH 057/619] Update Document-English.md --- Document-English.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Document-English.md b/Document-English.md index eb737bb4b..fa1785dd5 100644 --- a/Document-English.md +++ b/Document-English.md @@ -42,8 +42,8 @@ Add / expand an item | `"key+":Object`
The type of Object is decided by *key*. Types can be Number, String, JSONArray. Froms are 82001,"apijson",["url0","url1"] respectively. It’s only applicable to PUT request.| "praiseUserIdList+":[82001]. In SQL, it's
`json_insert(praiseUserIdList,82001)`.
Add an *id* that praised the Moment. Delete / decrease an item | `"Key-":Object`
It’s the contrary of "key+" | "balance-":100.00. In SQL, it's
`balance = balance - 100.00`,
meaning there's 100 less in balance. Operations | &, \|, !
They're used in logic operations. It’s the same as AND, OR, NOT in SQL respectively.
By default, for the same key, it’s ‘\|’ (OR)operation among conditions; for different keys, the default operation among conditions is ‘&’(AND).
| ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}})
In SQL, it's
`id>80000 AND id<=90000`,
meaning *id* needs to be id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}})
It's the same as "id{}":">90000,<=80000".
In SQL, it's
`id>80000 OR id<=90000`,
meaning that *id* needs to be id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}})
In SQL, it's
`id NOT IN(82001,38710)`,
meaning id needs to be ! (id=82001 \| id=38710). - Keywords in an Array: It can be self-defined. | As for `"key":Object`, *key* is the keyword of *{}* in *"[]":{}*. The type of *Object* is up to *key*.

① `"count":Integer` It's used to count the number. The default largest number is 100.

② `"page":Integer` It’s used for getting data from which page, starting from 0. The default largest number is 100. It’s usually used with COUNT.

③ `"query":Integer` Get the number of items that match conditions
When to get the object, the integer should be 0; when to get the total number, it’s 1; when both above, it’s 2.
You can get the total number with keyword total. It can be referred to other values.
Eg.
`"total@":"/[]/total"`
Put it as the same level of query.
*Query* and *total* are used in GET requests just for convenience. Generally, HEAD request is for getting numbers like the total number.

④ `"join":"&/Table0/key0@,Join tables:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
Where @ APP JOIN is in application layer.It’ll get all the keys in tables that refKeys in result tables are referred to, like refKeys:[value0, value1….]. Then, as the results get data according to `key=$refKey` a number of times (COUNT), it uses key `IN($refKeys)` to put these counts together in just one SQL query, in order to improve the performance.
Other JOIN functions are the same as those in SQL.
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
will return
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ `"otherKey":Object` Self-defined keyword other than those that already in the system. It also returns with self-defined keywords.| ① Get User arrays with maximum of 5:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})

② Look into User arrays on page 3. Show 5 of them each page.
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})

③ Get User Arrays and count the total number of Users:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal"})
Questions like total page numbers or if there's next page can be solved by total,count,page functions,
Total page number:
`int totalPage = Math.ceil(total / count)`
If this is the last page:
`boolean hasNextPage = total > count*page`
If this is the first page:
`boolean isFirstPage = page <= 0`
If it's the last page:
`boolean isLastPage = total <= count*page`
...

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join": "&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@": "/Moment/userId"
   },
   "Comment":{
     "momentId@": "/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ Add the current user to every level:
["User":{},
"[]":{
   "name@":"User/name", //self-defined keyword
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - Keywords in Objects: It can be self-defined. | `"@key":Object` @key is the keyword of {} in Table:{}. The type of Object is decided by @key

① `"@combine":"&key0,&key1,\|key2,key3,`
`!key4,!key5,&key6,key7..."`
First, it’ll group data with same operators. Within one group, it operates from left to right. Then it’ll follow the order of & \| ! to do the operation. Different groups are connected with &. So the expression above will be :
(key0 & key1 & key6 & other key) & (key2 \| key3 \| key7) & !(key4 \| key5)
\| is optional.

② `"@column":"column;function(arg)..."` Return with specific columns.

③ `"@order":"column0+,column1-..."` Decide the order of returning results:

④ `"@group":"column0,column1..."` How to group data. If @column has declared Table id, this id need to be included in @group. In other situations, at least one of the following needs to be done:
1.Group id is declared in @column
2.Primary Key of the table is declared in @group.

⑤ `@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2..."` Add conditions on return results with @having. Usually working with@group, it’s declared in @column.

⑥ `"@schema":"sys"` Can be set as default setting.

⑦ `"@database":"POSTGRESQL"` Get data from a different database.Can be set as default setting.

⑧ `"@role":"OWNER"` Get information of the user, including
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
Can be set as default setting.
You can self-define a new role or rewrite a role. Use`Verifier.verify` etc. to self-define validation methods.

⑨ `"@explain":true` Profiling. Can be set as default setting.

⑩ `"@otherKey":Object` Self-define keyword | ① Search *Users* that *name* or *tag* contains the letter "a":
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})

② Only search column id,sex,name and return with the same order:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})

③ Search Users that have descending order of name and default order of id:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})

④ Search Moment grouped with userId:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})

⑤ Search Moments that id equals or less than 100 and group with userId:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
You can also define the name of the returned function:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})

⑥ Check Users table in sys:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})

⑦ Check Users table in PostgreSQL:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL"}})

⑧ Check the current user's activity:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑨ Turn on profiling:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑩ Get the No.0 picture from pictureList:
["@position":0, //self-defined keyword
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + Keywords in an Array: It can be self-defined. | As for `"key":Object`, *key* is the keyword of *{}* in *"[]":{}*. The type of *Object* is up to *key*.

① `"count":Integer` It's used to count the number. The default largest number is 100.

② `"page":Integer` It’s used for getting data from which page, starting from 0. The default largest number is 100. It’s usually used with COUNT.

③ `"query":Integer` Get the number of items that match conditions
When to get the object, the integer should be 0; when to get the total number, it’s 1; when both above, it’s 2.
You can get the total number with keyword total. It can be referred to other values.
Eg.
`"total@":"/[]/total"`
Put it as the same level of query.
*Query* and *total* are used in GET requests just for convenience. Generally, HEAD request is for getting numbers like the total number.

④ `"join":"&/Table0,Join tables:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
Where @ APP JOIN is in application layer.It’ll get all the keys in tables that refKeys in result tables are referred to, like refKeys:[value0, value1….]. Then, as the results get data according to `key=$refKey` a number of times (COUNT), it uses key `IN($refKeys)` to put these counts together in just one SQL query, in order to improve the performance.
Other JOIN functions are the same as those in SQL.
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
will return
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ `"otherKey":Object` Self-defined keyword other than those that already in the system. It also returns with self-defined keywords.| ① Get User arrays with maximum of 5:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})

② Look into User arrays on page 3. Show 5 of them each page.
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})

③ Get User Arrays and count the total number of Users:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal"})
Questions like total page numbers or if there's next page can be solved by total,count,page functions,
Total page number:
`int totalPage = Math.ceil(total / count)`
If this is the last page:
`boolean hasNextPage = total > count*page`
If this is the first page:
`boolean isFirstPage = page <= 0`
If it's the last page:
`boolean isLastPage = total <= count*page`
...

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join": "&/User,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@": "/Moment/userId"
   },
   "Comment":{
     "momentId@": "/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser,\<%252FComment","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ Add the current user to every level:
["User":{},
"[]":{
   "name@":"User/name", //self-defined keyword
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) + Keywords in Objects: It can be self-defined. | `"@key":Object` @key is the keyword of {} in Table:{}. The type of Object is decided by @key

① `"@combine":"&key0,&key1,\|key2,key3,`
`!key4,!key5,&key6,key7..."`
First, it’ll group data with same operators. Within one group, it operates from left to right. Then it’ll follow the order of & \| ! to do the operation. Different groups are connected with &. So the expression above will be :
(key0 & key1 & key6 & other key) & (key2 \| key3 \| key7) & !(key4 \| key5)
\| is optional.

② `"@column":"column;function(arg)..."` Return with specific columns.

③ `"@order":"column0+,column1-..."` Decide the order of returning results:

④ `"@group":"column0,column1..."` How to group data. If @column has declared Table id, this id need to be included in @group. In other situations, at least one of the following needs to be done:
1.Group id is declared in @column
2.Primary Key of the table is declared in @group.

⑤ `@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2..."` Add conditions on return results with @having. Usually working with@group, it’s declared in @column.

⑥ `"@schema":"sys"` Can be set as default setting.

⑦ `"@database":"POSTGRESQL"` Get data from a different database.Can be set as default setting.

⑧ `"@role":"OWNER"` Get information of the user, including
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
Can be set as default setting.
You can self-define a new role or rewrite a role. Use`Verifier.verify` etc. to self-define validation methods.

⑨ `"@explain":true` Profiling. Can be set as default setting.

⑩ `"@otherKey":Object` Self-define keyword | ① Search *Users* that *name* or *tag* contains the letter "a":
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})

② Only search column id,sex,name and return with the same order:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})

③ Search Users that have descending order of name and default order of id:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})

④ Search Moment grouped with userId:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})

⑤ Search Moments that id equals or less than 100 and group with userId:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
You can also define the name of the returned function:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"(maxId)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"(maxId)>=100"}}})

⑥ Check Users table in sys:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})

⑦ Check Users table in PostgreSQL:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL"}})

⑧ Check the current user's activity:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑨ Turn on profiling:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑩ Get the No.0 picture from pictureList:
["@position":0, //self-defined keyword
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From df74e3a904f20d7717f53702e04835f5010c99a1 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 16 Apr 2022 15:35:13 +0800 Subject: [PATCH 058/619] Update README-English.md --- README-English.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README-English.md b/README-English.md index c0d40e09c..cc599efea 100644 --- a/README-English.md +++ b/README-English.md @@ -12,8 +12,8 @@ This source code is licensed under the Apache License Version 2.0

 中文版   Document  -  Video  -  Test  +  Video  +  Test 

From 286dd3d41f9114378f5c7e943cc1bd0f03725289 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 16 Apr 2022 15:35:27 +0800 Subject: [PATCH 059/619] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3937a0806..ca8cbc6ab 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This source code is licensed under the Apache License Version 2.0

English  通用文档 - 视频教程 + 视频教程 在线体验

From ec8c2601ff736f50aa0fd2d0ba4d365dcd324464 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 18 Apr 2022 01:00:17 +0800 Subject: [PATCH 060/619] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=EF=BC=8C=E5=AF=B9=E5=A4=96=E6=9A=B4=E9=9C=B2?= =?UTF-8?q?=E7=B1=BB=20RESTful=20=E6=8E=A5=E5=8F=A3=EF=BC=8C=E5=86=85?= =?UTF-8?q?=E9=83=A8=E8=BD=AC=E6=88=90=20APIJSON=20=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/APIJSON/apijson-router --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ca8cbc6ab..f2893e86c 100644 --- a/README.md +++ b/README.md @@ -451,8 +451,10 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [apijson-orm](https://github.com/APIJSON/apijson-orm) APIJSON ORM 库,可通过 Maven, Gradle 等远程依赖 -[apijson-framework](https://github.com/APIJSON/apijson-framework) APIJSON 服务端框架,可通过 Maven, Gradle 等远程依赖 +[apijson-framework](https://github.com/APIJSON/apijson-framework) APIJSON 服务端框架,通过数据库表配置角色权限、参数校验等,简化使用 +[apijson-router](https://github.com/APIJSON/apijson-router) APIJSON 的路由插件,对外暴露类 RESTful 接口,内部转成 APIJSON 接口执行 + [apijson-column](https://github.com/APIJSON/apijson-column) APIJSON 的字段插件,支持 字段名映射 和 !key 反选字段 [APIAuto](https://github.com/TommyLemon/APIAuto) 敏捷开发最强大易用的 HTTP 接口工具,机器学习零代码测试、生成代码与静态检查、生成文档与光标悬浮注释 From 30bbea94acef614fb2c1004266d75a5f08a40ead Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 18 Apr 2022 01:10:36 +0800 Subject: [PATCH 061/619] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=A7=92=E8=89=B2?= =?UTF-8?q?=E6=9D=83=E9=99=90=E3=80=81=E5=8F=82=E6=95=B0=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E3=80=81=E8=BF=9C=E7=A8=8B=E5=87=BD=E6=95=B0=E7=9A=84=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=EF=BC=9B=E8=A7=A3=E5=86=B3=20format:=20true?= =?UTF-8?q?=20=E5=9C=A8=20Log.DEBUG=20=E6=97=B6=E4=B9=9F=E4=B8=8D=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=20SQL=E3=80=81=E6=97=B6=E9=97=B4=E7=AD=89=E8=B0=83?= =?UTF-8?q?=E8=AF=95=E4=BF=A1=E6=81=AF=EF=BC=9B=E5=8D=87=E7=BA=A7=E8=87=AA?= =?UTF-8?q?=E8=BA=AB=E7=89=88=E6=9C=AC=E4=B8=BA=205.0.5=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 2 +- APIJSONORM/src/main/java/apijson/StringUtil.java | 16 ++++++++++++++-- .../java/apijson/orm/AbstractFunctionParser.java | 2 +- .../main/java/apijson/orm/AbstractParser.java | 14 +++++++------- .../main/java/apijson/orm/AbstractVerifier.java | 10 +++++----- 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index edd10dfb9..187aa087f 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 5.0.0 + 5.0.5 jar APIJSONORM diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index ecc64a083..795392e49 100755 --- a/APIJSONORM/src/main/java/apijson/StringUtil.java +++ b/APIJSONORM/src/main/java/apijson/StringUtil.java @@ -348,6 +348,7 @@ public static boolean isNotEmpty(String s, boolean trim) { public static final Pattern PATTERN_NAME; public static final Pattern PATTERN_ALPHA_BIG; public static final Pattern PATTERN_ALPHA_SMALL; + public static final Pattern PATTERN_BRANCH_URL; static { PATTERN_NUMBER = Pattern.compile("^[0-9]+$"); PATTERN_ALPHA = Pattern.compile("^[a-zA-Z]+$"); @@ -359,6 +360,7 @@ public static boolean isNotEmpty(String s, boolean trim) { PATTERN_EMAIL = Pattern.compile("^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$"); PATTERN_ID_CARD = Pattern.compile("(^[1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$)|(^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{2}$)"); PATTERN_PASSWORD = Pattern.compile("^[0-9a-zA-Z]+$"); + PATTERN_BRANCH_URL = Pattern.compile("^[0-9a-zA-Z-_/]+$"); } /**判断手机格式是否正确 @@ -392,7 +394,7 @@ public static boolean isNumberPassword(String s) { * @return */ public static boolean isEmail(String email) { - if (isNotEmpty(email, true) == false) { + if (isEmpty(email, true)) { return false; } @@ -512,7 +514,8 @@ public static boolean isIDCard(String number) { public static boolean isUrl(String url) { if (isNotEmpty(url, true) == false) { return false; - } else if (! url.startsWith(URL_PREFIX) && ! url.startsWith(URL_PREFIXs)) { + } + if (! url.startsWith(URL_PREFIX) && ! url.startsWith(URL_PREFIXs)) { return false; } @@ -520,6 +523,15 @@ public static boolean isUrl(String url) { return true; } + public static boolean isBranchUrl(String branchUrl) { + if (isEmpty(branchUrl, false)) { + return false; + } + + return PATTERN_BRANCH_URL.matcher(branchUrl).matches(); + } + + public static final String FILE_PATH_PREFIX = "file://"; /**判断文件路径是否存在 * @param path diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java index 3debe096a..880b917f0 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java @@ -28,7 +28,7 @@ public class AbstractFunctionParser implements FunctionParser { // // > - public static final Map FUNCTION_MAP; + public static Map FUNCTION_MAP; static { FUNCTION_MAP = new HashMap<>(); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index f56af8cc8..f3fa6e18c 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -441,16 +441,16 @@ public JSONObject parseResponse(JSONObject request) { long duration = endTime - startTime; if (Log.DEBUG) { - requestObject.put("sql:generate|cache|execute|maxExecute", getSQLExecutor().getGeneratedSQLCount() + "|" + getSQLExecutor().getCachedSQLCount() + "|" + getSQLExecutor().getExecutedSQLCount() + "|" + getMaxSQLCount()); - requestObject.put("depth:count|max", queryDepth + "|" + getMaxQueryDepth()); + res.put("sql:generate|cache|execute|maxExecute", getSQLExecutor().getGeneratedSQLCount() + "|" + getSQLExecutor().getCachedSQLCount() + "|" + getSQLExecutor().getExecutedSQLCount() + "|" + getMaxSQLCount()); + res.put("depth:count|max", queryDepth + "|" + getMaxQueryDepth()); executedSQLDuration += sqlExecutor.getExecutedSQLDuration() + sqlExecutor.getSqlResultDuration(); long parseDuration = duration - executedSQLDuration; - requestObject.put("time:start|duration|end|parse|sql", startTime + "|" + duration + "|" + endTime + "|" + parseDuration + "|" + executedSQLDuration); + res.put("time:start|duration|end|parse|sql", startTime + "|" + duration + "|" + endTime + "|" + parseDuration + "|" + executedSQLDuration); if (error != null) { - requestObject.put("trace:throw", error.getClass().getName()); - requestObject.put("trace:stack", error.getStackTrace()); + res.put("trace:throw", error.getClass().getName()); + res.put("trace:stack", error.getStackTrace()); } } @@ -947,7 +947,7 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, } if (result == null) { - if (AbstractVerifier.REQUEST_MAP.isEmpty() == false) { + if (Log.DEBUG == false && AbstractVerifier.REQUEST_MAP.isEmpty() == false) { return null; // 已使用 REQUEST_MAP 缓存全部,但没查到 } @@ -961,7 +961,7 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, where.put(JSONRequest.KEY_TAG, tag); if (version > 0) { - where.put(JSONRequest.KEY_VERSION + "{}", ">=" + version); + where.put(JSONRequest.KEY_VERSION + ">=", version); } config.setWhere(where); config.setOrder(JSONRequest.KEY_VERSION + (version > 0 ? "+" : "-")); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index feda82ac1..0431d1773 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -109,14 +109,14 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { // > // > @NotNull - public static final Map> SYSTEM_ACCESS_MAP; + public static Map> SYSTEM_ACCESS_MAP; @NotNull - public static final Map> ACCESS_MAP; + public static Map> ACCESS_MAP; // > // > @NotNull - public static final Map> REQUEST_MAP; + public static Map> REQUEST_MAP; // 正则匹配的别名快捷方式,例如用 "PHONE" 代替 "^((13[0-9])|(15[^4,\\D])|(18[0-2,5-9])|(17[0-9]))\\d{8}$" @NotNull @@ -164,7 +164,7 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { ACCESS_MAP = new HashMap<>(SYSTEM_ACCESS_MAP); - REQUEST_MAP = new HashMap<>(ACCESS_MAP.size()*6); // 单个与批量增删改 + REQUEST_MAP = new HashMap<>(ACCESS_MAP.size()*7); // 单个与批量增删改 COMPILE_MAP = new HashMap(); } @@ -1495,7 +1495,7 @@ public static void verifyRepeat(String table, String key, Object value, long exc } public static String getCacheKeyForRequest(String method, String tag) { - return method + " " + tag; + return method + "/" + tag; } From e5641b721cd8c2523533903a7235e3d9e2fccdef Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 19 Apr 2022 22:43:10 +0800 Subject: [PATCH 062/619] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 703b9224b..0cb35f97d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,7 +52,7 @@ Zerounary 提交的 6 个 Commits, 对 APIJSON 做出了 1,104 增加和 1 处
APIJSON 持续招募贡献者,即使是在 Issue 中回答问题,或者做一些简单的 Bug Fix ,也会给 APIJSON 带来很大的帮助。
APIJSON 已开发近 4 年,在此感谢所有开发者对于 APIJSON 的喜欢和支持,希望你能够成为 APIJSON 的核心贡献者,
-加入 APIJSON ,共同打造一个更棒的自动化 ORM 库!🍾🎉 +加入 APIJSON ,共同打造一个更棒的零代码、全自动、强安全 ORM 库!🍾🎉 ### 为什么一定要贡献代码? APIJSON 作为腾讯开源的知名热门项目,贡献代码除了可以给简历加亮点、为面试加分,还可以避免你碰到以下麻烦:
From b83a891b7e6d662e4e766b9d6a4c093ee73bd2e0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 22 Apr 2022 21:56:27 +0800 Subject: [PATCH 063/619] Update README.md --- README.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index f2893e86c..b02b4dfbe 100644 --- a/README.md +++ b/README.md @@ -65,13 +65,6 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 ### 特点功能 -#### 对于前端 -* 不用再向后端催接口、求文档 -* 数据和结构完全定制,要啥有啥 -* 看请求知结果,所求即所得 -* 可一次获取任何数据、任何结构 -* 能去除多余数据,节省流量提高速度 - #### 对于后端 * 提供通用接口,大部分 API 不用再写 * 自动生成文档,不用再编写和维护 @@ -79,6 +72,13 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 * 开放 API 无需划分版本,始终保持兼容 * 支持增删改查、复杂查询、跨库连表、远程函数等 +#### 对于前端 +* 不用再向后端催接口、求文档 +* 数据和结构完全定制,要啥有啥 +* 看请求知结果,所求即所得 +* 可一次获取任何数据、任何结构 +* 能去除多余数据,节省流量提高速度 +
### APIJSON 接口展示 @@ -385,11 +385,15 @@ https://github.com/Tencent/APIJSON/blob/master/Roadmap.md 如果你解决了某些bug,或者新增了一些功能,欢迎 [贡献代码](https://github.com/Tencent/APIJSON/pulls),感激不尽~
https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md -QQ 技术群: 734652054(新)、607020115(旧) +QQ 技术群: 734652054(新)、607020115(旧)
+ +**原则上优先解决 登记用户 和 贡献者 的问题,不解决 态度无礼 或 问题描述简陋 的问题!** + +如果你 [登记了自己使用 APIJSON 的公司](https://github.com/Tencent/APIJSON/issues/187),可以加专门的 企业用户支持群,作者亲自答疑。 如果你为 APIJSON 做出了以下任何一个贡献:
-[提交了 PR 且被合并](https://github.com/Tencent/APIJSON/pull/92)、[提交了优质 Issue](https://github.com/Tencent/APIJSON/issues/189)、[发表了优质文章](https://blog.csdn.net/qq_41829492/article/details/88670940)、[开发了可用的生态项目](https://github.com/zhangchunlin/uliweb-apijson) 或 [登记了你的公司](https://github.com/Tencent/APIJSON/issues/187),可以加
-贡献者微信群,注意联系 LonelyExplorer,加好友描述中附上贡献链接,谢谢 +[提交了 PR 且被合并](https://github.com/Tencent/APIJSON/pull/92)、[提交了优质 Issue](https://github.com/Tencent/APIJSON/issues/189)、[发表了优质文章](https://blog.csdn.net/qq_41829492/article/details/88670940)、[开发了可用的生态项目](https://github.com/zhangchunlin/uliweb-apijson),
+可以加 贡献者交流群,注意联系 LonelyExplorer,加好友描述中附上贡献链接,谢谢 ### 相关推荐 From e3ecce3a2c6643bedf4fd9e75e69e0263c6afdad Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 22 Apr 2022 22:11:31 +0800 Subject: [PATCH 064/619] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b02b4dfbe..29de492ac 100644 --- a/README.md +++ b/README.md @@ -387,7 +387,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md QQ 技术群: 734652054(新)、607020115(旧)
-**原则上优先解决 登记用户 和 贡献者 的问题,不解决 态度无礼 或 问题描述简陋 的问题!** +**时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,不解决 文档/视频/常见问题 已明确说明、描述简陋 或 态度无礼 的问题!** 如果你 [登记了自己使用 APIJSON 的公司](https://github.com/Tencent/APIJSON/issues/187),可以加专门的 企业用户支持群,作者亲自答疑。 From a591ff1784e7c78bd1619860ea7a09dc89bbcf4d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 22 Apr 2022 22:14:11 +0800 Subject: [PATCH 065/619] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 29de492ac..1d647188e 100644 --- a/README.md +++ b/README.md @@ -387,7 +387,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md QQ 技术群: 734652054(新)、607020115(旧)
-**时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,不解决 文档/视频/常见问题 已明确说明、描述简陋 或 态度无礼 的问题!** +时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,
+不解决 文档/视频/常见问题 已明确说明、描述简陋 或 态度无礼 的问题! 如果你 [登记了自己使用 APIJSON 的公司](https://github.com/Tencent/APIJSON/issues/187),可以加专门的 企业用户支持群,作者亲自答疑。 From ce636ceab65a2a228043c8a8e95a4abeb6e19d29 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 22 Apr 2022 22:16:13 +0800 Subject: [PATCH 066/619] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1d647188e..5eb054cfd 100644 --- a/README.md +++ b/README.md @@ -387,8 +387,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md QQ 技术群: 734652054(新)、607020115(旧)
-时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,
-不解决 文档/视频/常见问题 已明确说明、描述简陋 或 态度无礼 的问题! +**时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,**
+**不解决 文档/视频/常见问题 已明确说明、描述简陋 或 态度无礼 的问题!** 如果你 [登记了自己使用 APIJSON 的公司](https://github.com/Tencent/APIJSON/issues/187),可以加专门的 企业用户支持群,作者亲自答疑。 From 0580b3038c3184f0dca2f3cca6b0a05c6b5cfa0d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 22 Apr 2022 22:17:21 +0800 Subject: [PATCH 067/619] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5eb054cfd..79b085933 100644 --- a/README.md +++ b/README.md @@ -387,7 +387,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md QQ 技术群: 734652054(新)、607020115(旧)
-**时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,**
+**开发者时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,**
**不解决 文档/视频/常见问题 已明确说明、描述简陋 或 态度无礼 的问题!** 如果你 [登记了自己使用 APIJSON 的公司](https://github.com/Tencent/APIJSON/issues/187),可以加专门的 企业用户支持群,作者亲自答疑。 From 3d42b895b581aa9b6d319e4cbd6a91e973458dff Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Apr 2022 00:18:27 +0800 Subject: [PATCH 068/619] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 79b085933..0e326d860 100644 --- a/README.md +++ b/README.md @@ -66,11 +66,11 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 ### 特点功能 #### 对于后端 -* 提供通用接口,大部分 API 不用再写 -* 自动生成文档,不用再编写和维护 +* 提供万能通用接口,大部分 HTTP API 不用再写 +* 零代码增删改查、各种跨库连表、多层嵌套子查询等 +* 自动生成文档,不用再编写和维护,且自动静态检查 * 自动校验权限、自动管理版本、自动防 SQL 注入 -* 开放 API 无需划分版本,始终保持兼容 -* 支持增删改查、复杂查询、跨库连表、远程函数等 +* 开放 HTTP API 无需划分版本,始终保持兼容 #### 对于前端 * 不用再向后端催接口、求文档 From 61c21535509dacc35aa3cc4a4cccae9745473a91 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Apr 2022 01:08:56 +0800 Subject: [PATCH 069/619] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0e326d860..be1828fa7 100644 --- a/README.md +++ b/README.md @@ -54,11 +54,11 @@ This source code is licensed under the Apache License Version 2.0
APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
-为各种增删改查提供了完全自动化的万能 API,零代码实时满足千变万化的各种新增和变更需求。
+为各种增删改查提供了完全自动化的万能通用接口,零代码实时满足千变万化的各种新增和变更需求。
能大幅降低开发和沟通成本,简化开发流程,缩短开发周期。
适合中小型前后端分离的项目,尤其是 初创项目、内部项目、低代码/零代码、小程序、BaaS、Serverless 等。
-通过万能的 API,前端可以定制任何数据、任何结构。
+通过万能通用接口,前端可以定制任何数据、任何结构。
大部分 HTTP 请求后端再也不用写接口了,更不用写文档了。
前端再也不用和后端沟通接口或文档问题了。再也不会被文档各种错误坑了。
后端再也不用为了兼容旧接口写新版接口和文档了。再也不会被前端随时随地没完没了地烦了。 From c3c6a08c12f2ca4b7f4dfbae015f29f656a4cef8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Apr 2022 02:49:20 +0800 Subject: [PATCH 070/619] =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=8E=A8=E8=8D=90?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E3=80=8Aapijson=E5=9C=A8=E5=90=8C=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=E6=8E=A5=E5=8F=A3=E8=B0=83=E7=94=A8=E4=B8=AD=20?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E8=BF=9C=E7=A8=8B=E5=87=BD=E6=95=B0=E5=86=99?= =?UTF-8?q?=E5=85=A5=E6=9B=B4=E6=96=B0=E6=97=B6=E9=97=B4=E5=92=8C=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E6=97=B6=E9=97=B4=E3=80=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 点赞、收藏支持下博主吧~ https://blog.csdn.net/qietingfengsong/article/details/124097229 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index be1828fa7..c8574d809 100644 --- a/README.md +++ b/README.md @@ -435,6 +435,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [新手搭建 APIJSON 项目指北](https://github.com/jerrylususu/apijson_todo_demo/blob/master/FULLTEXT.md) [使用APIJSON写低代码Crud接口](https://blog.csdn.net/weixin_42375862/article/details/121654264) + +[apijson在同一个接口调用中 使用远程函数写入更新时间和创建时间](https://blog.csdn.net/qietingfengsong/article/details/124097229) [APIJSON(一:综述)](https://blog.csdn.net/qq_50861917/article/details/120556168) From 23032713dc90b4391694141134f98ac59e09d413 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Apr 2022 21:53:47 +0800 Subject: [PATCH 071/619] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index 6e545c22d..a9662be96 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -4,6 +4,26 @@ about: Create a report to help us improve --- +**提 bug 请发请求和响应的【完整截屏】,没图的自行解决! +开发者有限的时间和精力主要放在【维护项目源码和文档】上! +【描述不详细】 或 【文档/常见问题 已有答案】 的问题可能会被忽略!! +【态度 不文明/不友善】的可能会被拉黑,问题也可能不予解答!!!** + +请求参数 JSON 中表名、字段名、关键词及对应的值都是大小写敏感、逗号敏感、分号敏感、空格敏感、换行敏感, +大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 设计规范 来调用 API #181 + +1.尝试在 [常见问题](https://github.com/Tencent/APIJSON/issues/36) 和 [历史问题](https://github.com/TommyLemon/APIJSON/issues?q=is%3Aissue+is%3Aclosed) 搜索答案。 +2.尝试阅读 [通用文档](https://github.com/TommyLemon/APIJSON/blob/master/Document.md) 或看 [视频教程](https://search.bilibili.com/all?keyword=APIJSON) 找到答案。 +3.尝试阅读 [Demo 示例代码](https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoSQLConfig.java) 以找到答案。 +4.尝试自己 [检查或试验](http://apijson.cn/api) 以找到答案。 +5.尝试阅读 [源码和注释](https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java) 以找到答案。 + +如果以上都尝试过了请填写以下模板提一个新的issue。 +强烈推荐阅读 [《如何向开源社区提问题》](https://github.com/seajs/seajs/issues/545)、[《如何有效地报告 Bug》](http://www.chiark.greenend.org.uk/~sgtatham/bugs-cn.html)、[《如何向开源项目提交无法解答的问题》](https://zhuanlan.zhihu.com/p/25795393) +和 [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way),更好的问题更容易获得帮助。 + + + **环境信息** - 系统: - JDK: @@ -12,8 +32,9 @@ about: Create a report to help us improve **问题描述** - +可以输入具体的步骤,代码信息,或者截图,能帮助我们更快的解决您的问题 **错误信息** - \ No newline at end of file +运行日志面板错误信息,错误截图,或者【帮助>切换开发者工具】日志,以及【帮助>查看运行日志】log.log 文件 + From 614cc0a1c27971ecda0a06348337391b0e92ecb3 Mon Sep 17 00:00:00 2001 From: SingleDogL <100330117+SingleDogL@users.noreply.github.com> Date: Wed, 27 Apr 2022 10:38:09 +0800 Subject: [PATCH 072/619] =?UTF-8?q?=E4=BF=AE=E5=A4=8Doracle=E5=88=86?= =?UTF-8?q?=E9=A1=B5=E8=8E=B7=E5=8F=96=E6=97=B6=E6=97=A0=E6=B3=95=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E9=99=A4=E7=AC=AC=E4=B8=80=E9=A1=B5=E4=BB=A5=E5=A4=96?= =?UTF-8?q?=E7=9A=84=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原来的getLimitString的方法所生成的语句oracle并无法正确获取,因为oracle的between并不支持获取除1开始以外的数据 也就是 无法获取到除了第一页以外的数据 --- .../main/java/apijson/orm/AbstractSQLConfig.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 6698e4f4b..4c2c81aee 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -3946,13 +3946,20 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { && StringUtil.isNotEmpty(config.getGroup(),true)){ return explain + "SELECT count(*) FROM (SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); } - return explain + "SELECT * FROM (SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); + String sql = "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config); + return explain + config.getOraclePageSql(config, sql); } - return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString(); } - } + } + private String getOraclePageSql(AbstractSQLConfig config, String sql) { + int offset = getOffset(config.getPage(), config.getCount()); + String pageSql; + pageSql = "SELECT * FROM (SELECT t.*,ROWNUM RN FROM (" + sql + ") t WHERE ROWNUM <= " + (offset + count) + ") WHERE RN > " + offset; + return pageSql; + } + /**获取条件SQL字符串 * @param column * @param table From 4f40cbd56192dce95e2f51beb2723e994e313e7b Mon Sep 17 00:00:00 2001 From: SingleDogL <100330117+SingleDogL@users.noreply.github.com> Date: Fri, 29 Apr 2022 09:23:39 +0800 Subject: [PATCH 073/619] Update AbstractSQLConfig.java --- .../src/main/java/apijson/orm/AbstractSQLConfig.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 4c2c81aee..87d44f7d9 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -3952,7 +3952,12 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString(); } } - + + /**Oracle的分页获取 + * @param config + * @param sql + * @return + */ private String getOraclePageSql(AbstractSQLConfig config, String sql) { int offset = getOffset(config.getPage(), config.getCount()); String pageSql; From 5b7d54a492eb93ccfd57918bfa84cb792469b5b4 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 1 May 2022 16:24:44 +0800 Subject: [PATCH 074/619] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/RequestMethod.java | 6 ++-- .../java/apijson/orm/AbstractSQLConfig.java | 34 +++++++++++-------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/RequestMethod.java b/APIJSONORM/src/main/java/apijson/RequestMethod.java index e196e4dc0..035147d3a 100755 --- a/APIJSONORM/src/main/java/apijson/RequestMethod.java +++ b/APIJSONORM/src/main/java/apijson/RequestMethod.java @@ -52,8 +52,7 @@ public enum RequestMethod { * @return */ public static boolean isGetMethod(RequestMethod method, boolean containPrivate) { - boolean is = method == null || method == GET; - return containPrivate == false ? is : is || method == GETS; + return method == null || method == GET || (containPrivate && method == GETS); } /**是否为HEAD请求方法 @@ -62,8 +61,7 @@ public static boolean isGetMethod(RequestMethod method, boolean containPrivate) * @return */ public static boolean isHeadMethod(RequestMethod method, boolean containPrivate) { - boolean is = method == HEAD; - return containPrivate == false ? is : is || method == HEADS; + return method == HEAD || (containPrivate && method == HEADS); } /**是否为查询的请求方法 diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 87d44f7d9..2f6b2fba7 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -27,7 +27,6 @@ import static apijson.RequestMethod.DELETE; import static apijson.RequestMethod.GET; import static apijson.RequestMethod.GETS; -import static apijson.RequestMethod.HEAD; import static apijson.RequestMethod.HEADS; import static apijson.RequestMethod.POST; import static apijson.RequestMethod.PUT; @@ -3942,28 +3941,30 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { if (config.isOracle()) { //When config's database is oracle,Using subquery since Oracle12 below does not support OFFSET FETCH paging syntax. //针对oracle分组后条数的统计 - if ((config.getMethod() == HEAD || config.getMethod() == HEADS) - && StringUtil.isNotEmpty(config.getGroup(),true)){ + if (StringUtil.isNotEmpty(config.getGroup(),true) && RequestMethod.isHeadMethod(config.getMethod(), true)){ return explain + "SELECT count(*) FROM (SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); } + String sql = "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config); - return explain + config.getOraclePageSql(config, sql); + return explain + config.getOraclePageSql(sql); } + return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString(); } - } - + } + /**Oracle的分页获取 * @param config * @param sql * @return */ - private String getOraclePageSql(AbstractSQLConfig config, String sql) { - int offset = getOffset(config.getPage(), config.getCount()); - String pageSql; - pageSql = "SELECT * FROM (SELECT t.*,ROWNUM RN FROM (" + sql + ") t WHERE ROWNUM <= " + (offset + count) + ") WHERE RN > " + offset; - return pageSql; - } + protected String getOraclePageSql(String sql) { + int count = getCount(); + int offset = getOffset(getPage(), count); + String alias = getAliasWithQuote(); + + return "SELECT * FROM (SELECT " + alias + ".*, ROWNUM RN FROM (" + sql + ") " + alias + " WHERE ROWNUM <= " + (offset + count) + ") WHERE RN > " + offset; + } /**获取条件SQL字符串 * @param column @@ -3981,16 +3982,19 @@ private static String getConditionString(String column, String table, AbstractSQ } //根据方法不同,聚合语句不同。GROUP BY 和 HAVING 可以加在 HEAD 上, HAVING 可以加在 PUT, DELETE 上,GET 全加,POST 全都不加 - String aggregation = ""; + String aggregation; if (RequestMethod.isGetMethod(config.getMethod(), true)) { aggregation = config.getGroupString(true) + config.getHavingString(true) + config.getOrderString(true); } - if (RequestMethod.isHeadMethod(config.getMethod(), true)) { // TODO 加参数 isPagenation 判断是 GET 内分页 query:2 查总数,不用加这些条件 + else if (RequestMethod.isHeadMethod(config.getMethod(), true)) { // TODO 加参数 isPagenation 判断是 GET 内分页 query:2 查总数,不用加这些条件 aggregation = config.getGroupString(true) + config.getHavingString(true) ; } - if (config.getMethod() == PUT || config.getMethod() == DELETE) { + else if (config.getMethod() == PUT || config.getMethod() == DELETE) { aggregation = config.getHavingString(true) ; } + else { + aggregation = ""; + } String condition = table + config.getJoinString() + where + aggregation; ; //+ config.getLimitString(); From 0fb42569cfbce38d6ace21611f6ec3cf512747d6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 1 May 2022 16:34:06 +0800 Subject: [PATCH 075/619] =?UTF-8?q?=E4=BC=98=E5=8C=96=20AbstractSQLExecuto?= =?UTF-8?q?r=20=E5=85=B3=E4=BA=8E=E8=80=97=E6=97=B6=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLExecutor.java | 68 +++++-------------- 1 file changed, 17 insertions(+), 51 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 3606c5043..208fc165f 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -45,15 +45,9 @@ public abstract class AbstractSQLExecutor implements SQLExecutor { private static final String TAG = "AbstractSQLExecutor"; - - private int generatedSQLCount; - private int cachedSQLCount; - private int executedSQLCount; - public AbstractSQLExecutor() { - generatedSQLCount = 0; - cachedSQLCount = 0; - executedSQLCount = 0; - } + private int generatedSQLCount = 0; + private int cachedSQLCount = 0; + private int executedSQLCount = 0; @Override public int getGeneratedSQLCount() { @@ -68,26 +62,11 @@ public int getExecutedSQLCount() { return executedSQLCount; } - // 只要不是并发执行且执行完立刻获取,就不会是错的,否则需要一并返回,可以 JSONObject.put("@EXECUTED_SQL_TIME:START|DURATION|END", ) - private long executedSQLStartTime; - private long executedSQLEndTime; - private long executedSQLDuration; - private long sqlResultDuration; - - public long getExecutedSQLStartTime() { - return executedSQLStartTime; - } - public long getExecutedSQLEndTime() { - return executedSQLEndTime; - } + private long executedSQLDuration = 0; + private long sqlResultDuration = 0; @Override public long getExecutedSQLDuration() { - if (executedSQLDuration <= 0) { - long startTime = getExecutedSQLStartTime(); - long endTime = getExecutedSQLEndTime(); - executedSQLDuration = startTime <= 0 || endTime <= 0 ? 0 : endTime - startTime; // FIXME 有时莫名其妙地算出来是负数 - } - return executedSQLDuration < 0 ? 0 : executedSQLDuration; + return executedSQLDuration; } @Override @@ -96,7 +75,7 @@ public long getSqlResultDuration() { } /** - * 缓存map + * 缓存 Map */ protected Map> cacheMap = new HashMap<>(); @@ -104,7 +83,7 @@ public long getSqlResultDuration() { /**保存缓存 * @param sql * @param list - * @param isStatic + * @param type */ @Override public void putCache(String sql, List list, int type) { @@ -116,7 +95,7 @@ public void putCache(String sql, List list, int type) { } /**移除缓存 * @param sql - * @param isStatic + * @param type */ @Override public void removeCache(String sql, int type) { @@ -126,7 +105,10 @@ public void removeCache(String sql, int type) { } cacheMap.remove(sql); } - + /**获取缓存 + * @param sql + * @param type + */ @Override public List getCache(String sql, int type) { return cacheMap.get(sql); @@ -154,24 +136,18 @@ public JSONObject getCacheItem(String sql, int position, int type) { @Override public ResultSet executeQuery(@NotNull Statement statement, String sql) throws Exception { -// executedSQLStartTime = System.currentTimeMillis(); ResultSet rs = statement.executeQuery(sql); -// executedSQLEndTime = System.currentTimeMillis(); return rs; } @Override public int executeUpdate(@NotNull Statement statement, String sql) throws Exception { -// executedSQLStartTime = System.currentTimeMillis(); int c = statement.executeUpdate(sql); -// executedSQLEndTime = System.currentTimeMillis(); return c; } @Override public ResultSet execute(@NotNull Statement statement, String sql) throws Exception { -// executedSQLStartTime = System.currentTimeMillis(); statement.execute(sql); ResultSet rs = statement.getResultSet(); -// executedSQLEndTime = System.currentTimeMillis(); return rs; } @@ -182,10 +158,7 @@ public ResultSet execute(@NotNull Statement statement, String sql) throws Except */ @Override public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws Exception { -// executedSQLDuration = 0; - executedSQLStartTime = System.currentTimeMillis(); - executedSQLEndTime = executedSQLStartTime; -// sqlResultDuration = 0; + long executedSQLStartTime = System.currentTimeMillis(); boolean isPrepared = config.isPrepared(); @@ -231,8 +204,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws rs = execute(statement, sql); int updateCount = statement.getUpdateCount(); if (isExplain == false) { - executedSQLEndTime = System.currentTimeMillis(); - executedSQLDuration += executedSQLEndTime - executedSQLStartTime; + executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime; } result = new JSONObject(true); @@ -251,8 +223,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws } int updateCount = executeUpdate(config); if (isExplain == false) { - executedSQLEndTime = System.currentTimeMillis(); - executedSQLDuration += executedSQLEndTime - executedSQLStartTime; + executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime; } if (updateCount <= 0) { @@ -294,8 +265,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws } rs = executeQuery(config); //FIXME SQL Server 是一次返回两个结果集,包括查询结果和执行计划,需要 moreResults if (isExplain == false) { - executedSQLEndTime = System.currentTimeMillis(); - executedSQLDuration += executedSQLEndTime - executedSQLStartTime; + executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime; } break; @@ -1131,9 +1101,7 @@ public void close() { @Override public ResultSet executeQuery(@NotNull SQLConfig config) throws Exception { PreparedStatement stt = getStatement(config); - // 不准,getStatement 有时比 execute sql 更耗时 executedSQLStartTime = System.currentTimeMillis(); ResultSet rs = stt.executeQuery(); //PreparedStatement 不用传 SQL - // executedSQLEndTime = System.currentTimeMillis(); // if (config.isExplain() && (config.isSQLServer() || config.isOracle())) { // FIXME 返回的是 boolean 值 rs = stt.getMoreResults(Statement.CLOSE_CURRENT_RESULT); // } @@ -1144,9 +1112,7 @@ public ResultSet executeQuery(@NotNull SQLConfig config) throws Exception { @Override public int executeUpdate(@NotNull SQLConfig config) throws Exception { PreparedStatement stt = getStatement(config); -// 不准,getStatement 有时比 execute sql 更耗时 executedSQLStartTime = System.currentTimeMillis(); int count = stt.executeUpdate(); // PreparedStatement 不用传 SQL -// executedSQLEndTime = System.currentTimeMillis(); if (count <= 0 && config.isHive()) { count = 1; From b92fab305df2620b62904756614f5f0712703c08 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 1 May 2022 16:55:47 +0800 Subject: [PATCH 076/619] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index a9662be96..3683b0f3e 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -12,7 +12,7 @@ about: Create a report to help us improve 请求参数 JSON 中表名、字段名、关键词及对应的值都是大小写敏感、逗号敏感、分号敏感、空格敏感、换行敏感, 大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 设计规范 来调用 API #181 -1.尝试在 [常见问题](https://github.com/Tencent/APIJSON/issues/36) 和 [历史问题](https://github.com/TommyLemon/APIJSON/issues?q=is%3Aissue+is%3Aclosed) 搜索答案。 +1.尝试在 [常见问题](https://github.com/Tencent/APIJSON/issues/36) 和 [历史问题](https://github.com/TommyLemon/APIJSON/issues?q=is%3Aissue) 搜索答案。 2.尝试阅读 [通用文档](https://github.com/TommyLemon/APIJSON/blob/master/Document.md) 或看 [视频教程](https://search.bilibili.com/all?keyword=APIJSON) 找到答案。 3.尝试阅读 [Demo 示例代码](https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoSQLConfig.java) 以找到答案。 4.尝试自己 [检查或试验](http://apijson.cn/api) 以找到答案。 From 5d402171b0d6f1d5395a037b51eb6bac7d450f00 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 1 May 2022 17:10:25 +0800 Subject: [PATCH 077/619] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20JOIN=20=E5=89=AF?= =?UTF-8?q?=E8=A1=A8=E8=BF=94=E5=9B=9E=E7=A9=BA=E5=AF=B9=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractSQLExecutor.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 208fc165f..01cdb308d 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -783,9 +783,10 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r String lable = getKey(config, rs, rsmd, tablePosition, table, columnIndex, childMap); Object value = getValue(config, rs, rsmd, tablePosition, table, columnIndex, lable, childMap); - // 必须 put 进去,否则某个字段为 null 可能导致中断后续正常返回值 if (value != null) { - table.put(lable, value); - // } + // 主表必须 put 至少一个 null 进去,否则全部字段为 null 都不 put 会导致中断后续正常返回值 + if (value != null || (join == null && table.isEmpty())) { + table.put(lable, value); + } return table; } From 8df36e26d7ad74f2df4df4e9ce5b29530d814141 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 2 May 2022 00:32:51 +0800 Subject: [PATCH 078/619] =?UTF-8?q?=E4=BC=98=E5=8C=96=20SQL=20=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E7=BC=93=E5=AD=98=EF=BC=9B=E4=BC=98=E5=8C=96=E4=B8=BB?= =?UTF-8?q?=E9=94=AE=E6=B3=9B=E5=9E=8B=EF=BC=9B=E9=83=A8=E5=88=86=E5=B8=B8?= =?UTF-8?q?=E9=87=8F=E6=94=B9=E4=B8=BA=E5=8F=AF=E8=87=AA=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=E9=9D=99=E6=80=81=E5=8F=98=E9=87=8F=EF=BC=9B=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E9=AB=98=E5=B9=B6=E5=8F=91=E4=B8=8B=20id=20=E5=86=B2=E7=AA=81?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E6=96=B0=E5=A2=9E=E8=AE=B0=E5=BD=95=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/RequestMethod.java | 1 + .../apijson/orm/AbstractFunctionParser.java | 4 +- .../apijson/orm/AbstractObjectParser.java | 4 +- .../main/java/apijson/orm/AbstractParser.java | 84 ++++++++++--------- .../java/apijson/orm/AbstractSQLConfig.java | 50 ++++++----- .../java/apijson/orm/AbstractSQLExecutor.java | 66 ++++++++------- .../java/apijson/orm/AbstractVerifier.java | 39 ++++----- .../src/main/java/apijson/orm/Parser.java | 14 +--- .../main/java/apijson/orm/ParserCreator.java | 2 +- .../main/java/apijson/orm/SQLExecutor.java | 29 ++++--- .../src/main/java/apijson/orm/Subquery.java | 2 +- .../java/apijson/orm/VerifierCreator.java | 2 +- 12 files changed, 156 insertions(+), 141 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/RequestMethod.java b/APIJSONORM/src/main/java/apijson/RequestMethod.java index 035147d3a..caca99225 100755 --- a/APIJSONORM/src/main/java/apijson/RequestMethod.java +++ b/APIJSONORM/src/main/java/apijson/RequestMethod.java @@ -45,6 +45,7 @@ public enum RequestMethod { */ DELETE; + public static final RequestMethod[] ALL = new RequestMethod[]{ GET, HEAD, GETS, HEADS, POST, PUT, DELETE}; /**是否为GET请求方法 * @param method diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java index 880b917f0..cde0a6228 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java @@ -178,7 +178,7 @@ public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull Str + "\n请检查函数名和参数数量是否与已定义的函数一致!" + "\n且必须为 function(key0,key1,...) 这种单函数格式!" + "\nfunction必须符合Java函数命名,key是用于在request内取值的键!" - + "\n调用时不要有空格!"); + + "\n调用时不要有空格!" + e.getMessage()); } if (e instanceof InvocationTargetException) { Throwable te = ((InvocationTargetException) e).getTargetException(); @@ -186,7 +186,7 @@ public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull Str throw te instanceof Exception ? (Exception) te : new Exception(te.getMessage()); } throw new IllegalArgumentException("字符 " + function + " 对应的远程函数传参类型错误!" - + "\n请检查 key:value 中value的类型是否满足已定义的函数 " + getFunction(fb.getMethod(), fb.getKeys()) + " 的要求!"); + + "\n请检查 key:value 中value的类型是否满足已定义的函数 " + getFunction(fb.getMethod(), fb.getKeys()) + " 的要求!" + e.getMessage()); } throw e; } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index c64236960..bdbb366e7 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -908,7 +908,7 @@ public JSONObject onSQLExecute() throws Exception { && (table.equals(arrayTable)); // 提取并缓存数组主表的列表数据 - rawList = (List) result.remove(SQLExecutor.KEY_RAW_LIST); + rawList = (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST); if (rawList != null) { String arrayPath = parentPath.substring(0, parentPath.lastIndexOf("[]") + 2); @@ -936,7 +936,7 @@ public JSONObject onSQLExecute() throws Exception { parser.putQueryResult(path, result); // 解决获取关联数据时requestObject里不存在需要的关联数据 if (isSimpleArray && rawList != null) { - result.put(SQLExecutor.KEY_RAW_LIST, rawList); + result.put(AbstractSQLExecutor.KEY_RAW_LIST, rawList); } } } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index f3fa6e18c..5135a7279 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -50,7 +50,7 @@ /**parser for parsing request to JSONObject * @author Lemon */ -public abstract class AbstractParser implements Parser, ParserCreator, VerifierCreator, SQLCreator { +public abstract class AbstractParser implements Parser, ParserCreator, VerifierCreator, SQLCreator { protected static final String TAG = "AbstractParser"; /** @@ -72,6 +72,49 @@ public abstract class AbstractParser implements Parser, ParserCreator, public static boolean IS_PRINT_REQUEST_ENDTIME_LOG = false; + public static int DEFAULT_QUERY_COUNT = 10; + public static int MAX_QUERY_PAGE = 100; + public static int MAX_QUERY_COUNT = 100; + public static int MAX_UPDATE_COUNT = 10; + public static int MAX_SQL_COUNT = 200; + public static int MAX_OBJECT_COUNT = 5; + public static int MAX_ARRAY_COUNT = 5; + public static int MAX_QUERY_DEPTH = 5; + + @Override + public int getDefaultQueryCount() { + return DEFAULT_QUERY_COUNT; + } + @Override + public int getMaxQueryPage() { + return MAX_QUERY_PAGE; + } + @Override + public int getMaxQueryCount() { + return MAX_QUERY_COUNT; + } + @Override + public int getMaxUpdateCount() { + return MAX_UPDATE_COUNT; + } + @Override + public int getMaxSQLCount() { + return MAX_SQL_COUNT; + } + @Override + public int getMaxObjectCount() { + return MAX_OBJECT_COUNT; + } + @Override + public int getMaxArrayCount() { + return MAX_ARRAY_COUNT; + } + @Override + public int getMaxQueryDepth() { + return MAX_QUERY_DEPTH; + } + + /** * method = null */ @@ -1276,7 +1319,7 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // */ JSONObject fo = i != 0 || arrTableKey == null ? null : parent.getJSONObject(arrTableKey); @SuppressWarnings("unchecked") - List list = fo == null ? null : (List) fo.remove(SQLExecutor.KEY_RAW_LIST); + List list = fo == null ? null : (List) fo.remove(AbstractSQLExecutor.KEY_RAW_LIST); if (list != null && list.isEmpty() == false) { isExtract = false; @@ -1629,43 +1672,6 @@ else if (join != null){ return joinList; } - - - - @Override - public int getDefaultQueryCount() { - return DEFAULT_QUERY_COUNT; - } - @Override - public int getMaxQueryPage() { - return MAX_QUERY_PAGE; - } - @Override - public int getMaxQueryCount() { - return MAX_QUERY_COUNT; - } - @Override - public int getMaxUpdateCount() { - return MAX_UPDATE_COUNT; - } - @Override - public int getMaxSQLCount() { - return MAX_SQL_COUNT; - } - @Override - public int getMaxObjectCount() { - return MAX_OBJECT_COUNT; - } - @Override - public int getMaxArrayCount() { - return MAX_ARRAY_COUNT; - } - @Override - public int getMaxQueryDepth() { - return MAX_QUERY_DEPTH; - } - - /**根据路径取值 * @param parent * @param pathKeys diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 2f6b2fba7..c9c9c8623 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -103,22 +103,21 @@ public abstract class AbstractSQLConfig implements SQLConfig { public static String PREFFIX_DISTINCT = "DISTINCT "; // * 和 / 不能同时出现,防止 /* */ 段注释! # 和 -- 不能出现,防止行注释! ; 不能出现,防止隔断SQL语句!空格不能出现,防止 CRUD,DROP,SHOW TABLES等语句! - private static final Pattern PATTERN_RANGE; - private static final Pattern PATTERN_FUNCTION; + private static Pattern PATTERN_RANGE; + private static Pattern PATTERN_FUNCTION; /** * 表名映射,隐藏真实表名,对安全要求很高的表可以这么做 */ - public static final Map TABLE_KEY_MAP; - public static final List CONFIG_TABLE_LIST; - public static final List DATABASE_LIST; + public static Map TABLE_KEY_MAP; + public static List CONFIG_TABLE_LIST; + public static List DATABASE_LIST; // 自定义原始 SQL 片段 Map:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL - public static final Map RAW_MAP; + public static Map RAW_MAP; // 允许调用的 SQL 函数:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL - public static final Map SQL_AGGREGATE_FUNCTION_MAP; - public static final Map SQL_FUNCTION_MAP; - + public static Map SQL_AGGREGATE_FUNCTION_MAP; + public static Map SQL_FUNCTION_MAP; static { // 凡是 SQL 边界符、分隔符、注释符 都不允许,例如 ' " ` ( ) ; # -- /**/ ,以免拼接 SQL 时被注入意外可执行指令 PATTERN_RANGE = Pattern.compile("^[0-9%,!=\\<\\>/\\.\\+\\-\\*\\^]+$"); // ^[a-zA-Z0-9_*%!=<>(),"]+$ 导致 exists(select*from(Comment)) 通过! @@ -4339,7 +4338,7 @@ protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException * @return * @throws Exception */ - public static SQLConfig newSQLConfig(RequestMethod method, String table, String alias, JSONObject request, List joinList, boolean isProcedure, Callback callback) throws Exception { + public static SQLConfig newSQLConfig(RequestMethod method, String table, String alias, JSONObject request, List joinList, boolean isProcedure, Callback callback) throws Exception { if (request == null) { // User:{} 这种空内容在查询时也有效 throw new NullPointerException(TAG + ": newSQLConfig request == null!"); } @@ -4357,7 +4356,7 @@ public static SQLConfig newSQLConfig(RequestMethod method, String table, String String schema = request.getString(KEY_SCHEMA); String datasource = request.getString(KEY_DATASOURCE); - SQLConfig config = callback.getSQLConfig(method, database, schema, table); + SQLConfig config = callback.getSQLConfig(method, database, schema, datasource, table); config.setAlias(alias); config.setDatabase(database); //不删,后面表对象还要用的,必须放在 parseJoin 前 @@ -4404,7 +4403,7 @@ public static SQLConfig newSQLConfig(RequestMethod method, String table, String Object id = request.get(idKey); if (id == null && method == POST) { - id = callback.newId(method, database, schema, table); // null 表示数据库自增 id + id = callback.newId(method, database, schema, datasource, table); // null 表示数据库自增 id } if (id != null) { //null无效 @@ -4992,7 +4991,7 @@ else if (newHaving != null) { * @return * @throws Exception */ - public static SQLConfig parseJoin(RequestMethod method, SQLConfig config, List joinList, Callback callback) throws Exception { + public static SQLConfig parseJoin(RequestMethod method, SQLConfig config, List joinList, Callback callback) throws Exception { boolean isQuery = RequestMethod.isQueryMethod(method); config.setKeyPrefix(isQuery && config.isMain() == false); @@ -5203,7 +5202,7 @@ else if (key.endsWith("-")) {//缩减,PUT查询时处理 } - public static interface IdCallback { + public static interface IdCallback { /**为 post 请求新建 id, 只能是 Long 或 String * @param method * @param database @@ -5211,7 +5210,7 @@ public static interface IdCallback { * @param table * @return */ - Object newId(RequestMethod method, String database, String schema, String table); + T newId(RequestMethod method, String database, String schema, String datasource, String table); /**获取主键名 @@ -5231,7 +5230,7 @@ public static interface IdCallback { String getUserIdKey(String database, String schema, String datasource, String table); } - public static interface Callback extends IdCallback { + public static interface Callback extends IdCallback { /**获取 SQLConfig 的实例 * @param method * @param database @@ -5239,7 +5238,7 @@ public static interface Callback extends IdCallback { * @param table * @return */ - SQLConfig getSQLConfig(RequestMethod method, String database, String schema, String table); + SQLConfig getSQLConfig(RequestMethod method, String database, String schema, String datasource, String table); /**combine 里的 key 在 request 中 value 为 null 或不存在,即 request 中缺少用来作为 combine 条件的 key: value * @param combine @@ -5249,12 +5248,23 @@ public static interface Callback extends IdCallback { public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception; } - public static abstract class SimpleCallback implements Callback { + public static Long LAST_ID; + static { + LAST_ID = System.currentTimeMillis(); + } + public static abstract class SimpleCallback implements Callback { + @SuppressWarnings("unchecked") @Override - public Object newId(RequestMethod method, String database, String schema, String table) { - return System.currentTimeMillis(); + public T newId(RequestMethod method, String database, String schema, String datasource, String table) { + Long id = System.currentTimeMillis(); + if (id <= LAST_ID) { + id = LAST_ID + 1; // 解决高并发下 id 冲突导致新增记录失败 + } + LAST_ID = id; + + return (T) id; } @Override diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 01cdb308d..1095658bc 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -45,6 +45,8 @@ public abstract class AbstractSQLExecutor implements SQLExecutor { private static final String TAG = "AbstractSQLExecutor"; + public static String KEY_RAW_LIST = "@RAW@LIST"; // 避免和字段命名冲突,不用 $RAW@LIST$ 是因为 $ 会在 fastjson 内部转义,浪费性能 + private int generatedSQLCount = 0; private int cachedSQLCount = 0; private int executedSQLCount = 0; @@ -74,64 +76,66 @@ public long getSqlResultDuration() { return sqlResultDuration; } + /** * 缓存 Map */ protected Map> cacheMap = new HashMap<>(); - /**保存缓存 - * @param sql - * @param list - * @param type + * @param sql key + * @param list value + * @param config 一般主表 SQLConfig 不为 null,JOIN 副表的为 null */ @Override - public void putCache(String sql, List list, int type) { - if (sql == null || list == null) { //空map有效,说明查询过sql了 || list.isEmpty()) { + public void putCache(String sql, List list, SQLConfig config) { + if (sql == null || list == null) { // 空 list 有效,说明查询过 sql 了 || list.isEmpty()) { Log.i(TAG, "saveList sql == null || list == null >> return;"); return; } + cacheMap.put(sql, list); } - /**移除缓存 - * @param sql - * @param type - */ - @Override - public void removeCache(String sql, int type) { - if (sql == null) { - Log.i(TAG, "removeList sql == null >> return;"); - return; - } - cacheMap.remove(sql); - } + /**获取缓存 - * @param sql - * @param type + * @param sql key + * @param config 一般主表 SQLConfig 不为 null,JOIN 副表的为 null */ @Override - public List getCache(String sql, int type) { + public List getCache(String sql, SQLConfig config) { return cacheMap.get(sql); } /**获取缓存 - * @param sql + * @param sql key * @param position - * @param type + * @param config 一般主表 SQLConfig 不为 null,JOIN 副表的为 null * @return */ @Override - public JSONObject getCacheItem(String sql, int position, int type) { - List list = getCache(sql, type); - //只要map不为null,则如果 list.get(position) == null,则返回 {} ,避免再次SQL查询 + public JSONObject getCacheItem(String sql, int position, SQLConfig config) { + List list = getCache(sql, config); + // 只要 list 不为 null,则如果 list.get(position) == null,则返回 {} ,避免再次 SQL 查询 if (list == null) { return null; } - + JSONObject result = position >= list.size() ? null : list.get(position); return result != null ? result : new JSONObject(); } + /**移除缓存 + * @param sql key + * @param config + */ + @Override + public void removeCache(String sql, SQLConfig config) { + if (sql == null) { + Log.i(TAG, "removeList sql == null >> return;"); + return; + } + cacheMap.remove(sql); + } @Override @@ -250,7 +254,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws case GETS: case HEAD: case HEADS: - result = isHead || isExplain ? null : getCacheItem(sql, position, config.getCache()); + result = isHead || isExplain ? null : getCacheItem(sql, position, config); Log.i(TAG, ">>> execute result = getCache('" + sql + "', " + position + ") = " + result); if (result != null) { cachedSQLCount ++; @@ -306,7 +310,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws capacity = ((Collection) idIn).size(); } else { // 预估容量 - capacity = config.getCount() <= 0 ? Parser.MAX_QUERY_COUNT : config.getCount(); + capacity = config.getCount() <= 0 ? AbstractParser.MAX_QUERY_COUNT : config.getCount(); if (capacity > 100) { // 有 WHERE 条件,条件越多过滤数据越多,暂时不考虑 @combine:"a | (b & !c)" 里面 | OR 和 ! NOT 条件,太复杂也不是很必要 Map> combine = config.getCombineMap(); @@ -594,10 +598,10 @@ else if (curJoin.isOuterJoin() || curJoin.isAntiJoin()) { for (Entry entry : set) { List l = new ArrayList<>(); l.add(entry.getValue()); - putCache(entry.getKey(), l, JSONRequest.CACHE_ROM); + putCache(entry.getKey(), l, null); } - putCache(sql, resultList, config.getCache()); + putCache(sql, resultList, config); Log.i(TAG, ">>> execute putCache('" + sql + "', resultList); resultList.size() = " + resultList.size()); // 数组主表对象额外一次返回全部,方便 Parser 缓存来提高性能 diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 0431d1773..f62dfa94d 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -73,7 +73,7 @@ * @author Lemon * @param id 与 userId 的类型,一般为 Long */ -public abstract class AbstractVerifier implements Verifier, IdCallback { +public abstract class AbstractVerifier implements Verifier, IdCallback { private static final String TAG = "AbstractVerifier"; /**未登录,不明身份的用户 @@ -102,9 +102,9 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { // 共享 STRUCTURE_MAP 则不能 remove 等做任何变更,否则在并发情况下可能会出错,加锁效率又低,所以这里改为忽略对应的 key - public static final Map> ROLE_MAP; + public static Map> ROLE_MAP; - public static final List OPERATION_KEY_LIST; + public static List OPERATION_KEY_LIST; // > // > @@ -205,9 +205,10 @@ public String getUserIdKey(String database, String schema, String datasource, St return apijson.JSONObject.KEY_USER_ID; } + @SuppressWarnings("unchecked") @Override - public Object newId(RequestMethod method, String database, String schema, String table) { - return System.currentTimeMillis(); + public T newId(RequestMethod method, String database, String schema, String datasource, String table) { + return (T) Long.valueOf(System.currentTimeMillis()); } @@ -437,17 +438,17 @@ public void verifyUseRole(SQLConfig config, String table, RequestMethod method, public void verifyLogin() throws Exception { //未登录没有权限操作 if (visitorId == null) { - throw new NotLoggedInException("未登录,请登录后再操作!"); + throw new NotLoggedInException("未登录或登录超时,请登录后再操作!"); } if (visitorId instanceof Number) { if (((Number) visitorId).longValue() <= 0) { - throw new NotLoggedInException("未登录,请登录后再操作!"); + throw new NotLoggedInException("未登录或登录超时,请登录后再操作!"); } } else if (visitorId instanceof String) { if (StringUtil.isEmpty(visitorId, true)) { - throw new NotLoggedInException("未登录,请登录后再操作!"); + throw new NotLoggedInException("未登录或登录超时,请登录后再操作!"); } } else { @@ -539,7 +540,7 @@ public JSONObject verifyRequest(@NotNull final RequestMethod method, final Strin */ public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name , final JSONObject target, final JSONObject request, final SQLCreator creator) throws Exception { - return verifyRequest(method, name, target, request, Parser.MAX_UPDATE_COUNT, creator); + return verifyRequest(method, name, target, request, AbstractParser.MAX_UPDATE_COUNT, creator); } /**从request提取target指定的内容 * @param method @@ -568,9 +569,9 @@ public static JSONObject verifyRequest(@NotNull final RequestMethod method, fina * @return * @throws Exception */ - public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name + public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name , final JSONObject target, final JSONObject request, final int maxUpdateCount - , final String database, final String schema, final IdCallback idCallback, final SQLCreator creator) throws Exception { + , final String database, final String schema, final IdCallback idCallback, final SQLCreator creator) throws Exception { return verifyRequest(method, name, target, request, maxUpdateCount, database, schema, null, idCallback, creator); } /**从request提取target指定的内容 @@ -585,9 +586,9 @@ public static JSONObject verifyRequest(@NotNull final RequestMethod method, fina * @return * @throws Exception */ - public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name + public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name , final JSONObject target, final JSONObject request, final int maxUpdateCount - , final String database, final String schema, final String datasource, final IdCallback idCallback, final SQLCreator creator) throws Exception { + , final String database, final String schema, final String datasource, final IdCallback idCallback, final SQLCreator creator) throws Exception { Log.i(TAG, "verifyRequest method = " + method + "; name = " + name + "; target = \n" + JSON.toJSONString(target) @@ -782,9 +783,9 @@ public static JSONObject verifyResponse(@NotNull final RequestMethod method, fin * @return * @throws Exception */ - public static JSONObject verifyResponse(@NotNull final RequestMethod method, final String name + public static JSONObject verifyResponse(@NotNull final RequestMethod method, final String name , final JSONObject target, final JSONObject response, final String database, final String schema - , final IdCallback idKeyCallback, SQLCreator creator, OnParseCallback callback) throws Exception { + , final IdCallback idKeyCallback, SQLCreator creator, OnParseCallback callback) throws Exception { Log.i(TAG, "verifyResponse method = " + method + "; name = " + name + "; target = \n" + JSON.toJSONString(target) @@ -832,8 +833,8 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, * @return * @throws Exception */ - public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real - , final String database, final String schema, final IdCallback idCallback, SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { + public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real + , final String database, final String schema, final IdCallback idCallback, SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { return parse(method, name, target, real, database, schema, null, idCallback, creator, callback); } /**对request和response不同的解析用callback返回 @@ -850,8 +851,8 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, * @return * @throws Exception */ - public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real - , final String database, final String schema, final String datasource, final IdCallback idCallback, SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { + public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real + , final String database, final String schema, final String datasource, final IdCallback idCallback, SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { if (target == null) { return null; } diff --git a/APIJSONORM/src/main/java/apijson/orm/Parser.java b/APIJSONORM/src/main/java/apijson/orm/Parser.java index db98ea1f5..50b86b5f5 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Parser.java +++ b/APIJSONORM/src/main/java/apijson/orm/Parser.java @@ -17,18 +17,8 @@ /**解析器 * @author Lemon */ -public interface Parser { - - int DEFAULT_QUERY_COUNT = 10; - int MAX_QUERY_PAGE = 100; - int MAX_QUERY_COUNT = 100; - int MAX_UPDATE_COUNT = 10; - int MAX_SQL_COUNT = 200; - int MAX_OBJECT_COUNT = 5; - int MAX_ARRAY_COUNT = 5; - int MAX_QUERY_DEPTH = 5; - - +public interface Parser { + @NotNull Visitor getVisitor(); Parser setVisitor(@NotNull Visitor visitor); diff --git a/APIJSONORM/src/main/java/apijson/orm/ParserCreator.java b/APIJSONORM/src/main/java/apijson/orm/ParserCreator.java index 2af2767c3..4da410b46 100755 --- a/APIJSONORM/src/main/java/apijson/orm/ParserCreator.java +++ b/APIJSONORM/src/main/java/apijson/orm/ParserCreator.java @@ -10,7 +10,7 @@ /**SQL相关创建器 * @author Lemon */ -public interface ParserCreator { +public interface ParserCreator { @NotNull Parser createParser(); diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java index 3cb2bf60a..7e700c269 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java @@ -22,31 +22,34 @@ */ public interface SQLExecutor { - String KEY_RAW_LIST = "@RAW@LIST"; // 避免和字段命名冲突,不用 $RAW@LIST$ 是因为 $ 会在 fastjson 内部转义,浪费性能 - /**保存缓存 * @param sql - * @param map - * @param type + * @param list + * @param config */ - void putCache(String sql, List list, int type);; - - List getCache(String sql, int type); + void putCache(String sql, List list, SQLConfig config); - /**移除缓存 + /**获取缓存 * @param sql - * @param type + * @param config + * @return */ - void removeCache(String sql, int type); + List getCache(String sql, SQLConfig config); + /**获取缓存 * @param sql * @param position - * @param type + * @param config * @return */ - JSONObject getCacheItem(String sql, int position, int type); - + JSONObject getCacheItem(String sql, int position, SQLConfig config); + /**移除缓存 + * @param sql + * @param config + */ + void removeCache(String sql, SQLConfig config); + /**执行SQL * @param config * @return diff --git a/APIJSONORM/src/main/java/apijson/orm/Subquery.java b/APIJSONORM/src/main/java/apijson/orm/Subquery.java index 66fdf14df..de8603b6d 100644 --- a/APIJSONORM/src/main/java/apijson/orm/Subquery.java +++ b/APIJSONORM/src/main/java/apijson/orm/Subquery.java @@ -18,7 +18,7 @@ public class Subquery { private JSONObject originValue; // { "from": "Comment", "Comment": {...} } private String from; // Comment - private String range; // any, all + private String range; // ANY, ALL private String key; //id{} private SQLConfig config; diff --git a/APIJSONORM/src/main/java/apijson/orm/VerifierCreator.java b/APIJSONORM/src/main/java/apijson/orm/VerifierCreator.java index 735b1104d..0caa6f8b1 100644 --- a/APIJSONORM/src/main/java/apijson/orm/VerifierCreator.java +++ b/APIJSONORM/src/main/java/apijson/orm/VerifierCreator.java @@ -10,7 +10,7 @@ /**验证器相关创建器 * @author Lemon */ -public interface VerifierCreator { +public interface VerifierCreator { @NotNull Verifier createVerifier(); From 0e2b645465a3189e467aa7be4575d18cd1a16d10 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 2 May 2022 00:39:16 +0800 Subject: [PATCH 079/619] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E6=8A=A5=E9=94=99=E5=92=8C=E8=BF=9C=E7=A8=8B=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E6=8A=A5=E9=94=99=E7=9A=84=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractFunctionParser.java | 6 ++++-- APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java index cde0a6228..795062d7a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java @@ -16,6 +16,7 @@ import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; +import apijson.Log; import apijson.NotNull; import apijson.RequestMethod; import apijson.StringUtil; @@ -178,7 +179,7 @@ public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull Str + "\n请检查函数名和参数数量是否与已定义的函数一致!" + "\n且必须为 function(key0,key1,...) 这种单函数格式!" + "\nfunction必须符合Java函数命名,key是用于在request内取值的键!" - + "\n调用时不要有空格!" + e.getMessage()); + + "\n调用时不要有空格!" + (Log.DEBUG ? e.getMessage() : "")); } if (e instanceof InvocationTargetException) { Throwable te = ((InvocationTargetException) e).getTargetException(); @@ -186,7 +187,8 @@ public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull Str throw te instanceof Exception ? (Exception) te : new Exception(te.getMessage()); } throw new IllegalArgumentException("字符 " + function + " 对应的远程函数传参类型错误!" - + "\n请检查 key:value 中value的类型是否满足已定义的函数 " + getFunction(fb.getMethod(), fb.getKeys()) + " 的要求!" + e.getMessage()); + + "\n请检查 key:value 中value的类型是否满足已定义的函数 " + getFunction(fb.getMethod(), fb.getKeys()) + " 的要求!" + + (Log.DEBUG ? e.getMessage() : "")); } throw e; } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index f62dfa94d..c46b055bd 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -438,17 +438,17 @@ public void verifyUseRole(SQLConfig config, String table, RequestMethod method, public void verifyLogin() throws Exception { //未登录没有权限操作 if (visitorId == null) { - throw new NotLoggedInException("未登录或登录超时,请登录后再操作!"); + throw new NotLoggedInException("未登录或登录过期,请登录后再操作!"); } if (visitorId instanceof Number) { if (((Number) visitorId).longValue() <= 0) { - throw new NotLoggedInException("未登录或登录超时,请登录后再操作!"); + throw new NotLoggedInException("未登录或登录过期,请登录后再操作!"); } } else if (visitorId instanceof String) { if (StringUtil.isEmpty(visitorId, true)) { - throw new NotLoggedInException("未登录或登录超时,请登录后再操作!"); + throw new NotLoggedInException("未登录或登录过期,请登录后再操作!"); } } else { From 92c396ced2d13756487444cdeedf4f7b5967d053 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 2 May 2022 05:04:33 +0800 Subject: [PATCH 080/619] =?UTF-8?q?=E8=AF=B7=E6=B1=82=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=EF=BC=9AREFUSE=20=E6=96=B0=E5=A2=9E=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20!key=20=E6=8E=92=E9=99=A4=E7=A6=81=E6=AD=A2?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=EF=BC=8C=E4=BC=98=E5=8C=96=20MUST=20?= =?UTF-8?q?=E5=92=8C=20REFUSE=20=E5=A4=84=E7=90=86=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractVerifier.java | 93 +++++++++++++------ 1 file changed, 65 insertions(+), 28 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index c46b055bd..4b4c08b2c 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -103,7 +103,7 @@ public abstract class AbstractVerifier implements Verifier, // 共享 STRUCTURE_MAP 则不能 remove 等做任何变更,否则在并发情况下可能会出错,加锁效率又低,所以这里改为忽略对应的 key public static Map> ROLE_MAP; - + public static List OPERATION_KEY_LIST; // > @@ -129,7 +129,7 @@ public abstract class AbstractVerifier implements Verifier, ROLE_MAP.put(CIRCLE, new Entry("userId-()", "verifyCircle()")); // "userId{}", "circleIdList")); // 还是 {"userId":"currentUserId", "userId{}": "contactIdList", "@combine": "userId,userId{}" } ? ROLE_MAP.put(OWNER, new Entry("userId", "userId")); ROLE_MAP.put(ADMIN, new Entry("userId-()", "verifyAdmin()")); - + OPERATION_KEY_LIST = new ArrayList<>(); OPERATION_KEY_LIST.add(TYPE.name()); OPERATION_KEY_LIST.add(VERIFY.name()); @@ -204,7 +204,7 @@ public String getIdKey(String database, String schema, String datasource, String public String getUserIdKey(String database, String schema, String datasource, String table) { return apijson.JSONObject.KEY_USER_ID; } - + @SuppressWarnings("unchecked") @Override public T newId(RequestMethod method, String database, String schema, String datasource, String table) { @@ -247,7 +247,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception { if (table == null) { return true; } - + String role = config.getRole(); if (role == null) { role = UNKNOWN; @@ -265,10 +265,10 @@ public boolean verifyAccess(SQLConfig config) throws Exception { RequestMethod method = config.getMethod(); verifyRole(config, table, method, role); - + return true; } - + @Override public void verifyRole(SQLConfig config, String table, RequestMethod method, String role) throws Exception { verifyAllowRole(config, table, method, role); //验证允许的角色 @@ -289,7 +289,7 @@ public void verifyAllowRole(SQLConfig config, String table, RequestMethod method if (table == null) { table = config == null ? null : config.getTable(); } - + if (table != null) { if (method == null) { method = config == null ? GET : config.getMethod(); @@ -297,7 +297,7 @@ public void verifyAllowRole(SQLConfig config, String table, RequestMethod method if (role == null) { role = config == null ? UNKNOWN : config.getRole(); } - + Map map = ACCESS_MAP.get(table); if (map == null || Arrays.asList(map.get(method)).contains(role) == false) { @@ -329,7 +329,7 @@ public void verifyUseRole(SQLConfig config, String table, RequestMethod method, if (role == null) { role = config == null ? UNKNOWN : config.getRole(); } - + Object requestId; switch (role) { case LOGIN://verifyRole通过就行 @@ -882,11 +882,15 @@ public static JSONObject parse(@NotNull final RequestMethod m // 判断必要字段是否都有<<<<<<<<<<<<<<<<<<< String[] musts = StringUtil.split(must); - List mustList = musts == null ? new ArrayList() : Arrays.asList(musts); - for (String s : mustList) { - if (real.get(s) == null) { // 可能传null进来,这里还会通过 real.containsKey(s) == false) { - throw new IllegalArgumentException(method + "请求," + name - + " 里面不能缺少 " + s + " 等[" + must + "]内的任何字段!"); + Set mustSet = new HashSet(); + + if (musts != null && musts.length > 0) { + for (String s : musts) { + if (real.get(s) == null) { // 可能传null进来,这里还会通过 real.containsKey(s) == false) { + throw new IllegalArgumentException(method + "请求," + name + " 里面不能缺少 " + s + " 等[" + must + "]内的任何字段!"); + } + + mustSet.add(s); } } //判断必要字段是否都有>>>>>>>>>>>>>>>>>>> @@ -947,28 +951,61 @@ public static JSONObject parse(@NotNull final RequestMethod m Set rkset = real.keySet(); //解析内容并没有改变rkset //解析不允许的字段<<<<<<<<<<<<<<<<<<< - List refuseList = new ArrayList(); - if ("!".equals(refuse)) {//所有非 must,改成 !must 更好 - for (String key : rkset) {//对@key放行,@role,@column,自定义@position等 - if (key != null && key.startsWith("@") == false - && mustList.contains(key) == false && objKeySet.contains(key) == false) { - refuseList.add(key); + String[] refuses = StringUtil.split(refuse); + Set refuseSet = new HashSet(); + + if (refuses != null && refuses.length > 0) { + Set notRefuseSet = new HashSet(); + + for (String rfs : refuses) { + if (rfs == null) { // StringUtil.isEmpty(rfs, true) { + continue; + } + + if (rfs.startsWith("!")) { + rfs = rfs.substring(1); + + if (notRefuseSet.contains(rfs)) { + throw new ConflictException(REFUSE.name() + ":value 中出现了重复的 !" + rfs + " !不允许重复,也不允许一个 key 和取反 !key 同时使用!"); + } + if (refuseSet.contains(rfs)) { + throw new ConflictException(REFUSE.name() + ":value 中同时出现了 " + rfs + " 和 !" + rfs + " !不允许重复,也不允许一个 key 和取反 !key 同时使用!"); + } + + if (rfs.equals("")) { // 所有非 MUST + for (String key : rkset) { // 对@key放行,@role,@column,自定义@position等, @key:{ "Table":{} } 不会解析内部 + if (key == null || key.startsWith("@") || notRefuseSet.contains(key) || mustSet.contains(key) || objKeySet.contains(key)) { + continue; + } + + refuseSet.add(key); + } + } + else { // 排除 !key 后再禁传其它的 + notRefuseSet.add(rfs); + } + } + else { + if (refuseSet.contains(rfs)) { + throw new ConflictException(REFUSE.name() + ":value 中出现了重复的 " + rfs + " !不允许重复,也不允许一个 key 和取反 !key 同时使用!"); + } + if (notRefuseSet.contains(rfs)) { + throw new ConflictException(REFUSE.name() + ":value 中同时出现了 " + rfs + " 和 !" + rfs + " !不允许重复,也不允许一个 key 和取反 !key 同时使用!"); + } + + refuseSet.add(rfs); } - } - } else { - String[] refuses = StringUtil.split(refuse); - if (refuses != null && refuses.length > 0) { - refuseList.addAll(Arrays.asList(refuses)); } } + //解析不允许的字段>>>>>>>>>>>>>>>>>>> //判断不允许传的key<<<<<<<<<<<<<<<<<<<<<<<<< for (String rk : rkset) { - if (refuseList.contains(rk)) { //不允许的字段 + if (refuseSet.contains(rk)) { //不允许的字段 throw new IllegalArgumentException(method + "请求," + name - + " 里面不允许传 " + rk + " 等" + StringUtil.getString(refuseList) + "内的任何字段!"); + + " 里面不允许传 " + rk + " 等" + StringUtil.getString(refuseSet) + "内的任何字段!"); } if (rk == null) { //无效的key @@ -1391,7 +1428,7 @@ private static void verifyCondition(@NotNull String funChar, @NotNull JSONObject } finally { executor.close(); } - + if (result != null && JSONResponse.isExist(result.getIntValue(JSONResponse.KEY_COUNT)) == false) { throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 '" + tk + "': '" + tv + "' !"); } From f442543f7a1f21eae2444da187dbf72a9909db38 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 4 May 2022 01:08:08 +0800 Subject: [PATCH 081/619] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E9=A2=84=E4=BC=B0?= =?UTF-8?q?=E5=AE=B9=E9=87=8F=E5=88=A4=E6=96=AD=20NOT=20=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E7=94=A8=E9=94=99=E9=80=BB=E8=BE=91=20key?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 1095658bc..cd705a88a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -321,7 +321,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws List orList = combine == null ? null : combine.get("|"); int orCondCount = orList == null ? 0 : orList.size(); - List notList = combine == null ? null : combine.get("|"); + List notList = combine == null ? null : combine.get("!"); int notCondCount = notList == null ? 0 : notList.size(); // 有 GROUP BY 分组,字段越少过滤数据越多 From 95432dde2c7a1f5147f9fe2f77f5500d33c651d0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 4 May 2022 01:52:49 +0800 Subject: [PATCH 082/619] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7=E4=B8=BA=205.1.0=EF=BC=9B=E5=88=A0=E9=99=A4=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E9=9C=80=E8=A6=81=E7=9A=84=E4=BE=9D=E8=B5=96=20javax.?= =?UTF-8?q?activation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 7 +------ APIJSONORM/src/main/java/apijson/Log.java | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 187aa087f..63d7086c7 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 5.0.5 + 5.1.0 jar APIJSONORM @@ -23,11 +23,6 @@ fastjson 1.2.79 - - javax.activation - activation - 1.1.1 - diff --git a/APIJSONORM/src/main/java/apijson/Log.java b/APIJSONORM/src/main/java/apijson/Log.java index 9c2c60a98..f3328c768 100755 --- a/APIJSONORM/src/main/java/apijson/Log.java +++ b/APIJSONORM/src/main/java/apijson/Log.java @@ -14,7 +14,7 @@ public class Log { public static boolean DEBUG = true; - public static final String VERSION = "5.0.0"; + public static final String VERSION = "5.1.0"; public static final String KEY_SYSTEM_INFO_DIVIDER = "---|-----APIJSON SYSTEM INFO-----|---"; //默认的时间格式 From 9f724c171978c89f52bff957d208b23abf9b2f65 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 4 May 2022 06:14:40 +0800 Subject: [PATCH 083/619] =?UTF-8?q?=E8=BF=98=E5=8E=9F=E4=BE=9D=E8=B5=96=20?= =?UTF-8?q?javax.activation=EF=BC=8C=E5=AE=9E=E6=B5=8B=20JDK=2011,=2013=20?= =?UTF-8?q?=E9=83=BD=E9=9C=80=E8=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 否则 APIJSONBoot DemoApplication 第 95 行报错 NoClassDefFoundError static { Map COMPILE_MAP = AbstractVerifier.COMPILE_MAP; ... } Exception in thread "main" java.lang.NoClassDefFoundError: javax/activation/UnsupportedDataTypeException https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONBoot/src/main/java/apijson/boot/DemoApplication.java#L95 --- APIJSONORM/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 63d7086c7..c1bdf23dc 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -23,6 +23,11 @@ fastjson 1.2.79 + + javax.activation + activation + 1.1.1 + From ffdc33229dd044a7a22681e88e37a4942415c3c8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 7 May 2022 22:47:53 +0800 Subject: [PATCH 084/619] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c8574d809..236292a55 100644 --- a/README.md +++ b/README.md @@ -460,7 +460,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [apijson-framework](https://github.com/APIJSON/apijson-framework) APIJSON 服务端框架,通过数据库表配置角色权限、参数校验等,简化使用 -[apijson-router](https://github.com/APIJSON/apijson-router) APIJSON 的路由插件,对外暴露类 RESTful 接口,内部转成 APIJSON 接口执行 +[apijson-router](https://github.com/APIJSON/apijson-router) APIJSON 的路由插件,可控地对公网暴露类 RESTful 简单接口,内部转成 APIJSON 格式请求来执行。 [apijson-column](https://github.com/APIJSON/apijson-column) APIJSON 的字段插件,支持 字段名映射 和 !key 反选字段 From be268dc5a028a9f8fe0148e7b3bd52dcbc8590e6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 8 May 2022 01:22:38 +0800 Subject: [PATCH 085/619] Update README.md --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 236292a55..5b36395a1 100644 --- a/README.md +++ b/README.md @@ -175,6 +175,31 @@ https://github.com/Tencent/APIJSON/wiki * **工程轻量小巧** (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1) * **多年持续迭代** (自 2016 年开源至今已连续维护 5 年多,累计 2000+ Commits、80+ Releases,不断更新迭代中...) +![image](https://user-images.githubusercontent.com/5738175/167264836-9c5d8f8a-99e1-4e1e-9864-e8f906b8e704.png) + +### 用户反馈 +**腾讯 IEG 数据产品开发组负责人 xinlin:** +“腾讯的 APIJSON 开源方案,它可以做到零代码生成接口和文档,并且整个生成过程是自动化。当企业有元数据的时候,马上就可以获得接口” + +**腾讯科技 后台开发高级工程师 雷大锤:** +“可以抽出时间来看apijson了,这个可以为T10做准备,也是业界很火的东西,可以提升个人影响力!” + +**腾讯 bodian520:** +“在调试GET、POST、PUT接口时遇到了一些问题,把个人的摸索经验分享一下,希望作者能梳理下文档,方便我们更好的接入” + +**华为 minshiwu:** +“demo工程,默认使用apijson-framework,可以做到无任何配置即可体验apijson的各种能力。” + +**百度智慧城市研发 lpeng:** +“很兴奋的发现APIJSON很适合我们的一个开发场景,作为我们协议定义的一部分” + +**中兴工程师 duyijiang:** +“感谢腾讯大大提供的框架,很好用” + +https://github.com/Tencent/APIJSON/issues/132#issuecomment-1106669540 + +
+ ### 常见问题 #### 1.如何定制业务逻辑? 在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象、参数名称 等,然后对查到的数据自定义处理
From 19706c6fa5eef31bfd3854c0ea475945638c2954 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 8 May 2022 01:25:55 +0800 Subject: [PATCH 086/619] =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=8E=A8=E8=8D=90?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E8=85=BE=E8=AE=AF=E4=B8=9A=E5=8A=A1?= =?UTF-8?q?=E7=99=BE=E4=B8=87=E6=95=B0=E6=8D=AE=206s=20=E5=93=8D=E5=BA=94?= =?UTF-8?q?=EF=BC=8CAPIJSON=20=E6=80=A7=E8=83=BD=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=83=8C=E5=90=8E=E7=9A=84=E6=95=85=E4=BA=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://my.oschina.net/tommylemon/blog/5375645 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5b36395a1..f37bea636 100644 --- a/README.md +++ b/README.md @@ -435,6 +435,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [APIJSON对接分布式HTAP数据库TiDB](https://asktug.com/t/htap-tidb/395) +[腾讯业务百万数据 6s 响应,APIJSON 性能优化背后的故事](https://my.oschina.net/tommylemon/blog/5375645) + [APIJSON教程(一):上手apijson项目,学习apijson语法,并实现持久层配置](https://zhuanlan.zhihu.com/p/375681893) [apijson简单demo](https://blog.csdn.net/dmw412724/article/details/113558115) From beac8231a65899741f3a973524253ffe75da8509 Mon Sep 17 00:00:00 2001 From: Montos <1367654518@qq.com> Date: Tue, 10 May 2022 00:20:06 +0800 Subject: [PATCH 087/619] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AF=B9LocalDateTim?= =?UTF-8?q?e=E7=B1=BB=E5=9E=8B=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 增加对LocalDateTime类型支持 --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index cd705a88a..a02bd79b0 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -19,6 +19,7 @@ import java.sql.Statement; import java.sql.Time; import java.sql.Timestamp; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -879,6 +880,9 @@ else if (value instanceof Date) { else if (value instanceof Time) { value = ((Time) value).toString(); } + else if (value instanceof LocalDateTime) { + value = ((LocalDateTime) value).toString(); + } else if (value instanceof String && isJSONType(config, rsmd, columnIndex, lable)) { //json String castToJson = true; } From 89accdacdc24fabaa6c9527931fe475cccedb71b Mon Sep 17 00:00:00 2001 From: ysy Date: Sun, 29 May 2022 11:20:48 +0800 Subject: [PATCH 088/619] fastjson up2 2.0.4 --- APIJSONORM/pom.xml | 4 ++-- APIJSONORM/src/main/java/apijson/JSON.java | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index c1bdf23dc..3f776fb6a 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 5.1.0 + 5.1.0-F2 jar APIJSONORM @@ -21,7 +21,7 @@ com.alibaba fastjson - 1.2.79 + 2.0.4 javax.activation diff --git a/APIJSONORM/src/main/java/apijson/JSON.java b/APIJSONORM/src/main/java/apijson/JSON.java index 28c124cab..a5b501ead 100755 --- a/APIJSONORM/src/main/java/apijson/JSON.java +++ b/APIJSONORM/src/main/java/apijson/JSON.java @@ -64,18 +64,20 @@ public static String getCorrectJson(String s, boolean isArray) { * @param json * @return */ + private static final Feature[] DEFAULT_FASTJSON_FEATURES = {Feature.OrderedField, Feature.AllowSingleQuotes, Feature.DisableCircularReferenceDetect, Feature.UseBigDecimal, Feature.UseObjectArray}; public static Object parse(Object obj) { int features = com.alibaba.fastjson.JSON.DEFAULT_PARSER_FEATURE; features |= Feature.OrderedField.getMask(); try { - return com.alibaba.fastjson.JSON.parse(obj instanceof String ? (String) obj : toJSONString(obj), features); + return com.alibaba.fastjson.JSON.parse(obj instanceof String ? (String) obj : toJSONString(obj), DEFAULT_FASTJSON_FEATURES); } catch (Exception e) { Log.i(TAG, "parse catch \n" + e.getMessage()); } return null; } + /**obj转JSONObject - * @param json + * @param obj * @return */ public static JSONObject parseObject(Object obj) { @@ -89,16 +91,17 @@ public static JSONObject parseObject(Object obj) { * @return */ public static JSONObject parseObject(String json) { - int features = com.alibaba.fastjson.JSON.DEFAULT_PARSER_FEATURE; - features |= Feature.OrderedField.getMask(); - return parseObject(json, features); + return parseObject(json, DEFAULT_FASTJSON_FEATURES); } - /**json转JSONObject + + /** + * json转JSONObject + * * @param json * @param features * @return */ - public static JSONObject parseObject(String json, int features) { + public static JSONObject parseObject(String json, Feature... features) { try { return com.alibaba.fastjson.JSON.parseObject(getCorrectJson(json), JSONObject.class, features); } catch (Exception e) { @@ -127,7 +130,7 @@ public static T parseObject(String json, Class clazz) { try { int features = com.alibaba.fastjson.JSON.DEFAULT_PARSER_FEATURE; features |= Feature.OrderedField.getMask(); - return com.alibaba.fastjson.JSON.parseObject(getCorrectJson(json), clazz, features); + return com.alibaba.fastjson.JSON.parseObject(getCorrectJson(json), clazz, DEFAULT_FASTJSON_FEATURES); } catch (Exception e) { Log.i(TAG, "parseObject catch \n" + e.getMessage()); } From 641d9409413b9b8c0cb4bba03bdefce18c25374d Mon Sep 17 00:00:00 2001 From: ysy Date: Sun, 29 May 2022 12:33:57 +0800 Subject: [PATCH 089/619] fix dead loop --- APIJSONORM/src/main/java/apijson/JSON.java | 2 +- .../apijson/orm/AbstractObjectParser.java | 40 +++++++++---------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/JSON.java b/APIJSONORM/src/main/java/apijson/JSON.java index a5b501ead..09dcd058d 100755 --- a/APIJSONORM/src/main/java/apijson/JSON.java +++ b/APIJSONORM/src/main/java/apijson/JSON.java @@ -64,7 +64,7 @@ public static String getCorrectJson(String s, boolean isArray) { * @param json * @return */ - private static final Feature[] DEFAULT_FASTJSON_FEATURES = {Feature.OrderedField, Feature.AllowSingleQuotes, Feature.DisableCircularReferenceDetect, Feature.UseBigDecimal, Feature.UseObjectArray}; + private static final Feature[] DEFAULT_FASTJSON_FEATURES = {Feature.OrderedField, Feature.AllowSingleQuotes, Feature.UseBigDecimal, Feature.UseObjectArray}; public static Object parse(Object obj) { int features = com.alibaba.fastjson.JSON.DEFAULT_PARSER_FEATURE; features |= Feature.OrderedField.getMask(); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index bdbb366e7..a034d0e81 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -5,13 +5,19 @@ package apijson.orm; -import static apijson.JSONObject.KEY_COMBINE; -import static apijson.JSONObject.KEY_DROP; -import static apijson.JSONObject.KEY_TRY; -import static apijson.RequestMethod.POST; -import static apijson.RequestMethod.PUT; -import static apijson.orm.SQLConfig.TYPE_ITEM; +import apijson.JSONResponse; +import apijson.Log; +import apijson.NotNull; +import apijson.RequestMethod; +import apijson.StringUtil; +import apijson.orm.AbstractFunctionParser.FunctionBean; +import apijson.orm.exception.ConflictException; +import apijson.orm.exception.NotExistException; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import javax.activation.UnsupportedDataTypeException; import java.rmi.ServerException; import java.util.ArrayList; import java.util.Arrays; @@ -22,20 +28,12 @@ import java.util.Map.Entry; import java.util.Set; -import javax.activation.UnsupportedDataTypeException; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; - -import apijson.JSONResponse; -import apijson.Log; -import apijson.NotNull; -import apijson.RequestMethod; -import apijson.StringUtil; -import apijson.orm.AbstractFunctionParser.FunctionBean; -import apijson.orm.exception.ConflictException; -import apijson.orm.exception.NotExistException; +import static apijson.JSONObject.KEY_COMBINE; +import static apijson.JSONObject.KEY_DROP; +import static apijson.JSONObject.KEY_TRY; +import static apijson.RequestMethod.POST; +import static apijson.RequestMethod.PUT; +import static apijson.orm.SQLConfig.TYPE_ITEM; /**简化Parser,getObject和getArray(getArrayConfig)都能用 @@ -572,7 +570,7 @@ public JSON onChildParse(int index, String key, JSONObject value) throws Excepti invalidate(); } } - Log.i(TAG, "onChildParse ObjectParser.onParse key = " + key + "; child = " + child); +// Log.i(TAG, "onChildParse ObjectParser.onParse key = " + key + "; child = " + child); return isEmpty ? null : child;//只添加! isChildEmpty的值,可能数据库返回数据不够count } From be00ec44a1e4a56dfa67db68d3deeb06ff895511 Mon Sep 17 00:00:00 2001 From: ysy Date: Sun, 29 May 2022 15:47:55 +0800 Subject: [PATCH 090/619] fix field order --- APIJSONORM/src/main/java/apijson/JSON.java | 39 +++---------------- .../apijson/orm/AbstractObjectParser.java | 2 +- 2 files changed, 6 insertions(+), 35 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/JSON.java b/APIJSONORM/src/main/java/apijson/JSON.java index 09dcd058d..b610cfc5b 100755 --- a/APIJSONORM/src/main/java/apijson/JSON.java +++ b/APIJSONORM/src/main/java/apijson/JSON.java @@ -6,8 +6,8 @@ import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; -import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.serializer.SerializerFeature; +import com.alibaba.fastjson2.JSONReader; import java.util.List; @@ -64,12 +64,10 @@ public static String getCorrectJson(String s, boolean isArray) { * @param json * @return */ - private static final Feature[] DEFAULT_FASTJSON_FEATURES = {Feature.OrderedField, Feature.AllowSingleQuotes, Feature.UseBigDecimal, Feature.UseObjectArray}; + private static final JSONReader.Feature[] DEFAULT_FASTJSON_FEATURES = {JSONReader.Feature.FieldBased, JSONReader.Feature.UseBigDecimalForDoubles}; public static Object parse(Object obj) { - int features = com.alibaba.fastjson.JSON.DEFAULT_PARSER_FEATURE; - features |= Feature.OrderedField.getMask(); try { - return com.alibaba.fastjson.JSON.parse(obj instanceof String ? (String) obj : toJSONString(obj), DEFAULT_FASTJSON_FEATURES); + return com.alibaba.fastjson2.JSON.parse(obj instanceof String ? (String) obj : toJSONString(obj), DEFAULT_FASTJSON_FEATURES); } catch (Exception e) { Log.i(TAG, "parse catch \n" + e.getMessage()); } @@ -91,32 +89,7 @@ public static JSONObject parseObject(Object obj) { * @return */ public static JSONObject parseObject(String json) { - return parseObject(json, DEFAULT_FASTJSON_FEATURES); - } - - /** - * json转JSONObject - * - * @param json - * @param features - * @return - */ - public static JSONObject parseObject(String json, Feature... features) { - try { - return com.alibaba.fastjson.JSON.parseObject(getCorrectJson(json), JSONObject.class, features); - } catch (Exception e) { - Log.i(TAG, "parseObject catch \n" + e.getMessage()); - } - return null; - } - - /**JSONObject转实体类 - * @param object - * @param clazz - * @return - */ - public static T parseObject(JSONObject object, Class clazz) { - return parseObject(toJSONString(object), clazz); + return parseObject(json, JSONObject.class); } /**json转实体类 * @param json @@ -128,9 +101,7 @@ public static T parseObject(String json, Class clazz) { Log.e(TAG, "parseObject clazz == null >> return null;"); } else { try { - int features = com.alibaba.fastjson.JSON.DEFAULT_PARSER_FEATURE; - features |= Feature.OrderedField.getMask(); - return com.alibaba.fastjson.JSON.parseObject(getCorrectJson(json), clazz, DEFAULT_FASTJSON_FEATURES); + return com.alibaba.fastjson2.JSON.parseObject(getCorrectJson(json), clazz, DEFAULT_FASTJSON_FEATURES); } catch (Exception e) { Log.i(TAG, "parseObject catch \n" + e.getMessage()); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index a034d0e81..e571cd8b4 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -425,7 +425,7 @@ else if (value instanceof String) { // //key{}@ getRealKey, 引用赋值路径 String replaceKey = key.substring(0, key.length() - 1); // System.out.println("getObject key.endsWith(@) >> parseRelation = " + parseRelation); - String targetPath = AbstractParser.getValuePath(type == TYPE_ITEM ? path : parentPath, new String((String) value)); + String targetPath = AbstractParser.getValuePath(type == TYPE_ITEM ? path : parentPath, (String) value); //先尝试获取,尽量保留缺省依赖路径,这样就不需要担心路径改变 Object target = onReferenceParse(targetPath); From 3ed76c34a3fb700c45463e47664a7614a200b2f2 Mon Sep 17 00:00:00 2001 From: huangcanjia Date: Thu, 2 Jun 2022 13:07:17 +0800 Subject: [PATCH 091/619] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=A4=9A?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=8F=82=E4=B8=8Ejoin=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E5=91=BD=E4=B8=AD=E7=BC=93=E5=AD=98=E8=80=8C?= =?UTF-8?q?=E5=87=BA=E7=8E=B0=E7=9A=841+N=E6=9F=A5=E8=AF=A2=E6=80=A7?= =?UTF-8?q?=E8=83=BD=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在缓存副表数据到ChildMap时,反向遍历onList集合,避免除了idKey,userKey之外的字段在putWhere时,跟前端传参指定的顺序相反,导致没有命中缓存。 - 将onList反转 issue #402 --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index a02bd79b0..1f1d4bd96 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -519,6 +520,7 @@ else if (config.isClickHouse() && (sqlTable.startsWith("`") || sqlTable.startsWi if (viceConfig != null) { //FIXME 只有和主表关联才能用 item,否则应该从 childMap 查其它副表数据 List onList = curJoin.getOnList(); if (onList != null) { + Collections.reverse(onList); for (On on : onList) { if (on != null) { String ok = on.getOriginKey(); From 6ab88d13d17265bf637c81eda5f1b95239f68dcb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 8 Jun 2022 21:16:21 +0800 Subject: [PATCH 092/619] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f37bea636..5b7be0593 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ Tencent is pleased to support the open source community by making APIJSON availa Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
This source code is licensed under the Apache License Version 2.0
+[APIJSON 已加入 腾源会开源摘星计划(WeOpen Star),该计划提供奖励以鼓励你加入我们的社区](https://github.com/weopenprojects/WeOpen-Star/issues/79)

APIJSON From 51f94299189a58e1971913274ffb7769cca216b7 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 8 Jun 2022 21:24:40 +0800 Subject: [PATCH 093/619] Update CONTRIBUTING.md --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0cb35f97d..e424b7050 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,9 +50,9 @@ ruoranw 提交的 18 个 Commits, 对 APIJSON 做出了 328 增加和 520 处删 Zerounary 提交的 6 个 Commits, 对 APIJSON 做出了 1,104 增加和 1 处删减(截止 2020/11/04 日)。

-APIJSON 持续招募贡献者,即使是在 Issue 中回答问题,或者做一些简单的 Bug Fix ,也会给 APIJSON 带来很大的帮助。
-APIJSON 已开发近 4 年,在此感谢所有开发者对于 APIJSON 的喜欢和支持,希望你能够成为 APIJSON 的核心贡献者,
-加入 APIJSON ,共同打造一个更棒的零代码、全自动、强安全 ORM 库!🍾🎉 +APIJSON 持续招募贡献者,新增功能、修复 Bug、完善文档、修正错误、宣传推广、回答问题等,都能帮助项目及广大用户。
+APIJSON 已开发近 6 年,在此感谢所有开发者对于 APIJSON 的喜欢和支持,希望你能够成为 APIJSON 的核心贡献者,
+加入 APIJSON ,共同打造一个更棒的零代码、全功能、强安全 ORM 库,造福更多前后端开发者!🍾🎉 ### 为什么一定要贡献代码? APIJSON 作为腾讯开源的知名热门项目,贡献代码除了可以给简历加亮点、为面试加分,还可以避免你碰到以下麻烦:
From f8a3c6706b9dec303e8802ccb8caaf740a3ebc5d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 8 Jun 2022 21:25:42 +0800 Subject: [PATCH 094/619] Update CONTRIBUTING.md --- CONTRIBUTING.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e424b7050..939328aa7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,21 +61,6 @@ APIJSON 作为腾讯开源的知名热门项目,贡献代码除了可以给简 3.你需要自己维护你的代码,每次升级 APIJSON 版本时,你都需要下载 APIJSON 新代码再合并你自己的更改
#### 所以为了让你自己的更改始终能跟上项目版本,得到他人给予的可靠且持续的维护,强烈建议 [提交 Pull Request](/CONTRIBUTING.md#pull-request) 来贡献代码。 -​ - -## Issue 提交 - -#### 对于贡献者 - -在提 Issue 前请确保满足一下条件: - -- 必须是一个 Bug 或者功能新增。 -- 必须是 APIJSON 相关问题。 -- 已经在 Issue 中搜索过,并且没有找到相似的 Issue 或者解决方案。 -- 完善下面模板中的信息 - -如果已经满足以上条件,我们提供了 Issue 的标准模版,请按照模板填写。 - ​ ## Pull Request @@ -145,3 +130,19 @@ https://www.jianshu.com/p/00cf29d2d66c

如何在 Github 上给别人的项目贡献代码
https://git-scm.com/book/zh/v2/GitHub-%E5%AF%B9%E9%A1%B9%E7%9B%AE%E5%81%9A%E5%87%BA%E8%B4%A1%E7%8C%AE + + +​ + +## Issue 提交 + +#### 对于贡献者 + +在提 Issue 前请确保满足一下条件: + +- 必须是一个 Bug 或者功能新增。 +- 必须是 APIJSON 相关问题。 +- 已经在 Issue 中搜索过,并且没有找到相似的 Issue 或者解决方案。 +- 完善下面模板中的信息 + +如果已经满足以上条件,我们提供了 Issue 的标准模版,请按照模板填写。 From 9fb6c885f0f5d493bf160e8f60762266823e838c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 17:24:11 +0800 Subject: [PATCH 095/619] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5b7be0593..a9f52add0 100644 --- a/README.md +++ b/README.md @@ -219,8 +219,8 @@ https://github.com/Tencent/APIJSON/issues/36
### 注意事项 -请求参数 JSON 中表名、字段名、关键词及对应的值都是大小写敏感、逗号敏感、分号敏感、空格敏感、换行敏感,
-大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 [设计规范](https://github.com/Tencent/APIJSON/blob/master/Document.md#3) 来调用 API +**请求参数 JSON 中表名、字段名、关键词及对应的值都是大小写敏感、逗号敏感、分号敏感、空格敏感、换行敏感,
+大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 [设计规范](https://github.com/Tencent/APIJSON/blob/master/Document.md#3) 来调用 API !** [#181](https://github.com/Tencent/APIJSON/issues/181)

From ed935c1c4ad32157cf35ab91a0752985c14bfc73 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 17:27:56 +0800 Subject: [PATCH 096/619] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a9f52add0..17777f3a1 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这
#### APIAuto 展示 APIJSON -使用 APIAuto-机器学习接口工具 来管理和测试 HTTP API 可大幅提升接口联调效率
+**使用 APIAuto-机器学习接口工具 来管理和测试 HTTP API 可大幅 减少传参错误、提升联调效率**
(注意网页工具界面是 APIAuto,里面的 URL+JSON 才是 APIJSON 的 HTTP API):

From 0600a8e93436b0ac60ca8f18c9c02176287b6434 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 18:50:31 +0800 Subject: [PATCH 097/619] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index 3683b0f3e..35df8dccd 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -4,6 +4,12 @@ about: Create a report to help us improve --- +如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献谢谢,开源要大家参与贡献才会更美好。
+开发者也是人,也需要工作和休息,养活自己和家人,也会有心情不好和身体病痛,往往没有额外的时间精力顾及一些小问题,请理解和支持~ +https://github.com/Tencent/APIJSON/issues/406 + +_________________________________ + **提 bug 请发请求和响应的【完整截屏】,没图的自行解决! 开发者有限的时间和精力主要放在【维护项目源码和文档】上! 【描述不详细】 或 【文档/常见问题 已有答案】 的问题可能会被忽略!! From e2826242b89291ecd0505efe7aa61eb8f1eef1d5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 18:57:52 +0800 Subject: [PATCH 098/619] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index 35df8dccd..193baa055 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -4,8 +4,9 @@ about: Create a report to help us improve --- -如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献谢谢,开源要大家参与贡献才会更美好。
-开发者也是人,也需要工作和休息,养活自己和家人,也会有心情不好和身体病痛,往往没有额外的时间精力顾及一些小问题,请理解和支持~ +如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。 +开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛, +往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~ https://github.com/Tencent/APIJSON/issues/406 _________________________________ From bd3dc264f234f4834c3296c3f3bfd327d5e2165e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 18:58:19 +0800 Subject: [PATCH 099/619] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index 193baa055..c752ac3d0 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -4,9 +4,9 @@ about: Create a report to help us improve --- -如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。 -开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛, -往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~ +如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。
+开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛,
+往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~
https://github.com/Tencent/APIJSON/issues/406 _________________________________ From d95a1b90c8ead2e6a5d2b0f219b5795b706532e2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 18:58:59 +0800 Subject: [PATCH 100/619] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index c752ac3d0..1bb8e0264 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -4,9 +4,9 @@ about: Create a report to help us improve --- -如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。
-开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛,
-往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~
+如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。 +开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛, +往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~ https://github.com/Tencent/APIJSON/issues/406 _________________________________ From 88d895d0f06911693e1b6289a00a8a1ca51f3341 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 19:00:54 +0800 Subject: [PATCH 101/619] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index 1bb8e0264..545c06b9c 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -4,9 +4,9 @@ about: Create a report to help us improve --- -如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。 -开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛, -往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~ +如果你已经知道问题所在、怎么解决,请直接 提交 Pull Request 为社区做贡献,非常感谢。 +开发者也是人,也需要工作、休息、恋爱、陪伴家人、走亲访友等,也有心情不好和身体病痛, +往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~ https://github.com/Tencent/APIJSON/issues/406 _________________________________ From f4d63d1799f160c32390b462ea4e68ee2c7ac422 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 19:08:23 +0800 Subject: [PATCH 102/619] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index 545c06b9c..ec35b18dc 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -7,6 +7,7 @@ about: Create a report to help us improve 如果你已经知道问题所在、怎么解决,请直接 提交 Pull Request 为社区做贡献,非常感谢。 开发者也是人,也需要工作、休息、恋爱、陪伴家人、走亲访友等,也有心情不好和身体病痛, 往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~ +少数个人的热情终有被耗尽的一天,只有大家共同建设和繁荣社区,才能让开源可持续发展! https://github.com/Tencent/APIJSON/issues/406 _________________________________ From 76c91a9557c34a9b187fee1befedc5a8c36b0c6a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 19:09:12 +0800 Subject: [PATCH 103/619] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index ec35b18dc..072aba60f 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -5,7 +5,7 @@ about: Create a report to help us improve --- 如果你已经知道问题所在、怎么解决,请直接 提交 Pull Request 为社区做贡献,非常感谢。 -开发者也是人,也需要工作、休息、恋爱、陪伴家人、走亲访友等,也有心情不好和身体病痛, +开发者也是人,也需要工作、休息、恋爱、陪伴家人、走亲会友等,也有心情不好和身体病痛, 往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~ 少数个人的热情终有被耗尽的一天,只有大家共同建设和繁荣社区,才能让开源可持续发展! https://github.com/Tencent/APIJSON/issues/406 From 0a764540fdaf5cd80ec79728604080087d29bb78 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 23 Jun 2022 23:45:26 +0800 Subject: [PATCH 104/619] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index fefcd5eae..afc131f5a 100644 --- a/Document.md +++ b/Document.md @@ -409,7 +409,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 存储过程 | "@key()":"SQL函数表达式",函数表达式为
function(key0,key1...)
会调用后端数据库对应的存储过程 SQL函数
function(String key0, String key1...)
除了参数会提前赋值,其它和 远程函数 一致 | ["@limit":10,
"@offset":0,
"@procedure()":"getCommentByUserId(id,@limit,@offset)"](http://apijson.cn:8080/get/{"User":{"@limit":10,"@offset":0,"@procedure()":"getCommentByUserId(id,@limit,@offset)"}})
会转为
`getCommentByUserId(38710,10,0)`
来调用存储过程 SQL 函数
`getCommentByUserId(IN id bigint, IN limit int, IN offset int)`
然后变为
"procedure":{
   "count":-1,
   "update":false,
   "list":[]
}
其中 count 是指写操作影响记录行数,-1 表示不是写操作;update 是指是否为写操作(增删改);list 为返回结果集 引用赋值 | "key@":"key0/key1/.../refKey",引用路径为用/分隔的字符串。以/开头的是缺省引用路径,从声明key所处容器的父容器路径开始;其它是完整引用路径,从最外层开始。
被引用的refKey必须在声明key的上面。如果对refKey的容器指定了返回字段,则被引用的refKey必须写在@column对应的值内,例如 "@column":"refKey,key1,..." | ["Moment":{
   "userId":38710
},
"User":{
   "id@":"/Moment/userId"
}](http://apijson.cn:8080/get/{"Moment":{"userId":38710},"User":{"id@":"%252FMoment%252FuserId"}})
User内的id引用了与User同级的Moment内的userId,
即User.id = Moment.userId,请求完成后
"id@":"/Moment/userId" 会变成 "id":38710 子查询 | "key@":{
   "range":"ALL",
   "from":"Table",
   "Table":{ ... }
}
其中:
range 可为 ALL,ANY;
from 为目标表 Table 的名称;
@ 后面的对象类似数组对象,可使用 count 和 join 等功能。 | ["id@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id=(SELECT min(userId) FROM Comment) - 模糊搜索 | "key$":"SQL搜索表达式" => "key$":["SQL搜索表达式"],任意SQL搜索表达式字符串,如 %key%(包含key), key%(以key开始), %k%e%y%(包含字母k,e,y) 等,%表示任意字符 | ["name$":"%m%"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"name$":"%2525m%2525"}}}),对应SQL是`name LIKE '%m%'`,查询name包含"m"的一个User数组 + 模糊搜索 | `"key$":"SQL搜索表达式"` => `"key$":["SQL搜索表达式"]`,任意SQL搜索表达式字符串,如 %key%(包含key), key%(以key开始), %k%e%y%(包含字母k,e,y) 等,%表示任意字符 | ["name$":"%m%"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"name$":"%2525m%2525"}}}),对应SQL是`name LIKE '%m%'`,查询name包含"m"的一个User数组 正则匹配 | "key~":"正则表达式" => "key~":["正则表达式"],任意正则表达式字符串,如 ^[0-9]+$ ,*~ 忽略大小写,可用于高级搜索 | ["name~":"^[0-9]+$"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"name~":"^[0-9]%252B$"}}}),对应SQL是`name REGEXP '^[0-9]+$'`,查询name中字符全为数字的一个User数组 连续范围 | "key%":"start,end" => "key%":["start,end"],其中 start 和 end 都只能为 Boolean, Number, String 中的一种,如 "2017-01-01,2019-01-01" ,["1,90000", "82001,100000"] ,可用于连续范围内的筛选 | ["date%":"2017-10-01,2018-10-01"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"date%2525":"2017-10-01,2018-10-01"}}}),对应SQL是`date BETWEEN '2017-10-01' AND '2018-10-01'`,查询在2017-10-01和2018-10-01期间注册的用户的一个User数组 新建别名 | "name:alias",name映射为alias,用alias替代name。可用于 column,Table,SQL函数 等。只用于GET类型、HEAD类型的请求 | ["@column":"toId:parentId"](http://apijson.cn:8080/get/{"Comment":{"@column":"id,toId:parentId","id":51}}),对应SQL是`toId AS parentId`,将查询的字段toId变为parentId返回 From 4dfd9d4d4fa1b5a524634580cc8beed60027bdf9 Mon Sep 17 00:00:00 2001 From: huangcanjia Date: Wed, 6 Jul 2022 18:51:20 +0800 Subject: [PATCH 105/619] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=B7=A8?= =?UTF-8?q?=E5=B1=82=E7=BA=A7app=20join?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 优化app join模式下,一对多join表查询时的1+N性能问题 - 支持客户端join字段,path的多层路径指定 - Join类增加count字段,以支持在生成副表sql时,按照指定count数量生成 - 处理App Join的查询结果时,将'一条条缓存'调整为'攒一起再缓存',防止错误替换 --- .../main/java/apijson/orm/AbstractParser.java | 18 +++++++++---- .../java/apijson/orm/AbstractSQLConfig.java | 2 +- .../java/apijson/orm/AbstractSQLExecutor.java | 26 +++++++++++-------- .../src/main/java/apijson/orm/Join.java | 8 ++++++ 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 5135a7279..c472ceb80 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1457,10 +1457,11 @@ else if (join != null){ // } path = path.substring(index + 1); - index = path.indexOf("/"); + index = path.lastIndexOf("/"); String tableKey = index < 0 ? path : path.substring(0, index); // User:owner apijson.orm.Entry entry = Pair.parseEntry(tableKey, true); - String table = entry.getKey(); // User + String[] tablePath = entry.getKey().split("/"); // User + String table = tableKey = tablePath[tablePath.length - 1]; // path最后一级为真实table;如:@/A/b/id@,b为目录最后一级 if (StringUtil.isName(table) == false) { throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 Table 值 " + table + " 不合法!" + "必须为 &/Table0, onList = new ArrayList<>(); for (Entry refEntry : refSet) { @@ -1656,7 +1664,7 @@ else if (join != null){ if (refObj.size() != tableObj.size()) { // 把 key 强制放最前,AbstractSQLExcecutor 中 config.putWhere 也是放尽可能最前 refObj.putAll(tableObj); - request.put(tableKey, refObj); + parentPathObj.put(tableKey, refObj); // tableObj.clear(); // tableObj.putAll(refObj); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index c9c9c8623..cfe4df81d 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -5008,7 +5008,7 @@ public static SQLConfig parseJoin(RequestMethod method, SQLCo alias = j.getAlias(); //JOIN子查询不能设置LIMIT,因为ON关系是在子查询后处理的,会导致结果会错误 SQLConfig joinConfig = newSQLConfig(method, table, alias, j.getRequest(), null, false, callback); - SQLConfig cacheConfig = j.canCacheViceTable() == false ? null : newSQLConfig(method, table, alias, j.getRequest(), null, false, callback).setCount(1); + SQLConfig cacheConfig = j.canCacheViceTable() == false ? null : newSQLConfig(method, table, alias, j.getRequest(), null, false, callback).setCount(j.getCount()); if (j.isAppJoin() == false) { //除了 @ APP JOIN,其它都是 SQL JOIN,则副表要这样配置 if (joinConfig.getDatabase() == null) { diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 1f1d4bd96..fb5713be3 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -260,7 +260,9 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws Log.i(TAG, ">>> execute result = getCache('" + sql + "', " + position + ") = " + result); if (result != null) { cachedSQLCount ++; - + if (getCache(sql,config).size() > 1) { + result.put(KEY_RAW_LIST, getCache(sql,config)); + } Log.d(TAG, "\n\n execute result != null >> return result;" + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n"); return result; } @@ -589,19 +591,17 @@ else if (curJoin.isOuterJoin() || curJoin.isAntiJoin()) { if (isHead == false) { // @ APP JOIN 查询副表并缓存到 childMap <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - executeAppJoin(config, resultList, childMap); + Map> appJoinChildMap = new HashMap<>(); + executeAppJoin(config, resultList, appJoinChildMap); // @ APP JOIN 查询副表并缓存到 childMap >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //子查询 SELECT Moment.*, Comment.id 中的 Comment 内字段 - Set> set = childMap.entrySet(); + Set>> set = appJoinChildMap.entrySet(); // - for (Entry entry : set) { - List l = new ArrayList<>(); - l.add(entry.getValue()); - putCache(entry.getKey(), l, null); + for (Entry> entry : set) { + putCache(entry.getKey(), entry.getValue(), null); } putCache(sql, resultList, config); @@ -633,7 +633,7 @@ else if (curJoin.isOuterJoin() || curJoin.isAntiJoin()) { * @param childMap * @throws Exception */ - protected void executeAppJoin(SQLConfig config, List resultList, Map childMap) throws Exception { + protected void executeAppJoin(SQLConfig config, List resultList, Map> childMap) throws Exception { List joinList = config.getJoinList(); if (joinList != null) { @@ -737,8 +737,12 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map } } cacheSql = cc.getSQL(false); - childMap.put(cacheSql, result); - + List results = childMap.get(cacheSql); + if (results == null) { + results = new ArrayList<>(); + childMap.put(cacheSql,results); + } + results.add(result); Log.d(TAG, ">>> executeAppJoin childMap.put('" + cacheSql + "', result); childMap.size() = " + childMap.size()); } } diff --git a/APIJSONORM/src/main/java/apijson/orm/Join.java b/APIJSONORM/src/main/java/apijson/orm/Join.java index 4fb34b0a9..af3c2979e 100644 --- a/APIJSONORM/src/main/java/apijson/orm/Join.java +++ b/APIJSONORM/src/main/java/apijson/orm/Join.java @@ -22,6 +22,7 @@ public class Join { private String joinType; // "@" - APP, "<" - LEFT, ">" - RIGHT, "*" - CROSS, "&" - INNER, "|" - FULL, "!" - OUTER, "^" - SIDE, "(" - ANTI, ")" - FOREIGN private String table; // User private String alias; // owner + private int count = 1; // 当app join子表,需要返回子表的行数,默认1行; private List onList; // ON User.id = Moment.userId AND ... private JSONObject request; // { "id@":"/Moment/userId" } @@ -39,6 +40,13 @@ public void setPath(String path) { this.path = path; } + public int getCount() { + return count; + } + public void setCount(int count) { + this.count = count; + } + public String getJoinType() { return joinType; } From 5525eab38d5c3886a93d082102835b08ad338979 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 7 Jul 2022 05:52:44 +0800 Subject: [PATCH 106/619] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=AF=B9=20APP=20JOI?= =?UTF-8?q?N=20=E5=90=8C=E5=B1=82=E5=92=8C=E8=B7=A8=E5=B1=82=E7=9A=84?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=EF=BC=9B=E5=AE=8C=E5=96=84=E5=AF=B9=20APP=20?= =?UTF-8?q?JOIN=20=E7=9A=84=20SQL=20=E6=89=A7=E8=A1=8C=E4=B8=8E=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E6=AC=A1=E6=95=B0=E7=BB=9F=E8=AE=A1=EF=BC=9B=E8=A7=A3?= =?UTF-8?q?=E5=86=B3=E5=90=8C=E5=B1=82=20JOIN=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=9A=84=E6=8A=A5=E9=94=99=20bug=EF=BC=9B=E8=A7=A3=E5=86=B3=20?= =?UTF-8?q?APP=20JOIN=20=E5=89=AF=E8=A1=A8=E8=BF=94=E5=9B=9E=E5=86=85?= =?UTF-8?q?=E9=83=A8=E5=AD=97=E6=AE=B5=20@RAW@LIST=EF=BC=9Bfastjson2=20?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=201.2.79?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 2 +- APIJSONORM/src/main/java/apijson/JSON.java | 9 +- .../apijson/orm/AbstractObjectParser.java | 20 +-- .../main/java/apijson/orm/AbstractParser.java | 160 +++++++++++------- .../java/apijson/orm/AbstractSQLExecutor.java | 159 +++++++++-------- 5 files changed, 202 insertions(+), 148 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 3f776fb6a..fcc4ffa63 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -21,7 +21,7 @@ com.alibaba fastjson - 2.0.4 + 1.2.79 javax.activation diff --git a/APIJSONORM/src/main/java/apijson/JSON.java b/APIJSONORM/src/main/java/apijson/JSON.java index b610cfc5b..40ef46f9c 100755 --- a/APIJSONORM/src/main/java/apijson/JSON.java +++ b/APIJSONORM/src/main/java/apijson/JSON.java @@ -6,8 +6,9 @@ import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.serializer.SerializerFeature; -import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson.JSONReader; import java.util.List; @@ -64,10 +65,10 @@ public static String getCorrectJson(String s, boolean isArray) { * @param json * @return */ - private static final JSONReader.Feature[] DEFAULT_FASTJSON_FEATURES = {JSONReader.Feature.FieldBased, JSONReader.Feature.UseBigDecimalForDoubles}; + private static final Feature[] DEFAULT_FASTJSON_FEATURES = {Feature.OrderedField, Feature.UseBigDecimal}; public static Object parse(Object obj) { try { - return com.alibaba.fastjson2.JSON.parse(obj instanceof String ? (String) obj : toJSONString(obj), DEFAULT_FASTJSON_FEATURES); + return com.alibaba.fastjson.JSON.parse(obj instanceof String ? (String) obj : toJSONString(obj), DEFAULT_FASTJSON_FEATURES); } catch (Exception e) { Log.i(TAG, "parse catch \n" + e.getMessage()); } @@ -101,7 +102,7 @@ public static T parseObject(String json, Class clazz) { Log.e(TAG, "parseObject clazz == null >> return null;"); } else { try { - return com.alibaba.fastjson2.JSON.parseObject(getCorrectJson(json), clazz, DEFAULT_FASTJSON_FEATURES); + return com.alibaba.fastjson.JSON.parseObject(getCorrectJson(json), clazz, DEFAULT_FASTJSON_FEATURES); } catch (Exception e) { Log.i(TAG, "parseObject catch \n" + e.getMessage()); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index e571cd8b4..68b31a87e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -71,7 +71,7 @@ public AbstractObjectParser setParser(AbstractParser parser) { * @param parentPath * @param request * @param name - * @throws Exception + * @throws Exception */ public AbstractObjectParser(@NotNull JSONObject request, String parentPath, SQLConfig arrayConfig , boolean isSubquery, boolean isTable, boolean isArrayMainTable) throws Exception { @@ -400,7 +400,7 @@ public boolean onParse(@NotNull String key, @NotNull Object value) throws Except if (arrObj == null) { throw new IllegalArgumentException("子查询 " + path + "/" + key + ":{ from:value } 中 value 对应的主表对象 " + from + ":{} 不存在!"); } - // + // SQLConfig cfg = (SQLConfig) arrObj.get(AbstractParser.KEY_CONFIG); if (cfg == null) { throw new NotExistException(TAG + ".onParse cfg == null"); @@ -453,7 +453,7 @@ else if (value instanceof String) { // //key{}@ getRealKey, 引用赋值路径 Log.d(TAG, "onParse isTable(table) == false >> return true;"); return true;//舍去,对Table无影响 } - } + } //直接替换原来的key@:path为key:target Log.i(TAG, "onParse >> key = replaceKey; value = target;"); @@ -517,7 +517,7 @@ else if (isTable && key.startsWith("@") && JSONRequest.TABLE_KEY_LIST.contains(k /** * @param key * @param value - * @param isFirst + * @param isFirst * @return * @throws Exception */ @@ -553,7 +553,7 @@ public JSON onChildParse(int index, String key, JSONObject value) throws Excepti + "数组 []:{} 中每个 key:{} 都必须是表 TableKey:{} 或 数组 arrayKey[]:{} !"); } - if ( //避免使用 "test":{"Test":{}} 绕过限制,实现查询爆炸 isTableKey && + if ( //避免使用 "test":{"Test":{}} 绕过限制,实现查询爆炸 isTableKey && (arrayConfig == null || arrayConfig.getPosition() == 0)) { objectCount ++; int maxObjectCount = parser.getMaxObjectCount(); @@ -577,7 +577,7 @@ public JSON onChildParse(int index, String key, JSONObject value) throws Excepti - //TODO 改用 MySQL json_add,json_remove,json_contains 等函数! + //TODO 改用 MySQL json_add,json_remove,json_contains 等函数! /**PUT key:[] * @param key * @param array @@ -757,7 +757,7 @@ public AbstractObjectParser executeSQL() throws Exception { //执行SQL操作数据库 if (isTable == false) {//提高性能 sqlReponse = new JSONObject(sqlRequest); - } + } else { try { sqlReponse = onSQLExecute(); @@ -896,7 +896,8 @@ public JSONObject onSQLExecute() throws Exception { result = parser.executeSQL(sqlConfig, isSubquery); boolean isSimpleArray = false; - List rawList = null; + // 提取并缓存数组主表的列表数据 + List rawList = (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST); if (isArrayMainTable && position == 0 && result != null) { @@ -905,8 +906,7 @@ public JSONObject onSQLExecute() throws Exception { && (childMap == null || childMap.isEmpty()) && (table.equals(arrayTable)); - // 提取并缓存数组主表的列表数据 - rawList = (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST); + // APP JOIN 副表时副表返回了这个字段 rawList = (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST); if (rawList != null) { String arrayPath = parentPath.substring(0, parentPath.lastIndexOf("[]") + 2); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index c472ceb80..3f598df1d 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -6,6 +6,7 @@ package apijson.orm; import static apijson.JSONObject.KEY_EXPLAIN; +import static apijson.JSONObject.KEY_JSON; import static apijson.RequestMethod.GET; import java.io.UnsupportedEncodingException; @@ -80,7 +81,7 @@ public abstract class AbstractParser implements Parser, Par public static int MAX_OBJECT_COUNT = 5; public static int MAX_ARRAY_COUNT = 5; public static int MAX_QUERY_DEPTH = 5; - + @Override public int getDefaultQueryCount() { return DEFAULT_QUERY_COUNT; @@ -122,13 +123,13 @@ public AbstractParser() { this(null); } /**needVerify = true - * @param requestMethod null ? requestMethod = GET + * @param method null ? requestMethod = GET */ public AbstractParser(RequestMethod method) { this(method, true); } /** - * @param requestMethod null ? requestMethod = GET + * @param method null ? requestMethod = GET * @param needVerify 仅限于为服务端提供方法免验证特权,普通请求不要设置为 false ! 如果对应Table有权限也建议用默认值 true,保持和客户端权限一致 */ public AbstractParser(RequestMethod method, boolean needVerify) { @@ -136,7 +137,7 @@ public AbstractParser(RequestMethod method, boolean needVerify) { setMethod(method); setNeedVerify(needVerify); } - + protected boolean isRoot = true; public boolean isRoot() { return isRoot; @@ -145,7 +146,7 @@ public AbstractParser setRoot(boolean isRoot) { this.isRoot = isRoot; return this; } - + @NotNull protected Visitor visitor; @@ -464,7 +465,7 @@ public JSONObject parseResponse(JSONObject request) { try { queryDepth = 0; executedSQLDuration = 0; - + requestObject = onObjectParse(request, null, null, null, false); onCommit(); @@ -486,7 +487,7 @@ public JSONObject parseResponse(JSONObject request) { if (Log.DEBUG) { res.put("sql:generate|cache|execute|maxExecute", getSQLExecutor().getGeneratedSQLCount() + "|" + getSQLExecutor().getCachedSQLCount() + "|" + getSQLExecutor().getExecutedSQLCount() + "|" + getMaxSQLCount()); res.put("depth:count|max", queryDepth + "|" + getMaxQueryDepth()); - + executedSQLDuration += sqlExecutor.getExecutedSQLDuration() + sqlExecutor.getSqlResultDuration(); long parseDuration = duration - executedSQLDuration; res.put("time:start|duration|end|parse|sql", startTime + "|" + duration + "|" + endTime + "|" + parseDuration + "|" + executedSQLDuration); @@ -551,7 +552,7 @@ public void onVerifyRole(@NotNull SQLConfig config) throws Exception { /**解析请求JSONObject * @param request => URLDecoder.decode(request, UTF_8); * @return - * @throws Exception + * @throws Exception */ @NotNull public static JSONObject parseRequest(String request) throws Exception { @@ -624,7 +625,7 @@ public static JSONObject wrapRequest(RequestMethod method, String tag, JSONObjec String arrKey = key + "[]"; if (target.containsKey(arrKey) == false) { - target.put(arrKey, new JSONArray()); + target.put(arrKey, new JSONArray()); } try { @@ -680,7 +681,7 @@ public static JSONObject newResult(int code, String msg) { public static JSONObject newResult(int code, String msg, boolean isRoot) { return extendResult(null, code, msg, isRoot); } - + /**添加JSONObject的状态内容,一般用于错误提示结果 * @param object * @param code @@ -702,13 +703,13 @@ public static JSONObject extendResult(JSONObject object, int code, String msg, b + " \n | \n 常见问题:https://github.com/Tencent/APIJSON/issues/36" + " \n 通用文档:https://github.com/Tencent/APIJSON/blob/master/Document.md" + " \n 视频教程:https://search.bilibili.com/all?keyword=APIJSON"); - + msg = index >= 0 ? msg.substring(0, index) : msg; - + if (object == null) { object = new JSONObject(true); } - + if (object.containsKey(JSONResponse.KEY_OK) == false) { object.put(JSONResponse.KEY_OK, JSONResponse.isSuccess(code)); } @@ -720,12 +721,12 @@ public static JSONObject extendResult(JSONObject object, int code, String msg, b if (m.isEmpty() == false) { msg = m + " ;\n " + StringUtil.getString(msg); } - + object.put(JSONResponse.KEY_MSG, msg); if (debug != null) { object.put("debug:info|help", debug); } - + return object; } @@ -758,7 +759,7 @@ public static JSONObject newSuccessResult() { public static JSONObject newSuccessResult(boolean isRoot) { return newResult(JSONResponse.CODE_SUCCESS, JSONResponse.MSG_SUCCEED, isRoot); } - + /**添加请求成功的状态内容 * @param object * @param e @@ -872,7 +873,7 @@ public static JSONObject newErrorResult(Exception e, boolean isRoot) { int code; if (e instanceof UnsupportedEncodingException) { code = JSONResponse.CODE_UNSUPPORTED_ENCODING; - } + } else if (e instanceof IllegalAccessException) { code = JSONResponse.CODE_ILLEGAL_ACCESS; } @@ -890,7 +891,7 @@ else if (e instanceof NotLoggedInException) { } else if (e instanceof TimeoutException) { code = JSONResponse.CODE_TIME_OUT; - } + } else if (e instanceof ConflictException) { code = JSONResponse.CODE_CONFLICT; } @@ -921,10 +922,8 @@ else if (e instanceof NullPointerException) { //TODO 启动时一次性加载Request所有内容,作为初始化。 /**获取正确的请求,非GET请求必须是服务器指定的 - * @param method - * @param request * @return - * @throws Exception + * @throws Exception */ @Override public JSONObject parseCorrectRequest() throws Exception { @@ -1027,13 +1026,14 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, // protected SQLConfig itemConfig; /**获取单个对象,该对象处于parentObject内 - * @param parentPath parentObject的路径 - * @param name parentObject的key - * @param request parentObject的value - * @param config for array item - * @return - * @throws Exception - */ + * @param request parentObject 的 value + * @param parentPath parentObject 的路径 + * @param name parentObject 的 key + * @param arrayConfig config for array item + * @param isSubquery 是否为子查询 + * @return + * @throws Exception + */ @Override public JSONObject onObjectParse(final JSONObject request , String parentPath, String name, final SQLConfig arrayConfig, boolean isSubquery) throws Exception { @@ -1093,7 +1093,7 @@ public JSONObject onObjectParse(final JSONObject request if (type == SQLConfig.TYPE_ITEM_CHILD_0 && query != JSONRequest.QUERY_TABLE && position == 0) { //TODO 应在这里判断 @column 中是否有聚合函数,而不是 AbstractSQLConfig.getColumnString - + JSONObject rp; Boolean compat = arrayConfig.getCompat(); if (compat != null && compat) { @@ -1192,7 +1192,7 @@ public JSONObject onObjectParse(final JSONObject request * @param parentPath parentObject的路径 * @param name parentObject的key * @param request parentObject的value - * @return + * @return * @throws Exception */ @Override @@ -1283,7 +1283,7 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name String[] childKeys = StringUtil.split(childPath, "-", false); if (childKeys == null || childKeys.length <= 0 || request.containsKey(childKeys[0]) == false) { childKeys = null; - } + } else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // 可能无需提取,直接返回 rawList 即可 arrTableKey = childKeys[0]; } @@ -1402,12 +1402,12 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_ORDER); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_RAW); } - + /**JOIN 多表同时筛选 * @param join "&/User,0"} * @param request - * @return - * @throws Exception + * @return + * @throws Exception */ private List onJoinParse(Object join, JSONObject request) throws Exception { JSONObject joinMap = null; @@ -1440,7 +1440,7 @@ else if (join != null){ // 分割 /Table/key String path = e == null ? null : e.getKey(); Object outer = path == null ? null : e.getValue(); - + if (outer instanceof JSONObject == false) { throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中value不合法!" + "必须为 &/Table0/key0, entry = Pair.parseEntry(tableKey, true); - String[] tablePath = entry.getKey().split("/"); // User - String table = tableKey = tablePath[tablePath.length - 1]; // path最后一级为真实table;如:@/A/b/id@,b为目录最后一级 + int index2 = tableKey.lastIndexOf("/"); + String arrKey = index2 < 0 ? null : tableKey.substring(0, index2); + if (arrKey != null && JSONRequest.isArrayKey(arrKey) == false) { + throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 对应的 " + arrKey + " 不是合法的数组 key[] !" + + "@ APP JOIN 最多允许跨 1 层,只能是子数组,且数组对象中不能有 join: value 键值对!"); + } + + tableKey = index2 < 0 ? tableKey : tableKey.substring(index2+1); + + apijson.orm.Entry entry = Pair.parseEntry(tableKey, true); + String table = entry.getKey(); // User if (StringUtil.isName(table) == false) { throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 Table 值 " + table + " 不合法!" + "必须为 &/Table0,> tableSet = tableObj.entrySet(); // 取出所有 join 条件 JSONObject requestObj = new JSONObject(true); // (JSONObject) obj.clone(); @@ -1538,7 +1565,19 @@ else if (join != null){ apijson.orm.Entry te = tk == null || p.substring(ind2 + 1).indexOf("/") >= 0 ? null : Pair.parseEntry(tk, true); if (te != null && JSONRequest.isTableKey(te.getKey()) && request.get(tk) instanceof JSONObject) { - refObj.put(k, v); + if (isAppJoin) { + if (refObj.size() >= 1) { + throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":" + e.getKey() + " 中 " + k + " 不合法!" + + "@ APP JOIN 必须有且只有一个引用赋值键值对!"); + } + + if (StringUtil.isName(k.substring(0, k.length() - 1)) == false) { + throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 中 " + k + " 不合法 !" + + "@ APP JOIN 只允许 key@:/Table/refKey 这种 = 等价连接!"); + } + } + + refObj.put(k, v); continue; } } @@ -1585,8 +1624,9 @@ else if (join != null){ j.setAlias(alias); j.setOuter((JSONObject) outer); j.setRequest(requestObj); - if (parentPathObj != null) { - j.setCount(parentPathObj.getInteger("count") != null ? parentPathObj.getInteger("count") : 1); + if (arrKey != null) { + Integer count = parentPathObj.getInteger(JSONRequest.KEY_COUNT); + j.setCount(count == null ? getDefaultQueryCount() : count); } List onList = new ArrayList<>(); @@ -1598,7 +1638,7 @@ else if (join != null){ throw new IllegalArgumentException(e.getKey() + ":value 中 value 值 " + targetPath + " 不合法!必须为引用赋值的路径 '/targetTable/targetKey' !"); } - // 取出引用赋值路径 targetPath 对应的 Table 和 key + // 取出引用赋值路径 targetPath 对应的 Table 和 key index = targetPath.lastIndexOf("/"); String targetKey = index < 0 ? null : targetPath.substring(index + 1); if (StringUtil.isName(targetKey) == false) { @@ -1638,24 +1678,24 @@ else if (join != null){ if (targetObj == null) { throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中路径对应的对象 '" + targetTableKey + "':{} 不存在或值为 null !必须是 {} 这种 JSONObject 格式!"); } - + Join.On on = new Join.On(); on.setKeyAndType(j.getJoinType(), j.getTable(), originKey); if (StringUtil.isName(on.getKey()) == false) { throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 key@ 中 key 值 " + on.getKey() + " 不合法!必须满足英文单词变量名格式!"); } - + on.setOriginKey(originKey); on.setOriginValue((String) refEntry.getValue()); on.setTargetTable(targetTable); on.setTargetAlias(targetAlias); on.setTargetKey(targetKey); - + onList.add(on); } - + j.setOnList(onList); - + joinList.add(j); // onList.add(table + "." + key + " = " + targetTable + "." + targetKey); // ON User.id = Moment.userId @@ -1762,7 +1802,7 @@ public static String replaceArrayChildPath(String parentPath, String valuePath) pos = ps[i+1].contains("/") == false ? ps[i+1] : ps[i+1].substring(0, ps[i+1].indexOf("/")); if ( - //StringUtil.isNumer(pos) && + //StringUtil.isNumer(pos) && vs[i+1].startsWith(pos + "/") == false) { vs[i+1] = pos + "/" + vs[i+1]; } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index fb5713be3..1dcbb7544 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -65,14 +65,14 @@ public int getCachedSQLCount() { public int getExecutedSQLCount() { return executedSQLCount; } - + private long executedSQLDuration = 0; private long sqlResultDuration = 0; @Override public long getExecutedSQLDuration() { return executedSQLDuration; } - + @Override public long getSqlResultDuration() { return sqlResultDuration; @@ -95,7 +95,7 @@ public void putCache(String sql, List list, SQLConfig config) { Log.i(TAG, "saveList sql == null || list == null >> return;"); return; } - + cacheMap.put(sql, list); } @@ -165,7 +165,7 @@ public ResultSet execute(@NotNull Statement statement, String sql) throws Except @Override public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws Exception { long executedSQLStartTime = System.currentTimeMillis(); - + boolean isPrepared = config.isPrepared(); final String sql = config.getSQL(false); @@ -212,7 +212,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws if (isExplain == false) { executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime; } - + result = new JSONObject(true); result.put(JSONResponse.KEY_COUNT, updateCount); result.put("update", updateCount >= 0); @@ -241,7 +241,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws //id,id{}至少一个会有,一定会返回,不用抛异常来阻止关联写操作时前面错误导致后面无条件执行! result.put(JSONResponse.KEY_COUNT, updateCount);//返回修改的记录数 - + String idKey = config.getIdKey(); if (config.getId() != null) { result.put(idKey, config.getId()); @@ -249,7 +249,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws if (config.getIdIn() != null) { result.put(idKey + "[]", config.getIdIn()); } - + return result; case GET: @@ -271,7 +271,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws executedSQLCount ++; executedSQLStartTime = System.currentTimeMillis(); } - rs = executeQuery(config); //FIXME SQL Server 是一次返回两个结果集,包括查询结果和执行计划,需要 moreResults + rs = executeQuery(config); //FIXME SQL Server 是一次返回两个结果集,包括查询结果和执行计划,需要 moreResults if (isExplain == false) { executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime; } @@ -318,23 +318,23 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws if (capacity > 100) { // 有 WHERE 条件,条件越多过滤数据越多,暂时不考虑 @combine:"a | (b & !c)" 里面 | OR 和 ! NOT 条件,太复杂也不是很必要 Map> combine = config.getCombineMap(); - + List andList = combine == null ? null : combine.get("&"); int andCondCount = andList == null ? (config.getWhere() == null ? 0 : config.getWhere().size()) : andList.size(); - + List orList = combine == null ? null : combine.get("|"); int orCondCount = orList == null ? 0 : orList.size(); - + List notList = combine == null ? null : combine.get("!"); int notCondCount = notList == null ? 0 : notList.size(); - + // 有 GROUP BY 分组,字段越少过滤数据越多 String[] group = StringUtil.split(config.getGroup()); int groupCount = group == null ? 0 : group.length; if (groupCount > 0 && Arrays.asList(group).contains(config.getIdKey())) { groupCount = 0; } - + // 有 HAVING 聚合函数,字段越多过滤数据越多,暂时不考虑 @combine:"a | (b & !c)" 里面 | OR 和 ! NOT 条件,太复杂也不是很必要 Map having = config.getHaving(); int havingCount = having == null ? 0 : having.size(); @@ -350,7 +350,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws } } } - + resultList = new ArrayList<>(capacity); } @@ -363,7 +363,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws //