Date: Thu, 16 Sep 2021 12:58:45 +0800
Subject: [PATCH 069/754] =?UTF-8?q?1.=E6=A0=B9=E6=8D=AE=E6=96=B9=E6=B3=95?=
=?UTF-8?q?=E4=B8=8D=E5=90=8C=E6=8B=BC=E6=8E=A5=E8=81=9A=E5=90=88=E8=AF=AD?=
=?UTF-8?q?=E5=8F=A5=202.=E4=BF=AE=E6=94=B9Oracle=E5=88=86=E7=BB=84?=
=?UTF-8?q?=E7=BB=9F=E8=AE=A1=E8=AF=AD=E5=8F=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../java/apijson/orm/AbstractSQLConfig.java | 38 +++++++++++--------
1 file changed, 23 insertions(+), 15 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
index 82bdbf5b4..856c9e8d7 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
@@ -21,12 +21,7 @@
import static apijson.JSONObject.KEY_ROLE;
import static apijson.JSONObject.KEY_SCHEMA;
import static apijson.JSONObject.KEY_USER_ID;
-import static apijson.RequestMethod.DELETE;
-import static apijson.RequestMethod.GET;
-import static apijson.RequestMethod.GETS;
-import static apijson.RequestMethod.HEADS;
-import static apijson.RequestMethod.POST;
-import static apijson.RequestMethod.PUT;
+import static apijson.RequestMethod.*;
import static apijson.SQL.AND;
import static apijson.SQL.NOT;
import static apijson.SQL.OR;
@@ -890,7 +885,7 @@ public String getOrderString(boolean hasPrefix) {
// return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(order, joinOrder, ", ");
// }
- if (getCount() > 0 && (isOracle() || isSQLServer() || isDb2())) { // Oracle, SQL Server, DB2 的 OFFSET 必须加 ORDER BY
+ if (getCount() > 0 && (isSQLServer() || isDb2())) { // Oracle, SQL Server, DB2 的 OFFSET 必须加 ORDER BY.去掉Oracle,Oracle里面没有offset关键字
// String[] ss = StringUtil.split(order);
if (StringUtil.isEmpty(order, true)) { //SQL Server 子查询内必须指定 OFFSET 才能用 ORDER BY
@@ -2685,6 +2680,11 @@ public static String getSQL(AbstractSQLConfig config) throws Exception {
String column = config.getColumnString();
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)){
+ 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();
}
@@ -2693,10 +2693,9 @@ public static String getSQL(AbstractSQLConfig config) throws Exception {
}
/**获取条件SQL字符串
- * @param page
* @param column
* @param table
- * @param where
+ * @param config
* @return
* @throws Exception
*/
@@ -2708,11 +2707,21 @@ private static String getConditionString(String column, String table, AbstractSQ
table = config.getSubqueryString(from) + " AS " + config.getAliasWithQuote() + " ";
}
- String condition = table + config.getJoinString() + where + (
- RequestMethod.isGetMethod(config.getMethod(), true) == false ?
- "" : config.getGroupString(true) + config.getHavingString(true) + config.getOrderString(true)
- )
- ; //+ config.getLimitString();
+ //根据方法不同,聚合语句不同。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.isHeadMethod(config.getMethod(), true)){
+ aggregation = config.getGroupString(true) + config.getHavingString(true) ;
+ }
+ if (config.getMethod() == PUT || config.getMethod() == DELETE){
+ aggregation = config.getHavingString(true) ;
+ }
+
+ String condition = table + config.getJoinString() + where + aggregation;
+ ; //+ config.getLimitString();
//no need to optimize
// if (config.getPage() <= 0 || ID.equals(column.trim())) {
@@ -2749,7 +2758,6 @@ 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() {
From 7a7eed635520556eca3172eaca6127f79aed31c3 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 17 Sep 2021 17:39:49 +0800
Subject: [PATCH 070/754] =?UTF-8?q?=E7=94=9F=E6=80=81=E5=91=A8=E8=BE=B9?=
=?UTF-8?q?=E9=A1=B9=E7=9B=AE=E6=96=B0=E5=A2=9E=20apijson-practice?=
=?UTF-8?q?=EF=BC=8C=E6=84=9F=E8=B0=A2=E8=B4=A1=E7=8C=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
https://github.com/vcoolwind/apijson-practice
---
README.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/README.md b/README.md
index a521615f8..f63560f83 100644
--- a/README.md
+++ b/README.md
@@ -435,6 +435,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
[FfApiJson](https://gitee.com/own_3_0/ff-api-json) 用 JSON 格式直接生成 SQL,借鉴 APIJSON 支持多数据源
+[apijson-practice](https://github.com/vcoolwind/apijson-practice) 实践一下apijson,对做管理平台还是能有不少提效的
+
[APIJSON-ToDo-Demo](https://github.com/jerrylususu/apijson_todo_demo) 一个简单的 todo 示例项目,精简数据,简化上手流程,带自定义鉴权逻辑
[apijson-learn](https://github.com/rainboy-learn/apijson-learn) APIJSON 学习笔记和源码解析
From ac9dc853f3886678c7bffc69f108176a6ccde01e Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Sun, 19 Sep 2021 22:42:58 +0800
Subject: [PATCH 071/754] =?UTF-8?q?=E6=8A=BD=E5=8F=96=E6=A0=B9=E6=8D=AE=20?=
=?UTF-8?q?tag=20=E8=87=AA=E5=8A=A8=E5=8C=85=E8=A3=85=E8=AF=B7=E6=B1=82?=
=?UTF-8?q?=E7=BB=93=E6=9E=84=E7=9A=84=E6=96=B9=E6=B3=95=E4=B8=BA=20wrapRe?=
=?UTF-8?q?quest?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../main/java/apijson/orm/AbstractParser.java | 55 +++++++++++++------
1 file changed, 39 insertions(+), 16 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
index 7051b3e06..f5ba02636 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
@@ -30,6 +30,7 @@
import com.alibaba.fastjson.JSONObject;
import apijson.JSON;
+import apijson.JSONRequest;
import apijson.JSONResponse;
import apijson.Log;
import apijson.NotNull;
@@ -513,31 +514,53 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers
+ "非开放请求必须是后端 Request 表中校验规则允许的操作!\n " + error + "\n如果需要则在 Request 表中新增配置!");
}
- JSONObject target = object;
- if (object.containsKey(tag) == false) { //tag 是 Table 名或 Table[]
+ //获取指定的JSON结构 >>>>>>>>>>>>>>
+ JSONObject target = wrapRequest(object, tag, false);
+
+ //JSONObject clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {}
+ return getVerifier().verifyRequest(method, name, target, request, maxUpdateCount, getGlobleDatabase(), getGlobleSchema(), creator);
+ }
- boolean isArrayKey = tag.endsWith(":[]"); // JSONRequest.isArrayKey(tag);
- String key = isArrayKey ? tag.substring(0, tag.length() - 3) : tag;
- if (apijson.JSONObject.isTableKey(key)) {
- if (isArrayKey) { //自动为 tag = Comment:[] 的 { ... } 新增键值对 "Comment[]":[] 为 { "Comment[]":[], ... }
- target.put(key + "[]", new JSONArray());
- }
- else { //自动为 tag = Comment 的 { ... } 包一层为 { "Comment": { ... } }
- target = new JSONObject(true);
- target.put(tag, object);
+ /**自动根据 tag 是否为 TableKey 及是否被包含在 object 内来决定是否包装一层,改为 { tag: object, "tag": tag }
+ * @param object
+ * @param tag
+ * @return
+ */
+ public static JSONObject wrapRequest(JSONObject object, String tag, boolean putTag) {
+ if (object == null || object.containsKey(tag)) { //tag 是 Table 名或 Table[]
+ if (putTag) {
+ if (object == null) {
+ object = new JSONObject(true);
}
+ object.put(JSONRequest.KEY_TAG, tag);
}
+ return object;
}
- //获取指定的JSON结构 >>>>>>>>>>>>>>
-
+ boolean isDiffArrayKey = tag.endsWith(":[]");
+ boolean isArrayKey = isDiffArrayKey || JSONRequest.isArrayKey(tag);
+ String key = isArrayKey ? tag.substring(0, tag.length() - (isDiffArrayKey ? 3 : 2)) : tag;
- //JSONObject clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {}
- return getVerifier().verifyRequest(method, name, target, request, maxUpdateCount, getGlobleDatabase(), getGlobleSchema(), creator);
+ JSONObject target = object;
+ if (apijson.JSONObject.isTableKey(key)) {
+ if (isDiffArrayKey) { //自动为 tag = Comment:[] 的 { ... } 新增键值对 "Comment[]":[] 为 { "Comment[]":[], ... }
+ target.put(key + "[]", new JSONArray());
+ }
+ else { //自动为 tag = Comment 的 { ... } 包一层为 { "Comment": { ... } }
+ target = new JSONObject(true);
+ target.put(tag, object);
+ }
+ }
+
+ if (putTag) {
+ target.put(JSONRequest.KEY_TAG, tag);
+ }
+
+ return target;
}
-
+
/**新建带状态内容的JSONObject
* @param code
* @param msg
From 77c375b20de5dd4f5486fde26f1b8e773e562f1c Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 23 Sep 2021 18:18:47 +0800
Subject: [PATCH 072/754] =?UTF-8?q?=E5=AE=8C=E5=96=84=E8=AF=B7=E6=B1=82?=
=?UTF-8?q?=E5=8F=82=E6=95=B0=E5=9C=A8=E5=90=84=E7=A7=8D=20method=20?=
=?UTF-8?q?=E5=8F=8A=20tag=20=E4=B8=8B=E7=9A=84=E8=87=AA=E5=8A=A8=E8=A1=A5?=
=?UTF-8?q?=E5=85=A8=EF=BC=9B=E5=85=81=E8=AE=B8=20GETS=20=E9=80=9A?=
=?UTF-8?q?=E8=BF=87=20key[]:{}=20=E6=9D=A5=E6=9F=A5=E5=A4=9A=E6=9D=A1?=
=?UTF-8?q?=E8=AE=B0=E5=BD=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../main/java/apijson/orm/AbstractParser.java | 47 +++++++++++++++----
1 file changed, 38 insertions(+), 9 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
index f5ba02636..6965f2802 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
@@ -515,7 +515,7 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers
}
//获取指定的JSON结构 >>>>>>>>>>>>>>
- JSONObject target = wrapRequest(object, tag, false);
+ JSONObject target = wrapRequest(method, tag, object, true);
//JSONObject clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {}
return getVerifier().verifyRequest(method, name, target, request, maxUpdateCount, getGlobleDatabase(), getGlobleSchema(), creator);
@@ -527,7 +527,9 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers
* @param tag
* @return
*/
- public static JSONObject wrapRequest(JSONObject object, String tag, boolean putTag) {
+ public static JSONObject wrapRequest(RequestMethod method, String tag, JSONObject object, boolean isStructure) {
+ boolean putTag = ! isStructure;
+
if (object == null || object.containsKey(tag)) { //tag 是 Table 名或 Table[]
if (putTag) {
if (object == null) {
@@ -544,12 +546,39 @@ public static JSONObject wrapRequest(JSONObject object, String tag, boolean putT
JSONObject target = object;
if (apijson.JSONObject.isTableKey(key)) {
- if (isDiffArrayKey) { //自动为 tag = Comment:[] 的 { ... } 新增键值对 "Comment[]":[] 为 { "Comment[]":[], ... }
- target.put(key + "[]", new JSONArray());
+ if (isDiffArrayKey) { //自动为 tag = Comment:[] 的 { ... } 新增键值对为 { "Comment[]":[], "TYPE": { "Comment[]": "OBJECT[]" } ... }
+ if (isStructure && (method == RequestMethod.POST || method == RequestMethod.PUT)) {
+ String arrKey = key + "[]";
+
+ if (target.containsKey(arrKey) == false) {
+ target.put(arrKey, new JSONArray());
+ }
+
+ try {
+ JSONObject type = target.getJSONObject(Operation.TYPE.name());
+ if (type == null || (type.containsKey(arrKey) == false)) {
+ if (type == null) {
+ type = new JSONObject(true);
+ }
+
+ type.put(arrKey, "OBJECT[]");
+ target.put(Operation.TYPE.name(), type);
+ }
+ }
+ catch (Throwable e) {
+ Log.w(TAG, "wrapRequest try { JSONObject type = target.getJSONObject(Operation.TYPE.name()); } catch (Exception e) = " + e.getMessage());
+ }
+ }
}
else { //自动为 tag = Comment 的 { ... } 包一层为 { "Comment": { ... } }
- target = new JSONObject(true);
- target.put(tag, object);
+ if (isArrayKey == false || RequestMethod.isGetMethod(method, true)) {
+ target = new JSONObject(true);
+ target.put(tag, object);
+ }
+ else if (target.containsKey(key) == false) {
+ target = new JSONObject(true);
+ target.put(key, object);
+ }
}
}
@@ -958,10 +987,10 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name
}
//不能允许GETS,否则会被通过"[]":{"@role":"ADMIN"},"Table":{},"tag":"Table"绕过权限并能批量查询
- if (isSubquery == false && RequestMethod.isGetMethod(requestMethod, false) == false) {
- throw new UnsupportedOperationException("key[]:{}只支持GET方法!不允许传 " + name + ":{} !");
+ if (isSubquery == false && RequestMethod.isGetMethod(requestMethod, true) == false) {
+ throw new UnsupportedOperationException("key[]:{} 只支持 GET, GETS 方法!其它方法不允许传 " + name + ":{} 等这种 key[]:{} 格式!");
}
- if (request == null || request.isEmpty()) {//jsonKey-jsonValue条件
+ if (request == null || request.isEmpty()) { // jsonKey-jsonValue 条件
return null;
}
String path = getAbsPath(parentPath, name);
From bf1c7966bf97e5dab4150db0c05790f012790621 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 23 Sep 2021 21:51:11 +0800
Subject: [PATCH 073/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=80=9A=E7=94=A8?=
=?UTF-8?q?=E6=96=87=E6=A1=A3=E7=9A=84=E6=9C=AC=E8=BA=AB=E8=AF=B4=E6=98=8E?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Document.md | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/Document.md b/Document.md
index e1f10aab2..e01c28658 100644
--- a/Document.md
+++ b/Document.md
@@ -1,5 +1,10 @@
-# APIJSON通用文档
-后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(如果和本文档有出入,以本文档为准,例如正则匹配 key? 已废弃,全面用 key~ 替代。)
+# APIJSON 通用文档
+本文是通用文档,只和 APIJSON 协议有关,和 C#, Go, Java, JavaScript, Python, PHP 等开发语言无关。
+具体开发语言相关的 配置、运行、部署 等文档见各个相关项目的文档,可以在首页点击对应语言的入口来查看。
+https://github.com/Tencent/APIJSON
+
+
+后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(非官方,和本文档有出入的地方以本文档为准,例如正则匹配 key? 已废弃,全面用 key~ 替代。)
* ### [1.示例](#1)
* ### [2.对比传统方式](#2)
From 0c08c22232e7d59e735eee77f48fc810ba81ba6c Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 23 Sep 2021 21:53:57 +0800
Subject: [PATCH 074/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20apijson-go=20?=
=?UTF-8?q?=E7=9A=84=E9=93=BE=E6=8E=A5=EF=BC=8C=E6=84=9F=E8=B0=A2=E4=BD=9C?=
=?UTF-8?q?=E8=80=85=E7=9A=84=E8=B4=A1=E7=8C=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
https://gitee.com/tiangao/apijson-go
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index f63560f83..0bfb971a6 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,7 @@ This source code is licensed under the Apache License Version 2.0
+
From 326c4d24c84dbf74e5bd42d2dc581bbb540394f6 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 23 Sep 2021 21:55:40 +0800
Subject: [PATCH 075/754] Update Document.md
---
Document.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Document.md b/Document.md
index e01c28658..e125516e5 100644
--- a/Document.md
+++ b/Document.md
@@ -2,7 +2,8 @@
本文是通用文档,只和 APIJSON 协议有关,和 C#, Go, Java, JavaScript, Python, PHP 等开发语言无关。
具体开发语言相关的 配置、运行、部署 等文档见各个相关项目的文档,可以在首页点击对应语言的入口来查看。
https://github.com/Tencent/APIJSON
-
+
+
后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(非官方,和本文档有出入的地方以本文档为准,例如正则匹配 key? 已废弃,全面用 key~ 替代。)
From 02a287a98f2bc80499d8d7498a2eafd7c7a3d2c0 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 23 Sep 2021 21:56:56 +0800
Subject: [PATCH 076/754] Update Document.md
---
Document.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Document.md b/Document.md
index e125516e5..f9969e3e1 100644
--- a/Document.md
+++ b/Document.md
@@ -5,7 +5,7 @@ https://github.com/Tencent/APIJSON

-后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(非官方,和本文档有出入的地方以本文档为准,例如正则匹配 key? 已废弃,全面用 key~ 替代。)
+后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(非官方,和本文档有出入的点以本文档为准,例如正则匹配 key? 已废弃,用 key~ 替代。)
* ### [1.示例](#1)
* ### [2.对比传统方式](#2)
From 51b4d5ed278b5f0487b22b2fedbcec0ada1f7b4d Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 23 Sep 2021 23:01:33 +0800
Subject: [PATCH 077/754] Update Document.md
---
Document.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Document.md b/Document.md
index f9969e3e1..bd641f91e 100644
--- a/Document.md
+++ b/Document.md
@@ -5,7 +5,7 @@ https://github.com/Tencent/APIJSON

-后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(非官方,和本文档有出入的点以本文档为准,例如正则匹配 key? 已废弃,用 key~ 替代。)
+后端开发者可以先看 [图文入门教程1](https://vincentcheng.github.io/apijson-doc/zh) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (都非官方,和本文档有出入的点以本文档为准,例如正则匹配 key? 已废弃,用 key~ 替代。)
* ### [1.示例](#1)
* ### [2.对比传统方式](#2)
From 061507810b28d7d2a58fdf1ff82a926044d3b6c1 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 24 Sep 2021 03:29:49 +0800
Subject: [PATCH 078/754] =?UTF-8?q?FunctionsAndRaws=20=E4=BB=A3=E7=A0=81?=
=?UTF-8?q?=E7=A7=BB=E5=9B=9E=20AbstractSQLConfig=20=E5=85=BC=E5=AE=B9?=
=?UTF-8?q?=E7=8E=B0=E6=9C=89=E7=94=A8=E6=88=B7=E4=BB=A3=E7=A0=81=EF=BC=9B?=
=?UTF-8?q?=E5=AE=8C=E6=88=90=20PostgreSQL=20=E7=9A=84=E7=AA=97=E5=8F=A3?=
=?UTF-8?q?=E5=87=BD=E6=95=B0=EF=BC=9B=E8=A7=A3=E5=86=B3=20PUT=20=20"blanc?=
=?UTF-8?q?e+":=201=20=E6=9C=AA=E5=8A=A0=E5=88=B0=20update=20set=20?=
=?UTF-8?q?=E5=AF=BC=E8=87=B4=E6=8A=A5=E9=94=99=EF=BC=9B=E8=A7=A3=E5=86=B3?=
=?UTF-8?q?=20@column=20=E5=9C=A8=20OVER,=20MATCH=20=E7=AD=89=E5=87=BD?=
=?UTF-8?q?=E6=95=B0=E5=86=85=E9=83=A8=E5=88=86=E5=AD=97=E6=AE=B5=E8=A7=A3?=
=?UTF-8?q?=E6=9E=90=E5=87=BA=E9=94=99=E4=BB=A5=E5=8F=8A=E5=8F=AF=E8=83=BD?=
=?UTF-8?q?=E7=9A=84=20SQL=20=E6=B3=A8=E5=85=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../apijson/orm/AbstractObjectParser.java | 6 +-
.../java/apijson/orm/AbstractSQLConfig.java | 737 ++++++++++++++++--
.../java/apijson/orm/AbstractVerifier.java | 2 +-
.../java/apijson/orm/FunctionsAndRaws.java | 519 ------------
4 files changed, 660 insertions(+), 604 deletions(-)
delete mode 100644 APIJSONORM/src/main/java/apijson/orm/FunctionsAndRaws.java
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
index 979bce409..421c2d306 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
@@ -224,6 +224,8 @@ public AbstractObjectParser parse(String name, boolean isReuse) throws Exception
whereList = new ArrayList(Arrays.asList(combine != null ? combine : new String[]{}));
whereList.add(apijson.JSONRequest.KEY_ID);
whereList.add(apijson.JSONRequest.KEY_ID_IN);
+// whereList.add(apijson.JSONRequest.KEY_USER_ID);
+// whereList.add(apijson.JSONRequest.KEY_USER_ID_IN);
}
//条件>>>>>>>>>>>>>>>>>>>
@@ -261,8 +263,8 @@ else if ((method == POST || method == PUT) && value instanceof JSONArray
&& JSONRequest.isTableArray(key)) { // JSONArray,批量新增或修改,往下一级提取
onTableArrayParse(key, (JSONArray) value);
}
- else if (method == PUT && value instanceof JSONArray && (whereList == null || whereList.contains(key) == false) && StringUtil.isName(key.replaceFirst("[+-]$", "")))
- { // PUT JSONArray
+ else if (method == PUT && value instanceof JSONArray && (whereList == null || whereList.contains(key) == false)
+ && StringUtil.isName(key.replaceFirst("[+-]$", ""))) { // PUT JSONArray
onPUTArrayParse(key, (JSONArray) value);
}
else { // JSONArray或其它Object,直接填充
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
index 45cbff2d7..108ce9deb 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
@@ -21,7 +21,13 @@
import static apijson.JSONObject.KEY_ROLE;
import static apijson.JSONObject.KEY_SCHEMA;
import static apijson.JSONObject.KEY_USER_ID;
-import static apijson.RequestMethod.*;
+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;
import static apijson.SQL.AND;
import static apijson.SQL.NOT;
import static apijson.SQL.OR;
@@ -88,9 +94,14 @@ public abstract class AbstractSQLConfig implements SQLConfig {
public static final List CONFIG_TABLE_LIST;
public static final List DATABASE_LIST;
+ // 自定义原始 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_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 改成更好的正则,校验前面为单词,中间为操作符,后面为值
+ PATTERN_FUNCTION = Pattern.compile("^[A-Za-z0-9%,:_@&~`!=\\<\\>\\|\\[\\]\\{\\} /\\.\\+\\-\\*\\^\\?\\(\\)\\$]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值
PATTERN_STRING = Pattern.compile("^[,#;\"`]+$");
TABLE_KEY_MAP = new HashMap();
@@ -118,6 +129,531 @@ public abstract class AbstractSQLConfig implements SQLConfig {
DATABASE_LIST.add(DATABASE_ORACLE);
DATABASE_LIST.add(DATABASE_DB2);
DATABASE_LIST.add(DATABASE_CLICKHOUSE);
+
+
+ RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况
+
+ // mysql关键字
+ RAW_MAP.put("AS","");
+ RAW_MAP.put("VALUE","");
+ RAW_MAP.put("DISTINCT","");
+
+ //时间
+ RAW_MAP.put("DATE","");
+ RAW_MAP.put("now()","");
+ RAW_MAP.put("DATETIME","");
+ RAW_MAP.put("DateTime","");
+ RAW_MAP.put("SECOND","");
+ RAW_MAP.put("MINUTE","");
+ RAW_MAP.put("HOUR","");
+ RAW_MAP.put("DAY","");
+ RAW_MAP.put("WEEK","");
+ RAW_MAP.put("MONTH","");
+ RAW_MAP.put("QUARTER","");
+ RAW_MAP.put("YEAR","");
+ RAW_MAP.put("json","");
+ RAW_MAP.put("unit","");
+
+ //MYSQL 数据类型 BINARY,CHAR,DATETIME,TIME,DECIMAL,SIGNED,UNSIGNED
+ RAW_MAP.put("BINARY","");
+ RAW_MAP.put("SIGNED","");
+ RAW_MAP.put("DECIMAL","");
+ RAW_MAP.put("BINARY","");
+ RAW_MAP.put("UNSIGNED","");
+ RAW_MAP.put("CHAR","");
+ RAW_MAP.put("TIME","");
+
+ //窗口函数关键字
+ RAW_MAP.put("OVER", "");
+ RAW_MAP.put("INTERVAL", "");
+ RAW_MAP.put("GROUP BY", ""); //往前
+ RAW_MAP.put("GROUP", ""); //往前
+ RAW_MAP.put("ORDER BY", ""); //往前
+ RAW_MAP.put("ORDER", "");
+ RAW_MAP.put("PARTITION BY", ""); //往前
+ RAW_MAP.put("PARTITION", ""); //往前
+ RAW_MAP.put("BY", "");
+ RAW_MAP.put("DESC", "");
+ RAW_MAP.put("ASC", "");
+ RAW_MAP.put("FOLLOWING", "");//往后
+ RAW_MAP.put("BETWEEN", "");
+ RAW_MAP.put("AND", "");
+ RAW_MAP.put("ROWS", "");
+
+ RAW_MAP.put("AGAINST", "");
+ RAW_MAP.put("IN NATURAL LANGUAGE MODE", "");
+ RAW_MAP.put("IN BOOLEAN MODE", "");
+ RAW_MAP.put("IN", "");
+ RAW_MAP.put("BOOLEAN", "");
+ RAW_MAP.put("NATURAL", "");
+ RAW_MAP.put("LANGUAGE", "");
+ RAW_MAP.put("MODE", "");
+
+
+ SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况
+
+
+ //窗口函数
+ SQL_FUNCTION_MAP.put("rank", "");//得到数据项在分组中的排名,排名相等的时候会留下空位
+ SQL_FUNCTION_MAP.put("dense_rank", ""); //得到数据项在分组中的排名,排名相等的时候不会留下空位
+ SQL_FUNCTION_MAP.put("row_number", "");//按照分组中的顺序生成序列,不存在重复的序列
+ SQL_FUNCTION_MAP.put("ntile", "");//用于将分组数据按照顺序切分成N片,返回当前切片值,不支持ROWS_BETWEE
+ SQL_FUNCTION_MAP.put("first_value", "");//取分组排序后,截止到当前行,分组内第一个值
+ SQL_FUNCTION_MAP.put("last_value", "");//取分组排序后,截止到当前行,分组内的最后一个值
+ SQL_FUNCTION_MAP.put("lag", "");//统计窗口内往上第n行值。第一个参数为列名,第二个参数为往上第n行(可选,默认为1),第三个参数为默认值(当往上第n行为NULL时候,取默认值,如不指定,则为NULL)
+ SQL_FUNCTION_MAP.put("lead", "");//统计窗口内往下第n行值。第一个参数为列名,第二个参数为往下第n行(可选,默认为1),第三个参数为默认值(当往下第n行为NULL时候,取默认值,如不指定,则为NULL)
+ SQL_FUNCTION_MAP.put("cume_dist", "");//)返回(小于等于当前行值的行数)/(当前分组内的总行数)
+ SQL_FUNCTION_MAP.put("percent_rank", "");//返回(组内当前行的rank值-1)/(分组内做总行数-1)
+
+ // MySQL 字符串函数
+ SQL_FUNCTION_MAP.put("ascii", ""); // ASCII(s) 返回字符串 s 的第一个字符的 ASCII 码。
+ SQL_FUNCTION_MAP.put("char_length", ""); // CHAR_LENGTH(s) 返回字符串 s 的字符数
+ SQL_FUNCTION_MAP.put("character_length", ""); // CHARACTER_LENGTH(s) 返回字符串 s 的字符数
+ SQL_FUNCTION_MAP.put("concat", ""); // CONCAT(s1, s2...sn) 字符串 s1,s2 等多个字符串合并为一个字符串
+ SQL_FUNCTION_MAP.put("concat_ws", ""); // CONCAT_WS(x, s1, s2...sn) 同 CONCAT(s1, s2 ...) 函数,但是每个字符串之间要加上 x,x 可以是分隔符
+ SQL_FUNCTION_MAP.put("field", ""); // FIELD(s, s1, s2...) 返回第一个字符串 s 在字符串列表 (s1, s2...)中的位置
+ SQL_FUNCTION_MAP.put("find_in_set", ""); // FIND_IN_SET(s1, s2) 返回在字符串s2中与s1匹配的字符串的位置
+ SQL_FUNCTION_MAP.put("format", ""); // FORMAT(x, n) 函数可以将数字 x 进行格式化 "#,###.##", 将 x 保留到小数点后 n 位,最后一位四舍五入。
+ SQL_FUNCTION_MAP.put("insert", ""); // INSERT(s1, x, len, s2) 字符串 s2 替换 s1 的 x 位置开始长度为 len 的字符串
+ SQL_FUNCTION_MAP.put("locate", ""); // LOCATE(s1, s) 从字符串 s 中获取 s1 的开始位置
+ SQL_FUNCTION_MAP.put("lcase", ""); // LCASE(s) 将字符串 s 的所有字母变成小写字母
+ SQL_FUNCTION_MAP.put("left", ""); // LEFT(s, n) 返回字符串 s 的前 n 个字符
+ SQL_FUNCTION_MAP.put("length", ""); // LENGTH(s) 返回字符串 s 的字符数
+ SQL_FUNCTION_MAP.put("lower", ""); // LOWER(s) 将字符串 s 的所有字母变成小写字母
+ SQL_FUNCTION_MAP.put("lpad", ""); // LPAD(s1, len, s2) 在字符串 s1 的开始处填充字符串 s2,使字符串长度达到 len
+ SQL_FUNCTION_MAP.put("ltrim", ""); // LTRIM(s) 去掉字符串 s 开始处的空格
+ SQL_FUNCTION_MAP.put("mid", ""); // MID(s, n, len) 从字符串 s 的 n 位置截取长度为 len 的子字符串,同 SUBSTRING(s, n, len)
+ SQL_FUNCTION_MAP.put("position", ""); // POSITION(s, s1); 从字符串 s 中获取 s1 的开始位置
+ SQL_FUNCTION_MAP.put("repeat", ""); // REPEAT(s, n) 将字符串 s 重复 n 次
+ SQL_FUNCTION_MAP.put("replace", ""); // REPLACE(s, s1, s2) 将字符串 s2 替代字符串 s 中的字符串 s1
+ SQL_FUNCTION_MAP.put("reverse", ""); // REVERSE(s); // ) 将字符串s的顺序反过来
+ SQL_FUNCTION_MAP.put("right", ""); // RIGHT(s, n) 返回字符串 s 的后 n 个字符
+ SQL_FUNCTION_MAP.put("rpad", ""); // RPAD(s1, len, s2) 在字符串 s1 的结尾处添加字符串 s2,使字符串的长度达到 len
+ SQL_FUNCTION_MAP.put("rtrim", ""); // RTRIM", ""); // ) 去掉字符串 s 结尾处的空格
+ SQL_FUNCTION_MAP.put("space", ""); // SPACE(n) 返回 n 个空格
+ SQL_FUNCTION_MAP.put("strcmp", ""); // STRCMP(s1, s2) 比较字符串 s1 和 s2,如果 s1 与 s2 相等返回 0 ,如果 s1>s2 返回 1,如果 s1d2 之间相隔的天数
+ SQL_FUNCTION_MAP.put("date_add", ""); // DATE_ADD(d,INTERVAL expr type) 计算起始日期 d 加上一个时间段后的日期
+ SQL_FUNCTION_MAP.put("date_format", ""); // DATE_FORMAT(d,f) 按表达式 f的要求显示日期 d
+ SQL_FUNCTION_MAP.put("date_sub", ""); // DATE_SUB(date,INTERVAL expr type) 函数从日期减去指定的时间间隔。
+ SQL_FUNCTION_MAP.put("day", ""); // DAY(d) 返回日期值 d 的日期部分
+ SQL_FUNCTION_MAP.put("dayname", ""); // DAYNAME(d) 返回日期 d 是星期几,如 Monday,Tuesday
+ SQL_FUNCTION_MAP.put("dayofmonth", ""); // DAYOFMONTH(d) 计算日期 d 是本月的第几天
+ SQL_FUNCTION_MAP.put("dayofweek", ""); // DAYOFWEEK(d) 日期 d 今天是星期几,1 星期日,2 星期一,以此类推
+ SQL_FUNCTION_MAP.put("dayofyear", ""); // DAYOFYEAR(d) 计算日期 d 是本年的第几天
+ SQL_FUNCTION_MAP.put("extract", ""); // EXTRACT(type FROM d) 从日期 d 中获取指定的值,type 指定返回的值。
+ SQL_FUNCTION_MAP.put("from_days", ""); // FROM_DAYS(n) 计算从 0000 年 1 月 1 日开始 n 天后的日期
+ SQL_FUNCTION_MAP.put("hour", ""); // 'HOUR(t) 返回 t 中的小时值
+ SQL_FUNCTION_MAP.put("last_day", ""); // LAST_DAY(d) 返回给给定日期的那一月份的最后一天
+ SQL_FUNCTION_MAP.put("localtime", ""); // LOCALTIME() 返回当前日期和时间
+ SQL_FUNCTION_MAP.put("localtimestamp", ""); // LOCALTIMESTAMP() 返回当前日期和时间
+ SQL_FUNCTION_MAP.put("makedate", ""); // MAKEDATE(year, day-of-year) 基于给定参数年份 year 和所在年中的天数序号 day-of-year 返回一个日期
+ SQL_FUNCTION_MAP.put("maketime", ""); // MAKETIME(hour, minute, second) 组合时间,参数分别为小时、分钟、秒
+ SQL_FUNCTION_MAP.put("microsecond", ""); // MICROSECOND(date) 返回日期参数所对应的微秒数
+ SQL_FUNCTION_MAP.put("minute", ""); // MINUTE(t) 返回 t 中的分钟值
+ SQL_FUNCTION_MAP.put("monthname", ""); // MONTHNAME(d) 返回日期当中的月份名称,如 November
+ SQL_FUNCTION_MAP.put("month", ""); // MONTH(d) 返回日期d中的月份值,1 到 12
+ SQL_FUNCTION_MAP.put("now", ""); // NOW() 返回当前日期和时间
+ SQL_FUNCTION_MAP.put("period_add", ""); // PERIOD_ADD(period, number) 为 年-月 组合日期添加一个时段
+ SQL_FUNCTION_MAP.put("period_diff", ""); // PERIOD_DIFF(period1, period2) 返回两个时段之间的月份差值
+ SQL_FUNCTION_MAP.put("quarter", ""); // QUARTER(d) 返回日期d是第几季节,返回 1 到 4
+ SQL_FUNCTION_MAP.put("second", ""); // SECOND(t) 返回 t 中的秒钟值
+ SQL_FUNCTION_MAP.put("sec_to_time", ""); // SEC_TO_TIME", ""); // ) 将以秒为单位的时间 s 转换为时分秒的格式
+ SQL_FUNCTION_MAP.put("str_to_date", ""); // STR_TO_DATE", ""); // tring, format_mask) 将字符串转变为日期
+ SQL_FUNCTION_MAP.put("subdate", ""); // SUBDATE(d,n) 日期 d 减去 n 天后的日期
+ SQL_FUNCTION_MAP.put("subtime", ""); // SUBTIME(t,n) 时间 t 减去 n 秒的时间
+ SQL_FUNCTION_MAP.put("sysdate", ""); // SYSDATE() 返回当前日期和时间
+ SQL_FUNCTION_MAP.put("time", ""); // TIME(expression) 提取传入表达式的时间部分
+ SQL_FUNCTION_MAP.put("time_format", ""); // TIME_FORMAT(t,f) 按表达式 f 的要求显示时间 t
+ SQL_FUNCTION_MAP.put("time_to_sec", ""); // TIME_TO_SEC(t) 将时间 t 转换为秒
+ SQL_FUNCTION_MAP.put("timediff", ""); // TIMEDIFF(time1, time2) 计算时间差值
+ SQL_FUNCTION_MAP.put("timestamp", ""); // TIMESTAMP(expression, interval) 单个参数时,函数返回日期或日期时间表达式;有2个参数时,将参数加和
+ SQL_FUNCTION_MAP.put("to_days", ""); // TO_DAYS(d) 计算日期 d 距离 0000 年 1 月 1 日的天数
+ SQL_FUNCTION_MAP.put("week", ""); // WEEK(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53
+ SQL_FUNCTION_MAP.put("weekday", ""); // WEEKDAY(d) 日期 d 是星期几,0 表示星期一,1 表示星期二
+ SQL_FUNCTION_MAP.put("weekofyear", ""); // WEEKOFYEAR(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53
+ SQL_FUNCTION_MAP.put("year", ""); // YEAR(d) 返回年份
+ SQL_FUNCTION_MAP.put("yearweek", ""); // YEARWEEK(date, mode) 返回年份及第几周(0到53),mode 中 0 表示周天,1表示周一,以此类推
+ SQL_FUNCTION_MAP.put("unix_timestamp", ""); // UNIX_TIMESTAMP(date) 获取UNIX时间戳函数,返回一个以 UNIX 时间戳为基础的无符号整数
+ SQL_FUNCTION_MAP.put("from_unixtime", ""); // FROM_UNIXTIME(date) 将 UNIX 时间戳转换为时间格式,与UNIX_TIMESTAMP互为反函数
+
+ // MYSQL JSON 函数
+ SQL_FUNCTION_MAP.put("json_append", ""); // JSON_APPEND(json_doc, path, val[, path, val] ...)) 插入JSON数组
+ SQL_FUNCTION_MAP.put("json_array", ""); // JSON_ARRAY(val1, val2...) 创建JSON数组
+ SQL_FUNCTION_MAP.put("json_array_append", ""); // JSON_ARRAY_APPEND(json_doc, val) 将数据附加到JSON文档
+ SQL_FUNCTION_MAP.put("json_array_insert", ""); // JSON_ARRAY_INSERT(json_doc, val) 插入JSON数组
+ SQL_FUNCTION_MAP.put("json_contains", ""); // JSON_CONTAINS(json_doc, val) JSON文档是否在路径中包含特定对象
+ SQL_FUNCTION_MAP.put("json_contains_path", ""); // JSON_CONTAINS_PATH(json_doc, path) JSON文档是否在路径中包含任何数据
+ SQL_FUNCTION_MAP.put("json_depth", ""); // JSON_DEPTH(json_doc) JSON文档的最大深度
+ SQL_FUNCTION_MAP.put("json_extract", ""); // JSON_EXTRACT(json_doc, path) 从JSON文档返回数据
+ SQL_FUNCTION_MAP.put("json_insert", ""); // JSON_INSERT(json_doc, val) 将数据插入JSON文档
+ SQL_FUNCTION_MAP.put("json_keys", ""); // JSON_KEYS(json_doc[, path]) JSON文档中的键数组
+ SQL_FUNCTION_MAP.put("json_length", ""); // JSON_LENGTH(json_doc) JSON文档中的元素数
+ SQL_FUNCTION_MAP.put("json_merge", ""); // JSON_MERGE(json_doc1, json_doc2) (已弃用) 合并JSON文档,保留重复的键。JSON_MERGE_PRESERVE()的已弃用同义词
+ SQL_FUNCTION_MAP.put("json_merge_patch", ""); // JSON_MERGE_PATCH(json_doc1, json_doc2) 合并JSON文档,替换重复键的值
+ SQL_FUNCTION_MAP.put("json_merge_preserve", ""); // JSON_MERGE_PRESERVE(json_doc1, json_doc2) 合并JSON文档,保留重复的键
+ SQL_FUNCTION_MAP.put("json_object", ""); // JSON_OBJECT(key1, val1, key2, val2...) 创建JSON对象
+ SQL_FUNCTION_MAP.put("json_overlaps", ""); // JSON_OVERLAPS(json_doc1, json_doc2) (引入8.0.17) 比较两个JSON文档,如果它们具有相同的键值对或数组元素,则返回TRUE(1),否则返回FALSE(0)
+ SQL_FUNCTION_MAP.put("json_pretty", ""); // JSON_PRETTY(json_doc) 以易于阅读的格式打印JSON文档
+ SQL_FUNCTION_MAP.put("json_quote", ""); // JSON_QUOTE(json_doc1) 引用JSON文档
+ SQL_FUNCTION_MAP.put("json_remove", ""); // JSON_REMOVE(json_doc1, path) 从JSON文档中删除数据
+ SQL_FUNCTION_MAP.put("json_replace", ""); // JSON_REPLACE(json_doc1, val1, val2) 替换JSON文档中的值
+ SQL_FUNCTION_MAP.put("json_schema_valid", ""); // JSON_SCHEMA_VALID(json_doc) (引入8.0.17) 根据JSON模式验证JSON文档;如果文档针对架构进行验证,则返回TRUE / 1;否则,则返回FALSE / 0
+ SQL_FUNCTION_MAP.put("json_schema_validation_report", ""); // JSON_SCHEMA_VALIDATION_REPORT(json_doc, mode) (引入8.0.17) 根据JSON模式验证JSON文档;以JSON格式返回有关验证结果的报告,包括成功或失败以及失败原因
+ SQL_FUNCTION_MAP.put("json_search", ""); // JSON_SEARCH(json_doc, val) JSON文档中值的路径
+ SQL_FUNCTION_MAP.put("json_set", ""); // JSON_SET(json_doc, val) 将数据插入JSON文档
+ // SQL_FUNCTION_MAP.put("json_storage_free", ""); // JSON_STORAGE_FREE() 部分更新后,JSON列值的二进制表示形式中的可用空间
+ // SQL_FUNCTION_MAP.put("json_storage_size", ""); // JSON_STORAGE_SIZE() 用于存储JSON文档的二进制表示的空间
+ SQL_FUNCTION_MAP.put("json_table", ""); // JSON_TABLE() 从JSON表达式返回数据作为关系表
+ SQL_FUNCTION_MAP.put("json_type", ""); // JSON_TYPE(json_doc) JSON值类型
+ SQL_FUNCTION_MAP.put("json_unquote", ""); // JSON_UNQUOTE(json_doc) 取消引用JSON值
+ SQL_FUNCTION_MAP.put("json_valid", ""); // JSON_VALID(json_doc) JSON值是否有效
+ SQL_FUNCTION_MAP.put("json_arrayagg", ""); // JSON_ARRAYAGG(key) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 数组
+ SQL_FUNCTION_MAP.put("json_objectagg", ""); // JSON_OBJECTAGG(key, val)) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 对象
+
+ // MySQL 高级函数
+ // SQL_FUNCTION_MAP.put("bin", ""); // BIN(x) 返回 x 的二进制编码
+ // SQL_FUNCTION_MAP.put("binary", ""); // BINARY(s) 将字符串 s 转换为二进制字符串
+ SQL_FUNCTION_MAP.put("case", ""); // CASE 表示函数开始,END 表示函数结束。如果 condition1 成立,则返回 result1, 如果 condition2 成立,则返回 result2,当全部不成立则返回 result,而当有一个成立之后,后面的就不执行了。
+ SQL_FUNCTION_MAP.put("cast", ""); // CAST(x AS type) 转换数据类型
+ SQL_FUNCTION_MAP.put("coalesce", ""); // COALESCE(expr1, expr2, ...., expr_n) 返回参数中的第一个非空表达式(从左向右)
+ // SQL_FUNCTION_MAP.put("conv", ""); // CONV(x,f1,f2) 返回 f1 进制数变成 f2 进制数
+ // SQL_FUNCTION_MAP.put("convert", ""); // CONVERT(s, cs) 函数将字符串 s 的字符集变成 cs
+ SQL_FUNCTION_MAP.put("if", ""); // IF(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。
+ SQL_FUNCTION_MAP.put("ifnull", ""); // IFNULL(v1,v2) 如果 v1 的值不为 NULL,则返回 v1,否则返回 v2。
+ SQL_FUNCTION_MAP.put("isnull", ""); // ISNULL(expression) 判断表达式是否为 NULL
+ 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) 全文检索
+
+
+
+
+
+ //clickhouse 字符串函数 注释的函数表示返回的格式暂时不支持,如:返回数组 ,同时包含因版本不同 clickhosue不支持的函数,版本
+ SQL_FUNCTION_MAP.put("empty", ""); // empty(s) 对于空字符串s返回1,对于非空字符串返回0
+ SQL_FUNCTION_MAP.put("notEmpty", ""); //notEmpty(s) 对于空字符串返回0,对于非空字符串返回1。
+ SQL_FUNCTION_MAP.put("lengthUTF8", ""); //假定字符串以UTF-8编码组成的文本,返回此字符串的Unicode字符长度。如果传入的字符串不是UTF-8编码,则函数可能返回一个预期外的值
+ SQL_FUNCTION_MAP.put("lcase", ""); //将字符串中的ASCII转换为小写
+ SQL_FUNCTION_MAP.put("ucase", ""); //将字符串中的ASCII转换为大写。
+ SQL_FUNCTION_MAP.put("lowerUTF8", ""); //将字符串转换为小写,函数假设字符串是以UTF-8编码文本的字符集。
+ SQL_FUNCTION_MAP.put("upperUTF8", ""); //将字符串转换为大写,函数假设字符串是以UTF-8编码文本的字符集。
+ SQL_FUNCTION_MAP.put("isValidUTF8", ""); // 检查字符串是否为有效的UTF-8编码,是则返回1,否则返回0。
+ SQL_FUNCTION_MAP.put("toValidUTF8", "");//用�(U+FFFD)字符替换无效的UTF-8字符。所有连续的无效字符都会被替换为一个替换字符。
+ SQL_FUNCTION_MAP.put("reverseUTF8", "");//以Unicode字符为单位反转UTF-8编码的字符串。
+ SQL_FUNCTION_MAP.put("concatAssumeInjective", ""); // concatAssumeInjective(s1, s2, …) 与concat相同,区别在于,你需要保证concat(s1, s2, s3) -> s4是单射的,它将用于GROUP BY的优化。
+ SQL_FUNCTION_MAP.put("substringUTF8", ""); // substringUTF8(s,offset,length)¶ 与’substring’相同,但其操作单位为Unicode字符,函数假设字符串是以UTF-8进行编码的文本。如果不是则可能返回一个预期外的结果(不会抛出异常)。
+ SQL_FUNCTION_MAP.put("appendTrailingCharIfAbsent", ""); // appendTrailingCharIfAbsent(s,c) 如果’s’字符串非空并且末尾不包含’c’字符,则将’c’字符附加到末尾
+ SQL_FUNCTION_MAP.put("convertCharset", ""); // convertCharset(s,from,to) 返回从’from’中的编码转换为’to’中的编码的字符串’s’。
+ SQL_FUNCTION_MAP.put("base64Encode", ""); // base64Encode(s) 将字符串’s’编码成base64
+ SQL_FUNCTION_MAP.put("base64Decode", ""); //base64Decode(s) 使用base64将字符串解码成原始字符串。如果失败则抛出异常。
+ SQL_FUNCTION_MAP.put("tryBase64Decode", ""); //tryBase64Decode(s) 使用base64将字符串解码成原始字符串。但如果出现错误,将返回空字符串。
+ SQL_FUNCTION_MAP.put("endsWith", ""); //endsWith(s,后缀) 返回是否以指定的后缀结尾。如果字符串以指定的后缀结束,则返回1,否则返回0。
+ SQL_FUNCTION_MAP.put("startsWith", ""); //startsWith(s,前缀) 返回是否以指定的前缀开头。如果字符串以指定的前缀开头,则返回1,否则返回0。
+ SQL_FUNCTION_MAP.put("trimLeft", ""); //trimLeft(s)返回一个字符串,用于删除左侧的空白字符。
+ SQL_FUNCTION_MAP.put("trimRight", ""); //trimRight(s) 返回一个字符串,用于删除右侧的空白字符。
+ SQL_FUNCTION_MAP.put("trimBoth", ""); //trimBoth(s),用于删除任一侧的空白字符
+ SQL_FUNCTION_MAP.put("extractAllGroups", ""); //extractAllGroups(text, regexp) 从正则表达式匹配的非重叠子字符串中提取所有组
+ // SQL_FUNCTION_MAP.put("leftPad", ""); //leftPad('string', 'length'[, 'pad_string']) 用空格或指定的字符串从左边填充当前字符串(如果需要,可以多次),直到得到的字符串达到给定的长度
+ // SQL_FUNCTION_MAP.put("leftPadUTF8", ""); //leftPadUTF8('string','length'[, 'pad_string']) 用空格或指定的字符串从左边填充当前字符串(如果需要,可以多次),直到得到的字符串达到给定的长度
+ // SQL_FUNCTION_MAP.put("rightPad", ""); // rightPad('string', 'length'[, 'pad_string']) 用空格或指定的字符串(如果需要,可以多次)从右边填充当前字符串,直到得到的字符串达到给定的长度
+ // SQL_FUNCTION_MAP.put("rightPadUTF8", "");// rightPadUTF8('string','length'[, 'pad_string']) 用空格或指定的字符串(如果需要,可以多次)从右边填充当前字符串,直到得到的字符串达到给定的长度。
+ SQL_FUNCTION_MAP.put("normalizeQuery", ""); //normalizeQuery(x) 用占位符替换文字、文字序列和复杂的别名。
+ SQL_FUNCTION_MAP.put("normalizedQueryHash", ""); //normalizedQueryHash(x) 为类似查询返回相同的64位散列值,但不包含文字值。有助于对查询日志进行分析
+ SQL_FUNCTION_MAP.put("positionUTF8", ""); // positionUTF8(s, needle[, start_pos]) 返回在字符串中找到的子字符串的位置(以Unicode点表示),从1开始。
+ SQL_FUNCTION_MAP.put("multiSearchFirstIndex", ""); //multiSearchFirstIndex(s, [needle1, needle2, …, needlen]) 返回字符串s中最左边的needlei的索引i(从1开始),否则返回0
+ SQL_FUNCTION_MAP.put("multiSearchAny", ""); // multiSearchAny(s, [needle1, needle2, …, needlen])如果至少有一个字符串needlei匹配字符串s,则返回1,否则返回0。
+ SQL_FUNCTION_MAP.put("match", ""); //match(s, pattern) 检查字符串是否与模式正则表达式匹配。re2正则表达式。re2正则表达式的语法比Perl正则表达式的语法更有局限性。
+ SQL_FUNCTION_MAP.put("multiMatchAny", ""); //multiMatchAny(s, [pattern1, pattern2, …, patternn]) 与match相同,但是如果没有匹配的正则表达式返回0,如果有匹配的模式返回1
+ SQL_FUNCTION_MAP.put("multiMatchAnyIndex", ""); //multiMatchAnyIndex(s, [pattern1, pattern2, …, patternn]) 与multiMatchAny相同,但返回与干堆匹配的任何索引
+ SQL_FUNCTION_MAP.put("extract", ""); // extract(s, pattern) 使用正则表达式提取字符串的片段
+ SQL_FUNCTION_MAP.put("extractAll", ""); //extractAll(s, pattern) 使用正则表达式提取字符串的所有片段
+ SQL_FUNCTION_MAP.put("like", ""); //like(s, pattern) 检查字符串是否与简单正则表达式匹配
+ SQL_FUNCTION_MAP.put("notLike", "");// 和‘like’是一样的,但是是否定的
+ SQL_FUNCTION_MAP.put("countSubstrings", ""); //countSubstrings(s, needle[, start_pos])返回子字符串出现的次数
+ SQL_FUNCTION_MAP.put("countMatches", ""); //返回干s中的正则表达式匹配数。countMatches(s, pattern)
+ SQL_FUNCTION_MAP.put("replaceOne", ""); //replaceOne(s, pattern, replacement)将' s '中的' pattern '子串的第一个出现替换为' replacement '子串。
+
+ SQL_FUNCTION_MAP.put("replaceAll", ""); //replaceAll(s, pattern, replacement)/用' replacement '子串替换' s '中所有出现的' pattern '子串
+ SQL_FUNCTION_MAP.put("replaceRegexpOne", ""); //replaceRegexpOne(s, pattern, replacement)使用' pattern '正则表达式进行替换
+ SQL_FUNCTION_MAP.put("replaceRegexpAll", ""); //replaceRegexpAll(s, pattern, replacement)
+ SQL_FUNCTION_MAP.put("regexpQuoteMeta", ""); //regexpQuoteMeta(s)该函数在字符串中某些预定义字符之前添加一个反斜杠
+
+ //clickhouse日期函数
+ SQL_FUNCTION_MAP.put("toYear", ""); //将Date或DateTime转换为包含年份编号(AD)的UInt16类型的数字。
+ SQL_FUNCTION_MAP.put("toQuarter", ""); //将Date或DateTime转换为包含季度编号的UInt8类型的数字。
+ SQL_FUNCTION_MAP.put("toMonth", ""); //Date或DateTime转换为包含月份编号(1-12)的UInt8类型的数字。
+ SQL_FUNCTION_MAP.put("toDayOfYear", ""); //将Date或DateTime转换为包含一年中的某一天的编号的UInt16(1-366)类型的数字。
+ SQL_FUNCTION_MAP.put("toDayOfMonth", "");//将Date或DateTime转换为包含一月中的某一天的编号的UInt8(1-31)类型的数字。
+ SQL_FUNCTION_MAP.put("toDayOfWeek", ""); //将Date或DateTime转换为包含一周中的某一天的编号的UInt8(周一是1, 周日是7)类型的数字。
+ SQL_FUNCTION_MAP.put("toHour", ""); //将DateTime转换为包含24小时制(0-23)小时数的UInt8数字。
+ SQL_FUNCTION_MAP.put("toMinute", ""); //将DateTime转换为包含一小时中分钟数(0-59)的UInt8数字。
+ SQL_FUNCTION_MAP.put("toSecond", ""); //将DateTime转换为包含一分钟中秒数(0-59)的UInt8数字。
+ SQL_FUNCTION_MAP.put("toUnixTimestamp", ""); // 对于DateTime参数:将值转换为UInt32类型的数字-Unix时间戳
+ SQL_FUNCTION_MAP.put("toStartOfYear", ""); //将Date或DateTime向前取整到本年的第一天。
+ SQL_FUNCTION_MAP.put("toStartOfISOYear", ""); // 将Date或DateTime向前取整到ISO本年的第一天。
+ SQL_FUNCTION_MAP.put("toStartOfQuarter", "");//将Date或DateTime向前取整到本季度的第一天。
+ SQL_FUNCTION_MAP.put("toStartOfMonth", ""); //将Date或DateTime向前取整到本月的第一天。
+ SQL_FUNCTION_MAP.put("toMonday", ""); //将Date或DateTime向前取整到本周的星期
+ SQL_FUNCTION_MAP.put("toStartOfWeek", ""); //按mode将Date或DateTime向前取整到最近的星期日或星期一。
+ SQL_FUNCTION_MAP.put("toStartOfDay", ""); //将DateTime向前取整到今天的开始。
+ SQL_FUNCTION_MAP.put("toStartOfHour", ""); //将DateTime向前取整到当前小时的开始。
+ SQL_FUNCTION_MAP.put("toStartOfMinute", ""); //将DateTime向前取整到当前分钟的开始。
+ SQL_FUNCTION_MAP.put("toStartOfSecond", ""); //将DateTime向前取整到当前秒数的开始。
+ SQL_FUNCTION_MAP.put("toStartOfFiveMinute", "");//将DateTime以五分钟为单位向前取整到最接近的时间点。
+ SQL_FUNCTION_MAP.put("toStartOfTenMinutes", ""); //将DateTime以十分钟为单位向前取整到最接近的时间点。
+ SQL_FUNCTION_MAP.put("toStartOfFifteenMinutes", ""); //将DateTime以十五分钟为单位向前取整到最接近的时间点。
+ SQL_FUNCTION_MAP.put("toStartOfInterval", ""); //
+ SQL_FUNCTION_MAP.put("toTime", ""); //将DateTime中的日期转换为一个固定的日期,同时保留时间部分。
+ SQL_FUNCTION_MAP.put("toISOYear", ""); //将Date或DateTime转换为包含ISO年份的UInt16类型的编号。
+ SQL_FUNCTION_MAP.put("toISOWeek", ""); //
+ SQL_FUNCTION_MAP.put("toWeek", "");// 返回Date或DateTime的周数。
+ SQL_FUNCTION_MAP.put("toYearWeek", ""); //返回年和周的日期
+ SQL_FUNCTION_MAP.put("date_trunc", ""); //截断日期和时间数据到日期的指定部分
+ SQL_FUNCTION_MAP.put("date_diff", ""); //回两个日期或带有时间值的日期之间的差值。
+
+ SQL_FUNCTION_MAP.put("yesterday", ""); //不接受任何参数并在请求执行时的某一刻返回昨天的日期(Date)。
+ SQL_FUNCTION_MAP.put("today", ""); //不接受任何参数并在请求执行时的某一刻返回当前日期(Date)。
+ SQL_FUNCTION_MAP.put("timeSlot", ""); //将时间向前取整半小时。
+ SQL_FUNCTION_MAP.put("toYYYYMM", ""); //
+ SQL_FUNCTION_MAP.put("toYYYYMMDD", "");//
+ SQL_FUNCTION_MAP.put("toYYYYMMDDhhmmss", ""); //
+ SQL_FUNCTION_MAP.put("addYears", ""); // Function adds a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime
+ SQL_FUNCTION_MAP.put("addMonths", ""); //同上
+ SQL_FUNCTION_MAP.put("addWeeks", ""); //同上
+ SQL_FUNCTION_MAP.put("addDays", ""); //同上
+ SQL_FUNCTION_MAP.put("addHours", ""); //同上
+ SQL_FUNCTION_MAP.put("addMinutes", "");//同上
+ SQL_FUNCTION_MAP.put("addSeconds", ""); //同上
+ SQL_FUNCTION_MAP.put("addQuarters", ""); //同上
+ SQL_FUNCTION_MAP.put("subtractYears", ""); //Function subtract a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime
+ SQL_FUNCTION_MAP.put("subtractMonths", ""); //同上
+ SQL_FUNCTION_MAP.put("subtractWeeks", ""); //同上
+ SQL_FUNCTION_MAP.put("subtractDays", ""); //同上
+ SQL_FUNCTION_MAP.put("subtractours", "");//同上
+ SQL_FUNCTION_MAP.put("subtractMinutes", ""); //同上
+ SQL_FUNCTION_MAP.put("subtractSeconds", ""); //同上
+ SQL_FUNCTION_MAP.put("subtractQuarters", ""); //同上
+ SQL_FUNCTION_MAP.put("formatDateTime", ""); //函数根据给定的格式字符串来格式化时间
+ SQL_FUNCTION_MAP.put("timestamp_add", ""); //使用提供的日期或日期时间值添加指定的时间值。
+ SQL_FUNCTION_MAP.put("timestamp_sub", ""); //从提供的日期或带时间的日期中减去时间间隔。
+
+ //clickhouse json函数
+ SQL_FUNCTION_MAP.put("visitParamHas", ""); //visitParamHas(params, name)检查是否存在«name»名称的字段
+ SQL_FUNCTION_MAP.put("visitParamExtractUInt", ""); //visitParamExtractUInt(params, name)将名为«name»的字段的值解析成UInt64。
+ SQL_FUNCTION_MAP.put("visitParamExtractInt", ""); //与visitParamExtractUInt相同,但返回Int64。
+ SQL_FUNCTION_MAP.put("visitParamExtractFloat", ""); //与visitParamExtractUInt相同,但返回Float64。
+ SQL_FUNCTION_MAP.put("visitParamExtractBool", "");//解析true/false值。其结果是UInt8类型的。
+ SQL_FUNCTION_MAP.put("visitParamExtractRaw", ""); //返回字段的值,包含空格符。
+ SQL_FUNCTION_MAP.put("visitParamExtractString", ""); //使用双引号解析字符串。这个值没有进行转义。如果转义失败,它将返回一个空白字符串。
+ SQL_FUNCTION_MAP.put("JSONHas", ""); //如果JSON中存在该值,则返回1。
+ SQL_FUNCTION_MAP.put("JSONLength", ""); //返回JSON数组或JSON对象的长度。
+ SQL_FUNCTION_MAP.put("JSONType", ""); //返回JSON值的类型。
+ SQL_FUNCTION_MAP.put("JSONExtractUInt", ""); //解析JSON并提取值。这些函数类似于visitParam*函数。
+ SQL_FUNCTION_MAP.put("JSONExtractInt", ""); //
+ SQL_FUNCTION_MAP.put("JSONExtractFloat", ""); //
+ SQL_FUNCTION_MAP.put("JSONExtractBool", ""); //
+ SQL_FUNCTION_MAP.put("JSONExtractString", ""); //解析JSON并提取字符串。此函数类似于visitParamExtractString函数。
+ SQL_FUNCTION_MAP.put("JSONExtract", "");//解析JSON并提取给定ClickHouse数据类型的值。
+ SQL_FUNCTION_MAP.put("JSONExtractKeysAndValues", ""); //从JSON中解析键值对,其中值是给定的ClickHouse数据类型
+ SQL_FUNCTION_MAP.put("JSONExtractRaw", ""); //返回JSON的部分。
+ SQL_FUNCTION_MAP.put("toJSONString", ""); //
+
+ //clickhouse 类型转换函数
+ SQL_FUNCTION_MAP.put("toInt8", ""); //toInt8(expr) 转换一个输入值为Int类型
+ SQL_FUNCTION_MAP.put("toInt16", "");
+ SQL_FUNCTION_MAP.put("toInt32", "");
+ SQL_FUNCTION_MAP.put("toInt64", "");
+ SQL_FUNCTION_MAP.put("toInt8OrZero", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。
+ SQL_FUNCTION_MAP.put("toInt16OrZero", "");
+ SQL_FUNCTION_MAP.put("toInt32OrZero", "");
+ SQL_FUNCTION_MAP.put("toInt64OrZero", "");
+ SQL_FUNCTION_MAP.put("toInt8OrNull", "");//toInt(8|16|32|64)OrNull 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回NULL
+ SQL_FUNCTION_MAP.put("toInt16OrNull", "");
+ SQL_FUNCTION_MAP.put("toInt32OrNull", "");
+ SQL_FUNCTION_MAP.put("toInt64OrNull", "");
+ SQL_FUNCTION_MAP.put("toUInt8", ""); //toInt8(expr) 转换一个输入值为Int类型
+ SQL_FUNCTION_MAP.put("toUInt16", "");
+ SQL_FUNCTION_MAP.put("toUInt32", "");
+ SQL_FUNCTION_MAP.put("toUInt64", "");
+ SQL_FUNCTION_MAP.put("toUInt8OrZero", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。
+ SQL_FUNCTION_MAP.put("toUInt16OrZero", "");
+ SQL_FUNCTION_MAP.put("toUInt32OrZero", "");
+ SQL_FUNCTION_MAP.put("toUInt64OrZero", "");
+ SQL_FUNCTION_MAP.put("toUInt8OrNull", "");//toInt(8|16|32|64)OrNull 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回NULL
+ SQL_FUNCTION_MAP.put("toUInt16OrNull", "");
+ SQL_FUNCTION_MAP.put("toUInt32OrNull", "");
+ SQL_FUNCTION_MAP.put("toUInt64OrNull", "");
+
+ SQL_FUNCTION_MAP.put("toFloat32", "");
+ SQL_FUNCTION_MAP.put("toFloat64", "");
+ SQL_FUNCTION_MAP.put("toFloat32OrZero", "");
+ SQL_FUNCTION_MAP.put("toFloat64OrZero", "");
+ SQL_FUNCTION_MAP.put("toFloat32OrNull", "");
+ SQL_FUNCTION_MAP.put("toFloat64OrNull", "");
+
+ SQL_FUNCTION_MAP.put("toDate", ""); //
+ SQL_FUNCTION_MAP.put("toDateOrZero", ""); //toInt16(expr)
+ SQL_FUNCTION_MAP.put("toDateOrNull", ""); //toInt32(expr)
+ SQL_FUNCTION_MAP.put("toDateTimeOrZero", ""); //toInt64(expr)
+ SQL_FUNCTION_MAP.put("toDateTimeOrNull", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。
+
+ SQL_FUNCTION_MAP.put("toDecimal32", "");
+ SQL_FUNCTION_MAP.put("toFixedString", ""); // 将String类型的参数转换为FixedString(N)类型的值
+ SQL_FUNCTION_MAP.put("toStringCutToZero", ""); // 接受String或FixedString参数,返回String,其内容在找到的第一个零字节处被截断。
+ SQL_FUNCTION_MAP.put("toDecimal256", "");
+ SQL_FUNCTION_MAP.put("toDecimal32OrNull", "");
+ SQL_FUNCTION_MAP.put("toDecimal64OrNull", "");
+ SQL_FUNCTION_MAP.put("toDecimal128OrNull", "");
+ SQL_FUNCTION_MAP.put("toDecimal256OrNull", "");
+ SQL_FUNCTION_MAP.put("toDecimal32OrZero", "");
+ SQL_FUNCTION_MAP.put("toDecimal64OrZero", "");
+ SQL_FUNCTION_MAP.put("toDecimal128OrZero", "");
+ SQL_FUNCTION_MAP.put("toDecimal256OrZero", "");
+
+
+ SQL_FUNCTION_MAP.put("toIntervalSecond", ""); //把一个数值类型的值转换为Interval类型的数据。
+ SQL_FUNCTION_MAP.put("toIntervalMinute", "");
+ SQL_FUNCTION_MAP.put("toIntervalHour", "");
+ SQL_FUNCTION_MAP.put("toIntervalDay", "");
+ SQL_FUNCTION_MAP.put("toIntervalWeek", "");
+ SQL_FUNCTION_MAP.put("toIntervalMonth", "");
+ SQL_FUNCTION_MAP.put("toIntervalQuarter", "");
+ SQL_FUNCTION_MAP.put("toIntervalYear", "");
+ SQL_FUNCTION_MAP.put("parseDateTimeBestEffort", ""); //把String类型的时间日期转换为DateTime数据类型。
+ SQL_FUNCTION_MAP.put("parseDateTimeBestEffortOrNull", "");
+ SQL_FUNCTION_MAP.put("parseDateTimeBestEffortOrZero", "");
+ SQL_FUNCTION_MAP.put("toLowCardinality", "");
+
+
+
+ ////clickhouse hash函数
+ SQL_FUNCTION_MAP.put("halfMD5", ""); //计算字符串的MD5。然后获取结果的前8个字节并将它们作为UInt64(大端)返回
+ SQL_FUNCTION_MAP.put("MD5", ""); //计算字符串的MD5并将结果放入FixedString(16)中返回
+
+ //clickhouse ip地址函数
+ SQL_FUNCTION_MAP.put("IPv4NumToString", ""); //接受一个UInt32(大端)表示的IPv4的地址,返回相应IPv4的字符串表现形式,格式为A.B.C.D(以点分割的十进制数字)。
+ SQL_FUNCTION_MAP.put("IPv4StringToNum", ""); //与IPv4NumToString函数相反。如果IPv4地址格式无效,则返回0。
+ SQL_FUNCTION_MAP.put("IPv6NumToString", ""); //接受FixedString(16)类型的二进制格式的IPv6地址。以文本格式返回此地址的字符串。
+ SQL_FUNCTION_MAP.put("IPv6StringToNum", ""); //与IPv6NumToString的相反。如果IPv6地址格式无效,则返回空字节字符串。
+ SQL_FUNCTION_MAP.put("IPv4ToIPv6", ""); // 接受一个UInt32类型的IPv4地址,返回FixedString(16)类型的IPv6地址
+ SQL_FUNCTION_MAP.put("cutIPv6", ""); //接受一个FixedString(16)类型的IPv6地址,返回一个String,这个String中包含了删除指定位之后的地址的文本格
+ SQL_FUNCTION_MAP.put("toIPv4", ""); //IPv4StringToNum()的别名,
+ SQL_FUNCTION_MAP.put("toIPv6", ""); //IPv6StringToNum()的别名
+ SQL_FUNCTION_MAP.put("isIPAddressInRange", ""); //确定一个IP地址是否包含在以CIDR符号表示的网络中
+
+ //clickhouse Nullable处理函数
+ SQL_FUNCTION_MAP.put("isNull", ""); //检查参数是否为NULL。
+ SQL_FUNCTION_MAP.put("isNotNull", ""); //检查参数是否不为 NULL.
+ SQL_FUNCTION_MAP.put("ifNull", ""); //如果第一个参数为«NULL»,则返回第二个参数的值。
+ SQL_FUNCTION_MAP.put("assumeNotNull", ""); //将可为空类型的值转换为非Nullable类型的值。
+ SQL_FUNCTION_MAP.put("toNullable", ""); //将参数的类型转换为Nullable。
+
+ //clickhouse UUID函数
+ SQL_FUNCTION_MAP.put("generateUUIDv4", ""); // 生成一个UUID
+ SQL_FUNCTION_MAP.put("toUUID", ""); //toUUID(x) 将String类型的值转换为UUID类型的值。
+
+ //clickhouse 系统函数
+ SQL_FUNCTION_MAP.put("hostName", ""); //hostName()回一个字符串,其中包含执行此函数的主机的名称。
+ SQL_FUNCTION_MAP.put("getMacro", ""); //从服务器配置的宏部分获取指定值。
+ SQL_FUNCTION_MAP.put("FQDN", "");//返回完全限定的域名。
+ SQL_FUNCTION_MAP.put("basename", ""); //提取字符串最后一个斜杠或反斜杠之后的尾随部分
+ SQL_FUNCTION_MAP.put("currentUser", ""); //返回当前用户的登录。在分布式查询的情况下,将返回用户的登录,即发起的查询
+ SQL_FUNCTION_MAP.put("version", ""); //以字符串形式返回服务器版本。
+ SQL_FUNCTION_MAP.put("uptime", "");//以秒为单位返回服务器的正常运行时间。
+
+ //clickhouse 数学函数
+ SQL_FUNCTION_MAP.put("least", ""); //返回a和b中最小的值。
+ SQL_FUNCTION_MAP.put("greatest", ""); //返回a和b的最大值。
+ SQL_FUNCTION_MAP.put("plus", ""); //plus(a, b), a + b operator¶计算数值的总和。
+ SQL_FUNCTION_MAP.put("minus", ""); //minus(a, b), a - b operator 计算数值之间的差,结果总是有符号的。
+ SQL_FUNCTION_MAP.put("multiply", "");//multiply(a, b), a * b operator 计算数值的乘积
+ SQL_FUNCTION_MAP.put("divide", ""); //divide(a, b), a / b operator 计算数值的商。结果类型始终是浮点类型
+ SQL_FUNCTION_MAP.put("intDiv", ""); //intDiv(a,b)计算数值的商,向下舍入取整(按绝对值)。
+ SQL_FUNCTION_MAP.put("intDivOrZero", ""); // intDivOrZero(a,b)与’intDiv’的不同之处在于它在除以零或将最小负数除以-1时返回零。
+ SQL_FUNCTION_MAP.put("modulo", ""); //modulo(a, b), a % b operator 计算除法后的余数。
+ SQL_FUNCTION_MAP.put("moduloOrZero", ""); //和modulo不同之处在于,除以0时结果返回0
+ SQL_FUNCTION_MAP.put("negate", ""); //通过改变数值的符号位对数值取反,结果总是有符号
+ SQL_FUNCTION_MAP.put("gcd", ""); //gcd(a,b) 返回数值的最大公约数。
+ SQL_FUNCTION_MAP.put("lcm", ""); //lcm(a,b) 返回数值的最小公倍数
+ SQL_FUNCTION_MAP.put("e", ""); //e() 返回一个接近数学常量e的Float64数字。
+ SQL_FUNCTION_MAP.put("pi", ""); //pi() 返回一个接近数学常量π的Float64数字。
+ SQL_FUNCTION_MAP.put("exp2", ""); //exp2(x)¶接受一个数值类型的参数并返回它的2的x次幂。
+ SQL_FUNCTION_MAP.put("exp10", ""); //exp10(x)¶接受一个数值类型的参数并返回它的10的x次幂。
+ SQL_FUNCTION_MAP.put("cbrt", ""); //cbrt(x) 接受一个数值类型的参数并返回它的立方根。
+ SQL_FUNCTION_MAP.put("lgamma", ""); //lgamma(x) 返回x的绝对值的自然对数的伽玛函数。
+ SQL_FUNCTION_MAP.put("tgamma", ""); //tgamma(x)¶返回x的伽玛函数。
+ SQL_FUNCTION_MAP.put("intExp2", ""); //intExp2 接受一个数值类型的参数并返回它的2的x次幂(UInt64)
+ SQL_FUNCTION_MAP.put("intExp10", ""); //intExp10 接受一个数值类型的参数并返回它的10的x次幂(UInt64)。
+ SQL_FUNCTION_MAP.put("cosh", ""); // cosh(x)
+ SQL_FUNCTION_MAP.put("cosh", ""); //cosh(x)
+ SQL_FUNCTION_MAP.put("sinh", ""); //sinh(x)
+ SQL_FUNCTION_MAP.put("asinh", ""); //asinh(x)
+ SQL_FUNCTION_MAP.put("atanh", ""); //atanh(x)
+ SQL_FUNCTION_MAP.put("atan2", ""); //atan2(y, x)
+ SQL_FUNCTION_MAP.put("hypot", ""); //hypot(x, y)
+ SQL_FUNCTION_MAP.put("log1p", ""); //log1p(x)
+ SQL_FUNCTION_MAP.put("trunc", ""); //和truncate一样
+ SQL_FUNCTION_MAP.put("roundToExp2", ""); //接受一个数字。如果数字小于1,它返回0。
+ SQL_FUNCTION_MAP.put("roundDuration", ""); //接受一个数字。如果数字小于1,它返回0。
+ SQL_FUNCTION_MAP.put("roundAge", ""); // 接受一个数字。如果数字小于18,它返回0。
+ SQL_FUNCTION_MAP.put("roundDown", ""); //接受一个数字并将其舍入到指定数组中的一个元素
+ SQL_FUNCTION_MAP.put("bitAnd", ""); //bitAnd(a,b)
+ SQL_FUNCTION_MAP.put("bitOr", ""); //bitOr(a,b)
}
@@ -597,14 +1133,14 @@ public String getHavingString(boolean hasPrefix) {
method = expression.substring(0, start);
if (method.isEmpty() == false) {
- if (FunctionsAndRaws.SQL_FUNCTION_MAP == null || FunctionsAndRaws.SQL_FUNCTION_MAP.isEmpty()) {
+ 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 (FunctionsAndRaws.SQL_FUNCTION_MAP.containsKey(method) == false) {
+ else if (SQL_FUNCTION_MAP.containsKey(method) == false) {
throw new IllegalArgumentException("字符 " + method + " 不合法!"
+ "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\""
+ " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!");
@@ -796,14 +1332,14 @@ public String getRawSQL(String key, Object value) throws Exception {
+ "对应的 " + key + ":value 中 value 类型只能为 String!");
}
- String rawSQL = containRaw ? FunctionsAndRaws.RAW_MAP.get(value) : null;
+ 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 ("".equals(rawSQL)) {
+ if (rawSQL.isEmpty()) {
return (String) value;
}
}
@@ -865,7 +1401,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception {
for (String c : column) {
if (containRaw) {
// 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快
- if ("".equals(FunctionsAndRaws.RAW_MAP.get(c)) || FunctionsAndRaws.RAW_MAP.containsValue(c)) { // newSQLConfig 提前处理好的
+ if ("".equals(RAW_MAP.get(c)) || RAW_MAP.containsValue(c)) { // newSQLConfig 提前处理好的
//排除@raw中的值,以避免使用date_format(date,'%Y-%m-%d %H:%i:%s') 时,冒号的解析出错
//column.remove(c);
continue;
@@ -967,17 +1503,12 @@ public String getColumnString(boolean inSQLJoin) throws Exception {
List raw = getRaw();
boolean containRaw = raw != null && raw.contains(KEY_COLUMN);
- String expression;
- String method = null;
-
//...;fun0(arg0,arg1,...):fun0;fun1(arg0,arg1,...):fun1;...
for (int i = 0; i < keys.length; i++) {
-
- //fun(arg0,arg1,...)
- expression = keys[i];
+ String expression = keys[i]; //fun(arg0,arg1,...)
if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快
- if ("".equals(FunctionsAndRaws.RAW_MAP.get(expression)) || FunctionsAndRaws.RAW_MAP.containsValue(expression)) { // newSQLConfig 提前处理好的
+ if ("".equals(RAW_MAP.get(expression)) || RAW_MAP.containsValue(expression)) { // newSQLConfig 提前处理好的
continue;
}
@@ -996,12 +1527,12 @@ public String getColumnString(boolean inSQLJoin) throws Exception {
throw new UnsupportedOperationException("@column:value 的 value 中字符串 " + expression + " 不合法!"
+ "不允许传超过 100 个字符的函数或表达式!请用 @raw 简化传参!");
}
- keys[i] = getColumnPrase(expression);
+ keys[i] = getColumnPrase(expression, containRaw);
}
String c = StringUtil.getString(keys);
c = c + (StringUtil.isEmpty(joinColumn, true) ? "" : ", " + joinColumn);//不能在这里改,后续还要用到:
- return c;
+ return isMain() && isDistinct() ? PREFFIX_DISTINCT + c : c;
default:
throw new UnsupportedOperationException(
"服务器内部错误:getColumnString 不支持 " + RequestMethod.getName(getMethod())
@@ -1016,16 +1547,30 @@ public String getColumnString(boolean inSQLJoin) throws Exception {
* @param expression
* @return
*/
- public String getColumnPrase(String expression) {
+ public String getColumnPrase(String expression, boolean containRaw) {
String quote = getQuote();
int start = expression.indexOf('(');
if (start < 0) {
//没有函数 ,可能是字段,也可能是 DISTINCT xx
- String cks[] = parseArgsSplitWithComma(expression, true);
+ String[] cks = parseArgsSplitWithComma(expression, true, containRaw);
expression = StringUtil.getString(cks);
- } else {
+ } 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 (expression.indexOf("OVER") < 0) {
+ int overIndex = expression.indexOf(") OVER (");
+ int againstIndex = expression.indexOf(") AGAINST (");
+ boolean containOver = overIndex > 0 && overIndex < expression.length() - ") OVER (".length();
+ 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...\""
+ + " 中 function 必须符合小写英文单词的 SQL 函数名格式!不能同时存在窗口函数关键词 OVER 和全文索引关键词 AGAINST!");
+ }
+
+ if (containOver == false && containAgainst == false) {
int end = expression.lastIndexOf(")");
if (start >= end) {
throw new IllegalArgumentException("字符 " + expression + " 不合法!"
@@ -1033,13 +1578,13 @@ public String getColumnPrase(String expression) {
}
String fun = expression.substring(0, start);
if (fun.isEmpty() == false) {
- if (FunctionsAndRaws.SQL_FUNCTION_MAP == null || FunctionsAndRaws.SQL_FUNCTION_MAP.isEmpty()) {
+ 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...\""
+ " 中 function 必须符合小写英文单词的 SQL 函数名格式!");
}
- } else if (FunctionsAndRaws.SQL_FUNCTION_MAP.containsKey(fun) == false) {
+ } else if (SQL_FUNCTION_MAP.containsKey(fun) == false) {
throw new IllegalArgumentException("字符 " + fun + " 不合法!"
+ "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\""
+ " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!");
@@ -1047,8 +1592,13 @@ public String getColumnPrase(String expression) {
}
String s = expression.substring(start + 1, end);
+ boolean distinct = s.startsWith(PREFFIX_DISTINCT);
+ if (distinct) {
+ s = s.substring(PREFFIX_DISTINCT.length());
+ }
+
// 解析函数内的参数
- String ckeys[] = parseArgsSplitWithComma(s, false);
+ String ckeys[] = parseArgsSplitWithComma(s, false, containRaw);
String suffix = expression.substring(end + 1, expression.length()); //:contactCount
int index = suffix.lastIndexOf(":");
@@ -1066,14 +1616,14 @@ public String getColumnPrase(String expression) {
+ " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!");
}
- String origin = fun + "(" + StringUtil.getString(ckeys) + ")" + suffix;
+ String origin = fun + "(" + (distinct ? PREFFIX_DISTINCT : "") + StringUtil.getString(ckeys) + ")" + suffix;
expression = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote);
} else {
//是窗口函数 fun(arg0,agr1) OVER (agr0 agr1 ...)
- int overindex = expression.indexOf("OVER"); // OVER 的位置
- String s1 = expression.substring(0, overindex); // OVER 前半部分
- String s2 = expression.substring(overindex); // OVER 后半部分
+ int keyIndex = containOver ? overIndex : againstIndex;
+ String s1 = expression.substring(0, keyIndex + 1); // OVER 前半部分
+ String s2 = expression.substring(keyIndex + 1); // OVER 后半部分
int index1 = s1.indexOf("("); // 函数 "(" 的起始位置
String fun = s1.substring(0, index1); // 函数名称
@@ -1084,13 +1634,13 @@ public String getColumnPrase(String expression) {
+ "@column:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!");
}
if (fun.isEmpty() == false) {
- if (FunctionsAndRaws.SQL_FUNCTION_MAP == null || FunctionsAndRaws.SQL_FUNCTION_MAP.isEmpty()) {
+ 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...\""
+ " 中 function 必须符合小写英文单词的 SQL 函数名格式!");
}
- } else if (FunctionsAndRaws.SQL_FUNCTION_MAP.containsKey(fun) == false) {
+ } else if (SQL_FUNCTION_MAP.containsKey(fun) == false) {
throw new IllegalArgumentException("字符 " + fun + " 不合法!"
+ "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\""
+ " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!");
@@ -1098,18 +1648,18 @@ public String getColumnPrase(String expression) {
}
// 获取前半部分函数的参数解析 fun(arg0,agr1)
- String agrsString1[] = parseArgsSplitWithComma(s1.substring(index1 + 1, s1.lastIndexOf(")")), false);
+ String agrsString1[] = parseArgsSplitWithComma(s1.substring(index1 + 1, s1.lastIndexOf(")")), false, containRaw);
int index2 = s2.indexOf("("); // 后半部分 “(”的起始位置
String argString2 = s2.substring(index2 + 1, end); // 后半部分的参数
// 别名
- String alias = s2.lastIndexOf(":") < 0 ? null : s2.substring(s2.lastIndexOf(":") + 1);
+ String alias = s2.lastIndexOf(":") < s2.lastIndexOf(")") ? null : s2.substring(s2.lastIndexOf(":") + 1);
// 获取后半部分的参数解析 (agr0 agr1 ...)
- String argsString2[] = parseArgsSplitWithComma(argString2,false);
- expression = fun + "(" + StringUtil.getString(agrsString1) + ")" + " OVER " + "(" + StringUtil.getString(argsString2) + ")" + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); }
+ String argsString2[] = parseArgsSplitWithComma(argString2, false, containRaw);
+ expression = fun + "(" + StringUtil.getString(agrsString1) + (containOver ? ") OVER (" : ") AGAINST (") + StringUtil.getString(argsString2) + ")" + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); }
}
+
return expression;
-
}
/**
@@ -1119,7 +1669,7 @@ public String getColumnPrase(String expression) {
* @param isColumn true:不是函数参数。false:是函数参数
* @return
*/
- private String[] parseArgsSplitWithComma(String param, boolean isColumn) {
+ private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean containRaw) {
// 以"," 分割参数
String quote = getQuote();
String tableAlias = getAliasWithQuote();
@@ -1129,62 +1679,68 @@ private String[] parseArgsSplitWithComma(String param, boolean isColumn) {
String alias;
int index;
for (int i = 0; i < ckeys.length; i++) {
+ String ck = ckeys[i];
+
// 如果参数包含 "'" ,解析字符串
- if (ckeys[i].contains("'")) {
+ if (ck.contains("'")) {
int count = 0;
- for (int j = 0; j < ckeys[i].length(); j++) {
- if (ckeys[i].charAt(j) == '\'') count++;
+ for (int j = 0; j < ck.length(); j++) {
+ if (ck.charAt(j) == '\'') count++;
}
+ // FIXME 把 `column` 和 '2 values with [ / : ] ..' 按引号位置分割才能满足全文索引、窗口函数的需要
// 排除字符串中参数中包含 ' 的情况和不以' 开头和结尾的情况,同时排除 cast('s' as ...) 以空格分隔的参数中包含字符串的情况
- if (count != 2 || !(ckeys[i].startsWith("'") && ckeys[i].endsWith("'"))) {
- throw new IllegalArgumentException("字符串 " + ckeys[i] + " 不合法!"
+ if (count != 2 || !(ck.startsWith("'") && ck.endsWith("'"))) {
+ throw new IllegalArgumentException("字符串 " + ck + " 不合法!"
+ "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\""
+ " 中字符串参数不合法,必须以 ' 开头, ' 结尾,字符串中不能包含 ' ");
}
//sql 注入判断 判断
- origin = (ckeys[i].substring(1, ckeys[i].length() - 1));
+ origin = (ck.substring(1, ck.length() - 1));
if (origin.contains("--") || PATTERN_STRING.matcher(origin).matches() == true) {
- throw new IllegalArgumentException("字符 " + ckeys[i] + " 不合法!"
+ throw new IllegalArgumentException("字符 " + ck + " 不合法!"
+ "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\""
+ " 中所有字符串 arg 都必须不符合正则表达式 " + PATTERN_STRING + " 且不包含连续减号 -- !");
}
// 1.字符串不是字段也没有别名,所以不解析别名 2. 是字符串,进行预编译,使用getValue() ,对字符串进行截取
- ckeys[i] = getValue(ckeys[i].substring(1, ckeys[i].length() - 1)).toString();
-
- } else {
+ ckeys[i] = getValue(ck.substring(1, ck.length() - 1)).toString();
+ }
+ else {
// 参数不包含",",即不是字符串
// 解析参数:1. 字段 ,2. 是以空格分隔的参数 eg: cast(now() as date)
- index = ckeys[i].lastIndexOf(":"); //StringUtil.split返回数组中,子项不会有null
- origin = index < 0 ? ckeys[i] : ckeys[i].substring(0, index); //获取 : 之前的
- alias = index < 0 ? null : ckeys[i].substring(index + 1);
+ 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("字符 " + ckeys[i] + " 不合法!"
+ 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("字符 " + ckeys[i] + " 不合法!"
+ throw new IllegalArgumentException("字符 " + ck + " 不合法!"
+ "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\""
+ " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!");
}
}
}
// 以空格分割参数
- String mkes[] = StringUtil.split(ckeys[i], " ", true);
+ String[] mkes = containRaw ? StringUtil.split(ck, " ", true) : new String[]{ ck };
- boolean isName = false;
//如果参数中含有空格(少数情况) 比如 fun(arg1 arg2 arg3 ,arg4) 中的 arg1 arg2 arg3,比如 DISTINCT id
if (mkes != null && mkes.length >= 2) {
ckeys[i] = praseArgsSplitWithSpace(mkes);
} else {
- // 如果参数没有空格
- if ("".equals(FunctionsAndRaws.RAW_MAP.get(origin))) {
- // do nothing , 比如 toDate(now()) ,
+ boolean isName = false;
+
+ 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)) {
@@ -1193,14 +1749,16 @@ private String[] parseArgsSplitWithComma(String param, boolean isColumn) {
} else {
origin = getValue(origin).toString();
}
+
if (isName && isKeyPrefix()) {
- ckeys[i] = tableAlias + "." + origin;
- if (isColumn && StringUtil.isEmpty(alias, true) == false) {
- ckeys[i] += " AS " + quote + alias + quote;
- }
- } else {
- ckeys[i] = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote);
+ origin = tableAlias + "." + origin;
}
+
+ if (isColumn && StringUtil.isEmpty(alias, true) == false) {
+ origin += " AS " + quote + alias + quote;
+ }
+
+ ckeys[i] = origin;
}
}
@@ -1219,30 +1777,45 @@ private String[] parseArgsSplitWithComma(String param, boolean isColumn) {
private String praseArgsSplitWithSpace(String mkes[]) {
String quote = getQuote();
String tableAlias = getAliasWithQuote();
- boolean isName = false;
+
// 包含空格的参数 肯定不包含别名 不用处理别名
if (mkes != null && mkes.length > 0) {
for (int j = 0; j < mkes.length; j++) {
// now()/AS/ DISTINCT/VALUE 等等放在RAW_MAP中
- if ("".equals(FunctionsAndRaws.RAW_MAP.get(mkes[j]))) {
+ String origin = mkes[j];
+
+ String mk = RAW_MAP.get(origin);
+ if (mk != null) { // newSQLConfig 提前处理好的
+ if (mk.length() > 0) {
+ mkes[j] = mk;
+ }
continue;
- } else if (StringUtil.isNumer(mkes[j])) {
- // do nothing
- } else {
- //这里为什么还要做一次判断 是因为解析窗口函数调用的时候会判断一次
- if (isPrepared()) {
- if (mkes[j].startsWith("_") || mkes[j].contains("--") || PATTERN_FUNCTION.matcher(mkes[j]).matches() == false) {
- throw new IllegalArgumentException("字符 " + mkes[j] + " 不合法!"
- + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\""
- + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!");
- }
+ }
+
+ //这里为什么还要做一次判断 是因为解析窗口函数调用的时候会判断一次
+ if (isPrepared()) {
+ if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) {
+ throw new IllegalArgumentException("字符 " + origin + " 不合法!"
+ + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\""
+ + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!");
}
- mkes[j] = quote + mkes[j] + quote;
+ }
+
+ 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 (isName && isKeyPrefix()) {
- mkes[j] = tableAlias + "." + mkes[j];
+ origin = tableAlias + "." + origin;
}
+
+ mkes[j] = origin;
}
}
// 返回重新以" "拼接后的参数
@@ -2583,7 +3156,7 @@ public static String getSQL(AbstractSQLConfig config) throws Exception {
return "INSERT INTO " + tablePath + config.getColumnString() + " VALUES" + config.getValuesString();
case PUT:
if(config.isClickHouse()){
- return "ALTER TABLE " + tablePath + " UPDATE"+ config.getSetString()+ config.getWhereString(true);
+ return "ALTER TABLE " + tablePath + " UPDATE" + config.getSetString() + config.getWhereString(true);
}
return "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : "");
case DELETE:
@@ -2593,7 +3166,7 @@ public static String getSQL(AbstractSQLConfig config) throws Exception {
return "DELETE FROM " + tablePath + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); // PostgreSQL 不允许 LIMIT
default:
String explain = (config.isExplain() ? (config.isSQLServer() || config.isOracle() ? "SET STATISTICS PROFILE ON " : "EXPLAIN ") : "");
- if (config.isTest() && RequestMethod.isGetMethod(config.getMethod(), true)) {
+ if (config.isTest() && RequestMethod.isGetMethod(config.getMethod(), true)) { // FIXME 为啥是 code 而不是 count ?
String q = config.getQuote(); // 生成 SELECT ( (24 >=0 AND 24 <3) ) AS `code` LIMIT 1 OFFSET 0
return explain + "SELECT " + config.getWhereString(false) + " AS " + q + JSONResponse.KEY_CODE + q + config.getLimitString();
}
@@ -2605,9 +3178,9 @@ public static String getSQL(AbstractSQLConfig config) throws Exception {
//针对oracle分组后条数的统计
if ((config.getMethod() == HEAD || config.getMethod() == HEADS)
&& 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 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();
+ return explain + "SELECT * FROM (SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString();
}
return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString();
@@ -2826,9 +3399,9 @@ public static SQLConfig newSQLConfig(RequestMethod method, String table, String
}
- String idKey = callback.getIdKey(database, schema, table);
+ String idKey = callback.getIdKey(datasource, database, schema, table);
String idInKey = idKey + "{}";
- String userIdKey = callback.getUserIdKey(database, schema, table);
+ String userIdKey = callback.getUserIdKey(datasource, database, schema, table);
String userIdInKey = userIdKey + "{}";
//对id和id{}处理,这两个一定会作为条件
@@ -3063,7 +3636,7 @@ else if (w.startsWith("!")) {
}
//解决AccessVerifier新增userId没有作为条件,而是作为内容,导致PUT,DELETE出错
- if (isWhere || (StringUtil.isName(key) == false)) {
+ if (isWhere || (StringUtil.isName(key.replaceFirst("[+-]$", "")) == false)) {
tableWhere.put(key, value);
if (whereList == null || whereList.contains(key) == false) {
andList.add(key);
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java
index 448b54155..97ff9cd38 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java
@@ -970,7 +970,7 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name,
if (StringUtil.isEmpty(ds, false)) {
ds = datasource;
}
- String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, name);
+ String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, ds, name);
String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey;
//TODO放在operate前?考虑性能、operate修改后再验证的值是否和原来一样
diff --git a/APIJSONORM/src/main/java/apijson/orm/FunctionsAndRaws.java b/APIJSONORM/src/main/java/apijson/orm/FunctionsAndRaws.java
deleted file mode 100644
index e0b945340..000000000
--- a/APIJSONORM/src/main/java/apijson/orm/FunctionsAndRaws.java
+++ /dev/null
@@ -1,519 +0,0 @@
-package apijson.orm;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-
-public class FunctionsAndRaws {
- // 自定义原始 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_FUNCTION_MAP;
- static {
- RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况
-
- // mysql关键字
- RAW_MAP.put("AS","");
- RAW_MAP.put("VALUE","");
- RAW_MAP.put("DISTINCT","");
-
- //时间
- RAW_MAP.put("DATE","");
- RAW_MAP.put("now()","");
- RAW_MAP.put("DATETIME","");
- RAW_MAP.put("DateTime","");
- RAW_MAP.put("SECOND","");
- RAW_MAP.put("MINUTE","");
- RAW_MAP.put("HOUR","");
- RAW_MAP.put("DAY","");
- RAW_MAP.put("WEEK","");
- RAW_MAP.put("MONTH","");
- RAW_MAP.put("QUARTER","");
- RAW_MAP.put("YEAR","");
- RAW_MAP.put("json","");
- RAW_MAP.put("unit","");
-
- //MYSQL 数据类型 BINARY,CHAR,DATETIME,TIME,DECIMAL,SIGNED,UNSIGNED
- RAW_MAP.put("BINARY","");
- RAW_MAP.put("SIGNED","");
- RAW_MAP.put("DECIMAL","");
- RAW_MAP.put("BINARY","");
- RAW_MAP.put("UNSIGNED","");
- RAW_MAP.put("CHAR","");
- RAW_MAP.put("TIME","");
-
- //窗口函数关键字
- RAW_MAP.put("OVER","");
- RAW_MAP.put("INTERVAL","");
- RAW_MAP.put("ORDER","");
- RAW_MAP.put("BY","");
- RAW_MAP.put("PARTITION",""); //往前
- RAW_MAP.put("DESC","");
- RAW_MAP.put("ASC","");
- RAW_MAP.put("FOLLOWING","");//往后
- RAW_MAP.put("BETWEEN","");
- RAW_MAP.put("AND","");
- RAW_MAP.put("ROWS","");
-
-
- SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况
-
-
- //窗口函数
- SQL_FUNCTION_MAP.put("rank", "");//得到数据项在分组中的排名,排名相等的时候会留下空位
- SQL_FUNCTION_MAP.put("dense_rank", ""); //得到数据项在分组中的排名,排名相等的时候不会留下空位
- SQL_FUNCTION_MAP.put("row_number", "");//按照分组中的顺序生成序列,不存在重复的序列
- SQL_FUNCTION_MAP.put("ntile", "");//用于将分组数据按照顺序切分成N片,返回当前切片值,不支持ROWS_BETWEE
- SQL_FUNCTION_MAP.put("first_value", "");//取分组排序后,截止到当前行,分组内第一个值
- SQL_FUNCTION_MAP.put("last_value", "");//取分组排序后,截止到当前行,分组内的最后一个值
- SQL_FUNCTION_MAP.put("lag", "");//统计窗口内往上第n行值。第一个参数为列名,第二个参数为往上第n行(可选,默认为1),第三个参数为默认值(当往上第n行为NULL时候,取默认值,如不指定,则为NULL)
- SQL_FUNCTION_MAP.put("lead", "");//统计窗口内往下第n行值。第一个参数为列名,第二个参数为往下第n行(可选,默认为1),第三个参数为默认值(当往下第n行为NULL时候,取默认值,如不指定,则为NULL)
- SQL_FUNCTION_MAP.put("cume_dist", "");//)返回(小于等于当前行值的行数)/(当前分组内的总行数)
- SQL_FUNCTION_MAP.put("percent_rank", "");//返回(组内当前行的rank值-1)/(分组内做总行数-1)
-
- // MySQL 字符串函数
- SQL_FUNCTION_MAP.put("ascii", ""); // ASCII(s) 返回字符串 s 的第一个字符的 ASCII 码。
- SQL_FUNCTION_MAP.put("char_length", ""); // CHAR_LENGTH(s) 返回字符串 s 的字符数
- SQL_FUNCTION_MAP.put("character_length", ""); // CHARACTER_LENGTH(s) 返回字符串 s 的字符数
- SQL_FUNCTION_MAP.put("concat", ""); // CONCAT(s1, s2...sn) 字符串 s1,s2 等多个字符串合并为一个字符串
- SQL_FUNCTION_MAP.put("concat_ws", ""); // CONCAT_WS(x, s1, s2...sn) 同 CONCAT(s1, s2 ...) 函数,但是每个字符串之间要加上 x,x 可以是分隔符
- SQL_FUNCTION_MAP.put("field", ""); // FIELD(s, s1, s2...) 返回第一个字符串 s 在字符串列表 (s1, s2...)中的位置
- SQL_FUNCTION_MAP.put("find_in_set", ""); // FIND_IN_SET(s1, s2) 返回在字符串s2中与s1匹配的字符串的位置
- SQL_FUNCTION_MAP.put("format", ""); // FORMAT(x, n) 函数可以将数字 x 进行格式化 "#,###.##", 将 x 保留到小数点后 n 位,最后一位四舍五入。
- SQL_FUNCTION_MAP.put("insert", ""); // INSERT(s1, x, len, s2) 字符串 s2 替换 s1 的 x 位置开始长度为 len 的字符串
- SQL_FUNCTION_MAP.put("locate", ""); // LOCATE(s1, s) 从字符串 s 中获取 s1 的开始位置
- SQL_FUNCTION_MAP.put("lcase", ""); // LCASE(s) 将字符串 s 的所有字母变成小写字母
- SQL_FUNCTION_MAP.put("left", ""); // LEFT(s, n) 返回字符串 s 的前 n 个字符
- SQL_FUNCTION_MAP.put("length", ""); // LENGTH(s) 返回字符串 s 的字符数
- SQL_FUNCTION_MAP.put("lower", ""); // LOWER(s) 将字符串 s 的所有字母变成小写字母
- SQL_FUNCTION_MAP.put("lpad", ""); // LPAD(s1, len, s2) 在字符串 s1 的开始处填充字符串 s2,使字符串长度达到 len
- SQL_FUNCTION_MAP.put("ltrim", ""); // LTRIM(s) 去掉字符串 s 开始处的空格
- SQL_FUNCTION_MAP.put("mid", ""); // MID(s, n, len) 从字符串 s 的 n 位置截取长度为 len 的子字符串,同 SUBSTRING(s, n, len)
- SQL_FUNCTION_MAP.put("position", ""); // POSITION(s, s1); 从字符串 s 中获取 s1 的开始位置
- SQL_FUNCTION_MAP.put("repeat", ""); // REPEAT(s, n) 将字符串 s 重复 n 次
- SQL_FUNCTION_MAP.put("replace", ""); // REPLACE(s, s1, s2) 将字符串 s2 替代字符串 s 中的字符串 s1
- SQL_FUNCTION_MAP.put("reverse", ""); // REVERSE(s); // ) 将字符串s的顺序反过来
- SQL_FUNCTION_MAP.put("right", ""); // RIGHT(s, n) 返回字符串 s 的后 n 个字符
- SQL_FUNCTION_MAP.put("rpad", ""); // RPAD(s1, len, s2) 在字符串 s1 的结尾处添加字符串 s2,使字符串的长度达到 len
- SQL_FUNCTION_MAP.put("rtrim", ""); // RTRIM", ""); // ) 去掉字符串 s 结尾处的空格
- SQL_FUNCTION_MAP.put("space", ""); // SPACE(n) 返回 n 个空格
- SQL_FUNCTION_MAP.put("strcmp", ""); // STRCMP(s1, s2) 比较字符串 s1 和 s2,如果 s1 与 s2 相等返回 0 ,如果 s1>s2 返回 1,如果 s1d2 之间相隔的天数
- SQL_FUNCTION_MAP.put("date_add", ""); // DATE_ADD(d,INTERVAL expr type) 计算起始日期 d 加上一个时间段后的日期
- SQL_FUNCTION_MAP.put("date_format", ""); // DATE_FORMAT(d,f) 按表达式 f的要求显示日期 d
- SQL_FUNCTION_MAP.put("date_sub", ""); // DATE_SUB(date,INTERVAL expr type) 函数从日期减去指定的时间间隔。
- SQL_FUNCTION_MAP.put("day", ""); // DAY(d) 返回日期值 d 的日期部分
- SQL_FUNCTION_MAP.put("dayname", ""); // DAYNAME(d) 返回日期 d 是星期几,如 Monday,Tuesday
- SQL_FUNCTION_MAP.put("dayofmonth", ""); // DAYOFMONTH(d) 计算日期 d 是本月的第几天
- SQL_FUNCTION_MAP.put("dayofweek", ""); // DAYOFWEEK(d) 日期 d 今天是星期几,1 星期日,2 星期一,以此类推
- SQL_FUNCTION_MAP.put("dayofyear", ""); // DAYOFYEAR(d) 计算日期 d 是本年的第几天
- SQL_FUNCTION_MAP.put("extract", ""); // EXTRACT(type FROM d) 从日期 d 中获取指定的值,type 指定返回的值。
- SQL_FUNCTION_MAP.put("from_days", ""); // FROM_DAYS(n) 计算从 0000 年 1 月 1 日开始 n 天后的日期
- SQL_FUNCTION_MAP.put("hour", ""); // 'HOUR(t) 返回 t 中的小时值
- SQL_FUNCTION_MAP.put("last_day", ""); // LAST_DAY(d) 返回给给定日期的那一月份的最后一天
- SQL_FUNCTION_MAP.put("localtime", ""); // LOCALTIME() 返回当前日期和时间
- SQL_FUNCTION_MAP.put("localtimestamp", ""); // LOCALTIMESTAMP() 返回当前日期和时间
- SQL_FUNCTION_MAP.put("makedate", ""); // MAKEDATE(year, day-of-year) 基于给定参数年份 year 和所在年中的天数序号 day-of-year 返回一个日期
- SQL_FUNCTION_MAP.put("maketime", ""); // MAKETIME(hour, minute, second) 组合时间,参数分别为小时、分钟、秒
- SQL_FUNCTION_MAP.put("microsecond", ""); // MICROSECOND(date) 返回日期参数所对应的微秒数
- SQL_FUNCTION_MAP.put("minute", ""); // MINUTE(t) 返回 t 中的分钟值
- SQL_FUNCTION_MAP.put("monthname", ""); // MONTHNAME(d) 返回日期当中的月份名称,如 November
- SQL_FUNCTION_MAP.put("month", ""); // MONTH(d) 返回日期d中的月份值,1 到 12
- SQL_FUNCTION_MAP.put("now", ""); // NOW() 返回当前日期和时间
- SQL_FUNCTION_MAP.put("period_add", ""); // PERIOD_ADD(period, number) 为 年-月 组合日期添加一个时段
- SQL_FUNCTION_MAP.put("period_diff", ""); // PERIOD_DIFF(period1, period2) 返回两个时段之间的月份差值
- SQL_FUNCTION_MAP.put("quarter", ""); // QUARTER(d) 返回日期d是第几季节,返回 1 到 4
- SQL_FUNCTION_MAP.put("second", ""); // SECOND(t) 返回 t 中的秒钟值
- SQL_FUNCTION_MAP.put("sec_to_time", ""); // SEC_TO_TIME", ""); // ) 将以秒为单位的时间 s 转换为时分秒的格式
- SQL_FUNCTION_MAP.put("str_to_date", ""); // STR_TO_DATE", ""); // tring, format_mask) 将字符串转变为日期
- SQL_FUNCTION_MAP.put("subdate", ""); // SUBDATE(d,n) 日期 d 减去 n 天后的日期
- SQL_FUNCTION_MAP.put("subtime", ""); // SUBTIME(t,n) 时间 t 减去 n 秒的时间
- SQL_FUNCTION_MAP.put("sysdate", ""); // SYSDATE() 返回当前日期和时间
- SQL_FUNCTION_MAP.put("time", ""); // TIME(expression) 提取传入表达式的时间部分
- SQL_FUNCTION_MAP.put("time_format", ""); // TIME_FORMAT(t,f) 按表达式 f 的要求显示时间 t
- SQL_FUNCTION_MAP.put("time_to_sec", ""); // TIME_TO_SEC(t) 将时间 t 转换为秒
- SQL_FUNCTION_MAP.put("timediff", ""); // TIMEDIFF(time1, time2) 计算时间差值
- SQL_FUNCTION_MAP.put("timestamp", ""); // TIMESTAMP(expression, interval) 单个参数时,函数返回日期或日期时间表达式;有2个参数时,将参数加和
- SQL_FUNCTION_MAP.put("to_days", ""); // TO_DAYS(d) 计算日期 d 距离 0000 年 1 月 1 日的天数
- SQL_FUNCTION_MAP.put("week", ""); // WEEK(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53
- SQL_FUNCTION_MAP.put("weekday", ""); // WEEKDAY(d) 日期 d 是星期几,0 表示星期一,1 表示星期二
- SQL_FUNCTION_MAP.put("weekofyear", ""); // WEEKOFYEAR(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53
- SQL_FUNCTION_MAP.put("year", ""); // YEAR(d) 返回年份
- SQL_FUNCTION_MAP.put("yearweek", ""); // YEARWEEK(date, mode) 返回年份及第几周(0到53),mode 中 0 表示周天,1表示周一,以此类推
- SQL_FUNCTION_MAP.put("unix_timestamp", ""); // UNIX_TIMESTAMP(date) 获取UNIX时间戳函数,返回一个以 UNIX 时间戳为基础的无符号整数
- SQL_FUNCTION_MAP.put("from_unixtime", ""); // FROM_UNIXTIME(date) 将 UNIX 时间戳转换为时间格式,与UNIX_TIMESTAMP互为反函数
-
- // MYSQL JSON 函数
- SQL_FUNCTION_MAP.put("json_append", ""); // JSON_APPEND(json_doc, path, val[, path, val] ...)) 插入JSON数组
- SQL_FUNCTION_MAP.put("json_array", ""); // JSON_ARRAY(val1, val2...) 创建JSON数组
- SQL_FUNCTION_MAP.put("json_array_append", ""); // JSON_ARRAY_APPEND(json_doc, val) 将数据附加到JSON文档
- SQL_FUNCTION_MAP.put("json_array_insert", ""); // JSON_ARRAY_INSERT(json_doc, val) 插入JSON数组
- SQL_FUNCTION_MAP.put("json_contains", ""); // JSON_CONTAINS(json_doc, val) JSON文档是否在路径中包含特定对象
- SQL_FUNCTION_MAP.put("json_contains_path", ""); // JSON_CONTAINS_PATH(json_doc, path) JSON文档是否在路径中包含任何数据
- SQL_FUNCTION_MAP.put("json_depth", ""); // JSON_DEPTH(json_doc) JSON文档的最大深度
- SQL_FUNCTION_MAP.put("json_extract", ""); // JSON_EXTRACT(json_doc, path) 从JSON文档返回数据
- SQL_FUNCTION_MAP.put("json_insert", ""); // JSON_INSERT(json_doc, val) 将数据插入JSON文档
- SQL_FUNCTION_MAP.put("json_keys", ""); // JSON_KEYS(json_doc[, path]) JSON文档中的键数组
- SQL_FUNCTION_MAP.put("json_length", ""); // JSON_LENGTH(json_doc) JSON文档中的元素数
- SQL_FUNCTION_MAP.put("json_merge", ""); // JSON_MERGE(json_doc1, json_doc2) (已弃用) 合并JSON文档,保留重复的键。JSON_MERGE_PRESERVE()的已弃用同义词
- SQL_FUNCTION_MAP.put("json_merge_patch", ""); // JSON_MERGE_PATCH(json_doc1, json_doc2) 合并JSON文档,替换重复键的值
- SQL_FUNCTION_MAP.put("json_merge_preserve", ""); // JSON_MERGE_PRESERVE(json_doc1, json_doc2) 合并JSON文档,保留重复的键
- SQL_FUNCTION_MAP.put("json_object", ""); // JSON_OBJECT(key1, val1, key2, val2...) 创建JSON对象
- SQL_FUNCTION_MAP.put("json_overlaps", ""); // JSON_OVERLAPS(json_doc1, json_doc2) (引入8.0.17) 比较两个JSON文档,如果它们具有相同的键值对或数组元素,则返回TRUE(1),否则返回FALSE(0)
- SQL_FUNCTION_MAP.put("json_pretty", ""); // JSON_PRETTY(json_doc) 以易于阅读的格式打印JSON文档
- SQL_FUNCTION_MAP.put("json_quote", ""); // JSON_QUOTE(json_doc1) 引用JSON文档
- SQL_FUNCTION_MAP.put("json_remove", ""); // JSON_REMOVE(json_doc1, path) 从JSON文档中删除数据
- SQL_FUNCTION_MAP.put("json_replace", ""); // JSON_REPLACE(json_doc1, val1, val2) 替换JSON文档中的值
- SQL_FUNCTION_MAP.put("json_schema_valid", ""); // JSON_SCHEMA_VALID(json_doc) (引入8.0.17) 根据JSON模式验证JSON文档;如果文档针对架构进行验证,则返回TRUE / 1;否则,则返回FALSE / 0
- SQL_FUNCTION_MAP.put("json_schema_validation_report", ""); // JSON_SCHEMA_VALIDATION_REPORT(json_doc, mode) (引入8.0.17) 根据JSON模式验证JSON文档;以JSON格式返回有关验证结果的报告,包括成功或失败以及失败原因
- SQL_FUNCTION_MAP.put("json_search", ""); // JSON_SEARCH(json_doc, val) JSON文档中值的路径
- SQL_FUNCTION_MAP.put("json_set", ""); // JSON_SET(json_doc, val) 将数据插入JSON文档
- // SQL_FUNCTION_MAP.put("json_storage_free", ""); // JSON_STORAGE_FREE() 部分更新后,JSON列值的二进制表示形式中的可用空间
- // SQL_FUNCTION_MAP.put("json_storage_size", ""); // JSON_STORAGE_SIZE() 用于存储JSON文档的二进制表示的空间
- SQL_FUNCTION_MAP.put("json_table", ""); // JSON_TABLE() 从JSON表达式返回数据作为关系表
- SQL_FUNCTION_MAP.put("json_type", ""); // JSON_TYPE(json_doc) JSON值类型
- SQL_FUNCTION_MAP.put("json_unquote", ""); // JSON_UNQUOTE(json_doc) 取消引用JSON值
- SQL_FUNCTION_MAP.put("json_valid", ""); // JSON_VALID(json_doc) JSON值是否有效
- SQL_FUNCTION_MAP.put("json_arrayagg", ""); // JSON_ARRAYAGG(key) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 数组
- SQL_FUNCTION_MAP.put("json_objectagg", ""); // JSON_OBJECTAGG(key, val)) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 对象
-
- // MySQL 高级函数
- // SQL_FUNCTION_MAP.put("bin", ""); // BIN(x) 返回 x 的二进制编码
- // SQL_FUNCTION_MAP.put("binary", ""); // BINARY(s) 将字符串 s 转换为二进制字符串
- SQL_FUNCTION_MAP.put("case", ""); // CASE 表示函数开始,END 表示函数结束。如果 condition1 成立,则返回 result1, 如果 condition2 成立,则返回 result2,当全部不成立则返回 result,而当有一个成立之后,后面的就不执行了。
- SQL_FUNCTION_MAP.put("cast", ""); // CAST(x AS type) 转换数据类型
- SQL_FUNCTION_MAP.put("coalesce", ""); // COALESCE(expr1, expr2, ...., expr_n) 返回参数中的第一个非空表达式(从左向右)
- // SQL_FUNCTION_MAP.put("conv", ""); // CONV(x,f1,f2) 返回 f1 进制数变成 f2 进制数
- // SQL_FUNCTION_MAP.put("convert", ""); // CONVERT(s, cs) 函数将字符串 s 的字符集变成 cs
- SQL_FUNCTION_MAP.put("if", ""); // IF(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。
- SQL_FUNCTION_MAP.put("ifnull", ""); // IFNULL(v1,v2) 如果 v1 的值不为 NULL,则返回 v1,否则返回 v2。
- SQL_FUNCTION_MAP.put("isnull", ""); // ISNULL(expression) 判断表达式是否为 NULL
- SQL_FUNCTION_MAP.put("nullif", ""); // NULLIF(expr1, expr2) 比较两个字符串,如果字符串 expr1 与 expr2 相等 返回 NULL,否则返回 expr1
- SQL_FUNCTION_MAP.put("group_concat", ""); // GROUP_CONCAT([DISTINCT], s1, s2...)
-
- //clickhouse 字符串函数 注释的函数表示返回的格式暂时不支持,如:返回数组 ,同时包含因版本不同 clickhosue不支持的函数,版本
- SQL_FUNCTION_MAP.put("empty", ""); // empty(s) 对于空字符串s返回1,对于非空字符串返回0
- SQL_FUNCTION_MAP.put("notEmpty", ""); //notEmpty(s) 对于空字符串返回0,对于非空字符串返回1。
- SQL_FUNCTION_MAP.put("lengthUTF8", ""); //假定字符串以UTF-8编码组成的文本,返回此字符串的Unicode字符长度。如果传入的字符串不是UTF-8编码,则函数可能返回一个预期外的值
- SQL_FUNCTION_MAP.put("lcase", ""); //将字符串中的ASCII转换为小写
- SQL_FUNCTION_MAP.put("ucase", ""); //将字符串中的ASCII转换为大写。
- SQL_FUNCTION_MAP.put("lowerUTF8", ""); //将字符串转换为小写,函数假设字符串是以UTF-8编码文本的字符集。
- SQL_FUNCTION_MAP.put("upperUTF8", ""); //将字符串转换为大写,函数假设字符串是以UTF-8编码文本的字符集。
- SQL_FUNCTION_MAP.put("isValidUTF8", ""); // 检查字符串是否为有效的UTF-8编码,是则返回1,否则返回0。
- SQL_FUNCTION_MAP.put("toValidUTF8", "");//用�(U+FFFD)字符替换无效的UTF-8字符。所有连续的无效字符都会被替换为一个替换字符。
- SQL_FUNCTION_MAP.put("reverseUTF8", "");//以Unicode字符为单位反转UTF-8编码的字符串。
- SQL_FUNCTION_MAP.put("concatAssumeInjective", ""); // concatAssumeInjective(s1, s2, …) 与concat相同,区别在于,你需要保证concat(s1, s2, s3) -> s4是单射的,它将用于GROUP BY的优化。
- SQL_FUNCTION_MAP.put("substringUTF8", ""); // substringUTF8(s,offset,length)¶ 与’substring’相同,但其操作单位为Unicode字符,函数假设字符串是以UTF-8进行编码的文本。如果不是则可能返回一个预期外的结果(不会抛出异常)。
- SQL_FUNCTION_MAP.put("appendTrailingCharIfAbsent", ""); // appendTrailingCharIfAbsent(s,c) 如果’s’字符串非空并且末尾不包含’c’字符,则将’c’字符附加到末尾
- SQL_FUNCTION_MAP.put("convertCharset", ""); // convertCharset(s,from,to) 返回从’from’中的编码转换为’to’中的编码的字符串’s’。
- SQL_FUNCTION_MAP.put("base64Encode", ""); // base64Encode(s) 将字符串’s’编码成base64
- SQL_FUNCTION_MAP.put("base64Decode", ""); //base64Decode(s) 使用base64将字符串解码成原始字符串。如果失败则抛出异常。
- SQL_FUNCTION_MAP.put("tryBase64Decode", ""); //tryBase64Decode(s) 使用base64将字符串解码成原始字符串。但如果出现错误,将返回空字符串。
- SQL_FUNCTION_MAP.put("endsWith", ""); //endsWith(s,后缀) 返回是否以指定的后缀结尾。如果字符串以指定的后缀结束,则返回1,否则返回0。
- SQL_FUNCTION_MAP.put("startsWith", ""); //startsWith(s,前缀) 返回是否以指定的前缀开头。如果字符串以指定的前缀开头,则返回1,否则返回0。
- SQL_FUNCTION_MAP.put("trimLeft", ""); //trimLeft(s)返回一个字符串,用于删除左侧的空白字符。
- SQL_FUNCTION_MAP.put("trimRight", ""); //trimRight(s) 返回一个字符串,用于删除右侧的空白字符。
- SQL_FUNCTION_MAP.put("trimBoth", ""); //trimBoth(s),用于删除任一侧的空白字符
- SQL_FUNCTION_MAP.put("extractAllGroups", ""); //extractAllGroups(text, regexp) 从正则表达式匹配的非重叠子字符串中提取所有组
- // SQL_FUNCTION_MAP.put("leftPad", ""); //leftPad('string', 'length'[, 'pad_string']) 用空格或指定的字符串从左边填充当前字符串(如果需要,可以多次),直到得到的字符串达到给定的长度
- // SQL_FUNCTION_MAP.put("leftPadUTF8", ""); //leftPadUTF8('string','length'[, 'pad_string']) 用空格或指定的字符串从左边填充当前字符串(如果需要,可以多次),直到得到的字符串达到给定的长度
- // SQL_FUNCTION_MAP.put("rightPad", ""); // rightPad('string', 'length'[, 'pad_string']) 用空格或指定的字符串(如果需要,可以多次)从右边填充当前字符串,直到得到的字符串达到给定的长度
- // SQL_FUNCTION_MAP.put("rightPadUTF8", "");// rightPadUTF8('string','length'[, 'pad_string']) 用空格或指定的字符串(如果需要,可以多次)从右边填充当前字符串,直到得到的字符串达到给定的长度。
- SQL_FUNCTION_MAP.put("normalizeQuery", ""); //normalizeQuery(x) 用占位符替换文字、文字序列和复杂的别名。
- SQL_FUNCTION_MAP.put("normalizedQueryHash", ""); //normalizedQueryHash(x) 为类似查询返回相同的64位散列值,但不包含文字值。有助于对查询日志进行分析
- SQL_FUNCTION_MAP.put("positionUTF8", ""); // positionUTF8(s, needle[, start_pos]) 返回在字符串中找到的子字符串的位置(以Unicode点表示),从1开始。
- SQL_FUNCTION_MAP.put("multiSearchFirstIndex", ""); //multiSearchFirstIndex(s, [needle1, needle2, …, needlen]) 返回字符串s中最左边的needlei的索引i(从1开始),否则返回0
- SQL_FUNCTION_MAP.put("multiSearchAny", ""); // multiSearchAny(s, [needle1, needle2, …, needlen])如果至少有一个字符串needlei匹配字符串s,则返回1,否则返回0。
- SQL_FUNCTION_MAP.put("match", ""); //match(s, pattern) 检查字符串是否与模式正则表达式匹配。re2正则表达式。re2正则表达式的语法比Perl正则表达式的语法更有局限性。
- SQL_FUNCTION_MAP.put("multiMatchAny", ""); //multiMatchAny(s, [pattern1, pattern2, …, patternn]) 与match相同,但是如果没有匹配的正则表达式返回0,如果有匹配的模式返回1
- SQL_FUNCTION_MAP.put("multiMatchAnyIndex", ""); //multiMatchAnyIndex(s, [pattern1, pattern2, …, patternn]) 与multiMatchAny相同,但返回与干堆匹配的任何索引
- SQL_FUNCTION_MAP.put("extract", ""); // extract(s, pattern) 使用正则表达式提取字符串的片段
- SQL_FUNCTION_MAP.put("extractAll", ""); //extractAll(s, pattern) 使用正则表达式提取字符串的所有片段
- SQL_FUNCTION_MAP.put("like", ""); //like(s, pattern) 检查字符串是否与简单正则表达式匹配
- SQL_FUNCTION_MAP.put("notLike", "");// 和‘like’是一样的,但是是否定的
- SQL_FUNCTION_MAP.put("countSubstrings", ""); //countSubstrings(s, needle[, start_pos])返回子字符串出现的次数
- SQL_FUNCTION_MAP.put("countMatches", ""); //返回干s中的正则表达式匹配数。countMatches(s, pattern)
- SQL_FUNCTION_MAP.put("replaceOne", ""); //replaceOne(s, pattern, replacement)将' s '中的' pattern '子串的第一个出现替换为' replacement '子串。
-
- SQL_FUNCTION_MAP.put("replaceAll", ""); //replaceAll(s, pattern, replacement)/用' replacement '子串替换' s '中所有出现的' pattern '子串
- SQL_FUNCTION_MAP.put("replaceRegexpOne", ""); //replaceRegexpOne(s, pattern, replacement)使用' pattern '正则表达式进行替换
- SQL_FUNCTION_MAP.put("replaceRegexpAll", ""); //replaceRegexpAll(s, pattern, replacement)
- SQL_FUNCTION_MAP.put("regexpQuoteMeta", ""); //regexpQuoteMeta(s)该函数在字符串中某些预定义字符之前添加一个反斜杠
-
- //clickhouse日期函数
- SQL_FUNCTION_MAP.put("toYear", ""); //将Date或DateTime转换为包含年份编号(AD)的UInt16类型的数字。
- SQL_FUNCTION_MAP.put("toQuarter", ""); //将Date或DateTime转换为包含季度编号的UInt8类型的数字。
- SQL_FUNCTION_MAP.put("toMonth", ""); //Date或DateTime转换为包含月份编号(1-12)的UInt8类型的数字。
- SQL_FUNCTION_MAP.put("toDayOfYear", ""); //将Date或DateTime转换为包含一年中的某一天的编号的UInt16(1-366)类型的数字。
- SQL_FUNCTION_MAP.put("toDayOfMonth", "");//将Date或DateTime转换为包含一月中的某一天的编号的UInt8(1-31)类型的数字。
- SQL_FUNCTION_MAP.put("toDayOfWeek", ""); //将Date或DateTime转换为包含一周中的某一天的编号的UInt8(周一是1, 周日是7)类型的数字。
- SQL_FUNCTION_MAP.put("toHour", ""); //将DateTime转换为包含24小时制(0-23)小时数的UInt8数字。
- SQL_FUNCTION_MAP.put("toMinute", ""); //将DateTime转换为包含一小时中分钟数(0-59)的UInt8数字。
- SQL_FUNCTION_MAP.put("toSecond", ""); //将DateTime转换为包含一分钟中秒数(0-59)的UInt8数字。
- SQL_FUNCTION_MAP.put("toUnixTimestamp", ""); // 对于DateTime参数:将值转换为UInt32类型的数字-Unix时间戳
- SQL_FUNCTION_MAP.put("toStartOfYear", ""); //将Date或DateTime向前取整到本年的第一天。
- SQL_FUNCTION_MAP.put("toStartOfISOYear", ""); // 将Date或DateTime向前取整到ISO本年的第一天。
- SQL_FUNCTION_MAP.put("toStartOfQuarter", "");//将Date或DateTime向前取整到本季度的第一天。
- SQL_FUNCTION_MAP.put("toStartOfMonth", ""); //将Date或DateTime向前取整到本月的第一天。
- SQL_FUNCTION_MAP.put("toMonday", ""); //将Date或DateTime向前取整到本周的星期
- SQL_FUNCTION_MAP.put("toStartOfWeek", ""); //按mode将Date或DateTime向前取整到最近的星期日或星期一。
- SQL_FUNCTION_MAP.put("toStartOfDay", ""); //将DateTime向前取整到今天的开始。
- SQL_FUNCTION_MAP.put("toStartOfHour", ""); //将DateTime向前取整到当前小时的开始。
- SQL_FUNCTION_MAP.put("toStartOfMinute", ""); //将DateTime向前取整到当前分钟的开始。
- SQL_FUNCTION_MAP.put("toStartOfSecond", ""); //将DateTime向前取整到当前秒数的开始。
- SQL_FUNCTION_MAP.put("toStartOfFiveMinute", "");//将DateTime以五分钟为单位向前取整到最接近的时间点。
- SQL_FUNCTION_MAP.put("toStartOfTenMinutes", ""); //将DateTime以十分钟为单位向前取整到最接近的时间点。
- SQL_FUNCTION_MAP.put("toStartOfFifteenMinutes", ""); //将DateTime以十五分钟为单位向前取整到最接近的时间点。
- SQL_FUNCTION_MAP.put("toStartOfInterval", ""); //
- SQL_FUNCTION_MAP.put("toTime", ""); //将DateTime中的日期转换为一个固定的日期,同时保留时间部分。
- SQL_FUNCTION_MAP.put("toISOYear", ""); //将Date或DateTime转换为包含ISO年份的UInt16类型的编号。
- SQL_FUNCTION_MAP.put("toISOWeek", ""); //
- SQL_FUNCTION_MAP.put("toWeek", "");// 返回Date或DateTime的周数。
- SQL_FUNCTION_MAP.put("toYearWeek", ""); //返回年和周的日期
- SQL_FUNCTION_MAP.put("date_trunc", ""); //截断日期和时间数据到日期的指定部分
- SQL_FUNCTION_MAP.put("date_diff", ""); //回两个日期或带有时间值的日期之间的差值。
-
- SQL_FUNCTION_MAP.put("yesterday", ""); //不接受任何参数并在请求执行时的某一刻返回昨天的日期(Date)。
- SQL_FUNCTION_MAP.put("today", ""); //不接受任何参数并在请求执行时的某一刻返回当前日期(Date)。
- SQL_FUNCTION_MAP.put("timeSlot", ""); //将时间向前取整半小时。
- SQL_FUNCTION_MAP.put("toYYYYMM", ""); //
- SQL_FUNCTION_MAP.put("toYYYYMMDD", "");//
- SQL_FUNCTION_MAP.put("toYYYYMMDDhhmmss", ""); //
- SQL_FUNCTION_MAP.put("addYears", ""); // Function adds a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime
- SQL_FUNCTION_MAP.put("addMonths", ""); //同上
- SQL_FUNCTION_MAP.put("addWeeks", ""); //同上
- SQL_FUNCTION_MAP.put("addDays", ""); //同上
- SQL_FUNCTION_MAP.put("addHours", ""); //同上
- SQL_FUNCTION_MAP.put("addMinutes", "");//同上
- SQL_FUNCTION_MAP.put("addSeconds", ""); //同上
- SQL_FUNCTION_MAP.put("addQuarters", ""); //同上
- SQL_FUNCTION_MAP.put("subtractYears", ""); //Function subtract a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime
- SQL_FUNCTION_MAP.put("subtractMonths", ""); //同上
- SQL_FUNCTION_MAP.put("subtractWeeks", ""); //同上
- SQL_FUNCTION_MAP.put("subtractDays", ""); //同上
- SQL_FUNCTION_MAP.put("subtractours", "");//同上
- SQL_FUNCTION_MAP.put("subtractMinutes", ""); //同上
- SQL_FUNCTION_MAP.put("subtractSeconds", ""); //同上
- SQL_FUNCTION_MAP.put("subtractQuarters", ""); //同上
- SQL_FUNCTION_MAP.put("formatDateTime", ""); //函数根据给定的格式字符串来格式化时间
- SQL_FUNCTION_MAP.put("timestamp_add", ""); //使用提供的日期或日期时间值添加指定的时间值。
- SQL_FUNCTION_MAP.put("timestamp_sub", ""); //从提供的日期或带时间的日期中减去时间间隔。
-
- //clickhouse json函数
- SQL_FUNCTION_MAP.put("visitParamHas", ""); //visitParamHas(params, name)检查是否存在«name»名称的字段
- SQL_FUNCTION_MAP.put("visitParamExtractUInt", ""); //visitParamExtractUInt(params, name)将名为«name»的字段的值解析成UInt64。
- SQL_FUNCTION_MAP.put("visitParamExtractInt", ""); //与visitParamExtractUInt相同,但返回Int64。
- SQL_FUNCTION_MAP.put("visitParamExtractFloat", ""); //与visitParamExtractUInt相同,但返回Float64。
- SQL_FUNCTION_MAP.put("visitParamExtractBool", "");//解析true/false值。其结果是UInt8类型的。
- SQL_FUNCTION_MAP.put("visitParamExtractRaw", ""); //返回字段的值,包含空格符。
- SQL_FUNCTION_MAP.put("visitParamExtractString", ""); //使用双引号解析字符串。这个值没有进行转义。如果转义失败,它将返回一个空白字符串。
- SQL_FUNCTION_MAP.put("JSONHas", ""); //如果JSON中存在该值,则返回1。
- SQL_FUNCTION_MAP.put("JSONLength", ""); //返回JSON数组或JSON对象的长度。
- SQL_FUNCTION_MAP.put("JSONType", ""); //返回JSON值的类型。
- SQL_FUNCTION_MAP.put("JSONExtractUInt", ""); //解析JSON并提取值。这些函数类似于visitParam*函数。
- SQL_FUNCTION_MAP.put("JSONExtractInt", ""); //
- SQL_FUNCTION_MAP.put("JSONExtractFloat", ""); //
- SQL_FUNCTION_MAP.put("JSONExtractBool", ""); //
- SQL_FUNCTION_MAP.put("JSONExtractString", ""); //解析JSON并提取字符串。此函数类似于visitParamExtractString函数。
- SQL_FUNCTION_MAP.put("JSONExtract", "");//解析JSON并提取给定ClickHouse数据类型的值。
- SQL_FUNCTION_MAP.put("JSONExtractKeysAndValues", ""); //从JSON中解析键值对,其中值是给定的ClickHouse数据类型
- SQL_FUNCTION_MAP.put("JSONExtractRaw", ""); //返回JSON的部分。
- SQL_FUNCTION_MAP.put("toJSONString", ""); //
-
- //clickhouse 类型转换函数
- SQL_FUNCTION_MAP.put("toInt8", ""); //toInt8(expr) 转换一个输入值为Int类型
- SQL_FUNCTION_MAP.put("toInt16", "");
- SQL_FUNCTION_MAP.put("toInt32", "");
- SQL_FUNCTION_MAP.put("toInt64", "");
- SQL_FUNCTION_MAP.put("toInt8OrZero", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。
- SQL_FUNCTION_MAP.put("toInt16OrZero", "");
- SQL_FUNCTION_MAP.put("toInt32OrZero", "");
- SQL_FUNCTION_MAP.put("toInt64OrZero", "");
- SQL_FUNCTION_MAP.put("toInt8OrNull", "");//toInt(8|16|32|64)OrNull 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回NULL
- SQL_FUNCTION_MAP.put("toInt16OrNull", "");
- SQL_FUNCTION_MAP.put("toInt32OrNull", "");
- SQL_FUNCTION_MAP.put("toInt64OrNull", "");
- SQL_FUNCTION_MAP.put("toUInt8", ""); //toInt8(expr) 转换一个输入值为Int类型
- SQL_FUNCTION_MAP.put("toUInt16", "");
- SQL_FUNCTION_MAP.put("toUInt32", "");
- SQL_FUNCTION_MAP.put("toUInt64", "");
- SQL_FUNCTION_MAP.put("toUInt8OrZero", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。
- SQL_FUNCTION_MAP.put("toUInt16OrZero", "");
- SQL_FUNCTION_MAP.put("toUInt32OrZero", "");
- SQL_FUNCTION_MAP.put("toUInt64OrZero", "");
- SQL_FUNCTION_MAP.put("toUInt8OrNull", "");//toInt(8|16|32|64)OrNull 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回NULL
- SQL_FUNCTION_MAP.put("toUInt16OrNull", "");
- SQL_FUNCTION_MAP.put("toUInt32OrNull", "");
- SQL_FUNCTION_MAP.put("toUInt64OrNull", "");
-
- SQL_FUNCTION_MAP.put("toFloat32", "");
- SQL_FUNCTION_MAP.put("toFloat64", "");
- SQL_FUNCTION_MAP.put("toFloat32OrZero", "");
- SQL_FUNCTION_MAP.put("toFloat64OrZero", "");
- SQL_FUNCTION_MAP.put("toFloat32OrNull", "");
- SQL_FUNCTION_MAP.put("toFloat64OrNull", "");
-
- SQL_FUNCTION_MAP.put("toDate", ""); //
- SQL_FUNCTION_MAP.put("toDateOrZero", ""); //toInt16(expr)
- SQL_FUNCTION_MAP.put("toDateOrNull", ""); //toInt32(expr)
- SQL_FUNCTION_MAP.put("toDateTimeOrZero", ""); //toInt64(expr)
- SQL_FUNCTION_MAP.put("toDateTimeOrNull", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。
-
- SQL_FUNCTION_MAP.put("toDecimal32", "");
- SQL_FUNCTION_MAP.put("toFixedString", ""); // 将String类型的参数转换为FixedString(N)类型的值
- SQL_FUNCTION_MAP.put("toStringCutToZero", ""); // 接受String或FixedString参数,返回String,其内容在找到的第一个零字节处被截断。
- SQL_FUNCTION_MAP.put("toDecimal256", "");
- SQL_FUNCTION_MAP.put("toDecimal32OrNull", "");
- SQL_FUNCTION_MAP.put("toDecimal64OrNull", "");
- SQL_FUNCTION_MAP.put("toDecimal128OrNull", "");
- SQL_FUNCTION_MAP.put("toDecimal256OrNull", "");
- SQL_FUNCTION_MAP.put("toDecimal32OrZero", "");
- SQL_FUNCTION_MAP.put("toDecimal64OrZero", "");
- SQL_FUNCTION_MAP.put("toDecimal128OrZero", "");
- SQL_FUNCTION_MAP.put("toDecimal256OrZero", "");
-
-
- SQL_FUNCTION_MAP.put("toIntervalSecond", ""); //把一个数值类型的值转换为Interval类型的数据。
- SQL_FUNCTION_MAP.put("toIntervalMinute", "");
- SQL_FUNCTION_MAP.put("toIntervalHour", "");
- SQL_FUNCTION_MAP.put("toIntervalDay", "");
- SQL_FUNCTION_MAP.put("toIntervalWeek", "");
- SQL_FUNCTION_MAP.put("toIntervalMonth", "");
- SQL_FUNCTION_MAP.put("toIntervalQuarter", "");
- SQL_FUNCTION_MAP.put("toIntervalYear", "");
- SQL_FUNCTION_MAP.put("parseDateTimeBestEffort", ""); //把String类型的时间日期转换为DateTime数据类型。
- SQL_FUNCTION_MAP.put("parseDateTimeBestEffortOrNull", "");
- SQL_FUNCTION_MAP.put("parseDateTimeBestEffortOrZero", "");
- SQL_FUNCTION_MAP.put("toLowCardinality", "");
-
-
-
- ////clickhouse hash函数
- SQL_FUNCTION_MAP.put("halfMD5", ""); //计算字符串的MD5。然后获取结果的前8个字节并将它们作为UInt64(大端)返回
- SQL_FUNCTION_MAP.put("MD5", ""); //计算字符串的MD5并将结果放入FixedString(16)中返回
-
- //clickhouse ip地址函数
- SQL_FUNCTION_MAP.put("IPv4NumToString", ""); //接受一个UInt32(大端)表示的IPv4的地址,返回相应IPv4的字符串表现形式,格式为A.B.C.D(以点分割的十进制数字)。
- SQL_FUNCTION_MAP.put("IPv4StringToNum", ""); //与IPv4NumToString函数相反。如果IPv4地址格式无效,则返回0。
- SQL_FUNCTION_MAP.put("IPv6NumToString", ""); //接受FixedString(16)类型的二进制格式的IPv6地址。以文本格式返回此地址的字符串。
- SQL_FUNCTION_MAP.put("IPv6StringToNum", ""); //与IPv6NumToString的相反。如果IPv6地址格式无效,则返回空字节字符串。
- SQL_FUNCTION_MAP.put("IPv4ToIPv6", ""); // 接受一个UInt32类型的IPv4地址,返回FixedString(16)类型的IPv6地址
- SQL_FUNCTION_MAP.put("cutIPv6", ""); //接受一个FixedString(16)类型的IPv6地址,返回一个String,这个String中包含了删除指定位之后的地址的文本格
- SQL_FUNCTION_MAP.put("toIPv4", ""); //IPv4StringToNum()的别名,
- SQL_FUNCTION_MAP.put("toIPv6", ""); //IPv6StringToNum()的别名
- SQL_FUNCTION_MAP.put("isIPAddressInRange", ""); //确定一个IP地址是否包含在以CIDR符号表示的网络中
-
- //clickhouse Nullable处理函数
- SQL_FUNCTION_MAP.put("isNull", ""); //检查参数是否为NULL。
- SQL_FUNCTION_MAP.put("isNotNull", ""); //检查参数是否不为 NULL.
- SQL_FUNCTION_MAP.put("ifNull", ""); //如果第一个参数为«NULL»,则返回第二个参数的值。
- SQL_FUNCTION_MAP.put("assumeNotNull", ""); //将可为空类型的值转换为非Nullable类型的值。
- SQL_FUNCTION_MAP.put("toNullable", ""); //将参数的类型转换为Nullable。
-
- //clickhouse UUID函数
- SQL_FUNCTION_MAP.put("generateUUIDv4", ""); // 生成一个UUID
- SQL_FUNCTION_MAP.put("toUUID", ""); //toUUID(x) 将String类型的值转换为UUID类型的值。
-
- //clickhouse 系统函数
- SQL_FUNCTION_MAP.put("hostName", ""); //hostName()回一个字符串,其中包含执行此函数的主机的名称。
- SQL_FUNCTION_MAP.put("getMacro", ""); //从服务器配置的宏部分获取指定值。
- SQL_FUNCTION_MAP.put("FQDN", "");//返回完全限定的域名。
- SQL_FUNCTION_MAP.put("basename", ""); //提取字符串最后一个斜杠或反斜杠之后的尾随部分
- SQL_FUNCTION_MAP.put("currentUser", ""); //返回当前用户的登录。在分布式查询的情况下,将返回用户的登录,即发起的查询
- SQL_FUNCTION_MAP.put("version", ""); //以字符串形式返回服务器版本。
- SQL_FUNCTION_MAP.put("uptime", "");//以秒为单位返回服务器的正常运行时间。
-
- //clickhouse 数学函数
- SQL_FUNCTION_MAP.put("least", ""); //返回a和b中最小的值。
- SQL_FUNCTION_MAP.put("greatest", ""); //返回a和b的最大值。
- SQL_FUNCTION_MAP.put("plus", ""); //plus(a, b), a + b operator¶计算数值的总和。
- SQL_FUNCTION_MAP.put("minus", ""); //minus(a, b), a - b operator 计算数值之间的差,结果总是有符号的。
- SQL_FUNCTION_MAP.put("multiply", "");//multiply(a, b), a * b operator 计算数值的乘积
- SQL_FUNCTION_MAP.put("divide", ""); //divide(a, b), a / b operator 计算数值的商。结果类型始终是浮点类型
- SQL_FUNCTION_MAP.put("intDiv", ""); //intDiv(a,b)计算数值的商,向下舍入取整(按绝对值)。
- SQL_FUNCTION_MAP.put("intDivOrZero", ""); // intDivOrZero(a,b)与’intDiv’的不同之处在于它在除以零或将最小负数除以-1时返回零。
- SQL_FUNCTION_MAP.put("modulo", ""); //modulo(a, b), a % b operator 计算除法后的余数。
- SQL_FUNCTION_MAP.put("moduloOrZero", ""); //和modulo不同之处在于,除以0时结果返回0
- SQL_FUNCTION_MAP.put("negate", ""); //通过改变数值的符号位对数值取反,结果总是有符号
- SQL_FUNCTION_MAP.put("gcd", ""); //gcd(a,b) 返回数值的最大公约数。
- SQL_FUNCTION_MAP.put("lcm", ""); //lcm(a,b) 返回数值的最小公倍数
- SQL_FUNCTION_MAP.put("e", ""); //e() 返回一个接近数学常量e的Float64数字。
- SQL_FUNCTION_MAP.put("pi", ""); //pi() 返回一个接近数学常量π的Float64数字。
- SQL_FUNCTION_MAP.put("exp2", ""); //exp2(x)¶接受一个数值类型的参数并返回它的2的x次幂。
- SQL_FUNCTION_MAP.put("exp10", ""); //exp10(x)¶接受一个数值类型的参数并返回它的10的x次幂。
- SQL_FUNCTION_MAP.put("cbrt", ""); //cbrt(x) 接受一个数值类型的参数并返回它的立方根。
- SQL_FUNCTION_MAP.put("lgamma", ""); //lgamma(x) 返回x的绝对值的自然对数的伽玛函数。
- SQL_FUNCTION_MAP.put("tgamma", ""); //tgamma(x)¶返回x的伽玛函数。
- SQL_FUNCTION_MAP.put("intExp2", ""); //intExp2 接受一个数值类型的参数并返回它的2的x次幂(UInt64)
- SQL_FUNCTION_MAP.put("intExp10", ""); //intExp10 接受一个数值类型的参数并返回它的10的x次幂(UInt64)。
- SQL_FUNCTION_MAP.put("cosh", ""); // cosh(x)
- SQL_FUNCTION_MAP.put("cosh", ""); //cosh(x)
- SQL_FUNCTION_MAP.put("sinh", ""); //sinh(x)
- SQL_FUNCTION_MAP.put("asinh", ""); //asinh(x)
- SQL_FUNCTION_MAP.put("atanh", ""); //atanh(x)
- SQL_FUNCTION_MAP.put("atan2", ""); //atan2(y, x)
- SQL_FUNCTION_MAP.put("hypot", ""); //hypot(x, y)
- SQL_FUNCTION_MAP.put("log1p", ""); //log1p(x)
- SQL_FUNCTION_MAP.put("trunc", ""); //和truncate一样
- SQL_FUNCTION_MAP.put("roundToExp2", ""); //接受一个数字。如果数字小于1,它返回0。
- SQL_FUNCTION_MAP.put("roundDuration", ""); //接受一个数字。如果数字小于1,它返回0。
- SQL_FUNCTION_MAP.put("roundAge", ""); // 接受一个数字。如果数字小于18,它返回0。
- SQL_FUNCTION_MAP.put("roundDown", ""); //接受一个数字并将其舍入到指定数组中的一个元素
- SQL_FUNCTION_MAP.put("bitAnd", ""); //bitAnd(a,b)
- SQL_FUNCTION_MAP.put("bitOr", ""); //bitOr(a,b)
- }
-}
From d013830bbeee4233b32c8066d7bd82aef6ce9c47 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 24 Sep 2021 03:32:48 +0800
Subject: [PATCH 079/754] =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3?=
=?UTF-8?q?=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../apijson/orm/AbstractObjectParser.java | 18 +-
.../main/java/apijson/orm/AbstractParser.java | 68 +-
.../java/apijson/orm/AbstractSQLConfig.java | 1116 ++++++++---------
.../java/apijson/orm/AbstractSQLExecutor.java | 10 +-
4 files changed, 606 insertions(+), 606 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
index 421c2d306..d1ed399e4 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
@@ -87,10 +87,10 @@ public AbstractObjectParser(@NotNull JSONObject request, String parentPath, SQLC
this.type = arrayConfig == null ? 0 : arrayConfig.getType();
this.joinList = arrayConfig == null ? null : arrayConfig.getJoinList();
-
+
this.isTable = isTable; // apijson.JSONObject.isTableKey(table);
this.isArrayMainTable = isArrayMainTable; // isSubquery == false && this.isTable && this.type == SQLConfig.TYPE_ITEM_CHILD_0 && RequestMethod.isGetMethod(method, true);
-// this.isReuse = isReuse; // isArrayMainTable && arrayConfig != null && arrayConfig.getPosition() > 0;
+ // this.isReuse = isReuse; // isArrayMainTable && arrayConfig != null && arrayConfig.getPosition() > 0;
this.objectCount = 0;
this.arrayCount = 0;
@@ -182,11 +182,11 @@ public AbstractObjectParser parse(String name, boolean isReuse) throws Exception
apijson.orm.Entry tentry = Pair.parseEntry(name, true);
this.table = tentry.getKey();
this.alias = tentry.getValue();
-
+
Log.d(TAG, "AbstractObjectParser parentPath = " + parentPath + "; name = " + name + "; table = " + table + "; alias = " + alias);
Log.d(TAG, "AbstractObjectParser type = " + type + "; isTable = " + isTable + "; isArrayMainTable = " + isArrayMainTable);
Log.d(TAG, "AbstractObjectParser isEmpty = " + request.isEmpty() + "; tri = " + tri + "; drop = " + drop);
-
+
breakParse = false;
response = new JSONObject(true);//must init
@@ -224,8 +224,8 @@ public AbstractObjectParser parse(String name, boolean isReuse) throws Exception
whereList = new ArrayList(Arrays.asList(combine != null ? combine : new String[]{}));
whereList.add(apijson.JSONRequest.KEY_ID);
whereList.add(apijson.JSONRequest.KEY_ID_IN);
-// whereList.add(apijson.JSONRequest.KEY_USER_ID);
-// whereList.add(apijson.JSONRequest.KEY_USER_ID_IN);
+ // whereList.add(apijson.JSONRequest.KEY_USER_ID);
+ // whereList.add(apijson.JSONRequest.KEY_USER_ID_IN);
}
//条件>>>>>>>>>>>>>>>>>>>
@@ -292,7 +292,7 @@ else if (method == PUT && value instanceof JSONArray && (whereList == null || wh
if (parser.getGlobleDatasource() != null && sqlRequest.get(JSONRequest.KEY_DATASOURCE) == null) {
sqlRequest.put(JSONRequest.KEY_DATASOURCE, parser.getGlobleDatasource());
}
-
+
if (isSubquery == false) { //解决 SQL 语法报错,子查询不能 EXPLAIN
if (parser.getGlobleExplain() != null && sqlRequest.get(JSONRequest.KEY_EXPLAIN) == null) {
sqlRequest.put(JSONRequest.KEY_EXPLAIN, parser.getGlobleExplain());
@@ -818,10 +818,10 @@ public void onChildResponse() throws Exception {
if (child == null
|| (child instanceof JSONObject && ((JSONObject) child).isEmpty())
|| (child instanceof JSONArray && ((JSONArray) child).isEmpty())
- ) {
+ ) {
continue;
}
-
+
response.put(entry.getKey(), child );
index ++;
}
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
index 6965f2802..eb4556af2 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
@@ -209,7 +209,7 @@ public AbstractParser setGlobleDatasource(String globleDatasource) {
this.globleDatasource = globleDatasource;
return this;
}
-
+
protected Boolean globleExplain;
public AbstractParser setGlobleExplain(Boolean globleExplain) {
this.globleExplain = globleExplain;
@@ -516,7 +516,7 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers
//获取指定的JSON结构 >>>>>>>>>>>>>>
JSONObject target = wrapRequest(method, tag, object, true);
-
+
//JSONObject clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {}
return getVerifier().verifyRequest(method, name, target, request, maxUpdateCount, getGlobleDatabase(), getGlobleSchema(), creator);
}
@@ -529,7 +529,7 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers
*/
public static JSONObject wrapRequest(RequestMethod method, String tag, JSONObject object, boolean isStructure) {
boolean putTag = ! isStructure;
-
+
if (object == null || object.containsKey(tag)) { //tag 是 Table 名或 Table[]
if (putTag) {
if (object == null) {
@@ -549,18 +549,18 @@ public static JSONObject wrapRequest(RequestMethod method, String tag, JSONObjec
if (isDiffArrayKey) { //自动为 tag = Comment:[] 的 { ... } 新增键值对为 { "Comment[]":[], "TYPE": { "Comment[]": "OBJECT[]" } ... }
if (isStructure && (method == RequestMethod.POST || method == RequestMethod.PUT)) {
String arrKey = key + "[]";
-
+
if (target.containsKey(arrKey) == false) {
target.put(arrKey, new JSONArray());
}
-
+
try {
JSONObject type = target.getJSONObject(Operation.TYPE.name());
if (type == null || (type.containsKey(arrKey) == false)) {
if (type == null) {
type = new JSONObject(true);
}
-
+
type.put(arrKey, "OBJECT[]");
target.put(Operation.TYPE.name(), type);
}
@@ -581,15 +581,15 @@ else if (target.containsKey(key) == false) {
}
}
}
-
+
if (putTag) {
target.put(JSONRequest.KEY_TAG, tag);
}
-
+
return target;
}
-
+
/**新建带状态内容的JSONObject
* @param code
* @param msg
@@ -835,7 +835,7 @@ public JSONObject getStructure(@NotNull String table, String method, String tag,
protected Map arrayObjectParserCacheMap = new HashMap<>();
-
+
// protected SQLConfig itemConfig;
/**获取单个对象,该对象处于parentObject内
* @param parentPath parentObject的路径
@@ -871,7 +871,7 @@ public JSONObject onObjectParse(final JSONObject request
}
}
}
-
+
apijson.orm.Entry entry = Pair.parseEntry(name, true);
String table = entry.getKey(); //Comment
// String alias = entry.getValue(); //to
@@ -884,15 +884,15 @@ public JSONObject onObjectParse(final JSONObject request
if (isReuse) { // 数组主表使用专门的缓存数据
op = arrayObjectParserCacheMap.get(parentPath.substring(0, parentPath.lastIndexOf("[]") + 2));
}
-
+
if (op == null) {
op = createObjectParser(request, parentPath, arrayConfig, isSubquery, isTable, isArrayMainTable);
}
op = op.parse(name, isReuse);
-
+
JSONObject response = null;
if (op != null) {//SQL查询结果为空时,functionMap和customMap没有意义
-
+
if (arrayConfig == null) { //Common
response = op.setSQLConfig().executeSQL().response();
}
@@ -901,11 +901,11 @@ 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);
-
+
if (rp != null) {
int index = parentPath.lastIndexOf("]/");
if (index >= 0) {
@@ -934,7 +934,7 @@ public JSONObject onObjectParse(final JSONObject request
pagination.put(JSONResponse.KEY_MORE, page < max);
pagination.put(JSONResponse.KEY_FIRST, page == 0);
pagination.put(JSONResponse.KEY_LAST, page == max);
-
+
putQueryResult(pathPrefix + JSONResponse.KEY_INFO, pagination);
if (total <= count*page) {
@@ -963,9 +963,9 @@ public JSONObject onObjectParse(final JSONObject request
arrayObjectParserCacheMap.put(parentPath.substring(0, parentPath.lastIndexOf("[]") + 2), op);
}
}
-// else {
-// op.recycle();
-// }
+ // else {
+ // op.recycle();
+ // }
op = null;
}
@@ -1196,13 +1196,13 @@ else if (join != null){
throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 Table 值 " + table + " 不合法!"
+ "必须为 &/Table0/key0, 1) { // 把 key 强制放最前,AbstractSQLExcecutor 中 config.putWhere 也是放尽可能最前
JSONObject newTableObj = new JSONObject(tableObj.size(), true);
newTableObj.put(key, tableObj.remove(key));
newTableObj.putAll(tableObj);
-
+
tableObj = newTableObj;
request.put(tableKey, tableObj);
}
// 保证和 SQLExcecutor 缓存的 Config 里 where 顺序一致,生成的 SQL 也就一致 >>>>>>>>>
-
+
Join j = new Join();
j.setPath(path);
@@ -1285,7 +1285,7 @@ else if (join != null){
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() + " 不合法!必须满足英文单词变量名格式!");
}
@@ -1601,7 +1601,7 @@ public static JSONObject getJSONObject(JSONObject object, String key) {
public static final String KEY_CONFIG = "config";
public static final String KEY_SQL = "sql";
-
+
protected Map> arrayMainCacheMap = new HashMap<>();
public void putArrayMainCache(String arrayPath, List mainTableDataList) {
arrayMainCacheMap.put(arrayPath, mainTableDataList);
@@ -1613,8 +1613,8 @@ public JSONObject getArrayMainCacheItem(String arrayPath, int position) {
List list = getArrayMainCache(arrayPath);
return list == null || position >= list.size() ? null : list.get(position);
}
-
-
+
+
/**执行 SQL 并返回 JSONObject
* @param config
@@ -1636,7 +1636,7 @@ public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Except
try {
JSONObject result;
-
+
boolean explain = config.isExplain();
if (explain) {
//如果先执行 explain,则 execute 会死循环,所以只能先执行非 explain
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
index 108ce9deb..a9f99b7df 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
@@ -94,11 +94,11 @@ public abstract class AbstractSQLConfig implements SQLConfig {
public static final List CONFIG_TABLE_LIST;
public static final List DATABASE_LIST;
- // 自定义原始 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_FUNCTION_MAP;
-
+ // 自定义原始 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_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 改成更好的正则,校验前面为单词,中间为操作符,后面为值
@@ -129,531 +129,531 @@ public abstract class AbstractSQLConfig implements SQLConfig {
DATABASE_LIST.add(DATABASE_ORACLE);
DATABASE_LIST.add(DATABASE_DB2);
DATABASE_LIST.add(DATABASE_CLICKHOUSE);
-
-
- RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况
-
- // mysql关键字
- RAW_MAP.put("AS","");
- RAW_MAP.put("VALUE","");
- RAW_MAP.put("DISTINCT","");
-
- //时间
- RAW_MAP.put("DATE","");
- RAW_MAP.put("now()","");
- RAW_MAP.put("DATETIME","");
- RAW_MAP.put("DateTime","");
- RAW_MAP.put("SECOND","");
- RAW_MAP.put("MINUTE","");
- RAW_MAP.put("HOUR","");
- RAW_MAP.put("DAY","");
- RAW_MAP.put("WEEK","");
- RAW_MAP.put("MONTH","");
- RAW_MAP.put("QUARTER","");
- RAW_MAP.put("YEAR","");
- RAW_MAP.put("json","");
- RAW_MAP.put("unit","");
-
- //MYSQL 数据类型 BINARY,CHAR,DATETIME,TIME,DECIMAL,SIGNED,UNSIGNED
- RAW_MAP.put("BINARY","");
- RAW_MAP.put("SIGNED","");
- RAW_MAP.put("DECIMAL","");
- RAW_MAP.put("BINARY","");
- RAW_MAP.put("UNSIGNED","");
- RAW_MAP.put("CHAR","");
- RAW_MAP.put("TIME","");
-
- //窗口函数关键字
- RAW_MAP.put("OVER", "");
- RAW_MAP.put("INTERVAL", "");
- RAW_MAP.put("GROUP BY", ""); //往前
- RAW_MAP.put("GROUP", ""); //往前
- RAW_MAP.put("ORDER BY", ""); //往前
- RAW_MAP.put("ORDER", "");
- RAW_MAP.put("PARTITION BY", ""); //往前
- RAW_MAP.put("PARTITION", ""); //往前
- RAW_MAP.put("BY", "");
- RAW_MAP.put("DESC", "");
- RAW_MAP.put("ASC", "");
- RAW_MAP.put("FOLLOWING", "");//往后
- RAW_MAP.put("BETWEEN", "");
- RAW_MAP.put("AND", "");
- RAW_MAP.put("ROWS", "");
-
- RAW_MAP.put("AGAINST", "");
- RAW_MAP.put("IN NATURAL LANGUAGE MODE", "");
- RAW_MAP.put("IN BOOLEAN MODE", "");
- RAW_MAP.put("IN", "");
- RAW_MAP.put("BOOLEAN", "");
- RAW_MAP.put("NATURAL", "");
- RAW_MAP.put("LANGUAGE", "");
- RAW_MAP.put("MODE", "");
-
-
- SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况
-
-
- //窗口函数
- SQL_FUNCTION_MAP.put("rank", "");//得到数据项在分组中的排名,排名相等的时候会留下空位
- SQL_FUNCTION_MAP.put("dense_rank", ""); //得到数据项在分组中的排名,排名相等的时候不会留下空位
- SQL_FUNCTION_MAP.put("row_number", "");//按照分组中的顺序生成序列,不存在重复的序列
- SQL_FUNCTION_MAP.put("ntile", "");//用于将分组数据按照顺序切分成N片,返回当前切片值,不支持ROWS_BETWEE
- SQL_FUNCTION_MAP.put("first_value", "");//取分组排序后,截止到当前行,分组内第一个值
- SQL_FUNCTION_MAP.put("last_value", "");//取分组排序后,截止到当前行,分组内的最后一个值
- SQL_FUNCTION_MAP.put("lag", "");//统计窗口内往上第n行值。第一个参数为列名,第二个参数为往上第n行(可选,默认为1),第三个参数为默认值(当往上第n行为NULL时候,取默认值,如不指定,则为NULL)
- SQL_FUNCTION_MAP.put("lead", "");//统计窗口内往下第n行值。第一个参数为列名,第二个参数为往下第n行(可选,默认为1),第三个参数为默认值(当往下第n行为NULL时候,取默认值,如不指定,则为NULL)
- SQL_FUNCTION_MAP.put("cume_dist", "");//)返回(小于等于当前行值的行数)/(当前分组内的总行数)
- SQL_FUNCTION_MAP.put("percent_rank", "");//返回(组内当前行的rank值-1)/(分组内做总行数-1)
-
- // MySQL 字符串函数
- SQL_FUNCTION_MAP.put("ascii", ""); // ASCII(s) 返回字符串 s 的第一个字符的 ASCII 码。
- SQL_FUNCTION_MAP.put("char_length", ""); // CHAR_LENGTH(s) 返回字符串 s 的字符数
- SQL_FUNCTION_MAP.put("character_length", ""); // CHARACTER_LENGTH(s) 返回字符串 s 的字符数
- SQL_FUNCTION_MAP.put("concat", ""); // CONCAT(s1, s2...sn) 字符串 s1,s2 等多个字符串合并为一个字符串
- SQL_FUNCTION_MAP.put("concat_ws", ""); // CONCAT_WS(x, s1, s2...sn) 同 CONCAT(s1, s2 ...) 函数,但是每个字符串之间要加上 x,x 可以是分隔符
- SQL_FUNCTION_MAP.put("field", ""); // FIELD(s, s1, s2...) 返回第一个字符串 s 在字符串列表 (s1, s2...)中的位置
- SQL_FUNCTION_MAP.put("find_in_set", ""); // FIND_IN_SET(s1, s2) 返回在字符串s2中与s1匹配的字符串的位置
- SQL_FUNCTION_MAP.put("format", ""); // FORMAT(x, n) 函数可以将数字 x 进行格式化 "#,###.##", 将 x 保留到小数点后 n 位,最后一位四舍五入。
- SQL_FUNCTION_MAP.put("insert", ""); // INSERT(s1, x, len, s2) 字符串 s2 替换 s1 的 x 位置开始长度为 len 的字符串
- SQL_FUNCTION_MAP.put("locate", ""); // LOCATE(s1, s) 从字符串 s 中获取 s1 的开始位置
- SQL_FUNCTION_MAP.put("lcase", ""); // LCASE(s) 将字符串 s 的所有字母变成小写字母
- SQL_FUNCTION_MAP.put("left", ""); // LEFT(s, n) 返回字符串 s 的前 n 个字符
- SQL_FUNCTION_MAP.put("length", ""); // LENGTH(s) 返回字符串 s 的字符数
- SQL_FUNCTION_MAP.put("lower", ""); // LOWER(s) 将字符串 s 的所有字母变成小写字母
- SQL_FUNCTION_MAP.put("lpad", ""); // LPAD(s1, len, s2) 在字符串 s1 的开始处填充字符串 s2,使字符串长度达到 len
- SQL_FUNCTION_MAP.put("ltrim", ""); // LTRIM(s) 去掉字符串 s 开始处的空格
- SQL_FUNCTION_MAP.put("mid", ""); // MID(s, n, len) 从字符串 s 的 n 位置截取长度为 len 的子字符串,同 SUBSTRING(s, n, len)
- SQL_FUNCTION_MAP.put("position", ""); // POSITION(s, s1); 从字符串 s 中获取 s1 的开始位置
- SQL_FUNCTION_MAP.put("repeat", ""); // REPEAT(s, n) 将字符串 s 重复 n 次
- SQL_FUNCTION_MAP.put("replace", ""); // REPLACE(s, s1, s2) 将字符串 s2 替代字符串 s 中的字符串 s1
- SQL_FUNCTION_MAP.put("reverse", ""); // REVERSE(s); // ) 将字符串s的顺序反过来
- SQL_FUNCTION_MAP.put("right", ""); // RIGHT(s, n) 返回字符串 s 的后 n 个字符
- SQL_FUNCTION_MAP.put("rpad", ""); // RPAD(s1, len, s2) 在字符串 s1 的结尾处添加字符串 s2,使字符串的长度达到 len
- SQL_FUNCTION_MAP.put("rtrim", ""); // RTRIM", ""); // ) 去掉字符串 s 结尾处的空格
- SQL_FUNCTION_MAP.put("space", ""); // SPACE(n) 返回 n 个空格
- SQL_FUNCTION_MAP.put("strcmp", ""); // STRCMP(s1, s2) 比较字符串 s1 和 s2,如果 s1 与 s2 相等返回 0 ,如果 s1>s2 返回 1,如果 s1d2 之间相隔的天数
- SQL_FUNCTION_MAP.put("date_add", ""); // DATE_ADD(d,INTERVAL expr type) 计算起始日期 d 加上一个时间段后的日期
- SQL_FUNCTION_MAP.put("date_format", ""); // DATE_FORMAT(d,f) 按表达式 f的要求显示日期 d
- SQL_FUNCTION_MAP.put("date_sub", ""); // DATE_SUB(date,INTERVAL expr type) 函数从日期减去指定的时间间隔。
- SQL_FUNCTION_MAP.put("day", ""); // DAY(d) 返回日期值 d 的日期部分
- SQL_FUNCTION_MAP.put("dayname", ""); // DAYNAME(d) 返回日期 d 是星期几,如 Monday,Tuesday
- SQL_FUNCTION_MAP.put("dayofmonth", ""); // DAYOFMONTH(d) 计算日期 d 是本月的第几天
- SQL_FUNCTION_MAP.put("dayofweek", ""); // DAYOFWEEK(d) 日期 d 今天是星期几,1 星期日,2 星期一,以此类推
- SQL_FUNCTION_MAP.put("dayofyear", ""); // DAYOFYEAR(d) 计算日期 d 是本年的第几天
- SQL_FUNCTION_MAP.put("extract", ""); // EXTRACT(type FROM d) 从日期 d 中获取指定的值,type 指定返回的值。
- SQL_FUNCTION_MAP.put("from_days", ""); // FROM_DAYS(n) 计算从 0000 年 1 月 1 日开始 n 天后的日期
- SQL_FUNCTION_MAP.put("hour", ""); // 'HOUR(t) 返回 t 中的小时值
- SQL_FUNCTION_MAP.put("last_day", ""); // LAST_DAY(d) 返回给给定日期的那一月份的最后一天
- SQL_FUNCTION_MAP.put("localtime", ""); // LOCALTIME() 返回当前日期和时间
- SQL_FUNCTION_MAP.put("localtimestamp", ""); // LOCALTIMESTAMP() 返回当前日期和时间
- SQL_FUNCTION_MAP.put("makedate", ""); // MAKEDATE(year, day-of-year) 基于给定参数年份 year 和所在年中的天数序号 day-of-year 返回一个日期
- SQL_FUNCTION_MAP.put("maketime", ""); // MAKETIME(hour, minute, second) 组合时间,参数分别为小时、分钟、秒
- SQL_FUNCTION_MAP.put("microsecond", ""); // MICROSECOND(date) 返回日期参数所对应的微秒数
- SQL_FUNCTION_MAP.put("minute", ""); // MINUTE(t) 返回 t 中的分钟值
- SQL_FUNCTION_MAP.put("monthname", ""); // MONTHNAME(d) 返回日期当中的月份名称,如 November
- SQL_FUNCTION_MAP.put("month", ""); // MONTH(d) 返回日期d中的月份值,1 到 12
- SQL_FUNCTION_MAP.put("now", ""); // NOW() 返回当前日期和时间
- SQL_FUNCTION_MAP.put("period_add", ""); // PERIOD_ADD(period, number) 为 年-月 组合日期添加一个时段
- SQL_FUNCTION_MAP.put("period_diff", ""); // PERIOD_DIFF(period1, period2) 返回两个时段之间的月份差值
- SQL_FUNCTION_MAP.put("quarter", ""); // QUARTER(d) 返回日期d是第几季节,返回 1 到 4
- SQL_FUNCTION_MAP.put("second", ""); // SECOND(t) 返回 t 中的秒钟值
- SQL_FUNCTION_MAP.put("sec_to_time", ""); // SEC_TO_TIME", ""); // ) 将以秒为单位的时间 s 转换为时分秒的格式
- SQL_FUNCTION_MAP.put("str_to_date", ""); // STR_TO_DATE", ""); // tring, format_mask) 将字符串转变为日期
- SQL_FUNCTION_MAP.put("subdate", ""); // SUBDATE(d,n) 日期 d 减去 n 天后的日期
- SQL_FUNCTION_MAP.put("subtime", ""); // SUBTIME(t,n) 时间 t 减去 n 秒的时间
- SQL_FUNCTION_MAP.put("sysdate", ""); // SYSDATE() 返回当前日期和时间
- SQL_FUNCTION_MAP.put("time", ""); // TIME(expression) 提取传入表达式的时间部分
- SQL_FUNCTION_MAP.put("time_format", ""); // TIME_FORMAT(t,f) 按表达式 f 的要求显示时间 t
- SQL_FUNCTION_MAP.put("time_to_sec", ""); // TIME_TO_SEC(t) 将时间 t 转换为秒
- SQL_FUNCTION_MAP.put("timediff", ""); // TIMEDIFF(time1, time2) 计算时间差值
- SQL_FUNCTION_MAP.put("timestamp", ""); // TIMESTAMP(expression, interval) 单个参数时,函数返回日期或日期时间表达式;有2个参数时,将参数加和
- SQL_FUNCTION_MAP.put("to_days", ""); // TO_DAYS(d) 计算日期 d 距离 0000 年 1 月 1 日的天数
- SQL_FUNCTION_MAP.put("week", ""); // WEEK(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53
- SQL_FUNCTION_MAP.put("weekday", ""); // WEEKDAY(d) 日期 d 是星期几,0 表示星期一,1 表示星期二
- SQL_FUNCTION_MAP.put("weekofyear", ""); // WEEKOFYEAR(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53
- SQL_FUNCTION_MAP.put("year", ""); // YEAR(d) 返回年份
- SQL_FUNCTION_MAP.put("yearweek", ""); // YEARWEEK(date, mode) 返回年份及第几周(0到53),mode 中 0 表示周天,1表示周一,以此类推
- SQL_FUNCTION_MAP.put("unix_timestamp", ""); // UNIX_TIMESTAMP(date) 获取UNIX时间戳函数,返回一个以 UNIX 时间戳为基础的无符号整数
- SQL_FUNCTION_MAP.put("from_unixtime", ""); // FROM_UNIXTIME(date) 将 UNIX 时间戳转换为时间格式,与UNIX_TIMESTAMP互为反函数
-
- // MYSQL JSON 函数
- SQL_FUNCTION_MAP.put("json_append", ""); // JSON_APPEND(json_doc, path, val[, path, val] ...)) 插入JSON数组
- SQL_FUNCTION_MAP.put("json_array", ""); // JSON_ARRAY(val1, val2...) 创建JSON数组
- SQL_FUNCTION_MAP.put("json_array_append", ""); // JSON_ARRAY_APPEND(json_doc, val) 将数据附加到JSON文档
- SQL_FUNCTION_MAP.put("json_array_insert", ""); // JSON_ARRAY_INSERT(json_doc, val) 插入JSON数组
- SQL_FUNCTION_MAP.put("json_contains", ""); // JSON_CONTAINS(json_doc, val) JSON文档是否在路径中包含特定对象
- SQL_FUNCTION_MAP.put("json_contains_path", ""); // JSON_CONTAINS_PATH(json_doc, path) JSON文档是否在路径中包含任何数据
- SQL_FUNCTION_MAP.put("json_depth", ""); // JSON_DEPTH(json_doc) JSON文档的最大深度
- SQL_FUNCTION_MAP.put("json_extract", ""); // JSON_EXTRACT(json_doc, path) 从JSON文档返回数据
- SQL_FUNCTION_MAP.put("json_insert", ""); // JSON_INSERT(json_doc, val) 将数据插入JSON文档
- SQL_FUNCTION_MAP.put("json_keys", ""); // JSON_KEYS(json_doc[, path]) JSON文档中的键数组
- SQL_FUNCTION_MAP.put("json_length", ""); // JSON_LENGTH(json_doc) JSON文档中的元素数
- SQL_FUNCTION_MAP.put("json_merge", ""); // JSON_MERGE(json_doc1, json_doc2) (已弃用) 合并JSON文档,保留重复的键。JSON_MERGE_PRESERVE()的已弃用同义词
- SQL_FUNCTION_MAP.put("json_merge_patch", ""); // JSON_MERGE_PATCH(json_doc1, json_doc2) 合并JSON文档,替换重复键的值
- SQL_FUNCTION_MAP.put("json_merge_preserve", ""); // JSON_MERGE_PRESERVE(json_doc1, json_doc2) 合并JSON文档,保留重复的键
- SQL_FUNCTION_MAP.put("json_object", ""); // JSON_OBJECT(key1, val1, key2, val2...) 创建JSON对象
- SQL_FUNCTION_MAP.put("json_overlaps", ""); // JSON_OVERLAPS(json_doc1, json_doc2) (引入8.0.17) 比较两个JSON文档,如果它们具有相同的键值对或数组元素,则返回TRUE(1),否则返回FALSE(0)
- SQL_FUNCTION_MAP.put("json_pretty", ""); // JSON_PRETTY(json_doc) 以易于阅读的格式打印JSON文档
- SQL_FUNCTION_MAP.put("json_quote", ""); // JSON_QUOTE(json_doc1) 引用JSON文档
- SQL_FUNCTION_MAP.put("json_remove", ""); // JSON_REMOVE(json_doc1, path) 从JSON文档中删除数据
- SQL_FUNCTION_MAP.put("json_replace", ""); // JSON_REPLACE(json_doc1, val1, val2) 替换JSON文档中的值
- SQL_FUNCTION_MAP.put("json_schema_valid", ""); // JSON_SCHEMA_VALID(json_doc) (引入8.0.17) 根据JSON模式验证JSON文档;如果文档针对架构进行验证,则返回TRUE / 1;否则,则返回FALSE / 0
- SQL_FUNCTION_MAP.put("json_schema_validation_report", ""); // JSON_SCHEMA_VALIDATION_REPORT(json_doc, mode) (引入8.0.17) 根据JSON模式验证JSON文档;以JSON格式返回有关验证结果的报告,包括成功或失败以及失败原因
- SQL_FUNCTION_MAP.put("json_search", ""); // JSON_SEARCH(json_doc, val) JSON文档中值的路径
- SQL_FUNCTION_MAP.put("json_set", ""); // JSON_SET(json_doc, val) 将数据插入JSON文档
- // SQL_FUNCTION_MAP.put("json_storage_free", ""); // JSON_STORAGE_FREE() 部分更新后,JSON列值的二进制表示形式中的可用空间
- // SQL_FUNCTION_MAP.put("json_storage_size", ""); // JSON_STORAGE_SIZE() 用于存储JSON文档的二进制表示的空间
- SQL_FUNCTION_MAP.put("json_table", ""); // JSON_TABLE() 从JSON表达式返回数据作为关系表
- SQL_FUNCTION_MAP.put("json_type", ""); // JSON_TYPE(json_doc) JSON值类型
- SQL_FUNCTION_MAP.put("json_unquote", ""); // JSON_UNQUOTE(json_doc) 取消引用JSON值
- SQL_FUNCTION_MAP.put("json_valid", ""); // JSON_VALID(json_doc) JSON值是否有效
- SQL_FUNCTION_MAP.put("json_arrayagg", ""); // JSON_ARRAYAGG(key) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 数组
- SQL_FUNCTION_MAP.put("json_objectagg", ""); // JSON_OBJECTAGG(key, val)) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 对象
-
- // MySQL 高级函数
- // SQL_FUNCTION_MAP.put("bin", ""); // BIN(x) 返回 x 的二进制编码
- // SQL_FUNCTION_MAP.put("binary", ""); // BINARY(s) 将字符串 s 转换为二进制字符串
- SQL_FUNCTION_MAP.put("case", ""); // CASE 表示函数开始,END 表示函数结束。如果 condition1 成立,则返回 result1, 如果 condition2 成立,则返回 result2,当全部不成立则返回 result,而当有一个成立之后,后面的就不执行了。
- SQL_FUNCTION_MAP.put("cast", ""); // CAST(x AS type) 转换数据类型
- SQL_FUNCTION_MAP.put("coalesce", ""); // COALESCE(expr1, expr2, ...., expr_n) 返回参数中的第一个非空表达式(从左向右)
- // SQL_FUNCTION_MAP.put("conv", ""); // CONV(x,f1,f2) 返回 f1 进制数变成 f2 进制数
- // SQL_FUNCTION_MAP.put("convert", ""); // CONVERT(s, cs) 函数将字符串 s 的字符集变成 cs
- SQL_FUNCTION_MAP.put("if", ""); // IF(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。
- SQL_FUNCTION_MAP.put("ifnull", ""); // IFNULL(v1,v2) 如果 v1 的值不为 NULL,则返回 v1,否则返回 v2。
- SQL_FUNCTION_MAP.put("isnull", ""); // ISNULL(expression) 判断表达式是否为 NULL
- 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) 全文检索
-
-
-
-
-
- //clickhouse 字符串函数 注释的函数表示返回的格式暂时不支持,如:返回数组 ,同时包含因版本不同 clickhosue不支持的函数,版本
- SQL_FUNCTION_MAP.put("empty", ""); // empty(s) 对于空字符串s返回1,对于非空字符串返回0
- SQL_FUNCTION_MAP.put("notEmpty", ""); //notEmpty(s) 对于空字符串返回0,对于非空字符串返回1。
- SQL_FUNCTION_MAP.put("lengthUTF8", ""); //假定字符串以UTF-8编码组成的文本,返回此字符串的Unicode字符长度。如果传入的字符串不是UTF-8编码,则函数可能返回一个预期外的值
- SQL_FUNCTION_MAP.put("lcase", ""); //将字符串中的ASCII转换为小写
- SQL_FUNCTION_MAP.put("ucase", ""); //将字符串中的ASCII转换为大写。
- SQL_FUNCTION_MAP.put("lowerUTF8", ""); //将字符串转换为小写,函数假设字符串是以UTF-8编码文本的字符集。
- SQL_FUNCTION_MAP.put("upperUTF8", ""); //将字符串转换为大写,函数假设字符串是以UTF-8编码文本的字符集。
- SQL_FUNCTION_MAP.put("isValidUTF8", ""); // 检查字符串是否为有效的UTF-8编码,是则返回1,否则返回0。
- SQL_FUNCTION_MAP.put("toValidUTF8", "");//用�(U+FFFD)字符替换无效的UTF-8字符。所有连续的无效字符都会被替换为一个替换字符。
- SQL_FUNCTION_MAP.put("reverseUTF8", "");//以Unicode字符为单位反转UTF-8编码的字符串。
- SQL_FUNCTION_MAP.put("concatAssumeInjective", ""); // concatAssumeInjective(s1, s2, …) 与concat相同,区别在于,你需要保证concat(s1, s2, s3) -> s4是单射的,它将用于GROUP BY的优化。
- SQL_FUNCTION_MAP.put("substringUTF8", ""); // substringUTF8(s,offset,length)¶ 与’substring’相同,但其操作单位为Unicode字符,函数假设字符串是以UTF-8进行编码的文本。如果不是则可能返回一个预期外的结果(不会抛出异常)。
- SQL_FUNCTION_MAP.put("appendTrailingCharIfAbsent", ""); // appendTrailingCharIfAbsent(s,c) 如果’s’字符串非空并且末尾不包含’c’字符,则将’c’字符附加到末尾
- SQL_FUNCTION_MAP.put("convertCharset", ""); // convertCharset(s,from,to) 返回从’from’中的编码转换为’to’中的编码的字符串’s’。
- SQL_FUNCTION_MAP.put("base64Encode", ""); // base64Encode(s) 将字符串’s’编码成base64
- SQL_FUNCTION_MAP.put("base64Decode", ""); //base64Decode(s) 使用base64将字符串解码成原始字符串。如果失败则抛出异常。
- SQL_FUNCTION_MAP.put("tryBase64Decode", ""); //tryBase64Decode(s) 使用base64将字符串解码成原始字符串。但如果出现错误,将返回空字符串。
- SQL_FUNCTION_MAP.put("endsWith", ""); //endsWith(s,后缀) 返回是否以指定的后缀结尾。如果字符串以指定的后缀结束,则返回1,否则返回0。
- SQL_FUNCTION_MAP.put("startsWith", ""); //startsWith(s,前缀) 返回是否以指定的前缀开头。如果字符串以指定的前缀开头,则返回1,否则返回0。
- SQL_FUNCTION_MAP.put("trimLeft", ""); //trimLeft(s)返回一个字符串,用于删除左侧的空白字符。
- SQL_FUNCTION_MAP.put("trimRight", ""); //trimRight(s) 返回一个字符串,用于删除右侧的空白字符。
- SQL_FUNCTION_MAP.put("trimBoth", ""); //trimBoth(s),用于删除任一侧的空白字符
- SQL_FUNCTION_MAP.put("extractAllGroups", ""); //extractAllGroups(text, regexp) 从正则表达式匹配的非重叠子字符串中提取所有组
- // SQL_FUNCTION_MAP.put("leftPad", ""); //leftPad('string', 'length'[, 'pad_string']) 用空格或指定的字符串从左边填充当前字符串(如果需要,可以多次),直到得到的字符串达到给定的长度
- // SQL_FUNCTION_MAP.put("leftPadUTF8", ""); //leftPadUTF8('string','length'[, 'pad_string']) 用空格或指定的字符串从左边填充当前字符串(如果需要,可以多次),直到得到的字符串达到给定的长度
- // SQL_FUNCTION_MAP.put("rightPad", ""); // rightPad('string', 'length'[, 'pad_string']) 用空格或指定的字符串(如果需要,可以多次)从右边填充当前字符串,直到得到的字符串达到给定的长度
- // SQL_FUNCTION_MAP.put("rightPadUTF8", "");// rightPadUTF8('string','length'[, 'pad_string']) 用空格或指定的字符串(如果需要,可以多次)从右边填充当前字符串,直到得到的字符串达到给定的长度。
- SQL_FUNCTION_MAP.put("normalizeQuery", ""); //normalizeQuery(x) 用占位符替换文字、文字序列和复杂的别名。
- SQL_FUNCTION_MAP.put("normalizedQueryHash", ""); //normalizedQueryHash(x) 为类似查询返回相同的64位散列值,但不包含文字值。有助于对查询日志进行分析
- SQL_FUNCTION_MAP.put("positionUTF8", ""); // positionUTF8(s, needle[, start_pos]) 返回在字符串中找到的子字符串的位置(以Unicode点表示),从1开始。
- SQL_FUNCTION_MAP.put("multiSearchFirstIndex", ""); //multiSearchFirstIndex(s, [needle1, needle2, …, needlen]) 返回字符串s中最左边的needlei的索引i(从1开始),否则返回0
- SQL_FUNCTION_MAP.put("multiSearchAny", ""); // multiSearchAny(s, [needle1, needle2, …, needlen])如果至少有一个字符串needlei匹配字符串s,则返回1,否则返回0。
- SQL_FUNCTION_MAP.put("match", ""); //match(s, pattern) 检查字符串是否与模式正则表达式匹配。re2正则表达式。re2正则表达式的语法比Perl正则表达式的语法更有局限性。
- SQL_FUNCTION_MAP.put("multiMatchAny", ""); //multiMatchAny(s, [pattern1, pattern2, …, patternn]) 与match相同,但是如果没有匹配的正则表达式返回0,如果有匹配的模式返回1
- SQL_FUNCTION_MAP.put("multiMatchAnyIndex", ""); //multiMatchAnyIndex(s, [pattern1, pattern2, …, patternn]) 与multiMatchAny相同,但返回与干堆匹配的任何索引
- SQL_FUNCTION_MAP.put("extract", ""); // extract(s, pattern) 使用正则表达式提取字符串的片段
- SQL_FUNCTION_MAP.put("extractAll", ""); //extractAll(s, pattern) 使用正则表达式提取字符串的所有片段
- SQL_FUNCTION_MAP.put("like", ""); //like(s, pattern) 检查字符串是否与简单正则表达式匹配
- SQL_FUNCTION_MAP.put("notLike", "");// 和‘like’是一样的,但是是否定的
- SQL_FUNCTION_MAP.put("countSubstrings", ""); //countSubstrings(s, needle[, start_pos])返回子字符串出现的次数
- SQL_FUNCTION_MAP.put("countMatches", ""); //返回干s中的正则表达式匹配数。countMatches(s, pattern)
- SQL_FUNCTION_MAP.put("replaceOne", ""); //replaceOne(s, pattern, replacement)将' s '中的' pattern '子串的第一个出现替换为' replacement '子串。
-
- SQL_FUNCTION_MAP.put("replaceAll", ""); //replaceAll(s, pattern, replacement)/用' replacement '子串替换' s '中所有出现的' pattern '子串
- SQL_FUNCTION_MAP.put("replaceRegexpOne", ""); //replaceRegexpOne(s, pattern, replacement)使用' pattern '正则表达式进行替换
- SQL_FUNCTION_MAP.put("replaceRegexpAll", ""); //replaceRegexpAll(s, pattern, replacement)
- SQL_FUNCTION_MAP.put("regexpQuoteMeta", ""); //regexpQuoteMeta(s)该函数在字符串中某些预定义字符之前添加一个反斜杠
-
- //clickhouse日期函数
- SQL_FUNCTION_MAP.put("toYear", ""); //将Date或DateTime转换为包含年份编号(AD)的UInt16类型的数字。
- SQL_FUNCTION_MAP.put("toQuarter", ""); //将Date或DateTime转换为包含季度编号的UInt8类型的数字。
- SQL_FUNCTION_MAP.put("toMonth", ""); //Date或DateTime转换为包含月份编号(1-12)的UInt8类型的数字。
- SQL_FUNCTION_MAP.put("toDayOfYear", ""); //将Date或DateTime转换为包含一年中的某一天的编号的UInt16(1-366)类型的数字。
- SQL_FUNCTION_MAP.put("toDayOfMonth", "");//将Date或DateTime转换为包含一月中的某一天的编号的UInt8(1-31)类型的数字。
- SQL_FUNCTION_MAP.put("toDayOfWeek", ""); //将Date或DateTime转换为包含一周中的某一天的编号的UInt8(周一是1, 周日是7)类型的数字。
- SQL_FUNCTION_MAP.put("toHour", ""); //将DateTime转换为包含24小时制(0-23)小时数的UInt8数字。
- SQL_FUNCTION_MAP.put("toMinute", ""); //将DateTime转换为包含一小时中分钟数(0-59)的UInt8数字。
- SQL_FUNCTION_MAP.put("toSecond", ""); //将DateTime转换为包含一分钟中秒数(0-59)的UInt8数字。
- SQL_FUNCTION_MAP.put("toUnixTimestamp", ""); // 对于DateTime参数:将值转换为UInt32类型的数字-Unix时间戳
- SQL_FUNCTION_MAP.put("toStartOfYear", ""); //将Date或DateTime向前取整到本年的第一天。
- SQL_FUNCTION_MAP.put("toStartOfISOYear", ""); // 将Date或DateTime向前取整到ISO本年的第一天。
- SQL_FUNCTION_MAP.put("toStartOfQuarter", "");//将Date或DateTime向前取整到本季度的第一天。
- SQL_FUNCTION_MAP.put("toStartOfMonth", ""); //将Date或DateTime向前取整到本月的第一天。
- SQL_FUNCTION_MAP.put("toMonday", ""); //将Date或DateTime向前取整到本周的星期
- SQL_FUNCTION_MAP.put("toStartOfWeek", ""); //按mode将Date或DateTime向前取整到最近的星期日或星期一。
- SQL_FUNCTION_MAP.put("toStartOfDay", ""); //将DateTime向前取整到今天的开始。
- SQL_FUNCTION_MAP.put("toStartOfHour", ""); //将DateTime向前取整到当前小时的开始。
- SQL_FUNCTION_MAP.put("toStartOfMinute", ""); //将DateTime向前取整到当前分钟的开始。
- SQL_FUNCTION_MAP.put("toStartOfSecond", ""); //将DateTime向前取整到当前秒数的开始。
- SQL_FUNCTION_MAP.put("toStartOfFiveMinute", "");//将DateTime以五分钟为单位向前取整到最接近的时间点。
- SQL_FUNCTION_MAP.put("toStartOfTenMinutes", ""); //将DateTime以十分钟为单位向前取整到最接近的时间点。
- SQL_FUNCTION_MAP.put("toStartOfFifteenMinutes", ""); //将DateTime以十五分钟为单位向前取整到最接近的时间点。
- SQL_FUNCTION_MAP.put("toStartOfInterval", ""); //
- SQL_FUNCTION_MAP.put("toTime", ""); //将DateTime中的日期转换为一个固定的日期,同时保留时间部分。
- SQL_FUNCTION_MAP.put("toISOYear", ""); //将Date或DateTime转换为包含ISO年份的UInt16类型的编号。
- SQL_FUNCTION_MAP.put("toISOWeek", ""); //
- SQL_FUNCTION_MAP.put("toWeek", "");// 返回Date或DateTime的周数。
- SQL_FUNCTION_MAP.put("toYearWeek", ""); //返回年和周的日期
- SQL_FUNCTION_MAP.put("date_trunc", ""); //截断日期和时间数据到日期的指定部分
- SQL_FUNCTION_MAP.put("date_diff", ""); //回两个日期或带有时间值的日期之间的差值。
-
- SQL_FUNCTION_MAP.put("yesterday", ""); //不接受任何参数并在请求执行时的某一刻返回昨天的日期(Date)。
- SQL_FUNCTION_MAP.put("today", ""); //不接受任何参数并在请求执行时的某一刻返回当前日期(Date)。
- SQL_FUNCTION_MAP.put("timeSlot", ""); //将时间向前取整半小时。
- SQL_FUNCTION_MAP.put("toYYYYMM", ""); //
- SQL_FUNCTION_MAP.put("toYYYYMMDD", "");//
- SQL_FUNCTION_MAP.put("toYYYYMMDDhhmmss", ""); //
- SQL_FUNCTION_MAP.put("addYears", ""); // Function adds a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime
- SQL_FUNCTION_MAP.put("addMonths", ""); //同上
- SQL_FUNCTION_MAP.put("addWeeks", ""); //同上
- SQL_FUNCTION_MAP.put("addDays", ""); //同上
- SQL_FUNCTION_MAP.put("addHours", ""); //同上
- SQL_FUNCTION_MAP.put("addMinutes", "");//同上
- SQL_FUNCTION_MAP.put("addSeconds", ""); //同上
- SQL_FUNCTION_MAP.put("addQuarters", ""); //同上
- SQL_FUNCTION_MAP.put("subtractYears", ""); //Function subtract a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime
- SQL_FUNCTION_MAP.put("subtractMonths", ""); //同上
- SQL_FUNCTION_MAP.put("subtractWeeks", ""); //同上
- SQL_FUNCTION_MAP.put("subtractDays", ""); //同上
- SQL_FUNCTION_MAP.put("subtractours", "");//同上
- SQL_FUNCTION_MAP.put("subtractMinutes", ""); //同上
- SQL_FUNCTION_MAP.put("subtractSeconds", ""); //同上
- SQL_FUNCTION_MAP.put("subtractQuarters", ""); //同上
- SQL_FUNCTION_MAP.put("formatDateTime", ""); //函数根据给定的格式字符串来格式化时间
- SQL_FUNCTION_MAP.put("timestamp_add", ""); //使用提供的日期或日期时间值添加指定的时间值。
- SQL_FUNCTION_MAP.put("timestamp_sub", ""); //从提供的日期或带时间的日期中减去时间间隔。
-
- //clickhouse json函数
- SQL_FUNCTION_MAP.put("visitParamHas", ""); //visitParamHas(params, name)检查是否存在«name»名称的字段
- SQL_FUNCTION_MAP.put("visitParamExtractUInt", ""); //visitParamExtractUInt(params, name)将名为«name»的字段的值解析成UInt64。
- SQL_FUNCTION_MAP.put("visitParamExtractInt", ""); //与visitParamExtractUInt相同,但返回Int64。
- SQL_FUNCTION_MAP.put("visitParamExtractFloat", ""); //与visitParamExtractUInt相同,但返回Float64。
- SQL_FUNCTION_MAP.put("visitParamExtractBool", "");//解析true/false值。其结果是UInt8类型的。
- SQL_FUNCTION_MAP.put("visitParamExtractRaw", ""); //返回字段的值,包含空格符。
- SQL_FUNCTION_MAP.put("visitParamExtractString", ""); //使用双引号解析字符串。这个值没有进行转义。如果转义失败,它将返回一个空白字符串。
- SQL_FUNCTION_MAP.put("JSONHas", ""); //如果JSON中存在该值,则返回1。
- SQL_FUNCTION_MAP.put("JSONLength", ""); //返回JSON数组或JSON对象的长度。
- SQL_FUNCTION_MAP.put("JSONType", ""); //返回JSON值的类型。
- SQL_FUNCTION_MAP.put("JSONExtractUInt", ""); //解析JSON并提取值。这些函数类似于visitParam*函数。
- SQL_FUNCTION_MAP.put("JSONExtractInt", ""); //
- SQL_FUNCTION_MAP.put("JSONExtractFloat", ""); //
- SQL_FUNCTION_MAP.put("JSONExtractBool", ""); //
- SQL_FUNCTION_MAP.put("JSONExtractString", ""); //解析JSON并提取字符串。此函数类似于visitParamExtractString函数。
- SQL_FUNCTION_MAP.put("JSONExtract", "");//解析JSON并提取给定ClickHouse数据类型的值。
- SQL_FUNCTION_MAP.put("JSONExtractKeysAndValues", ""); //从JSON中解析键值对,其中值是给定的ClickHouse数据类型
- SQL_FUNCTION_MAP.put("JSONExtractRaw", ""); //返回JSON的部分。
- SQL_FUNCTION_MAP.put("toJSONString", ""); //
-
- //clickhouse 类型转换函数
- SQL_FUNCTION_MAP.put("toInt8", ""); //toInt8(expr) 转换一个输入值为Int类型
- SQL_FUNCTION_MAP.put("toInt16", "");
- SQL_FUNCTION_MAP.put("toInt32", "");
- SQL_FUNCTION_MAP.put("toInt64", "");
- SQL_FUNCTION_MAP.put("toInt8OrZero", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。
- SQL_FUNCTION_MAP.put("toInt16OrZero", "");
- SQL_FUNCTION_MAP.put("toInt32OrZero", "");
- SQL_FUNCTION_MAP.put("toInt64OrZero", "");
- SQL_FUNCTION_MAP.put("toInt8OrNull", "");//toInt(8|16|32|64)OrNull 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回NULL
- SQL_FUNCTION_MAP.put("toInt16OrNull", "");
- SQL_FUNCTION_MAP.put("toInt32OrNull", "");
- SQL_FUNCTION_MAP.put("toInt64OrNull", "");
- SQL_FUNCTION_MAP.put("toUInt8", ""); //toInt8(expr) 转换一个输入值为Int类型
- SQL_FUNCTION_MAP.put("toUInt16", "");
- SQL_FUNCTION_MAP.put("toUInt32", "");
- SQL_FUNCTION_MAP.put("toUInt64", "");
- SQL_FUNCTION_MAP.put("toUInt8OrZero", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。
- SQL_FUNCTION_MAP.put("toUInt16OrZero", "");
- SQL_FUNCTION_MAP.put("toUInt32OrZero", "");
- SQL_FUNCTION_MAP.put("toUInt64OrZero", "");
- SQL_FUNCTION_MAP.put("toUInt8OrNull", "");//toInt(8|16|32|64)OrNull 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回NULL
- SQL_FUNCTION_MAP.put("toUInt16OrNull", "");
- SQL_FUNCTION_MAP.put("toUInt32OrNull", "");
- SQL_FUNCTION_MAP.put("toUInt64OrNull", "");
-
- SQL_FUNCTION_MAP.put("toFloat32", "");
- SQL_FUNCTION_MAP.put("toFloat64", "");
- SQL_FUNCTION_MAP.put("toFloat32OrZero", "");
- SQL_FUNCTION_MAP.put("toFloat64OrZero", "");
- SQL_FUNCTION_MAP.put("toFloat32OrNull", "");
- SQL_FUNCTION_MAP.put("toFloat64OrNull", "");
-
- SQL_FUNCTION_MAP.put("toDate", ""); //
- SQL_FUNCTION_MAP.put("toDateOrZero", ""); //toInt16(expr)
- SQL_FUNCTION_MAP.put("toDateOrNull", ""); //toInt32(expr)
- SQL_FUNCTION_MAP.put("toDateTimeOrZero", ""); //toInt64(expr)
- SQL_FUNCTION_MAP.put("toDateTimeOrNull", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。
-
- SQL_FUNCTION_MAP.put("toDecimal32", "");
- SQL_FUNCTION_MAP.put("toFixedString", ""); // 将String类型的参数转换为FixedString(N)类型的值
- SQL_FUNCTION_MAP.put("toStringCutToZero", ""); // 接受String或FixedString参数,返回String,其内容在找到的第一个零字节处被截断。
- SQL_FUNCTION_MAP.put("toDecimal256", "");
- SQL_FUNCTION_MAP.put("toDecimal32OrNull", "");
- SQL_FUNCTION_MAP.put("toDecimal64OrNull", "");
- SQL_FUNCTION_MAP.put("toDecimal128OrNull", "");
- SQL_FUNCTION_MAP.put("toDecimal256OrNull", "");
- SQL_FUNCTION_MAP.put("toDecimal32OrZero", "");
- SQL_FUNCTION_MAP.put("toDecimal64OrZero", "");
- SQL_FUNCTION_MAP.put("toDecimal128OrZero", "");
- SQL_FUNCTION_MAP.put("toDecimal256OrZero", "");
-
-
- SQL_FUNCTION_MAP.put("toIntervalSecond", ""); //把一个数值类型的值转换为Interval类型的数据。
- SQL_FUNCTION_MAP.put("toIntervalMinute", "");
- SQL_FUNCTION_MAP.put("toIntervalHour", "");
- SQL_FUNCTION_MAP.put("toIntervalDay", "");
- SQL_FUNCTION_MAP.put("toIntervalWeek", "");
- SQL_FUNCTION_MAP.put("toIntervalMonth", "");
- SQL_FUNCTION_MAP.put("toIntervalQuarter", "");
- SQL_FUNCTION_MAP.put("toIntervalYear", "");
- SQL_FUNCTION_MAP.put("parseDateTimeBestEffort", ""); //把String类型的时间日期转换为DateTime数据类型。
- SQL_FUNCTION_MAP.put("parseDateTimeBestEffortOrNull", "");
- SQL_FUNCTION_MAP.put("parseDateTimeBestEffortOrZero", "");
- SQL_FUNCTION_MAP.put("toLowCardinality", "");
-
-
-
- ////clickhouse hash函数
- SQL_FUNCTION_MAP.put("halfMD5", ""); //计算字符串的MD5。然后获取结果的前8个字节并将它们作为UInt64(大端)返回
- SQL_FUNCTION_MAP.put("MD5", ""); //计算字符串的MD5并将结果放入FixedString(16)中返回
-
- //clickhouse ip地址函数
- SQL_FUNCTION_MAP.put("IPv4NumToString", ""); //接受一个UInt32(大端)表示的IPv4的地址,返回相应IPv4的字符串表现形式,格式为A.B.C.D(以点分割的十进制数字)。
- SQL_FUNCTION_MAP.put("IPv4StringToNum", ""); //与IPv4NumToString函数相反。如果IPv4地址格式无效,则返回0。
- SQL_FUNCTION_MAP.put("IPv6NumToString", ""); //接受FixedString(16)类型的二进制格式的IPv6地址。以文本格式返回此地址的字符串。
- SQL_FUNCTION_MAP.put("IPv6StringToNum", ""); //与IPv6NumToString的相反。如果IPv6地址格式无效,则返回空字节字符串。
- SQL_FUNCTION_MAP.put("IPv4ToIPv6", ""); // 接受一个UInt32类型的IPv4地址,返回FixedString(16)类型的IPv6地址
- SQL_FUNCTION_MAP.put("cutIPv6", ""); //接受一个FixedString(16)类型的IPv6地址,返回一个String,这个String中包含了删除指定位之后的地址的文本格
- SQL_FUNCTION_MAP.put("toIPv4", ""); //IPv4StringToNum()的别名,
- SQL_FUNCTION_MAP.put("toIPv6", ""); //IPv6StringToNum()的别名
- SQL_FUNCTION_MAP.put("isIPAddressInRange", ""); //确定一个IP地址是否包含在以CIDR符号表示的网络中
-
- //clickhouse Nullable处理函数
- SQL_FUNCTION_MAP.put("isNull", ""); //检查参数是否为NULL。
- SQL_FUNCTION_MAP.put("isNotNull", ""); //检查参数是否不为 NULL.
- SQL_FUNCTION_MAP.put("ifNull", ""); //如果第一个参数为«NULL»,则返回第二个参数的值。
- SQL_FUNCTION_MAP.put("assumeNotNull", ""); //将可为空类型的值转换为非Nullable类型的值。
- SQL_FUNCTION_MAP.put("toNullable", ""); //将参数的类型转换为Nullable。
-
- //clickhouse UUID函数
- SQL_FUNCTION_MAP.put("generateUUIDv4", ""); // 生成一个UUID
- SQL_FUNCTION_MAP.put("toUUID", ""); //toUUID(x) 将String类型的值转换为UUID类型的值。
-
- //clickhouse 系统函数
- SQL_FUNCTION_MAP.put("hostName", ""); //hostName()回一个字符串,其中包含执行此函数的主机的名称。
- SQL_FUNCTION_MAP.put("getMacro", ""); //从服务器配置的宏部分获取指定值。
- SQL_FUNCTION_MAP.put("FQDN", "");//返回完全限定的域名。
- SQL_FUNCTION_MAP.put("basename", ""); //提取字符串最后一个斜杠或反斜杠之后的尾随部分
- SQL_FUNCTION_MAP.put("currentUser", ""); //返回当前用户的登录。在分布式查询的情况下,将返回用户的登录,即发起的查询
- SQL_FUNCTION_MAP.put("version", ""); //以字符串形式返回服务器版本。
- SQL_FUNCTION_MAP.put("uptime", "");//以秒为单位返回服务器的正常运行时间。
-
- //clickhouse 数学函数
- SQL_FUNCTION_MAP.put("least", ""); //返回a和b中最小的值。
- SQL_FUNCTION_MAP.put("greatest", ""); //返回a和b的最大值。
- SQL_FUNCTION_MAP.put("plus", ""); //plus(a, b), a + b operator¶计算数值的总和。
- SQL_FUNCTION_MAP.put("minus", ""); //minus(a, b), a - b operator 计算数值之间的差,结果总是有符号的。
- SQL_FUNCTION_MAP.put("multiply", "");//multiply(a, b), a * b operator 计算数值的乘积
- SQL_FUNCTION_MAP.put("divide", ""); //divide(a, b), a / b operator 计算数值的商。结果类型始终是浮点类型
- SQL_FUNCTION_MAP.put("intDiv", ""); //intDiv(a,b)计算数值的商,向下舍入取整(按绝对值)。
- SQL_FUNCTION_MAP.put("intDivOrZero", ""); // intDivOrZero(a,b)与’intDiv’的不同之处在于它在除以零或将最小负数除以-1时返回零。
- SQL_FUNCTION_MAP.put("modulo", ""); //modulo(a, b), a % b operator 计算除法后的余数。
- SQL_FUNCTION_MAP.put("moduloOrZero", ""); //和modulo不同之处在于,除以0时结果返回0
- SQL_FUNCTION_MAP.put("negate", ""); //通过改变数值的符号位对数值取反,结果总是有符号
- SQL_FUNCTION_MAP.put("gcd", ""); //gcd(a,b) 返回数值的最大公约数。
- SQL_FUNCTION_MAP.put("lcm", ""); //lcm(a,b) 返回数值的最小公倍数
- SQL_FUNCTION_MAP.put("e", ""); //e() 返回一个接近数学常量e的Float64数字。
- SQL_FUNCTION_MAP.put("pi", ""); //pi() 返回一个接近数学常量π的Float64数字。
- SQL_FUNCTION_MAP.put("exp2", ""); //exp2(x)¶接受一个数值类型的参数并返回它的2的x次幂。
- SQL_FUNCTION_MAP.put("exp10", ""); //exp10(x)¶接受一个数值类型的参数并返回它的10的x次幂。
- SQL_FUNCTION_MAP.put("cbrt", ""); //cbrt(x) 接受一个数值类型的参数并返回它的立方根。
- SQL_FUNCTION_MAP.put("lgamma", ""); //lgamma(x) 返回x的绝对值的自然对数的伽玛函数。
- SQL_FUNCTION_MAP.put("tgamma", ""); //tgamma(x)¶返回x的伽玛函数。
- SQL_FUNCTION_MAP.put("intExp2", ""); //intExp2 接受一个数值类型的参数并返回它的2的x次幂(UInt64)
- SQL_FUNCTION_MAP.put("intExp10", ""); //intExp10 接受一个数值类型的参数并返回它的10的x次幂(UInt64)。
- SQL_FUNCTION_MAP.put("cosh", ""); // cosh(x)
- SQL_FUNCTION_MAP.put("cosh", ""); //cosh(x)
- SQL_FUNCTION_MAP.put("sinh", ""); //sinh(x)
- SQL_FUNCTION_MAP.put("asinh", ""); //asinh(x)
- SQL_FUNCTION_MAP.put("atanh", ""); //atanh(x)
- SQL_FUNCTION_MAP.put("atan2", ""); //atan2(y, x)
- SQL_FUNCTION_MAP.put("hypot", ""); //hypot(x, y)
- SQL_FUNCTION_MAP.put("log1p", ""); //log1p(x)
- SQL_FUNCTION_MAP.put("trunc", ""); //和truncate一样
- SQL_FUNCTION_MAP.put("roundToExp2", ""); //接受一个数字。如果数字小于1,它返回0。
- SQL_FUNCTION_MAP.put("roundDuration", ""); //接受一个数字。如果数字小于1,它返回0。
- SQL_FUNCTION_MAP.put("roundAge", ""); // 接受一个数字。如果数字小于18,它返回0。
- SQL_FUNCTION_MAP.put("roundDown", ""); //接受一个数字并将其舍入到指定数组中的一个元素
- SQL_FUNCTION_MAP.put("bitAnd", ""); //bitAnd(a,b)
- SQL_FUNCTION_MAP.put("bitOr", ""); //bitOr(a,b)
+
+
+ RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况
+
+ // mysql关键字
+ RAW_MAP.put("AS","");
+ RAW_MAP.put("VALUE","");
+ RAW_MAP.put("DISTINCT","");
+
+ //时间
+ RAW_MAP.put("DATE","");
+ RAW_MAP.put("now()","");
+ RAW_MAP.put("DATETIME","");
+ RAW_MAP.put("DateTime","");
+ RAW_MAP.put("SECOND","");
+ RAW_MAP.put("MINUTE","");
+ RAW_MAP.put("HOUR","");
+ RAW_MAP.put("DAY","");
+ RAW_MAP.put("WEEK","");
+ RAW_MAP.put("MONTH","");
+ RAW_MAP.put("QUARTER","");
+ RAW_MAP.put("YEAR","");
+ RAW_MAP.put("json","");
+ RAW_MAP.put("unit","");
+
+ //MYSQL 数据类型 BINARY,CHAR,DATETIME,TIME,DECIMAL,SIGNED,UNSIGNED
+ RAW_MAP.put("BINARY","");
+ RAW_MAP.put("SIGNED","");
+ RAW_MAP.put("DECIMAL","");
+ RAW_MAP.put("BINARY","");
+ RAW_MAP.put("UNSIGNED","");
+ RAW_MAP.put("CHAR","");
+ RAW_MAP.put("TIME","");
+
+ //窗口函数关键字
+ RAW_MAP.put("OVER", "");
+ RAW_MAP.put("INTERVAL", "");
+ RAW_MAP.put("GROUP BY", ""); //往前
+ RAW_MAP.put("GROUP", ""); //往前
+ RAW_MAP.put("ORDER BY", ""); //往前
+ RAW_MAP.put("ORDER", "");
+ RAW_MAP.put("PARTITION BY", ""); //往前
+ RAW_MAP.put("PARTITION", ""); //往前
+ RAW_MAP.put("BY", "");
+ RAW_MAP.put("DESC", "");
+ RAW_MAP.put("ASC", "");
+ RAW_MAP.put("FOLLOWING", "");//往后
+ RAW_MAP.put("BETWEEN", "");
+ RAW_MAP.put("AND", "");
+ RAW_MAP.put("ROWS", "");
+
+ RAW_MAP.put("AGAINST", "");
+ RAW_MAP.put("IN NATURAL LANGUAGE MODE", "");
+ RAW_MAP.put("IN BOOLEAN MODE", "");
+ RAW_MAP.put("IN", "");
+ RAW_MAP.put("BOOLEAN", "");
+ RAW_MAP.put("NATURAL", "");
+ RAW_MAP.put("LANGUAGE", "");
+ RAW_MAP.put("MODE", "");
+
+
+ SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况
+
+
+ //窗口函数
+ SQL_FUNCTION_MAP.put("rank", "");//得到数据项在分组中的排名,排名相等的时候会留下空位
+ SQL_FUNCTION_MAP.put("dense_rank", ""); //得到数据项在分组中的排名,排名相等的时候不会留下空位
+ SQL_FUNCTION_MAP.put("row_number", "");//按照分组中的顺序生成序列,不存在重复的序列
+ SQL_FUNCTION_MAP.put("ntile", "");//用于将分组数据按照顺序切分成N片,返回当前切片值,不支持ROWS_BETWEE
+ SQL_FUNCTION_MAP.put("first_value", "");//取分组排序后,截止到当前行,分组内第一个值
+ SQL_FUNCTION_MAP.put("last_value", "");//取分组排序后,截止到当前行,分组内的最后一个值
+ SQL_FUNCTION_MAP.put("lag", "");//统计窗口内往上第n行值。第一个参数为列名,第二个参数为往上第n行(可选,默认为1),第三个参数为默认值(当往上第n行为NULL时候,取默认值,如不指定,则为NULL)
+ SQL_FUNCTION_MAP.put("lead", "");//统计窗口内往下第n行值。第一个参数为列名,第二个参数为往下第n行(可选,默认为1),第三个参数为默认值(当往下第n行为NULL时候,取默认值,如不指定,则为NULL)
+ SQL_FUNCTION_MAP.put("cume_dist", "");//)返回(小于等于当前行值的行数)/(当前分组内的总行数)
+ SQL_FUNCTION_MAP.put("percent_rank", "");//返回(组内当前行的rank值-1)/(分组内做总行数-1)
+
+ // MySQL 字符串函数
+ SQL_FUNCTION_MAP.put("ascii", ""); // ASCII(s) 返回字符串 s 的第一个字符的 ASCII 码。
+ SQL_FUNCTION_MAP.put("char_length", ""); // CHAR_LENGTH(s) 返回字符串 s 的字符数
+ SQL_FUNCTION_MAP.put("character_length", ""); // CHARACTER_LENGTH(s) 返回字符串 s 的字符数
+ SQL_FUNCTION_MAP.put("concat", ""); // CONCAT(s1, s2...sn) 字符串 s1,s2 等多个字符串合并为一个字符串
+ SQL_FUNCTION_MAP.put("concat_ws", ""); // CONCAT_WS(x, s1, s2...sn) 同 CONCAT(s1, s2 ...) 函数,但是每个字符串之间要加上 x,x 可以是分隔符
+ SQL_FUNCTION_MAP.put("field", ""); // FIELD(s, s1, s2...) 返回第一个字符串 s 在字符串列表 (s1, s2...)中的位置
+ SQL_FUNCTION_MAP.put("find_in_set", ""); // FIND_IN_SET(s1, s2) 返回在字符串s2中与s1匹配的字符串的位置
+ SQL_FUNCTION_MAP.put("format", ""); // FORMAT(x, n) 函数可以将数字 x 进行格式化 "#,###.##", 将 x 保留到小数点后 n 位,最后一位四舍五入。
+ SQL_FUNCTION_MAP.put("insert", ""); // INSERT(s1, x, len, s2) 字符串 s2 替换 s1 的 x 位置开始长度为 len 的字符串
+ SQL_FUNCTION_MAP.put("locate", ""); // LOCATE(s1, s) 从字符串 s 中获取 s1 的开始位置
+ SQL_FUNCTION_MAP.put("lcase", ""); // LCASE(s) 将字符串 s 的所有字母变成小写字母
+ SQL_FUNCTION_MAP.put("left", ""); // LEFT(s, n) 返回字符串 s 的前 n 个字符
+ SQL_FUNCTION_MAP.put("length", ""); // LENGTH(s) 返回字符串 s 的字符数
+ SQL_FUNCTION_MAP.put("lower", ""); // LOWER(s) 将字符串 s 的所有字母变成小写字母
+ SQL_FUNCTION_MAP.put("lpad", ""); // LPAD(s1, len, s2) 在字符串 s1 的开始处填充字符串 s2,使字符串长度达到 len
+ SQL_FUNCTION_MAP.put("ltrim", ""); // LTRIM(s) 去掉字符串 s 开始处的空格
+ SQL_FUNCTION_MAP.put("mid", ""); // MID(s, n, len) 从字符串 s 的 n 位置截取长度为 len 的子字符串,同 SUBSTRING(s, n, len)
+ SQL_FUNCTION_MAP.put("position", ""); // POSITION(s, s1); 从字符串 s 中获取 s1 的开始位置
+ SQL_FUNCTION_MAP.put("repeat", ""); // REPEAT(s, n) 将字符串 s 重复 n 次
+ SQL_FUNCTION_MAP.put("replace", ""); // REPLACE(s, s1, s2) 将字符串 s2 替代字符串 s 中的字符串 s1
+ SQL_FUNCTION_MAP.put("reverse", ""); // REVERSE(s); // ) 将字符串s的顺序反过来
+ SQL_FUNCTION_MAP.put("right", ""); // RIGHT(s, n) 返回字符串 s 的后 n 个字符
+ SQL_FUNCTION_MAP.put("rpad", ""); // RPAD(s1, len, s2) 在字符串 s1 的结尾处添加字符串 s2,使字符串的长度达到 len
+ SQL_FUNCTION_MAP.put("rtrim", ""); // RTRIM", ""); // ) 去掉字符串 s 结尾处的空格
+ SQL_FUNCTION_MAP.put("space", ""); // SPACE(n) 返回 n 个空格
+ SQL_FUNCTION_MAP.put("strcmp", ""); // STRCMP(s1, s2) 比较字符串 s1 和 s2,如果 s1 与 s2 相等返回 0 ,如果 s1>s2 返回 1,如果 s1d2 之间相隔的天数
+ SQL_FUNCTION_MAP.put("date_add", ""); // DATE_ADD(d,INTERVAL expr type) 计算起始日期 d 加上一个时间段后的日期
+ SQL_FUNCTION_MAP.put("date_format", ""); // DATE_FORMAT(d,f) 按表达式 f的要求显示日期 d
+ SQL_FUNCTION_MAP.put("date_sub", ""); // DATE_SUB(date,INTERVAL expr type) 函数从日期减去指定的时间间隔。
+ SQL_FUNCTION_MAP.put("day", ""); // DAY(d) 返回日期值 d 的日期部分
+ SQL_FUNCTION_MAP.put("dayname", ""); // DAYNAME(d) 返回日期 d 是星期几,如 Monday,Tuesday
+ SQL_FUNCTION_MAP.put("dayofmonth", ""); // DAYOFMONTH(d) 计算日期 d 是本月的第几天
+ SQL_FUNCTION_MAP.put("dayofweek", ""); // DAYOFWEEK(d) 日期 d 今天是星期几,1 星期日,2 星期一,以此类推
+ SQL_FUNCTION_MAP.put("dayofyear", ""); // DAYOFYEAR(d) 计算日期 d 是本年的第几天
+ SQL_FUNCTION_MAP.put("extract", ""); // EXTRACT(type FROM d) 从日期 d 中获取指定的值,type 指定返回的值。
+ SQL_FUNCTION_MAP.put("from_days", ""); // FROM_DAYS(n) 计算从 0000 年 1 月 1 日开始 n 天后的日期
+ SQL_FUNCTION_MAP.put("hour", ""); // 'HOUR(t) 返回 t 中的小时值
+ SQL_FUNCTION_MAP.put("last_day", ""); // LAST_DAY(d) 返回给给定日期的那一月份的最后一天
+ SQL_FUNCTION_MAP.put("localtime", ""); // LOCALTIME() 返回当前日期和时间
+ SQL_FUNCTION_MAP.put("localtimestamp", ""); // LOCALTIMESTAMP() 返回当前日期和时间
+ SQL_FUNCTION_MAP.put("makedate", ""); // MAKEDATE(year, day-of-year) 基于给定参数年份 year 和所在年中的天数序号 day-of-year 返回一个日期
+ SQL_FUNCTION_MAP.put("maketime", ""); // MAKETIME(hour, minute, second) 组合时间,参数分别为小时、分钟、秒
+ SQL_FUNCTION_MAP.put("microsecond", ""); // MICROSECOND(date) 返回日期参数所对应的微秒数
+ SQL_FUNCTION_MAP.put("minute", ""); // MINUTE(t) 返回 t 中的分钟值
+ SQL_FUNCTION_MAP.put("monthname", ""); // MONTHNAME(d) 返回日期当中的月份名称,如 November
+ SQL_FUNCTION_MAP.put("month", ""); // MONTH(d) 返回日期d中的月份值,1 到 12
+ SQL_FUNCTION_MAP.put("now", ""); // NOW() 返回当前日期和时间
+ SQL_FUNCTION_MAP.put("period_add", ""); // PERIOD_ADD(period, number) 为 年-月 组合日期添加一个时段
+ SQL_FUNCTION_MAP.put("period_diff", ""); // PERIOD_DIFF(period1, period2) 返回两个时段之间的月份差值
+ SQL_FUNCTION_MAP.put("quarter", ""); // QUARTER(d) 返回日期d是第几季节,返回 1 到 4
+ SQL_FUNCTION_MAP.put("second", ""); // SECOND(t) 返回 t 中的秒钟值
+ SQL_FUNCTION_MAP.put("sec_to_time", ""); // SEC_TO_TIME", ""); // ) 将以秒为单位的时间 s 转换为时分秒的格式
+ SQL_FUNCTION_MAP.put("str_to_date", ""); // STR_TO_DATE", ""); // tring, format_mask) 将字符串转变为日期
+ SQL_FUNCTION_MAP.put("subdate", ""); // SUBDATE(d,n) 日期 d 减去 n 天后的日期
+ SQL_FUNCTION_MAP.put("subtime", ""); // SUBTIME(t,n) 时间 t 减去 n 秒的时间
+ SQL_FUNCTION_MAP.put("sysdate", ""); // SYSDATE() 返回当前日期和时间
+ SQL_FUNCTION_MAP.put("time", ""); // TIME(expression) 提取传入表达式的时间部分
+ SQL_FUNCTION_MAP.put("time_format", ""); // TIME_FORMAT(t,f) 按表达式 f 的要求显示时间 t
+ SQL_FUNCTION_MAP.put("time_to_sec", ""); // TIME_TO_SEC(t) 将时间 t 转换为秒
+ SQL_FUNCTION_MAP.put("timediff", ""); // TIMEDIFF(time1, time2) 计算时间差值
+ SQL_FUNCTION_MAP.put("timestamp", ""); // TIMESTAMP(expression, interval) 单个参数时,函数返回日期或日期时间表达式;有2个参数时,将参数加和
+ SQL_FUNCTION_MAP.put("to_days", ""); // TO_DAYS(d) 计算日期 d 距离 0000 年 1 月 1 日的天数
+ SQL_FUNCTION_MAP.put("week", ""); // WEEK(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53
+ SQL_FUNCTION_MAP.put("weekday", ""); // WEEKDAY(d) 日期 d 是星期几,0 表示星期一,1 表示星期二
+ SQL_FUNCTION_MAP.put("weekofyear", ""); // WEEKOFYEAR(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53
+ SQL_FUNCTION_MAP.put("year", ""); // YEAR(d) 返回年份
+ SQL_FUNCTION_MAP.put("yearweek", ""); // YEARWEEK(date, mode) 返回年份及第几周(0到53),mode 中 0 表示周天,1表示周一,以此类推
+ SQL_FUNCTION_MAP.put("unix_timestamp", ""); // UNIX_TIMESTAMP(date) 获取UNIX时间戳函数,返回一个以 UNIX 时间戳为基础的无符号整数
+ SQL_FUNCTION_MAP.put("from_unixtime", ""); // FROM_UNIXTIME(date) 将 UNIX 时间戳转换为时间格式,与UNIX_TIMESTAMP互为反函数
+
+ // MYSQL JSON 函数
+ SQL_FUNCTION_MAP.put("json_append", ""); // JSON_APPEND(json_doc, path, val[, path, val] ...)) 插入JSON数组
+ SQL_FUNCTION_MAP.put("json_array", ""); // JSON_ARRAY(val1, val2...) 创建JSON数组
+ SQL_FUNCTION_MAP.put("json_array_append", ""); // JSON_ARRAY_APPEND(json_doc, val) 将数据附加到JSON文档
+ SQL_FUNCTION_MAP.put("json_array_insert", ""); // JSON_ARRAY_INSERT(json_doc, val) 插入JSON数组
+ SQL_FUNCTION_MAP.put("json_contains", ""); // JSON_CONTAINS(json_doc, val) JSON文档是否在路径中包含特定对象
+ SQL_FUNCTION_MAP.put("json_contains_path", ""); // JSON_CONTAINS_PATH(json_doc, path) JSON文档是否在路径中包含任何数据
+ SQL_FUNCTION_MAP.put("json_depth", ""); // JSON_DEPTH(json_doc) JSON文档的最大深度
+ SQL_FUNCTION_MAP.put("json_extract", ""); // JSON_EXTRACT(json_doc, path) 从JSON文档返回数据
+ SQL_FUNCTION_MAP.put("json_insert", ""); // JSON_INSERT(json_doc, val) 将数据插入JSON文档
+ SQL_FUNCTION_MAP.put("json_keys", ""); // JSON_KEYS(json_doc[, path]) JSON文档中的键数组
+ SQL_FUNCTION_MAP.put("json_length", ""); // JSON_LENGTH(json_doc) JSON文档中的元素数
+ SQL_FUNCTION_MAP.put("json_merge", ""); // JSON_MERGE(json_doc1, json_doc2) (已弃用) 合并JSON文档,保留重复的键。JSON_MERGE_PRESERVE()的已弃用同义词
+ SQL_FUNCTION_MAP.put("json_merge_patch", ""); // JSON_MERGE_PATCH(json_doc1, json_doc2) 合并JSON文档,替换重复键的值
+ SQL_FUNCTION_MAP.put("json_merge_preserve", ""); // JSON_MERGE_PRESERVE(json_doc1, json_doc2) 合并JSON文档,保留重复的键
+ SQL_FUNCTION_MAP.put("json_object", ""); // JSON_OBJECT(key1, val1, key2, val2...) 创建JSON对象
+ SQL_FUNCTION_MAP.put("json_overlaps", ""); // JSON_OVERLAPS(json_doc1, json_doc2) (引入8.0.17) 比较两个JSON文档,如果它们具有相同的键值对或数组元素,则返回TRUE(1),否则返回FALSE(0)
+ SQL_FUNCTION_MAP.put("json_pretty", ""); // JSON_PRETTY(json_doc) 以易于阅读的格式打印JSON文档
+ SQL_FUNCTION_MAP.put("json_quote", ""); // JSON_QUOTE(json_doc1) 引用JSON文档
+ SQL_FUNCTION_MAP.put("json_remove", ""); // JSON_REMOVE(json_doc1, path) 从JSON文档中删除数据
+ SQL_FUNCTION_MAP.put("json_replace", ""); // JSON_REPLACE(json_doc1, val1, val2) 替换JSON文档中的值
+ SQL_FUNCTION_MAP.put("json_schema_valid", ""); // JSON_SCHEMA_VALID(json_doc) (引入8.0.17) 根据JSON模式验证JSON文档;如果文档针对架构进行验证,则返回TRUE / 1;否则,则返回FALSE / 0
+ SQL_FUNCTION_MAP.put("json_schema_validation_report", ""); // JSON_SCHEMA_VALIDATION_REPORT(json_doc, mode) (引入8.0.17) 根据JSON模式验证JSON文档;以JSON格式返回有关验证结果的报告,包括成功或失败以及失败原因
+ SQL_FUNCTION_MAP.put("json_search", ""); // JSON_SEARCH(json_doc, val) JSON文档中值的路径
+ SQL_FUNCTION_MAP.put("json_set", ""); // JSON_SET(json_doc, val) 将数据插入JSON文档
+ // SQL_FUNCTION_MAP.put("json_storage_free", ""); // JSON_STORAGE_FREE() 部分更新后,JSON列值的二进制表示形式中的可用空间
+ // SQL_FUNCTION_MAP.put("json_storage_size", ""); // JSON_STORAGE_SIZE() 用于存储JSON文档的二进制表示的空间
+ SQL_FUNCTION_MAP.put("json_table", ""); // JSON_TABLE() 从JSON表达式返回数据作为关系表
+ SQL_FUNCTION_MAP.put("json_type", ""); // JSON_TYPE(json_doc) JSON值类型
+ SQL_FUNCTION_MAP.put("json_unquote", ""); // JSON_UNQUOTE(json_doc) 取消引用JSON值
+ SQL_FUNCTION_MAP.put("json_valid", ""); // JSON_VALID(json_doc) JSON值是否有效
+ SQL_FUNCTION_MAP.put("json_arrayagg", ""); // JSON_ARRAYAGG(key) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 数组
+ SQL_FUNCTION_MAP.put("json_objectagg", ""); // JSON_OBJECTAGG(key, val)) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 对象
+
+ // MySQL 高级函数
+ // SQL_FUNCTION_MAP.put("bin", ""); // BIN(x) 返回 x 的二进制编码
+ // SQL_FUNCTION_MAP.put("binary", ""); // BINARY(s) 将字符串 s 转换为二进制字符串
+ SQL_FUNCTION_MAP.put("case", ""); // CASE 表示函数开始,END 表示函数结束。如果 condition1 成立,则返回 result1, 如果 condition2 成立,则返回 result2,当全部不成立则返回 result,而当有一个成立之后,后面的就不执行了。
+ SQL_FUNCTION_MAP.put("cast", ""); // CAST(x AS type) 转换数据类型
+ SQL_FUNCTION_MAP.put("coalesce", ""); // COALESCE(expr1, expr2, ...., expr_n) 返回参数中的第一个非空表达式(从左向右)
+ // SQL_FUNCTION_MAP.put("conv", ""); // CONV(x,f1,f2) 返回 f1 进制数变成 f2 进制数
+ // SQL_FUNCTION_MAP.put("convert", ""); // CONVERT(s, cs) 函数将字符串 s 的字符集变成 cs
+ SQL_FUNCTION_MAP.put("if", ""); // IF(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。
+ SQL_FUNCTION_MAP.put("ifnull", ""); // IFNULL(v1,v2) 如果 v1 的值不为 NULL,则返回 v1,否则返回 v2。
+ SQL_FUNCTION_MAP.put("isnull", ""); // ISNULL(expression) 判断表达式是否为 NULL
+ 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) 全文检索
+
+
+
+
+
+ //clickhouse 字符串函数 注释的函数表示返回的格式暂时不支持,如:返回数组 ,同时包含因版本不同 clickhosue不支持的函数,版本
+ SQL_FUNCTION_MAP.put("empty", ""); // empty(s) 对于空字符串s返回1,对于非空字符串返回0
+ SQL_FUNCTION_MAP.put("notEmpty", ""); //notEmpty(s) 对于空字符串返回0,对于非空字符串返回1。
+ SQL_FUNCTION_MAP.put("lengthUTF8", ""); //假定字符串以UTF-8编码组成的文本,返回此字符串的Unicode字符长度。如果传入的字符串不是UTF-8编码,则函数可能返回一个预期外的值
+ SQL_FUNCTION_MAP.put("lcase", ""); //将字符串中的ASCII转换为小写
+ SQL_FUNCTION_MAP.put("ucase", ""); //将字符串中的ASCII转换为大写。
+ SQL_FUNCTION_MAP.put("lowerUTF8", ""); //将字符串转换为小写,函数假设字符串是以UTF-8编码文本的字符集。
+ SQL_FUNCTION_MAP.put("upperUTF8", ""); //将字符串转换为大写,函数假设字符串是以UTF-8编码文本的字符集。
+ SQL_FUNCTION_MAP.put("isValidUTF8", ""); // 检查字符串是否为有效的UTF-8编码,是则返回1,否则返回0。
+ SQL_FUNCTION_MAP.put("toValidUTF8", "");//用�(U+FFFD)字符替换无效的UTF-8字符。所有连续的无效字符都会被替换为一个替换字符。
+ SQL_FUNCTION_MAP.put("reverseUTF8", "");//以Unicode字符为单位反转UTF-8编码的字符串。
+ SQL_FUNCTION_MAP.put("concatAssumeInjective", ""); // concatAssumeInjective(s1, s2, …) 与concat相同,区别在于,你需要保证concat(s1, s2, s3) -> s4是单射的,它将用于GROUP BY的优化。
+ SQL_FUNCTION_MAP.put("substringUTF8", ""); // substringUTF8(s,offset,length)¶ 与’substring’相同,但其操作单位为Unicode字符,函数假设字符串是以UTF-8进行编码的文本。如果不是则可能返回一个预期外的结果(不会抛出异常)。
+ SQL_FUNCTION_MAP.put("appendTrailingCharIfAbsent", ""); // appendTrailingCharIfAbsent(s,c) 如果’s’字符串非空并且末尾不包含’c’字符,则将’c’字符附加到末尾
+ SQL_FUNCTION_MAP.put("convertCharset", ""); // convertCharset(s,from,to) 返回从’from’中的编码转换为’to’中的编码的字符串’s’。
+ SQL_FUNCTION_MAP.put("base64Encode", ""); // base64Encode(s) 将字符串’s’编码成base64
+ SQL_FUNCTION_MAP.put("base64Decode", ""); //base64Decode(s) 使用base64将字符串解码成原始字符串。如果失败则抛出异常。
+ SQL_FUNCTION_MAP.put("tryBase64Decode", ""); //tryBase64Decode(s) 使用base64将字符串解码成原始字符串。但如果出现错误,将返回空字符串。
+ SQL_FUNCTION_MAP.put("endsWith", ""); //endsWith(s,后缀) 返回是否以指定的后缀结尾。如果字符串以指定的后缀结束,则返回1,否则返回0。
+ SQL_FUNCTION_MAP.put("startsWith", ""); //startsWith(s,前缀) 返回是否以指定的前缀开头。如果字符串以指定的前缀开头,则返回1,否则返回0。
+ SQL_FUNCTION_MAP.put("trimLeft", ""); //trimLeft(s)返回一个字符串,用于删除左侧的空白字符。
+ SQL_FUNCTION_MAP.put("trimRight", ""); //trimRight(s) 返回一个字符串,用于删除右侧的空白字符。
+ SQL_FUNCTION_MAP.put("trimBoth", ""); //trimBoth(s),用于删除任一侧的空白字符
+ SQL_FUNCTION_MAP.put("extractAllGroups", ""); //extractAllGroups(text, regexp) 从正则表达式匹配的非重叠子字符串中提取所有组
+ // SQL_FUNCTION_MAP.put("leftPad", ""); //leftPad('string', 'length'[, 'pad_string']) 用空格或指定的字符串从左边填充当前字符串(如果需要,可以多次),直到得到的字符串达到给定的长度
+ // SQL_FUNCTION_MAP.put("leftPadUTF8", ""); //leftPadUTF8('string','length'[, 'pad_string']) 用空格或指定的字符串从左边填充当前字符串(如果需要,可以多次),直到得到的字符串达到给定的长度
+ // SQL_FUNCTION_MAP.put("rightPad", ""); // rightPad('string', 'length'[, 'pad_string']) 用空格或指定的字符串(如果需要,可以多次)从右边填充当前字符串,直到得到的字符串达到给定的长度
+ // SQL_FUNCTION_MAP.put("rightPadUTF8", "");// rightPadUTF8('string','length'[, 'pad_string']) 用空格或指定的字符串(如果需要,可以多次)从右边填充当前字符串,直到得到的字符串达到给定的长度。
+ SQL_FUNCTION_MAP.put("normalizeQuery", ""); //normalizeQuery(x) 用占位符替换文字、文字序列和复杂的别名。
+ SQL_FUNCTION_MAP.put("normalizedQueryHash", ""); //normalizedQueryHash(x) 为类似查询返回相同的64位散列值,但不包含文字值。有助于对查询日志进行分析
+ SQL_FUNCTION_MAP.put("positionUTF8", ""); // positionUTF8(s, needle[, start_pos]) 返回在字符串中找到的子字符串的位置(以Unicode点表示),从1开始。
+ SQL_FUNCTION_MAP.put("multiSearchFirstIndex", ""); //multiSearchFirstIndex(s, [needle1, needle2, …, needlen]) 返回字符串s中最左边的needlei的索引i(从1开始),否则返回0
+ SQL_FUNCTION_MAP.put("multiSearchAny", ""); // multiSearchAny(s, [needle1, needle2, …, needlen])如果至少有一个字符串needlei匹配字符串s,则返回1,否则返回0。
+ SQL_FUNCTION_MAP.put("match", ""); //match(s, pattern) 检查字符串是否与模式正则表达式匹配。re2正则表达式。re2正则表达式的语法比Perl正则表达式的语法更有局限性。
+ SQL_FUNCTION_MAP.put("multiMatchAny", ""); //multiMatchAny(s, [pattern1, pattern2, …, patternn]) 与match相同,但是如果没有匹配的正则表达式返回0,如果有匹配的模式返回1
+ SQL_FUNCTION_MAP.put("multiMatchAnyIndex", ""); //multiMatchAnyIndex(s, [pattern1, pattern2, …, patternn]) 与multiMatchAny相同,但返回与干堆匹配的任何索引
+ SQL_FUNCTION_MAP.put("extract", ""); // extract(s, pattern) 使用正则表达式提取字符串的片段
+ SQL_FUNCTION_MAP.put("extractAll", ""); //extractAll(s, pattern) 使用正则表达式提取字符串的所有片段
+ SQL_FUNCTION_MAP.put("like", ""); //like(s, pattern) 检查字符串是否与简单正则表达式匹配
+ SQL_FUNCTION_MAP.put("notLike", "");// 和‘like’是一样的,但是是否定的
+ SQL_FUNCTION_MAP.put("countSubstrings", ""); //countSubstrings(s, needle[, start_pos])返回子字符串出现的次数
+ SQL_FUNCTION_MAP.put("countMatches", ""); //返回干s中的正则表达式匹配数。countMatches(s, pattern)
+ SQL_FUNCTION_MAP.put("replaceOne", ""); //replaceOne(s, pattern, replacement)将' s '中的' pattern '子串的第一个出现替换为' replacement '子串。
+
+ SQL_FUNCTION_MAP.put("replaceAll", ""); //replaceAll(s, pattern, replacement)/用' replacement '子串替换' s '中所有出现的' pattern '子串
+ SQL_FUNCTION_MAP.put("replaceRegexpOne", ""); //replaceRegexpOne(s, pattern, replacement)使用' pattern '正则表达式进行替换
+ SQL_FUNCTION_MAP.put("replaceRegexpAll", ""); //replaceRegexpAll(s, pattern, replacement)
+ SQL_FUNCTION_MAP.put("regexpQuoteMeta", ""); //regexpQuoteMeta(s)该函数在字符串中某些预定义字符之前添加一个反斜杠
+
+ //clickhouse日期函数
+ SQL_FUNCTION_MAP.put("toYear", ""); //将Date或DateTime转换为包含年份编号(AD)的UInt16类型的数字。
+ SQL_FUNCTION_MAP.put("toQuarter", ""); //将Date或DateTime转换为包含季度编号的UInt8类型的数字。
+ SQL_FUNCTION_MAP.put("toMonth", ""); //Date或DateTime转换为包含月份编号(1-12)的UInt8类型的数字。
+ SQL_FUNCTION_MAP.put("toDayOfYear", ""); //将Date或DateTime转换为包含一年中的某一天的编号的UInt16(1-366)类型的数字。
+ SQL_FUNCTION_MAP.put("toDayOfMonth", "");//将Date或DateTime转换为包含一月中的某一天的编号的UInt8(1-31)类型的数字。
+ SQL_FUNCTION_MAP.put("toDayOfWeek", ""); //将Date或DateTime转换为包含一周中的某一天的编号的UInt8(周一是1, 周日是7)类型的数字。
+ SQL_FUNCTION_MAP.put("toHour", ""); //将DateTime转换为包含24小时制(0-23)小时数的UInt8数字。
+ SQL_FUNCTION_MAP.put("toMinute", ""); //将DateTime转换为包含一小时中分钟数(0-59)的UInt8数字。
+ SQL_FUNCTION_MAP.put("toSecond", ""); //将DateTime转换为包含一分钟中秒数(0-59)的UInt8数字。
+ SQL_FUNCTION_MAP.put("toUnixTimestamp", ""); // 对于DateTime参数:将值转换为UInt32类型的数字-Unix时间戳
+ SQL_FUNCTION_MAP.put("toStartOfYear", ""); //将Date或DateTime向前取整到本年的第一天。
+ SQL_FUNCTION_MAP.put("toStartOfISOYear", ""); // 将Date或DateTime向前取整到ISO本年的第一天。
+ SQL_FUNCTION_MAP.put("toStartOfQuarter", "");//将Date或DateTime向前取整到本季度的第一天。
+ SQL_FUNCTION_MAP.put("toStartOfMonth", ""); //将Date或DateTime向前取整到本月的第一天。
+ SQL_FUNCTION_MAP.put("toMonday", ""); //将Date或DateTime向前取整到本周的星期
+ SQL_FUNCTION_MAP.put("toStartOfWeek", ""); //按mode将Date或DateTime向前取整到最近的星期日或星期一。
+ SQL_FUNCTION_MAP.put("toStartOfDay", ""); //将DateTime向前取整到今天的开始。
+ SQL_FUNCTION_MAP.put("toStartOfHour", ""); //将DateTime向前取整到当前小时的开始。
+ SQL_FUNCTION_MAP.put("toStartOfMinute", ""); //将DateTime向前取整到当前分钟的开始。
+ SQL_FUNCTION_MAP.put("toStartOfSecond", ""); //将DateTime向前取整到当前秒数的开始。
+ SQL_FUNCTION_MAP.put("toStartOfFiveMinute", "");//将DateTime以五分钟为单位向前取整到最接近的时间点。
+ SQL_FUNCTION_MAP.put("toStartOfTenMinutes", ""); //将DateTime以十分钟为单位向前取整到最接近的时间点。
+ SQL_FUNCTION_MAP.put("toStartOfFifteenMinutes", ""); //将DateTime以十五分钟为单位向前取整到最接近的时间点。
+ SQL_FUNCTION_MAP.put("toStartOfInterval", ""); //
+ SQL_FUNCTION_MAP.put("toTime", ""); //将DateTime中的日期转换为一个固定的日期,同时保留时间部分。
+ SQL_FUNCTION_MAP.put("toISOYear", ""); //将Date或DateTime转换为包含ISO年份的UInt16类型的编号。
+ SQL_FUNCTION_MAP.put("toISOWeek", ""); //
+ SQL_FUNCTION_MAP.put("toWeek", "");// 返回Date或DateTime的周数。
+ SQL_FUNCTION_MAP.put("toYearWeek", ""); //返回年和周的日期
+ SQL_FUNCTION_MAP.put("date_trunc", ""); //截断日期和时间数据到日期的指定部分
+ SQL_FUNCTION_MAP.put("date_diff", ""); //回两个日期或带有时间值的日期之间的差值。
+
+ SQL_FUNCTION_MAP.put("yesterday", ""); //不接受任何参数并在请求执行时的某一刻返回昨天的日期(Date)。
+ SQL_FUNCTION_MAP.put("today", ""); //不接受任何参数并在请求执行时的某一刻返回当前日期(Date)。
+ SQL_FUNCTION_MAP.put("timeSlot", ""); //将时间向前取整半小时。
+ SQL_FUNCTION_MAP.put("toYYYYMM", ""); //
+ SQL_FUNCTION_MAP.put("toYYYYMMDD", "");//
+ SQL_FUNCTION_MAP.put("toYYYYMMDDhhmmss", ""); //
+ SQL_FUNCTION_MAP.put("addYears", ""); // Function adds a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime
+ SQL_FUNCTION_MAP.put("addMonths", ""); //同上
+ SQL_FUNCTION_MAP.put("addWeeks", ""); //同上
+ SQL_FUNCTION_MAP.put("addDays", ""); //同上
+ SQL_FUNCTION_MAP.put("addHours", ""); //同上
+ SQL_FUNCTION_MAP.put("addMinutes", "");//同上
+ SQL_FUNCTION_MAP.put("addSeconds", ""); //同上
+ SQL_FUNCTION_MAP.put("addQuarters", ""); //同上
+ SQL_FUNCTION_MAP.put("subtractYears", ""); //Function subtract a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime
+ SQL_FUNCTION_MAP.put("subtractMonths", ""); //同上
+ SQL_FUNCTION_MAP.put("subtractWeeks", ""); //同上
+ SQL_FUNCTION_MAP.put("subtractDays", ""); //同上
+ SQL_FUNCTION_MAP.put("subtractours", "");//同上
+ SQL_FUNCTION_MAP.put("subtractMinutes", ""); //同上
+ SQL_FUNCTION_MAP.put("subtractSeconds", ""); //同上
+ SQL_FUNCTION_MAP.put("subtractQuarters", ""); //同上
+ SQL_FUNCTION_MAP.put("formatDateTime", ""); //函数根据给定的格式字符串来格式化时间
+ SQL_FUNCTION_MAP.put("timestamp_add", ""); //使用提供的日期或日期时间值添加指定的时间值。
+ SQL_FUNCTION_MAP.put("timestamp_sub", ""); //从提供的日期或带时间的日期中减去时间间隔。
+
+ //clickhouse json函数
+ SQL_FUNCTION_MAP.put("visitParamHas", ""); //visitParamHas(params, name)检查是否存在«name»名称的字段
+ SQL_FUNCTION_MAP.put("visitParamExtractUInt", ""); //visitParamExtractUInt(params, name)将名为«name»的字段的值解析成UInt64。
+ SQL_FUNCTION_MAP.put("visitParamExtractInt", ""); //与visitParamExtractUInt相同,但返回Int64。
+ SQL_FUNCTION_MAP.put("visitParamExtractFloat", ""); //与visitParamExtractUInt相同,但返回Float64。
+ SQL_FUNCTION_MAP.put("visitParamExtractBool", "");//解析true/false值。其结果是UInt8类型的。
+ SQL_FUNCTION_MAP.put("visitParamExtractRaw", ""); //返回字段的值,包含空格符。
+ SQL_FUNCTION_MAP.put("visitParamExtractString", ""); //使用双引号解析字符串。这个值没有进行转义。如果转义失败,它将返回一个空白字符串。
+ SQL_FUNCTION_MAP.put("JSONHas", ""); //如果JSON中存在该值,则返回1。
+ SQL_FUNCTION_MAP.put("JSONLength", ""); //返回JSON数组或JSON对象的长度。
+ SQL_FUNCTION_MAP.put("JSONType", ""); //返回JSON值的类型。
+ SQL_FUNCTION_MAP.put("JSONExtractUInt", ""); //解析JSON并提取值。这些函数类似于visitParam*函数。
+ SQL_FUNCTION_MAP.put("JSONExtractInt", ""); //
+ SQL_FUNCTION_MAP.put("JSONExtractFloat", ""); //
+ SQL_FUNCTION_MAP.put("JSONExtractBool", ""); //
+ SQL_FUNCTION_MAP.put("JSONExtractString", ""); //解析JSON并提取字符串。此函数类似于visitParamExtractString函数。
+ SQL_FUNCTION_MAP.put("JSONExtract", "");//解析JSON并提取给定ClickHouse数据类型的值。
+ SQL_FUNCTION_MAP.put("JSONExtractKeysAndValues", ""); //从JSON中解析键值对,其中值是给定的ClickHouse数据类型
+ SQL_FUNCTION_MAP.put("JSONExtractRaw", ""); //返回JSON的部分。
+ SQL_FUNCTION_MAP.put("toJSONString", ""); //
+
+ //clickhouse 类型转换函数
+ SQL_FUNCTION_MAP.put("toInt8", ""); //toInt8(expr) 转换一个输入值为Int类型
+ SQL_FUNCTION_MAP.put("toInt16", "");
+ SQL_FUNCTION_MAP.put("toInt32", "");
+ SQL_FUNCTION_MAP.put("toInt64", "");
+ SQL_FUNCTION_MAP.put("toInt8OrZero", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。
+ SQL_FUNCTION_MAP.put("toInt16OrZero", "");
+ SQL_FUNCTION_MAP.put("toInt32OrZero", "");
+ SQL_FUNCTION_MAP.put("toInt64OrZero", "");
+ SQL_FUNCTION_MAP.put("toInt8OrNull", "");//toInt(8|16|32|64)OrNull 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回NULL
+ SQL_FUNCTION_MAP.put("toInt16OrNull", "");
+ SQL_FUNCTION_MAP.put("toInt32OrNull", "");
+ SQL_FUNCTION_MAP.put("toInt64OrNull", "");
+ SQL_FUNCTION_MAP.put("toUInt8", ""); //toInt8(expr) 转换一个输入值为Int类型
+ SQL_FUNCTION_MAP.put("toUInt16", "");
+ SQL_FUNCTION_MAP.put("toUInt32", "");
+ SQL_FUNCTION_MAP.put("toUInt64", "");
+ SQL_FUNCTION_MAP.put("toUInt8OrZero", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。
+ SQL_FUNCTION_MAP.put("toUInt16OrZero", "");
+ SQL_FUNCTION_MAP.put("toUInt32OrZero", "");
+ SQL_FUNCTION_MAP.put("toUInt64OrZero", "");
+ SQL_FUNCTION_MAP.put("toUInt8OrNull", "");//toInt(8|16|32|64)OrNull 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回NULL
+ SQL_FUNCTION_MAP.put("toUInt16OrNull", "");
+ SQL_FUNCTION_MAP.put("toUInt32OrNull", "");
+ SQL_FUNCTION_MAP.put("toUInt64OrNull", "");
+
+ SQL_FUNCTION_MAP.put("toFloat32", "");
+ SQL_FUNCTION_MAP.put("toFloat64", "");
+ SQL_FUNCTION_MAP.put("toFloat32OrZero", "");
+ SQL_FUNCTION_MAP.put("toFloat64OrZero", "");
+ SQL_FUNCTION_MAP.put("toFloat32OrNull", "");
+ SQL_FUNCTION_MAP.put("toFloat64OrNull", "");
+
+ SQL_FUNCTION_MAP.put("toDate", ""); //
+ SQL_FUNCTION_MAP.put("toDateOrZero", ""); //toInt16(expr)
+ SQL_FUNCTION_MAP.put("toDateOrNull", ""); //toInt32(expr)
+ SQL_FUNCTION_MAP.put("toDateTimeOrZero", ""); //toInt64(expr)
+ SQL_FUNCTION_MAP.put("toDateTimeOrNull", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。
+
+ SQL_FUNCTION_MAP.put("toDecimal32", "");
+ SQL_FUNCTION_MAP.put("toFixedString", ""); // 将String类型的参数转换为FixedString(N)类型的值
+ SQL_FUNCTION_MAP.put("toStringCutToZero", ""); // 接受String或FixedString参数,返回String,其内容在找到的第一个零字节处被截断。
+ SQL_FUNCTION_MAP.put("toDecimal256", "");
+ SQL_FUNCTION_MAP.put("toDecimal32OrNull", "");
+ SQL_FUNCTION_MAP.put("toDecimal64OrNull", "");
+ SQL_FUNCTION_MAP.put("toDecimal128OrNull", "");
+ SQL_FUNCTION_MAP.put("toDecimal256OrNull", "");
+ SQL_FUNCTION_MAP.put("toDecimal32OrZero", "");
+ SQL_FUNCTION_MAP.put("toDecimal64OrZero", "");
+ SQL_FUNCTION_MAP.put("toDecimal128OrZero", "");
+ SQL_FUNCTION_MAP.put("toDecimal256OrZero", "");
+
+
+ SQL_FUNCTION_MAP.put("toIntervalSecond", ""); //把一个数值类型的值转换为Interval类型的数据。
+ SQL_FUNCTION_MAP.put("toIntervalMinute", "");
+ SQL_FUNCTION_MAP.put("toIntervalHour", "");
+ SQL_FUNCTION_MAP.put("toIntervalDay", "");
+ SQL_FUNCTION_MAP.put("toIntervalWeek", "");
+ SQL_FUNCTION_MAP.put("toIntervalMonth", "");
+ SQL_FUNCTION_MAP.put("toIntervalQuarter", "");
+ SQL_FUNCTION_MAP.put("toIntervalYear", "");
+ SQL_FUNCTION_MAP.put("parseDateTimeBestEffort", ""); //把String类型的时间日期转换为DateTime数据类型。
+ SQL_FUNCTION_MAP.put("parseDateTimeBestEffortOrNull", "");
+ SQL_FUNCTION_MAP.put("parseDateTimeBestEffortOrZero", "");
+ SQL_FUNCTION_MAP.put("toLowCardinality", "");
+
+
+
+ ////clickhouse hash函数
+ SQL_FUNCTION_MAP.put("halfMD5", ""); //计算字符串的MD5。然后获取结果的前8个字节并将它们作为UInt64(大端)返回
+ SQL_FUNCTION_MAP.put("MD5", ""); //计算字符串的MD5并将结果放入FixedString(16)中返回
+
+ //clickhouse ip地址函数
+ SQL_FUNCTION_MAP.put("IPv4NumToString", ""); //接受一个UInt32(大端)表示的IPv4的地址,返回相应IPv4的字符串表现形式,格式为A.B.C.D(以点分割的十进制数字)。
+ SQL_FUNCTION_MAP.put("IPv4StringToNum", ""); //与IPv4NumToString函数相反。如果IPv4地址格式无效,则返回0。
+ SQL_FUNCTION_MAP.put("IPv6NumToString", ""); //接受FixedString(16)类型的二进制格式的IPv6地址。以文本格式返回此地址的字符串。
+ SQL_FUNCTION_MAP.put("IPv6StringToNum", ""); //与IPv6NumToString的相反。如果IPv6地址格式无效,则返回空字节字符串。
+ SQL_FUNCTION_MAP.put("IPv4ToIPv6", ""); // 接受一个UInt32类型的IPv4地址,返回FixedString(16)类型的IPv6地址
+ SQL_FUNCTION_MAP.put("cutIPv6", ""); //接受一个FixedString(16)类型的IPv6地址,返回一个String,这个String中包含了删除指定位之后的地址的文本格
+ SQL_FUNCTION_MAP.put("toIPv4", ""); //IPv4StringToNum()的别名,
+ SQL_FUNCTION_MAP.put("toIPv6", ""); //IPv6StringToNum()的别名
+ SQL_FUNCTION_MAP.put("isIPAddressInRange", ""); //确定一个IP地址是否包含在以CIDR符号表示的网络中
+
+ //clickhouse Nullable处理函数
+ SQL_FUNCTION_MAP.put("isNull", ""); //检查参数是否为NULL。
+ SQL_FUNCTION_MAP.put("isNotNull", ""); //检查参数是否不为 NULL.
+ SQL_FUNCTION_MAP.put("ifNull", ""); //如果第一个参数为«NULL»,则返回第二个参数的值。
+ SQL_FUNCTION_MAP.put("assumeNotNull", ""); //将可为空类型的值转换为非Nullable类型的值。
+ SQL_FUNCTION_MAP.put("toNullable", ""); //将参数的类型转换为Nullable。
+
+ //clickhouse UUID函数
+ SQL_FUNCTION_MAP.put("generateUUIDv4", ""); // 生成一个UUID
+ SQL_FUNCTION_MAP.put("toUUID", ""); //toUUID(x) 将String类型的值转换为UUID类型的值。
+
+ //clickhouse 系统函数
+ SQL_FUNCTION_MAP.put("hostName", ""); //hostName()回一个字符串,其中包含执行此函数的主机的名称。
+ SQL_FUNCTION_MAP.put("getMacro", ""); //从服务器配置的宏部分获取指定值。
+ SQL_FUNCTION_MAP.put("FQDN", "");//返回完全限定的域名。
+ SQL_FUNCTION_MAP.put("basename", ""); //提取字符串最后一个斜杠或反斜杠之后的尾随部分
+ SQL_FUNCTION_MAP.put("currentUser", ""); //返回当前用户的登录。在分布式查询的情况下,将返回用户的登录,即发起的查询
+ SQL_FUNCTION_MAP.put("version", ""); //以字符串形式返回服务器版本。
+ SQL_FUNCTION_MAP.put("uptime", "");//以秒为单位返回服务器的正常运行时间。
+
+ //clickhouse 数学函数
+ SQL_FUNCTION_MAP.put("least", ""); //返回a和b中最小的值。
+ SQL_FUNCTION_MAP.put("greatest", ""); //返回a和b的最大值。
+ SQL_FUNCTION_MAP.put("plus", ""); //plus(a, b), a + b operator¶计算数值的总和。
+ SQL_FUNCTION_MAP.put("minus", ""); //minus(a, b), a - b operator 计算数值之间的差,结果总是有符号的。
+ SQL_FUNCTION_MAP.put("multiply", "");//multiply(a, b), a * b operator 计算数值的乘积
+ SQL_FUNCTION_MAP.put("divide", ""); //divide(a, b), a / b operator 计算数值的商。结果类型始终是浮点类型
+ SQL_FUNCTION_MAP.put("intDiv", ""); //intDiv(a,b)计算数值的商,向下舍入取整(按绝对值)。
+ SQL_FUNCTION_MAP.put("intDivOrZero", ""); // intDivOrZero(a,b)与’intDiv’的不同之处在于它在除以零或将最小负数除以-1时返回零。
+ SQL_FUNCTION_MAP.put("modulo", ""); //modulo(a, b), a % b operator 计算除法后的余数。
+ SQL_FUNCTION_MAP.put("moduloOrZero", ""); //和modulo不同之处在于,除以0时结果返回0
+ SQL_FUNCTION_MAP.put("negate", ""); //通过改变数值的符号位对数值取反,结果总是有符号
+ SQL_FUNCTION_MAP.put("gcd", ""); //gcd(a,b) 返回数值的最大公约数。
+ SQL_FUNCTION_MAP.put("lcm", ""); //lcm(a,b) 返回数值的最小公倍数
+ SQL_FUNCTION_MAP.put("e", ""); //e() 返回一个接近数学常量e的Float64数字。
+ SQL_FUNCTION_MAP.put("pi", ""); //pi() 返回一个接近数学常量π的Float64数字。
+ SQL_FUNCTION_MAP.put("exp2", ""); //exp2(x)¶接受一个数值类型的参数并返回它的2的x次幂。
+ SQL_FUNCTION_MAP.put("exp10", ""); //exp10(x)¶接受一个数值类型的参数并返回它的10的x次幂。
+ SQL_FUNCTION_MAP.put("cbrt", ""); //cbrt(x) 接受一个数值类型的参数并返回它的立方根。
+ SQL_FUNCTION_MAP.put("lgamma", ""); //lgamma(x) 返回x的绝对值的自然对数的伽玛函数。
+ SQL_FUNCTION_MAP.put("tgamma", ""); //tgamma(x)¶返回x的伽玛函数。
+ SQL_FUNCTION_MAP.put("intExp2", ""); //intExp2 接受一个数值类型的参数并返回它的2的x次幂(UInt64)
+ SQL_FUNCTION_MAP.put("intExp10", ""); //intExp10 接受一个数值类型的参数并返回它的10的x次幂(UInt64)。
+ SQL_FUNCTION_MAP.put("cosh", ""); // cosh(x)
+ SQL_FUNCTION_MAP.put("cosh", ""); //cosh(x)
+ SQL_FUNCTION_MAP.put("sinh", ""); //sinh(x)
+ SQL_FUNCTION_MAP.put("asinh", ""); //asinh(x)
+ SQL_FUNCTION_MAP.put("atanh", ""); //atanh(x)
+ SQL_FUNCTION_MAP.put("atan2", ""); //atan2(y, x)
+ SQL_FUNCTION_MAP.put("hypot", ""); //hypot(x, y)
+ SQL_FUNCTION_MAP.put("log1p", ""); //log1p(x)
+ SQL_FUNCTION_MAP.put("trunc", ""); //和truncate一样
+ SQL_FUNCTION_MAP.put("roundToExp2", ""); //接受一个数字。如果数字小于1,它返回0。
+ SQL_FUNCTION_MAP.put("roundDuration", ""); //接受一个数字。如果数字小于1,它返回0。
+ SQL_FUNCTION_MAP.put("roundAge", ""); // 接受一个数字。如果数字小于18,它返回0。
+ SQL_FUNCTION_MAP.put("roundDown", ""); //接受一个数字并将其舍入到指定数组中的一个元素
+ SQL_FUNCTION_MAP.put("bitAnd", ""); //bitAnd(a,b)
+ SQL_FUNCTION_MAP.put("bitOr", ""); //bitOr(a,b)
}
@@ -908,7 +908,7 @@ public AbstractSQLConfig setSchema(String schema) {
this.schema = schema;
return this;
}
-
+
@Override
public String getDatasource() {
return datasource;
@@ -918,7 +918,7 @@ public SQLConfig setDatasource(String datasource) {
this.datasource = datasource;
return this;
}
-
+
/**请求传进来的Table名
* @return
* @see {@link #getSQLTable()}
@@ -1385,15 +1385,15 @@ public String getColumnString() throws Exception {
@JSONField(serialize = false)
public String getColumnString(boolean inSQLJoin) throws Exception {
List column = getColumn();
-
+
switch (getMethod()) {
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;
@@ -1407,7 +1407,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception {
continue;
}
}
-
+
index = c.lastIndexOf(":"); //StringUtil.split返回数组中,子项不会有null
origin = index < 0 ? c : c.substring(0, index);
alias = index < 0 ? null : c.substring(index + 1);
@@ -1423,7 +1423,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception {
throw new IllegalArgumentException("HEAD请求: 字符" + origin + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项"
+ " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!");
}
-
+
if (start > 0 && StringUtil.isName(origin.substring(0, start)) == false) {
throw new IllegalArgumentException("HEAD请求: 字符 " + origin.substring(0, start) + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项"
+ " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!");
@@ -1557,19 +1557,19 @@ public String getColumnPrase(String expression, boolean containRaw) {
} 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
-
+
//有函数,但不是窗口函数
int overIndex = expression.indexOf(") OVER (");
int againstIndex = expression.indexOf(") AGAINST (");
boolean containOver = overIndex > 0 && overIndex < expression.length() - ") OVER (".length();
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...\""
+ " 中 function 必须符合小写英文单词的 SQL 函数名格式!不能同时存在窗口函数关键词 OVER 和全文索引关键词 AGAINST!");
}
-
+
if (containOver == false && containAgainst == false) {
int end = expression.lastIndexOf(")");
if (start >= end) {
@@ -1596,7 +1596,7 @@ public String getColumnPrase(String expression, boolean containRaw) {
if (distinct) {
s = s.substring(PREFFIX_DISTINCT.length());
}
-
+
// 解析函数内的参数
String ckeys[] = parseArgsSplitWithComma(s, false, containRaw);
@@ -1658,7 +1658,7 @@ public String getColumnPrase(String expression, boolean containRaw) {
String argsString2[] = parseArgsSplitWithComma(argString2, false, containRaw);
expression = fun + "(" + StringUtil.getString(agrsString1) + (containOver ? ") OVER (" : ") AGAINST (") + StringUtil.getString(argsString2) + ")" + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); }
}
-
+
return expression;
}
@@ -1680,7 +1680,7 @@ private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean
int index;
for (int i = 0; i < ckeys.length; i++) {
String ck = ckeys[i];
-
+
// 如果参数包含 "'" ,解析字符串
if (ck.contains("'")) {
int count = 0;
@@ -1749,15 +1749,15 @@ private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean
} else {
origin = getValue(origin).toString();
}
-
+
if (isName && isKeyPrefix()) {
origin = tableAlias + "." + origin;
}
-
+
if (isColumn && StringUtil.isEmpty(alias, true) == false) {
origin += " AS " + quote + alias + quote;
}
-
+
ckeys[i] = origin;
}
@@ -1777,13 +1777,13 @@ private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean
private String praseArgsSplitWithSpace(String mkes[]) {
String quote = getQuote();
String tableAlias = getAliasWithQuote();
-
+
// 包含空格的参数 肯定不包含别名 不用处理别名
if (mkes != null && mkes.length > 0) {
for (int j = 0; j < mkes.length; j++) {
// now()/AS/ DISTINCT/VALUE 等等放在RAW_MAP中
String origin = mkes[j];
-
+
String mk = RAW_MAP.get(origin);
if (mk != null) { // newSQLConfig 提前处理好的
if (mk.length() > 0) {
@@ -1800,7 +1800,7 @@ private String praseArgsSplitWithSpace(String mkes[]) {
+ " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!");
}
}
-
+
boolean isName = false;
if (StringUtil.isNumer(origin)) {
//do nothing
@@ -1810,11 +1810,11 @@ private String praseArgsSplitWithSpace(String mkes[]) {
} else {
origin = getValue(origin).toString();
}
-
+
if (isName && isKeyPrefix()) {
origin = tableAlias + "." + origin;
}
-
+
mkes[j] = origin;
}
}
@@ -2569,7 +2569,7 @@ public String getSearchString(String key, Object[] values, int type) throws Ille
// if (((String) v).contains("%%")) { // 需要通过 %\%% 来模糊搜索 %
// throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + " 中包含 %% !不允许有连续的 % !");
// }
-
+
condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getLikeString(key, v);
}
@@ -3182,7 +3182,7 @@ public static String getSQL(AbstractSQLConfig config) throws Exception {
}
return explain + "SELECT * FROM (SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString();
}
-
+
return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString();
}
}
@@ -3797,7 +3797,7 @@ else if (joinConfig.getDatabase().equals(config.getDatabase()) == false) {
if (joinConfig.getSchema() == null) {
joinConfig.setSchema(config.getSchema()); //主表 JOIN 副表,默认 schema 一致
}
-
+
if (cacheConfig != null) {
cacheConfig.setDatabase(joinConfig.getDatabase()).setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致
}
@@ -3967,7 +3967,7 @@ public static interface IdCallback {
*/
@Deprecated
String getIdKey(String database, String schema, String table);
-
+
/**获取主键名
* @param database
* @param schema
@@ -3984,7 +3984,7 @@ public static interface IdCallback {
*/
@Deprecated
String getUserIdKey(String database, String schema, String table);
-
+
/**获取 User 的主键名
* @param database
* @param schema
@@ -4024,7 +4024,7 @@ public Object newId(RequestMethod method, String database, String schema, String
public String getIdKey(String database, String schema, String table) {
return KEY_ID;
}
-
+
@Override
public String getIdKey(String database, String schema, String datasource, String table) {
return getIdKey(database, schema, table);
@@ -4034,7 +4034,7 @@ public String getIdKey(String database, String schema, String datasource, String
public String getUserIdKey(String database, String schema, String table) {
return KEY_USER_ID;
}
-
+
@Override
public String getUserIdKey(String database, String schema, String datasource, String table) {
return getUserIdKey(database, schema, table);
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
index 1a7d0f3f6..760781c3a 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
@@ -158,7 +158,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws
Log.e(TAG, "execute StringUtil.isEmpty(sql, true) >> return null;");
return null;
}
-
+
boolean isExplain = config.isExplain();
boolean isHead = RequestMethod.isHeadMethod(config.getMethod(), true);
@@ -216,7 +216,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws
result.put(config.getIdKey() + "[]", config.getWhere(config.getIdKey() + "{}", true));
}
return result;
-
+
case GET:
case GETS:
case HEAD:
@@ -243,7 +243,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws
}
}
-
+
if (isExplain == false && isHead) {
if (rs.next() == false) {
return AbstractParser.newErrorResult(new SQLException("数据库错误, rs.next() 失败!"));
@@ -337,7 +337,7 @@ else if (config.getSQLTable().equalsIgnoreCase(sqlTable) == false) {
result.put("list", resultList);
return result;
}
-
+
if (isHead == false) {
// @ APP JOIN 查询副表并缓存到 childMap <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
@@ -370,7 +370,7 @@ else if (config.getSQLTable().equalsIgnoreCase(sqlTable) == false) {
result.put(KEY_RAW_LIST, resultList);
}
}
-
+
long endTime = System.currentTimeMillis();
Log.d(TAG, "\n\n execute endTime = " + endTime + "; duration = " + (endTime - startTime)
+ "\n return resultList.get(" + position + ");" + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n");
From 2107040c96223a7c8ac0e35d895cfcea7537b3ad Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 24 Sep 2021 03:35:45 +0800
Subject: [PATCH 080/754] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=90=8D=E8=BD=A6?=
=?UTF-8?q?=E5=92=8C=E7=A9=BA=E6=A0=BC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../java/apijson/orm/AbstractSQLConfig.java | 68 +++++++++----------
1 file changed, 34 insertions(+), 34 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
index a9f99b7df..b5a830223 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
@@ -133,35 +133,35 @@ public abstract class AbstractSQLConfig implements SQLConfig {
RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况
- // mysql关键字
- RAW_MAP.put("AS","");
- RAW_MAP.put("VALUE","");
- RAW_MAP.put("DISTINCT","");
+ // MySQL 关键字
+ RAW_MAP.put("AS", "");
+ RAW_MAP.put("VALUE", "");
+ RAW_MAP.put("DISTINCT", "");
//时间
- RAW_MAP.put("DATE","");
- RAW_MAP.put("now()","");
- RAW_MAP.put("DATETIME","");
- RAW_MAP.put("DateTime","");
- RAW_MAP.put("SECOND","");
- RAW_MAP.put("MINUTE","");
- RAW_MAP.put("HOUR","");
- RAW_MAP.put("DAY","");
- RAW_MAP.put("WEEK","");
- RAW_MAP.put("MONTH","");
- RAW_MAP.put("QUARTER","");
- RAW_MAP.put("YEAR","");
- RAW_MAP.put("json","");
- RAW_MAP.put("unit","");
+ RAW_MAP.put("DATE", "");
+ RAW_MAP.put("now()", "");
+ RAW_MAP.put("DATETIME", "");
+ RAW_MAP.put("DateTime", "");
+ RAW_MAP.put("SECOND", "");
+ RAW_MAP.put("MINUTE", "");
+ RAW_MAP.put("HOUR", "");
+ RAW_MAP.put("DAY", "");
+ RAW_MAP.put("WEEK", "");
+ RAW_MAP.put("MONTH", "");
+ RAW_MAP.put("QUARTER", "");
+ RAW_MAP.put("YEAR", "");
+ RAW_MAP.put("json", "");
+ RAW_MAP.put("unit", "");
//MYSQL 数据类型 BINARY,CHAR,DATETIME,TIME,DECIMAL,SIGNED,UNSIGNED
- RAW_MAP.put("BINARY","");
- RAW_MAP.put("SIGNED","");
- RAW_MAP.put("DECIMAL","");
- RAW_MAP.put("BINARY","");
- RAW_MAP.put("UNSIGNED","");
- RAW_MAP.put("CHAR","");
- RAW_MAP.put("TIME","");
+ RAW_MAP.put("BINARY", "");
+ RAW_MAP.put("SIGNED", "");
+ RAW_MAP.put("DECIMAL", "");
+ RAW_MAP.put("BINARY", "");
+ RAW_MAP.put("UNSIGNED", "");
+ RAW_MAP.put("CHAR", "");
+ RAW_MAP.put("TIME", "");
//窗口函数关键字
RAW_MAP.put("OVER", "");
@@ -384,7 +384,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
- //clickhouse 字符串函数 注释的函数表示返回的格式暂时不支持,如:返回数组 ,同时包含因版本不同 clickhosue不支持的函数,版本
+ //ClickHouse 字符串函数 注释的函数表示返回的格式暂时不支持,如:返回数组 ,同时包含因版本不同 clickhosue不支持的函数,版本
SQL_FUNCTION_MAP.put("empty", ""); // empty(s) 对于空字符串s返回1,对于非空字符串返回0
SQL_FUNCTION_MAP.put("notEmpty", ""); //notEmpty(s) 对于空字符串返回0,对于非空字符串返回1。
SQL_FUNCTION_MAP.put("lengthUTF8", ""); //假定字符串以UTF-8编码组成的文本,返回此字符串的Unicode字符长度。如果传入的字符串不是UTF-8编码,则函数可能返回一个预期外的值
@@ -492,7 +492,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
SQL_FUNCTION_MAP.put("timestamp_add", ""); //使用提供的日期或日期时间值添加指定的时间值。
SQL_FUNCTION_MAP.put("timestamp_sub", ""); //从提供的日期或带时间的日期中减去时间间隔。
- //clickhouse json函数
+ //ClickHouse json函数
SQL_FUNCTION_MAP.put("visitParamHas", ""); //visitParamHas(params, name)检查是否存在«name»名称的字段
SQL_FUNCTION_MAP.put("visitParamExtractUInt", ""); //visitParamExtractUInt(params, name)将名为«name»的字段的值解析成UInt64。
SQL_FUNCTION_MAP.put("visitParamExtractInt", ""); //与visitParamExtractUInt相同,但返回Int64。
@@ -513,7 +513,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
SQL_FUNCTION_MAP.put("JSONExtractRaw", ""); //返回JSON的部分。
SQL_FUNCTION_MAP.put("toJSONString", ""); //
- //clickhouse 类型转换函数
+ //ClickHouse 类型转换函数
SQL_FUNCTION_MAP.put("toInt8", ""); //toInt8(expr) 转换一个输入值为Int类型
SQL_FUNCTION_MAP.put("toInt16", "");
SQL_FUNCTION_MAP.put("toInt32", "");
@@ -581,11 +581,11 @@ public abstract class AbstractSQLConfig implements SQLConfig {
- ////clickhouse hash函数
+ ////ClickHouse hash函数
SQL_FUNCTION_MAP.put("halfMD5", ""); //计算字符串的MD5。然后获取结果的前8个字节并将它们作为UInt64(大端)返回
SQL_FUNCTION_MAP.put("MD5", ""); //计算字符串的MD5并将结果放入FixedString(16)中返回
- //clickhouse ip地址函数
+ //ClickHouse ip地址函数
SQL_FUNCTION_MAP.put("IPv4NumToString", ""); //接受一个UInt32(大端)表示的IPv4的地址,返回相应IPv4的字符串表现形式,格式为A.B.C.D(以点分割的十进制数字)。
SQL_FUNCTION_MAP.put("IPv4StringToNum", ""); //与IPv4NumToString函数相反。如果IPv4地址格式无效,则返回0。
SQL_FUNCTION_MAP.put("IPv6NumToString", ""); //接受FixedString(16)类型的二进制格式的IPv6地址。以文本格式返回此地址的字符串。
@@ -596,18 +596,18 @@ public abstract class AbstractSQLConfig implements SQLConfig {
SQL_FUNCTION_MAP.put("toIPv6", ""); //IPv6StringToNum()的别名
SQL_FUNCTION_MAP.put("isIPAddressInRange", ""); //确定一个IP地址是否包含在以CIDR符号表示的网络中
- //clickhouse Nullable处理函数
+ //ClickHouse Nullable处理函数
SQL_FUNCTION_MAP.put("isNull", ""); //检查参数是否为NULL。
SQL_FUNCTION_MAP.put("isNotNull", ""); //检查参数是否不为 NULL.
SQL_FUNCTION_MAP.put("ifNull", ""); //如果第一个参数为«NULL»,则返回第二个参数的值。
SQL_FUNCTION_MAP.put("assumeNotNull", ""); //将可为空类型的值转换为非Nullable类型的值。
SQL_FUNCTION_MAP.put("toNullable", ""); //将参数的类型转换为Nullable。
- //clickhouse UUID函数
+ //ClickHouse UUID函数
SQL_FUNCTION_MAP.put("generateUUIDv4", ""); // 生成一个UUID
SQL_FUNCTION_MAP.put("toUUID", ""); //toUUID(x) 将String类型的值转换为UUID类型的值。
- //clickhouse 系统函数
+ //ClickHouse 系统函数
SQL_FUNCTION_MAP.put("hostName", ""); //hostName()回一个字符串,其中包含执行此函数的主机的名称。
SQL_FUNCTION_MAP.put("getMacro", ""); //从服务器配置的宏部分获取指定值。
SQL_FUNCTION_MAP.put("FQDN", "");//返回完全限定的域名。
@@ -616,7 +616,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
SQL_FUNCTION_MAP.put("version", ""); //以字符串形式返回服务器版本。
SQL_FUNCTION_MAP.put("uptime", "");//以秒为单位返回服务器的正常运行时间。
- //clickhouse 数学函数
+ //ClickHouse 数学函数
SQL_FUNCTION_MAP.put("least", ""); //返回a和b中最小的值。
SQL_FUNCTION_MAP.put("greatest", ""); //返回a和b的最大值。
SQL_FUNCTION_MAP.put("plus", ""); //plus(a, b), a + b operator¶计算数值的总和。
From d46d1f321613d8ee37cd38b84d922ef300328f95 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 24 Sep 2021 04:06:44 +0800
Subject: [PATCH 081/754] =?UTF-8?q?RAW=5FMAP=20=E9=BB=98=E8=AE=A4=E6=94=AF?=
=?UTF-8?q?=E6=8C=81=20=E4=B8=8E=E6=88=96=E9=9D=9E=20=E5=92=8C=20IS=20NULL?=
=?UTF-8?q?=20=E7=AD=89=E5=85=B3=E9=94=AE=E8=AF=8D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/main/java/apijson/orm/AbstractSQLConfig.java | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
index b5a830223..396ed23a8 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
@@ -135,6 +135,13 @@ public abstract class AbstractSQLConfig implements SQLConfig {
// MySQL 关键字
RAW_MAP.put("AS", "");
+ RAW_MAP.put("IS NOT NULL", "");
+ RAW_MAP.put("IS NULL", "");
+ RAW_MAP.put("IS", "");
+ RAW_MAP.put("NULL", "");
+ RAW_MAP.put("AND", "");
+ RAW_MAP.put("OR", "");
+ RAW_MAP.put("NOT", "");
RAW_MAP.put("VALUE", "");
RAW_MAP.put("DISTINCT", "");
From 5c682cbf348d6f7660ece8da6c9556c9a4fed771 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Sat, 25 Sep 2021 00:58:30 +0800
Subject: [PATCH 082/754] =?UTF-8?q?=E9=87=8D=E6=9E=84=20enum=20RequestRole?=
=?UTF-8?q?=20=E4=B8=BA=20String=20=E6=96=B9=E4=BE=BF=E7=94=A8=E6=88=B7?=
=?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E6=89=A9=E5=B1=95=EF=BC=9B=E5=88=A0?=
=?UTF-8?q?=E9=99=A4=E9=83=A8=E5=88=86=E5=B7=B2=E5=BA=9F=E5=BC=83=E7=9A=84?=
=?UTF-8?q?=E6=96=B9=E6=B3=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/main/java/apijson/MethodAccess.java | 26 ++--
.../src/main/java/apijson/RequestRole.java | 60 ---------
.../main/java/apijson/orm/AbstractParser.java | 11 +-
.../java/apijson/orm/AbstractSQLConfig.java | 47 ++-----
.../java/apijson/orm/AbstractVerifier.java | 121 +++++++++++-------
.../src/main/java/apijson/orm/Parser.java | 3 +-
.../src/main/java/apijson/orm/SQLConfig.java | 5 +-
.../src/main/java/apijson/orm/Verifier.java | 12 +-
.../main/java/apijson/orm/model/Document.java | 4 +-
.../java/apijson/orm/model/TestRecord.java | 4 +-
10 files changed, 108 insertions(+), 185 deletions(-)
delete mode 100755 APIJSONORM/src/main/java/apijson/RequestRole.java
diff --git a/APIJSONORM/src/main/java/apijson/MethodAccess.java b/APIJSONORM/src/main/java/apijson/MethodAccess.java
index 3eff1ae3d..31d45843e 100755
--- a/APIJSONORM/src/main/java/apijson/MethodAccess.java
+++ b/APIJSONORM/src/main/java/apijson/MethodAccess.java
@@ -10,12 +10,12 @@
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
-import static apijson.RequestRole.ADMIN;
-import static apijson.RequestRole.CIRCLE;
-import static apijson.RequestRole.CONTACT;
-import static apijson.RequestRole.LOGIN;
-import static apijson.RequestRole.OWNER;
-import static apijson.RequestRole.UNKNOWN;
+import static apijson.orm.AbstractVerifier.ADMIN;
+import static apijson.orm.AbstractVerifier.CIRCLE;
+import static apijson.orm.AbstractVerifier.CONTACT;
+import static apijson.orm.AbstractVerifier.LOGIN;
+import static apijson.orm.AbstractVerifier.OWNER;
+import static apijson.orm.AbstractVerifier.UNKNOWN;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@@ -31,36 +31,36 @@
/**@see {@link RequestMethod#GET}
* @return 该请求方法允许的角色 default {UNKNOWN, LOGIN, CONTACT, CIRCLE, OWNER, ADMIN};
*/
- RequestRole[] GET() default {UNKNOWN, LOGIN, CONTACT, CIRCLE, OWNER, ADMIN};
+ String[] GET() default {UNKNOWN, LOGIN, CONTACT, CIRCLE, OWNER, ADMIN};
/**@see {@link RequestMethod#HEAD}
* @return 该请求方法允许的角色 default {UNKNOWN, LOGIN, CONTACT, CIRCLE, OWNER, ADMIN};
*/
- RequestRole[] HEAD() default {UNKNOWN, LOGIN, CONTACT, CIRCLE, OWNER, ADMIN};
+ String[] HEAD() default {UNKNOWN, LOGIN, CONTACT, CIRCLE, OWNER, ADMIN};
/**@see {@link RequestMethod#GETS}
* @return 该请求方法允许的角色 default {LOGIN, CONTACT, CIRCLE, OWNER, ADMIN};
*/
- RequestRole[] GETS() default {LOGIN, CONTACT, CIRCLE, OWNER, ADMIN};
+ String[] GETS() default {LOGIN, CONTACT, CIRCLE, OWNER, ADMIN};
/**@see {@link RequestMethod#HEADS}
* @return 该请求方法允许的角色 default {LOGIN, CONTACT, CIRCLE, OWNER, ADMIN};
*/
- RequestRole[] HEADS() default {LOGIN, CONTACT, CIRCLE, OWNER, ADMIN};
+ String[] HEADS() default {LOGIN, CONTACT, CIRCLE, OWNER, ADMIN};
/**@see {@link RequestMethod#POST}
* @return 该请求方法允许的角色 default {LOGIN, ADMIN};
*/
- RequestRole[] POST() default {OWNER, ADMIN};
+ String[] POST() default {OWNER, ADMIN};
/**@see {@link RequestMethod#PUT}
* @return 该请求方法允许的角色 default {OWNER, ADMIN};
*/
- RequestRole[] PUT() default {OWNER, ADMIN};
+ String[] PUT() default {OWNER, ADMIN};
/**@see {@link RequestMethod#DELETE}
* @return 该请求方法允许的角色 default {OWNER, ADMIN};
*/
- RequestRole[] DELETE() default {OWNER, ADMIN};
+ String[] DELETE() default {OWNER, ADMIN};
}
diff --git a/APIJSONORM/src/main/java/apijson/RequestRole.java b/APIJSONORM/src/main/java/apijson/RequestRole.java
deleted file mode 100755
index d1d00b18b..000000000
--- a/APIJSONORM/src/main/java/apijson/RequestRole.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
-
-This source code is licensed under the Apache License Version 2.0.*/
-
-
-package apijson;
-
-/**来访的用户角色
- * @author Lemon
- */
-public enum RequestRole {
-
- /**未登录,不明身份的用户
- */
- UNKNOWN,
-
- /**已登录的用户
- */
- LOGIN,
-
- /**联系人,必须已登录
- */
- CONTACT,
-
- /**圈子成员(CONTACT + OWNER),必须已登录
- */
- CIRCLE,
-
- /**拥有者,必须已登录
- */
- OWNER,
-
- /**管理员,必须已登录
- */
- ADMIN;
-
- //似乎不管怎么做,外部引用后都是空值。并且如果在注解内的位置不是最前的,还会导致被注解的类在其它类中import报错。
- //虽然直接打印显示正常,但被@MethodAccess内RequestRole[] GET()等方法引用后获取的是空值
- // public static final RequestRole[] ALL = {RequestRole.UNKNOWN};//values();//所有
- // public static final RequestRole[] HIGHS;//高级
- // static {
- // HIGHS = new RequestRole[] {OWNER, ADMIN};
- // }
-
- public static final String[] NAMES = {
- UNKNOWN.name(), LOGIN.name(), CONTACT.name(), CIRCLE.name(), OWNER.name(), ADMIN.name()
- };
-
- public static RequestRole get(String name) throws Exception {
- if (name == null) {
- return null;
- }
- try { //Enum.valueOf只要找不到对应的值就会抛异常
- return RequestRole.valueOf(name);
- } catch (Exception e) {
- throw new IllegalArgumentException("角色 " + name + " 不存在!只能是[" + StringUtil.getString(NAMES) + "]中的一种!", e);
- }
- }
-
-}
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
index eb4556af2..cf154a93e 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
@@ -35,7 +35,6 @@
import apijson.Log;
import apijson.NotNull;
import apijson.RequestMethod;
-import apijson.RequestRole;
import apijson.StringUtil;
import apijson.orm.exception.ConditionErrorException;
import apijson.orm.exception.ConflictException;
@@ -173,13 +172,13 @@ public AbstractParser setGlobleFormat(Boolean globleFormat) {
public Boolean getGlobleFormat() {
return globleFormat;
}
- protected RequestRole globleRole;
- public AbstractParser setGlobleRole(RequestRole globleRole) {
+ protected String globleRole;
+ public AbstractParser setGlobleRole(String globleRole) {
this.globleRole = globleRole;
return this;
}
@Override
- public RequestRole getGlobleRole() {
+ public String getGlobleRole() {
return globleRole;
}
protected String globleDatabase;
@@ -361,7 +360,7 @@ public JSONObject parseResponse(JSONObject request) {
//必须在parseCorrectRequest后面,因为parseCorrectRequest可能会添加 @role
if (isNeedVerifyRole() && globleRole == null) {
try {
- setGlobleRole(RequestRole.get(requestObject.getString(JSONRequest.KEY_ROLE)));
+ setGlobleRole(requestObject.getString(JSONRequest.KEY_ROLE));
requestObject.remove(JSONRequest.KEY_ROLE);
} catch (Exception e) {
return extendErrorResult(requestObject, e);
@@ -466,7 +465,7 @@ public void onVerifyRole(@NotNull SQLConfig config) throws Exception {
if (globleRole != null) {
config.setRole(globleRole);
} else {
- config.setRole(getVisitor().getId() == null ? RequestRole.UNKNOWN : RequestRole.LOGIN);
+ config.setRole(getVisitor().getId() == null ? AbstractVerifier.UNKNOWN : AbstractVerifier.LOGIN);
}
}
getVerifier().verifyAccess(config);
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
index 396ed23a8..fc3bb3ba5 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
@@ -54,7 +54,6 @@
import apijson.Log;
import apijson.NotNull;
import apijson.RequestMethod;
-import apijson.RequestRole;
import apijson.SQL;
import apijson.StringUtil;
import apijson.orm.exception.NotExistException;
@@ -196,10 +195,10 @@ public abstract class AbstractSQLConfig implements SQLConfig {
RAW_MAP.put("LANGUAGE", "");
RAW_MAP.put("MODE", "");
+
SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况
-
//窗口函数
SQL_FUNCTION_MAP.put("rank", "");//得到数据项在分组中的排名,排名相等的时候会留下空位
SQL_FUNCTION_MAP.put("dense_rank", ""); //得到数据项在分组中的排名,排名相等的时候不会留下空位
@@ -689,7 +688,7 @@ public String getUserIdKey() {
/**
* TODO 被关联的表通过就忽略关联的表?(这个不行 User:{"sex@":"/Comment/toId"})
*/
- private RequestRole role; //发送请求的用户的角色
+ private String role; //发送请求的用户的角色
private boolean distinct = false;
private String database; //表所在的数据库类型
private String schema; //表所在的数据库名
@@ -789,15 +788,12 @@ public AbstractSQLConfig setId(Object id) {
}
@Override
- public RequestRole getRole() {
+ public String getRole() {
//不能 @NotNull , AbstractParser#getSQLObject 内当getRole() == null时填充默认值
return role;
}
- public AbstractSQLConfig setRole(String roleName) throws Exception {
- return setRole(RequestRole.get(roleName));
- }
@Override
- public AbstractSQLConfig setRole(RequestRole role) {
+ public AbstractSQLConfig setRole(String role) {
this.role = role;
return this;
}
@@ -3175,7 +3171,7 @@ public static String getSQL(AbstractSQLConfig config) throws Exception {
String explain = (config.isExplain() ? (config.isSQLServer() || config.isOracle() ? "SET STATISTICS PROFILE ON " : "EXPLAIN ") : "");
if (config.isTest() && RequestMethod.isGetMethod(config.getMethod(), true)) { // FIXME 为啥是 code 而不是 count ?
String q = config.getQuote(); // 生成 SELECT ( (24 >=0 AND 24 <3) ) AS `code` LIMIT 1 OFFSET 0
- return explain + "SELECT " + config.getWhereString(false) + " AS " + q + JSONResponse.KEY_CODE + q + config.getLimitString();
+ return explain + "SELECT " + config.getWhereString(false) + " AS " + q + JSONResponse.KEY_COUNT + q + config.getLimitString();
}
config.setPreparedValueList(new ArrayList());
@@ -3728,7 +3724,7 @@ else if (whereList != null && whereList.contains(key)) {
config.setId(id);
//在 tableWhere 第0个 config.setIdIn(idIn);
- config.setRole(RequestRole.get(role));
+ config.setRole(role);
config.setGroup(group);
config.setHaving(having);
config.setOrder(order);
@@ -3966,14 +3962,6 @@ public static interface IdCallback {
*/
Object newId(RequestMethod method, String database, String schema, String table);
- /**已废弃,最早 5.0.0 移除,改用 {@link #getIdKey(String, String, String, String)}
- * @param database
- * @param schema
- * @param table
- * @return
- */
- @Deprecated
- String getIdKey(String database, String schema, String table);
/**获取主键名
* @param database
@@ -3983,15 +3971,6 @@ public static interface IdCallback {
*/
String getIdKey(String database, String schema, String datasource, String table);
- /**已废弃,最早 5.0.0 移除,改用 {@link #getUserIdKey(String, String, String, String)}
- * @param database
- * @param schema
- * @param table
- * @return
- */
- @Deprecated
- String getUserIdKey(String database, String schema, String table);
-
/**获取 User 的主键名
* @param database
* @param schema
@@ -4027,24 +4006,14 @@ public Object newId(RequestMethod method, String database, String schema, String
return System.currentTimeMillis();
}
- @Override
- public String getIdKey(String database, String schema, String table) {
- return KEY_ID;
- }
-
@Override
public String getIdKey(String database, String schema, String datasource, String table) {
- return getIdKey(database, schema, table);
- }
-
- @Override
- public String getUserIdKey(String database, String schema, String table) {
- return KEY_USER_ID;
+ return KEY_ID;
}
@Override
public String getUserIdKey(String database, String schema, String datasource, String table) {
- return getUserIdKey(database, schema, table);
+ return KEY_USER_ID;
}
@Override
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java
index 97ff9cd38..5527f17c4 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java
@@ -32,10 +32,10 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.regex.Pattern;
@@ -51,7 +51,6 @@
import apijson.MethodAccess;
import apijson.NotNull;
import apijson.RequestMethod;
-import apijson.RequestRole;
import apijson.StringUtil;
import apijson.orm.AbstractSQLConfig.IdCallback;
import apijson.orm.exception.ConflictException;
@@ -78,16 +77,42 @@
public abstract class AbstractVerifier implements Verifier, IdCallback {
private static final String TAG = "AbstractVerifier";
+ /**未登录,不明身份的用户
+ */
+ public static final String UNKNOWN = "UNKNOWN";
+
+ /**已登录的用户
+ */
+ public static final String LOGIN = "LOGIN";
+
+ /**联系人,必须已登录
+ */
+ public static final String CONTACT = "CONTACT";
+
+ /**圈子成员(CONTACT + OWNER),必须已登录
+ */
+ public static final String CIRCLE = "CIRCLE";
+
+ /**拥有者,必须已登录
+ */
+ public static final String OWNER = "OWNER";
+
+ /**管理员,必须已登录
+ */
+ public static final String ADMIN = "ADMIN";
+
// 共享 STRUCTURE_MAP 则不能 remove 等做任何变更,否则在并发情况下可能会出错,加锁效率又低,所以这里改为忽略对应的 key
+ public static final Map> ROLE_MAP;
+
public static final List OPERATION_KEY_LIST;
// >
// >
@NotNull
- public static final Map> SYSTEM_ACCESS_MAP;
+ public static final Map> SYSTEM_ACCESS_MAP;
@NotNull
- public static final Map> ACCESS_MAP;
+ public static final Map> ACCESS_MAP;
// >
// >
@@ -98,6 +123,14 @@ public abstract class AbstractVerifier implements Verifier, IdCallback {
@NotNull
public static final Map COMPILE_MAP;
static {
+ ROLE_MAP = new LinkedHashMap<>();
+ ROLE_MAP.put(UNKNOWN, new Entry());
+ ROLE_MAP.put(LOGIN, new Entry("userId>", 0));
+ ROLE_MAP.put(CONTACT, new Entry("userId{}", "contactIdList"));
+ 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());
@@ -111,7 +144,7 @@ public abstract class AbstractVerifier implements Verifier, IdCallback {
OPERATION_KEY_LIST.add(REFUSE.name());
- SYSTEM_ACCESS_MAP = new HashMap>();
+ SYSTEM_ACCESS_MAP = new HashMap>();
SYSTEM_ACCESS_MAP.put(Access.class.getSimpleName(), getAccessMap(Access.class.getAnnotation(MethodAccess.class)));
SYSTEM_ACCESS_MAP.put(Function.class.getSimpleName(), getAccessMap(Function.class.getAnnotation(MethodAccess.class)));
@@ -142,12 +175,12 @@ public abstract class AbstractVerifier implements Verifier, IdCallback {
* @param access
* @return
*/
- public static HashMap getAccessMap(MethodAccess access) {
+ public static HashMap getAccessMap(MethodAccess access) {
if (access == null) {
return null;
}
- HashMap map = new HashMap<>();
+ HashMap map = new HashMap<>();
map.put(GET, access.GET());
map.put(HEAD, access.HEAD());
map.put(GETS, access.GETS());
@@ -165,22 +198,15 @@ public String getVisitorIdKey(SQLConfig config) {
return config.getUserIdKey();
}
- @Override
- public String getIdKey(String database, String schema, String table) {
- return apijson.JSONObject.KEY_ID;
- }
@Override
public String getIdKey(String database, String schema, String datasource, String table) {
- return getIdKey(database, schema, table);
- }
- @Override
- public String getUserIdKey(String database, String schema, String table) {
- return apijson.JSONObject.KEY_USER_ID;
+ return apijson.JSONObject.KEY_ID;
}
@Override
public String getUserIdKey(String database, String schema, String datasource, String table) {
- return getUserIdKey(database, schema, table);
+ return apijson.JSONObject.KEY_USER_ID;
}
+
@Override
public Object newId(RequestMethod method, String database, String schema, String table) {
return System.currentTimeMillis();
@@ -201,7 +227,7 @@ public AbstractVerifier setVisitor(Visitor visitor) {
this.visitor = visitor;
this.visitorId = visitor == null ? null : visitor.getId();
- //导致内部调用且放行校验(noVerifyLogin, noVerifyRole)也抛异常
+ //导致内部调用且放行校验(needVerifyLogin, needVerifyRole)也抛异常
// if (visitorId == null) {
// throw new NullPointerException(TAG + ".setVisitor visitorId == null !!! 可能导致权限校验失效,引发安全问题!");
// }
@@ -210,16 +236,6 @@ public AbstractVerifier setVisitor(Visitor visitor) {
}
- /**验证权限是否通过
- * @param config
- * @param visitor
- * @return
- * @throws Exception
- */
- @Deprecated
- public boolean verify(SQLConfig config) throws Exception {
- return verifyAccess(config);
- }
/**验证权限是否通过
* @param config
* @param visitor
@@ -231,13 +247,20 @@ public boolean verifyAccess(SQLConfig config) throws Exception {
if (table == null) {
return true;
}
- RequestRole role = config.getRole();
+
+ String role = config.getRole();
if (role == null) {
- role = RequestRole.UNKNOWN;
- }
+ role = UNKNOWN;
+ }
+ else {
+ if (ROLE_MAP.containsKey(role) == false) {
+ Set NAMES = ROLE_MAP.keySet();
+ throw new IllegalArgumentException("角色 " + role + " 不存在!只能是[" + StringUtil.getString(NAMES.toArray()) + "]中的一种!");
+ }
- if (role != RequestRole.UNKNOWN) {//未登录的角色
- verifyLogin();
+ if (role.equals(UNKNOWN) == false) { //未登录的角色
+ verifyLogin();
+ }
}
RequestMethod method = config.getMethod();
@@ -259,7 +282,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception {
//不能在Visitor内null -> [] ! 否则会导致某些查询加上不需要的条件!
List list = visitor.getContactIdList() == null
? new ArrayList() : new ArrayList(visitor.getContactIdList());
- if (role == RequestRole.CIRCLE) {
+ if (role == CIRCLE) {
list.add(visitorId);
}
@@ -287,7 +310,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception {
}
if (list.contains(Long.valueOf("" + id)) == false) {//Integer等转为Long才能正确判断。强转崩溃
throw new IllegalAccessException(visitorIdkey + " = " + id + " 的 " + table
- + " 不允许 " + role.name() + " 用户的 " + method.name() + " 请求!");
+ + " 不允许 " + role + " 用户的 " + method.name() + " 请求!");
}
}
}
@@ -307,7 +330,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception {
oid = ovl == null || index >= ovl.size() ? null : ovl.get(index);
if (oid == null || StringUtil.getString(oid).equals("" + visitorId) == false) {
throw new IllegalAccessException(visitorIdkey + " = " + oid + " 的 " + table
- + " 不允许 " + role.name() + " 用户的 " + method.name() + " 请求!");
+ + " 不允许 " + role + " 用户的 " + method.name() + " 请求!");
}
}
}
@@ -331,13 +354,13 @@ public boolean verifyAccess(SQLConfig config) throws Exception {
requestId = config.getWhere(visitorIdkey, true);//JSON里数值不能保证是Long,可能是Integer
if (requestId != null && StringUtil.getString(requestId).equals(StringUtil.getString(visitorId)) == false) {
throw new IllegalAccessException(visitorIdkey + " = " + requestId + " 的 " + table
- + " 不允许 " + role.name() + " 用户的 " + method.name() + " 请求!");
+ + " 不允许 " + role + " 用户的 " + method.name() + " 请求!");
}
config.putWhere(visitorIdkey, visitorId, true);
}
break;
- case ADMIN://这里不好做,在特定接口内部判。 可以是 /get/admin + 固定秘钥 Parser#noVerify,之后全局跳过验证
+ case ADMIN://这里不好做,在特定接口内部判。 可以是 /get/admin + 固定秘钥 Parser#needVerify,之后全局跳过验证
verifyAdmin();
break;
default://unknown,verifyRole通过就行
@@ -362,19 +385,20 @@ public boolean verifyAccess(SQLConfig config) throws Exception {
* @throws Exception
* @see {@link apijson.JSONObject#KEY_ROLE}
*/
- public void verifyRole(String table, RequestMethod method, RequestRole role) throws Exception {
+ 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 = RequestRole.UNKNOWN;
+ role = UNKNOWN;
}
- Map map = ACCESS_MAP.get(table);
+
+ Map map = ACCESS_MAP.get(table);
if (map == null || Arrays.asList(map.get(method)).contains(role) == false) {
- throw new IllegalAccessException(table + " 不允许 " + role.name() + " 用户的 " + method.name() + " 请求!");
+ throw new IllegalAccessException(table + " 不允许 " + role + " 用户的 " + method.name() + " 请求!");
}
}
}
@@ -551,7 +575,7 @@ public static JSONObject verifyRequest(@NotNull final RequestMethod method, fina
}
//已在 Verifier 中处理
- // if (RequestRole.get(request.getString(JSONRequest.KEY_ROLE)) == RequestRole.ADMIN) {
+ // if (get(request.getString(JSONRequest.KEY_ROLE)) == ADMIN) {
// throw new IllegalArgumentException("角色设置错误!不允许在写操作Request中传 " + name +
// ":{ " + JSONRequest.KEY_ROLE + ":admin } !");
// }
@@ -847,13 +871,13 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name,
//解析内容<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
- Set> set = new LinkedHashSet<>(target.entrySet());
+ Set> set = new LinkedHashSet<>(target.entrySet());
if (set.isEmpty() == false) {
String key;
Object tvalue;
Object rvalue;
- for (Entry entry : set) {
+ for (Map.Entry entry : set) {
key = entry == null ? null : entry.getKey();
if (key == null || OPERATION_KEY_LIST.contains(key)) {
continue;
@@ -1019,11 +1043,11 @@ private static JSONObject operate(Operation opt, JSONObject targetChild, JSONObj
}
- Set> set = new LinkedHashSet<>(targetChild.entrySet());
+ Set> set = new LinkedHashSet<>(targetChild.entrySet());
String tk;
Object tv;
- for (Entry e : set) {
+ for (Map.Entry e : set) {
tk = e == null ? null : e.getKey();
if (tk == null || OPERATION_KEY_LIST.contains(tk)) {
continue;
@@ -1342,7 +1366,8 @@ private static void verifyCondition(@NotNull String funChar, @NotNull JSONObject
} finally {
executor.close();
}
- if (result != null && JSONResponse.isExist(result.getIntValue(JSONResponse.KEY_CODE)) == false) {
+
+ if (result != null && JSONResponse.isExist(result.getIntValue(JSONResponse.KEY_COUNT)) == false) {
throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 '" + tk + "': '" + tv + "' !");
}
}
diff --git a/APIJSONORM/src/main/java/apijson/orm/Parser.java b/APIJSONORM/src/main/java/apijson/orm/Parser.java
index 6e0af5368..43b76d5e4 100755
--- a/APIJSONORM/src/main/java/apijson/orm/Parser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/Parser.java
@@ -13,7 +13,6 @@
import apijson.NotNull;
import apijson.RequestMethod;
-import apijson.RequestRole;
/**解析器
* @author Lemon
@@ -121,7 +120,7 @@ JSONObject parseCorrectRequest(RequestMethod method, String tag, int version, St
Boolean getGlobleFormat();
- RequestRole getGlobleRole();
+ String getGlobleRole();
String getGlobleDatabase();
String getGlobleSchema();
String getGlobleDatasource();
diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java
index 8710b6e97..298065d31 100755
--- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java
@@ -10,7 +10,6 @@
import apijson.NotNull;
import apijson.RequestMethod;
-import apijson.RequestRole;
/**SQL配置
* @author Lemon
@@ -113,8 +112,8 @@ public interface SQLConfig {
Object getId();
SQLConfig setId(Object id);
- RequestRole getRole();
- SQLConfig setRole(RequestRole role); // TODO 提供 String 类型的,方便扩展
+ String getRole();
+ SQLConfig setRole(String role);
public boolean isDistinct();
public SQLConfig setDistinct(boolean distinct);
diff --git a/APIJSONORM/src/main/java/apijson/orm/Verifier.java b/APIJSONORM/src/main/java/apijson/orm/Verifier.java
index b91549ec0..903b69530 100755
--- a/APIJSONORM/src/main/java/apijson/orm/Verifier.java
+++ b/APIJSONORM/src/main/java/apijson/orm/Verifier.java
@@ -9,21 +9,13 @@
import apijson.NotNull;
import apijson.RequestMethod;
-import apijson.RequestRole;
/**校验器(权限、请求参数、返回结果等)
* @author Lemon
*/
public interface Verifier {
- /**验证权限是否通过,用 verifyAccess 替代,最早 4.5.0 移除
- * @param config
- * @param visitor
- * @return
- * @throws Exception
- */
- @Deprecated
- boolean verify(SQLConfig config) throws Exception;
+
/**验证权限是否通过
* @param config
* @param visitor
@@ -40,7 +32,7 @@ public interface Verifier {
* @throws Exception
* @see {@link apijson.JSONObject#KEY_ROLE}
*/
- void verifyRole(String table, RequestMethod method, RequestRole role) throws Exception;
+ void verifyRole(String table, RequestMethod method, String role) throws Exception;
/**登录校验
* @param config
diff --git a/APIJSONORM/src/main/java/apijson/orm/model/Document.java b/APIJSONORM/src/main/java/apijson/orm/model/Document.java
index 6d5ded353..6f2a8bba2 100755
--- a/APIJSONORM/src/main/java/apijson/orm/model/Document.java
+++ b/APIJSONORM/src/main/java/apijson/orm/model/Document.java
@@ -5,8 +5,8 @@
package apijson.orm.model;
-import static apijson.RequestRole.ADMIN;
-import static apijson.RequestRole.LOGIN;
+import static apijson.orm.AbstractVerifier.ADMIN;
+import static apijson.orm.AbstractVerifier.LOGIN;
import java.io.Serializable;
import java.sql.Timestamp;
diff --git a/APIJSONORM/src/main/java/apijson/orm/model/TestRecord.java b/APIJSONORM/src/main/java/apijson/orm/model/TestRecord.java
index b8c519e75..b1ceaa77c 100644
--- a/APIJSONORM/src/main/java/apijson/orm/model/TestRecord.java
+++ b/APIJSONORM/src/main/java/apijson/orm/model/TestRecord.java
@@ -5,8 +5,8 @@
package apijson.orm.model;
-import static apijson.RequestRole.ADMIN;
-import static apijson.RequestRole.LOGIN;
+import static apijson.orm.AbstractVerifier.ADMIN;
+import static apijson.orm.AbstractVerifier.LOGIN;
import java.io.Serializable;
import java.sql.Timestamp;
From 47961e3ee4f9db13717ddf3f0d75e76dbefd4524 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Sun, 26 Sep 2021 23:57:11 +0800
Subject: [PATCH 083/754] =?UTF-8?q?Parser=20=E7=A7=BB=E9=99=A4=E6=B2=A1?=
=?UTF-8?q?=E5=BF=85=E8=A6=81=E7=9A=84=E6=96=B9=E6=B3=95=20parseCorrectRes?=
=?UTF-8?q?ponse?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../apijson/orm/AbstractObjectParser.java | 7 ++--
.../main/java/apijson/orm/AbstractParser.java | 36 +++++--------------
.../src/main/java/apijson/orm/Parser.java | 5 ++-
3 files changed, 15 insertions(+), 33 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
index d1ed399e4..7e38e0393 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
@@ -852,14 +852,17 @@ public JSONObject onSQLExecute() throws Exception {
if (list != null) {
String arrayPath = parentPath.substring(0, parentPath.lastIndexOf("[]") + 2);
+ long startTime = System.currentTimeMillis();
for (int i = 1; i < list.size(); i++) { // 从 1 开始,0 已经处理过
- JSONObject obj = parser.parseCorrectResponse(table, list.get(i));
- list.set(i, obj);
+ JSONObject obj = list.get(i);
if (obj != null) {
parser.putQueryResult(arrayPath + "/" + i + "/" + name, obj); //解决获取关联数据时requestObject里不存在需要的关联数据
}
}
+
+ long endTime = System.currentTimeMillis();
+ Log.e(TAG, "onSQLExecute for (int i = 1; i < list.size(); i++) startTime = " + startTime + "; endTime = " + endTime + "; duration = " + (endTime - startTime));
parser.putArrayMainCache(arrayPath, list);
}
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
index cf154a93e..c5ddd5564 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
@@ -719,33 +719,6 @@ public JSONObject parseCorrectRequest() throws Exception {
}
- //TODO 优化性能!
- /**获取正确的返回结果
- * @param method
- * @param response
- * @return
- * @throws Exception
- */
- @Override
- public JSONObject parseCorrectResponse(String table, JSONObject response) throws Exception {
- // Log.d(TAG, "getCorrectResponse method = " + method + "; table = " + table);
- // if (response == null || response.isEmpty()) {//避免无效空result:{}添加内容后变有效
- // Log.e(TAG, "getCorrectResponse response == null || response.isEmpty() >> return response;");
- return response;
- // }
- //
- // JSONObject target = apijson.JSONObject.isTableKey(table) == false
- // ? new JSONObject() : getStructure(method, "Response", "model", table);
- //
- // return MethodStructure.parseResponse(method, table, target, response, new OnParseCallback() {
- //
- // @Override
- // protected JSONObject onParseJSONObject(String key, JSONObject tobj, JSONObject robj) throws Exception {
- // return getCorrectResponse(method, key, robj);
- // }
- // });
- }
-
/**获取Request或Response内指定JSON结构
* @param table
* @param method
@@ -1075,15 +1048,22 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name
.setJoinList(onJoinParse(join, request));
JSONObject parent;
+
+ long startTime = System.currentTimeMillis();
//生成size个
for (int i = 0; i < (isSubquery ? 1 : size); i++) {
parent = onObjectParse(request, isSubquery ? parentPath : path, isSubquery ? name : "" + i, config.setType(SQLConfig.TYPE_ITEM).setPosition(i), isSubquery);
if (parent == null || parent.isEmpty()) {
break;
}
+
//key[]:{Table:{}}中key equals Table时 提取Table
response.add(getValue(parent, childKeys)); //null有意义
}
+
+ long endTime = System.currentTimeMillis();
+ Log.e(TAG, "onArrayParse for for (int i = 0; i < (isSubquery ? 1 : size); i++) startTime = " + startTime + "; endTime = " + endTime + "; duration = " + (endTime - startTime));
+
//Table>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
@@ -1665,7 +1645,7 @@ public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Except
result = getSQLExecutor().execute(config, false);
}
- return parseCorrectResponse(config.getTable(), result);
+ return result;
}
catch (Exception e) {
if (Log.DEBUG == false && e instanceof SQLException) {
diff --git a/APIJSONORM/src/main/java/apijson/orm/Parser.java b/APIJSONORM/src/main/java/apijson/orm/Parser.java
index 43b76d5e4..135dc1c7a 100755
--- a/APIJSONORM/src/main/java/apijson/orm/Parser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/Parser.java
@@ -64,6 +64,7 @@ public interface Parser {
JSONObject parseResponse(String request);
JSONObject parseResponse(JSONObject request);
+ // 没必要性能还差 JSONObject parseCorrectResponse(String table, JSONObject response) throws Exception;
JSONObject parseCorrectRequest() throws Exception;
@@ -71,12 +72,10 @@ public interface Parser {
JSONObject parseCorrectRequest(RequestMethod method, String tag, int version, String name, JSONObject request,
int maxUpdateCount, SQLCreator creator) throws Exception;
- JSONObject parseCorrectResponse(String table, JSONObject response) throws Exception;
-
+
JSONObject getStructure(String table, String method, String tag, int version) throws Exception;
-
JSONObject onObjectParse(JSONObject request, String parentPath, String name, SQLConfig arrayConfig, boolean isSubquery) throws Exception;
JSONArray onArrayParse(JSONObject request, String parentPath, String name, boolean isSubquery) throws Exception;
From ed036ef025d26356d9af0f23d4a5970319748770 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 27 Sep 2021 00:40:41 +0800
Subject: [PATCH 084/754] =?UTF-8?q?=E4=BC=98=E5=8C=96=20Table[]:{=20Table:?=
=?UTF-8?q?{}=20}=20=E8=BF=99=E7=A7=8D=E5=8D=95=E8=A1=A8=E6=95=B0=E7=BB=84?=
=?UTF-8?q?=E7=9A=84=E6=9F=A5=E8=AF=A2=E6=80=A7=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../apijson/orm/AbstractObjectParser.java | 48 ++++++++++++------
.../main/java/apijson/orm/AbstractParser.java | 49 ++++++++++++++++---
.../src/main/java/apijson/orm/SQLConfig.java | 4 +-
3 files changed, 78 insertions(+), 23 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
index 7e38e0393..7fc29e1fb 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
@@ -58,6 +58,7 @@ public AbstractObjectParser setParser(AbstractParser> parser) {
protected boolean isSubquery;
protected final int type;
+ protected final String arrayTable;
protected final List joinList;
protected final boolean isTable;
protected final boolean isArrayMainTable;
@@ -86,6 +87,7 @@ public AbstractObjectParser(@NotNull JSONObject request, String parentPath, SQLC
this.isSubquery = isSubquery;
this.type = arrayConfig == null ? 0 : arrayConfig.getType();
+ this.arrayTable = arrayConfig == null ? null : arrayConfig.getTable();
this.joinList = arrayConfig == null ? null : arrayConfig.getJoinList();
this.isTable = isTable; // apijson.JSONObject.isTableKey(table);
@@ -835,6 +837,7 @@ public Object onReferenceParse(@NotNull String path) {
return parser.getValueByPath(path);
}
+ @SuppressWarnings("unchecked")
@Override
public JSONObject onSQLExecute() throws Exception {
int position = getPosition();
@@ -846,30 +849,47 @@ public JSONObject onSQLExecute() throws Exception {
else {
result = parser.executeSQL(sqlConfig, isSubquery);
- if (isArrayMainTable && position == 0 && result != null) { // 提取并缓存数组主表的列表数据
- @SuppressWarnings("unchecked")
- List list = (List) result.remove(SQLExecutor.KEY_RAW_LIST);
- if (list != null) {
+ boolean isSimpleArray = false;
+ List rawList = null;
+
+ if (isArrayMainTable && position == 0 && result != null) {
+
+ isSimpleArray = (functionMap == null || functionMap.isEmpty())
+ && (customMap == null || customMap.isEmpty())
+ && (table.equals(arrayTable));
+
+ // 提取并缓存数组主表的列表数据
+ rawList = (List) result.remove(SQLExecutor.KEY_RAW_LIST);
+ if (rawList != null) {
String arrayPath = parentPath.substring(0, parentPath.lastIndexOf("[]") + 2);
- long startTime = System.currentTimeMillis();
- for (int i = 1; i < list.size(); i++) { // 从 1 开始,0 已经处理过
- JSONObject obj = list.get(i);
- if (obj != null) {
- parser.putQueryResult(arrayPath + "/" + i + "/" + name, obj); //解决获取关联数据时requestObject里不存在需要的关联数据
+ if (isSimpleArray == false) {
+ long startTime = System.currentTimeMillis();
+
+ for (int i = 1; i < rawList.size(); i++) { // 从 1 开始,0 已经处理过
+ JSONObject obj = rawList.get(i);
+
+ if (obj != null) {
+ parser.putQueryResult(arrayPath + "/" + i + "/" + name, obj); // 解决获取关联数据时requestObject里不存在需要的关联数据
+ }
}
+
+ long endTime = System.currentTimeMillis(); // 3ms - 8ms
+ Log.e(TAG, "\n onSQLExecute <<<<<<<<<<<<<<<<<<<<<<<<<<<<\n for (int i = 1; i < list.size(); i++) startTime = " + startTime
+ + "; endTime = " + endTime + "; duration = " + (endTime - startTime) + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n ");
}
-
- long endTime = System.currentTimeMillis();
- Log.e(TAG, "onSQLExecute for (int i = 1; i < list.size(); i++) startTime = " + startTime + "; endTime = " + endTime + "; duration = " + (endTime - startTime));
- parser.putArrayMainCache(arrayPath, list);
+ parser.putArrayMainCache(arrayPath, rawList);
}
}
if (isSubquery == false && result != null) {
- parser.putQueryResult(path, result);//解决获取关联数据时requestObject里不存在需要的关联数据
+ parser.putQueryResult(path, result); // 解决获取关联数据时requestObject里不存在需要的关联数据
+
+ if (isSimpleArray && rawList != null) {
+ result.put(SQLExecutor.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 c5ddd5564..172e3c83e 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
@@ -1031,10 +1031,14 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name
int index = isSubquery || name == null ? -1 : name.lastIndexOf("[]");
String childPath = index <= 0 ? null : Pair.parseEntry(name.substring(0, index), true).getKey(); // Table-key1-key2...
+ String arrTableKey = null;
//判断第一个key,即Table是否存在,如果存在就提取
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];
}
@@ -1045,11 +1049,13 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name
.setCount(size)
.setPage(page)
.setQuery(query2)
+ .setTable(arrTableKey)
.setJoinList(onJoinParse(join, request));
JSONObject parent;
- long startTime = System.currentTimeMillis();
+ boolean isExtract = true;
+
//生成size个
for (int i = 0; i < (isSubquery ? 1 : size); i++) {
parent = onObjectParse(request, isSubquery ? parentPath : path, isSubquery ? name : "" + i, config.setType(SQLConfig.TYPE_ITEM).setPosition(i), isSubquery);
@@ -1057,13 +1063,32 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name
break;
}
+ long startTime = System.currentTimeMillis();
+
+ /* 这里优化了 Table[]: { Table:{} } 这种情况下的性能
+ * 如果把 List 改成 JSONArray 来减少以下 addAll 一次复制,则会导致 AbstractSQLExecutor 等其它很多地方 get 要改为 getJSONObject,
+ * 修改类型会导致不兼容旧版依赖 ORM 的项目,而且整体上性能只有特殊情况下性能提升,其它非特殊情况下因为多出很多 instanceof JSONObject 的判断而降低了性能。
+ */
+ JSONObject fo = i != 0 || arrTableKey == null ? null : parent.getJSONObject(arrTableKey);
+ @SuppressWarnings("unchecked")
+ List list = fo == null ? null : (List) fo.remove(SQLExecutor.KEY_RAW_LIST);
+
+ if (list != null && list.isEmpty() == false) {
+ isExtract = false;
+
+ list.set(0, fo); // 不知道为啥第 0 项也加了 @RAW@LIST
+ response.addAll(list); // List cannot match List response = new JSONArray(list);
+
+ long endTime = System.currentTimeMillis(); // 0ms
+ Log.d(TAG, "\n onArrayParse <<<<<<<<<<<<<<<<<<<<<<<<<<<<\n for (int i = 0; i < (isSubquery ? 1 : size); i++) "
+ + " startTime = " + startTime + "; endTime = " + endTime + "; duration = " + (endTime - startTime) + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+ break;
+ }
+
//key[]:{Table:{}}中key equals Table时 提取Table
response.add(getValue(parent, childKeys)); //null有意义
}
- long endTime = System.currentTimeMillis();
- Log.e(TAG, "onArrayParse for for (int i = 0; i < (isSubquery ? 1 : size); i++) startTime = " + startTime + "; endTime = " + endTime + "; duration = " + (endTime - startTime));
-
//Table>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
@@ -1082,12 +1107,20 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name
}
}
*/
- Object fo = childKeys == null || response.isEmpty() ? null : response.get(0);
- if (fo instanceof Boolean || fo instanceof Number || fo instanceof String) { //[{}] 和 [[]] 都没意义
- putQueryResult(path, response);
+ if (isExtract) {
+ long startTime = System.currentTimeMillis();
+
+ Object fo = childKeys == null || response.isEmpty() ? null : response.get(0);
+ if (fo instanceof Boolean || fo instanceof Number || fo instanceof String) { //[{}] 和 [[]] 都没意义
+ putQueryResult(path, response);
+ }
+
+ long endTime = System.currentTimeMillis();
+ Log.d(TAG, "\n onArrayParse <<<<<<<<<<<<<<<<<<<<<<<<<<<<\n isExtract >> putQueryResult "
+ + " startTime = " + startTime + "; endTime = " + endTime + "; duration = " + (endTime - startTime) + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
}
- } finally {
+ } finally {
//后面还可能用到,要还原
request.put(JSONRequest.KEY_QUERY, query);
request.put(JSONRequest.KEY_COUNT, count);
diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java
index 298065d31..ab8e2d254 100755
--- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java
@@ -134,6 +134,9 @@ public interface SQLConfig {
* @see {@link #getSQLTable()}
*/
String getTable();
+
+ SQLConfig setTable(String table);
+
/**数据库里的真实Table名
* 通过 {@link #TABLE_KEY_MAP} 映射
* @return
@@ -145,7 +148,6 @@ public interface SQLConfig {
List getRaw();
SQLConfig setRaw(List raw);
- SQLConfig setTable(String table);
String getGroup();
SQLConfig setGroup(String group);
From 8d780ddcb0e1dce8e9c39dd1424e81821ea954ce Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 27 Sep 2021 15:20:33 +0800
Subject: [PATCH 085/754] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E8=A1=A8=E5=AF=B9?=
=?UTF-8?q?=E8=B1=A1=E4=B8=AD=E7=9A=84=E5=AD=90=E8=A1=A8=E5=AF=B9=E8=B1=A1?=
=?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=80=BB=E6=98=AF=E4=B8=80=E6=A0=B7=E4=BB=A5?=
=?UTF-8?q?=E5=8F=8A=E5=9C=A8=20Table[]:{=20Table:{=20ChildTable:{}=20}=20?=
=?UTF-8?q?}=20=E6=83=85=E5=86=B5=E4=B8=8B=E5=8F=AA=E6=9C=89=E9=A6=96?=
=?UTF-8?q?=E4=B8=AA=20Table=20=E9=87=8C=E8=BF=94=E5=9B=9E=E4=BA=86=20Chil?=
=?UTF-8?q?dTable?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../java/apijson/orm/AbstractObjectParser.java | 16 ++++++++++++----
.../main/java/apijson/orm/AbstractParser.java | 1 +
.../src/main/java/apijson/orm/ObjectParser.java | 4 +++-
3 files changed, 16 insertions(+), 5 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
index 7fc29e1fb..fa4da155d 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
@@ -112,7 +112,16 @@ public AbstractObjectParser(@NotNull JSONObject request, String parentPath, SQLC
}
-
+ @Override
+ public String getParentPath() {
+ return parentPath;
+ }
+
+ @Override
+ public AbstractObjectParser setParentPath(String parentPath) {
+ this.parentPath = parentPath;
+ return this;
+ }
protected int position;
public int getPosition() {
@@ -144,7 +153,6 @@ public boolean isBreakParse() {
protected String table;
protected String alias;
protected boolean isReuse;
- protected String parentName;
protected String path;
protected JSONObject response;
@@ -824,7 +832,7 @@ public void onChildResponse() throws Exception {
continue;
}
- response.put(entry.getKey(), child );
+ response.put(entry.getKey(), child);
index ++;
}
}
@@ -856,6 +864,7 @@ public JSONObject onSQLExecute() throws Exception {
isSimpleArray = (functionMap == null || functionMap.isEmpty())
&& (customMap == null || customMap.isEmpty())
+ && (childMap == null || childMap.isEmpty())
&& (table.equals(arrayTable));
// 提取并缓存数组主表的列表数据
@@ -863,7 +872,6 @@ public JSONObject onSQLExecute() throws Exception {
if (rawList != null) {
String arrayPath = parentPath.substring(0, parentPath.lastIndexOf("[]") + 2);
-
if (isSimpleArray == false) {
long startTime = System.currentTimeMillis();
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
index 172e3c83e..b4de3e7c1 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
@@ -855,6 +855,7 @@ public JSONObject onObjectParse(final JSONObject request
ObjectParser op = null;
if (isReuse) { // 数组主表使用专门的缓存数据
op = arrayObjectParserCacheMap.get(parentPath.substring(0, parentPath.lastIndexOf("[]") + 2));
+ op.setParentPath(parentPath);
}
if (op == null) {
diff --git a/APIJSONORM/src/main/java/apijson/orm/ObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/ObjectParser.java
index 452ced293..3fbea8557 100755
--- a/APIJSONORM/src/main/java/apijson/orm/ObjectParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/ObjectParser.java
@@ -20,6 +20,9 @@
*/
public interface ObjectParser {
+ String getParentPath();
+ ObjectParser setParentPath(String parentPath);
+
/**解析成员
* response重新赋值
* @param parentPath
@@ -140,7 +143,6 @@ public interface ObjectParser {
void recycle();
-
ObjectParser setMethod(RequestMethod method);
RequestMethod getMethod();
From b23d884466bd7500463c77295e229c3c9dcef31f Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 27 Sep 2021 16:15:44 +0800
Subject: [PATCH 086/754] =?UTF-8?q?=E6=96=87=E6=A1=A3=EF=BC=9A3.2=20?=
=?UTF-8?q?=E5=8A=9F=E8=83=BD=E7=AC=A6=20=E6=96=B0=E5=A2=9E=E5=85=A8?=
=?UTF-8?q?=E5=B1=80=E5=85=B3=E9=94=AE=E8=AF=8D=E7=9A=84=E8=AF=B4=E6=98=8E?=
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 bd641f91e..bb5acd50c 100644
--- a/Document.md
+++ b/Document.md
@@ -5,7 +5,7 @@ https://github.com/Tencent/APIJSON

-后端开发者可以先看 [图文入门教程1](https://vincentcheng.github.io/apijson-doc/zh) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (都非官方,和本文档有出入的点以本文档为准,例如正则匹配 key? 已废弃,用 key~ 替代。)
+后端开发者可以先看 [图文入门教程1](https://vincentcheng.github.io/apijson-doc/zh) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (都非官方,和本文档有出入的点以本文档为准,例如正则匹配 key? 已废弃,用 key~ 替代;例如 "@column":"store_id,sum(amt):totAmt" 中逗号 , 有误,应该用分号 ; 隔开 SQL 函数)
* ### [1.示例](#1)
* ### [2.对比传统方式](#2)
@@ -378,5 +378,5 @@ DELETE: 删除数据 | base_url/delete/ | { TableName:{<
逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 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",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置 ⑧ "@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}}) ⑧ 将 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:8080/gets/{"tag":"Privacy","Privacy":{"id":82001}}) ② 使用第 1 版接口查隐私信息: [{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn:8080/gets/{"version":1,"tag":"Privacy","Privacy":{"id":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 5ccb064d98a9dfcdbb3c7fc75b44cae1fb625b96 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 27 Sep 2021 16:16:58 +0800
Subject: [PATCH 087/754] Update Document.md
---
Document.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Document.md b/Document.md
index bb5acd50c..a9320f146 100644
--- a/Document.md
+++ b/Document.md
@@ -5,7 +5,7 @@ https://github.com/Tencent/APIJSON

-后端开发者可以先看 [图文入门教程1](https://vincentcheng.github.io/apijson-doc/zh) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (都非官方,和本文档有出入的点以本文档为准,例如正则匹配 key? 已废弃,用 key~ 替代;例如 "@column":"store_id,sum(amt):totAmt" 中逗号 , 有误,应该用分号 ; 隔开 SQL 函数)
+后端开发者可以先看 [图文入门教程1](https://vincentcheng.github.io/apijson-doc/zh) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (都非官方,和本文档有出入的点以本文档为准,例如正则匹配 key? 已废弃,用 key~ 替代;例如 "@column":"store_id,sum(amt):totAmt" 中逗号 , 有误,应该用分号 ; 隔开 SQL 函数,改为 "@column":"store_id;sum(amt):totAmt")
* ### [1.示例](#1)
* ### [2.对比传统方式](#2)
@@ -346,7 +346,7 @@ DELETE: 删除数据 | base_url/delete/ | { TableName:{<
1.TableName指要查询的数据库表Table的名称字符串。第一个字符为大写字母,剩下的字符要符合英语字母、数字、下划线中的任何一种。对应的值的类型为JSONObject,结构是 {...},里面放的是Table的字段(列名)。下同。
-2."tag":tag 后面的tag是非GET、HEAD请求中匹配请求的JSON结构的标识,一般是要查询的table的名称,由后端Request表中指定。下同。
+2."tag":tag 后面的tag是非GET、HEAD请求中匹配请求的JSON结构的标识,一般是要查询的Table的名称,由后端Request表中指定。下同。
3.GET、HEAD请求是开放请求,可任意组合任意嵌套。其它请求为受限制的安全/私密请求,对应的 方法(method), 标识(tag), 版本(version), 结构(structure) 都必须和 后端Request表中所指定的 一一对应,否则请求将不被通过。version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本。下同。
4.GETS与GET、HEADS与HEAD分别为同一类型的操作方法,请求稍有不同但返回结果相同。下同。
5.在HTTP通信中,自动化接口(get,gets,head,heads,post,put,delete) 全用HTTP POST请求。下同。
From 581cace5f37e176e8639b242a05d97ecceb5e90f Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 27 Sep 2021 16:17:25 +0800
Subject: [PATCH 088/754] Update Document.md
---
Document.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Document.md b/Document.md
index a9320f146..00b3d2fc3 100644
--- a/Document.md
+++ b/Document.md
@@ -5,7 +5,7 @@ https://github.com/Tencent/APIJSON

-后端开发者可以先看 [图文入门教程1](https://vincentcheng.github.io/apijson-doc/zh) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (都非官方,和本文档有出入的点以本文档为准,例如正则匹配 key? 已废弃,用 key~ 替代;例如 "@column":"store_id,sum(amt):totAmt" 中逗号 , 有误,应该用分号 ; 隔开 SQL 函数,改为 "@column":"store_id;sum(amt):totAmt")
+后端开发者可以先看 [图文入门教程1](https://vincentcheng.github.io/apijson-doc/zh) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (都非官方,和本文档有出入的点以本文档为准。例如正则匹配 key? 已废弃,用 key~ 替代;例如 "@column":"store_id,sum(amt):totAmt" 中逗号 , 有误,应该用分号 ; 隔开 SQL 函数,改为 "@column":"store_id;sum(amt):totAmt")
* ### [1.示例](#1)
* ### [2.对比传统方式](#2)
From 97fc8125458278aa6891a70f0d381fe72fb76daf Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 27 Sep 2021 16:27:39 +0800
Subject: [PATCH 089/754] Update Document.md
---
Document.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Document.md b/Document.md
index 00b3d2fc3..394174341 100644
--- a/Document.md
+++ b/Document.md
@@ -378,5 +378,5 @@ DELETE: 删除数据 | base_url/delete/ | { TableName:{<
逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 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",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置 ⑧ "@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}}) ⑧ 将 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:8080/gets/{"tag":"Privacy","Privacy":{"id":82001}}) ② 使用第 1 版接口查隐私信息: [{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn:8080/gets/{"version":1,"tag":"Privacy","Privacy":{"id":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"}}}})
+ 全局关键词 | 为最外层对象 {} 内的关键词。其中 @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%2Fapi%3A8080%2Fgets&type=JSON&req={%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%2Fapi%3A8080%2Fgets&type=JSON&req={%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 d0cf03908ed64f7f4c5f41e8961ac4e05182a551 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 27 Sep 2021 16:53:17 +0800
Subject: [PATCH 090/754] Update Document.md
---
Document.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Document.md b/Document.md
index 394174341..8ba0dbc9f 100644
--- a/Document.md
+++ b/Document.md
@@ -378,5 +378,5 @@ DELETE: 删除数据 | base_url/delete/ | { TableName:{<
逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 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",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置 ⑧ "@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}}) ⑧ 将 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%2Fapi%3A8080%2Fgets&type=JSON&req={%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%2Fapi%3A8080%2Fgets&type=JSON&req={%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"}}}})
+ 全局关键词 | 为最外层对象 {} 内的关键词。其中 @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 e5638a3b70e21960faf5a4caff8f0d1e6e715439 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 27 Sep 2021 17:35:20 +0800
Subject: [PATCH 091/754] Update Document.md
---
Document.md | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/Document.md b/Document.md
index 8ba0dbc9f..27eff492d 100644
--- a/Document.md
+++ b/Document.md
@@ -336,13 +336,13 @@ https://github.com/Tencent/APIJSON
方法及说明 | URL | Request | Response
------------ | ------------ | ------------ | ------------
-GET: 普通获取数据, 可用浏览器调试 | base_url/get/ | { TableName:{ … } } {…}内为限制条件 例如获取一个 id = 235 的 Moment: { "Moment":{ "id":235 } } 后端校验通过后自动解析为 SQL 并执行: `SELECT * FROM Moment WHERE id=235 LIMIT 1` | { TableName:{ ... }, "code":200, "msg":"success" } 例如 { "Moment":{ "id":235, "userId":38710, "content":"APIJSON,let interfaces and documents go to hell !" }, "code":200, "msg":"success" }
-HEAD: 普通获取数量, 可用浏览器调试 | base_url/head/ | { TableName:{ … } } {…}内为限制条件 例如获取一个 id = 38710 的 User 所发布的 Moment 总数: { "Moment":{ "userId":38710 } } 后端校验通过后自动解析为 SQL 并执行: `SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | { TableName:{ "code":200, "msg":"success", "count":10 }, "code":200, "msg":"success" } 例如 { "Moment":{ "code":200, "msg":"success", "count":10 }, "code":200, "msg":"success" }
+GET: 普通获取数据, 可用浏览器调试 | base_url/get/ | { TableName:{ … } } {…}内为限制条件 例如获取一个 id = 235 的 Moment: [{ "Moment":{ "id":235 } }](url=http%3A%2F%2Flocalhost%3A8080%2Fget&type=JSON&json={"Moment":{"id":235}}) 后端校验通过后自动解析为 SQL 并执行: `SELECT * FROM Moment WHERE id=235 LIMIT 1` | { TableName:{ ... }, "code":200, "msg":"success" } 例如 { "Moment":{ "id":235, "userId":38710, "content":"APIJSON,let interfaces and documents go to hell !" }, "code":200, "msg":"success" }
+HEAD: 普通获取数量, 可用浏览器调试 | base_url/head/ | { TableName:{ … } } {…}内为限制条件 例如获取一个 id = 38710 的 User 所发布的 Moment 总数: [{ "Moment":{ "userId":38710 } }](url=http%3A%2F%2Flocalhost%3A8080%2Fhead&type=JSON&json={"Moment":{"userId":38710}}) 后端校验通过后自动解析为 SQL 并执行: `SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | { TableName:{ "code":200, "msg":"success", "count":10 }, "code":200, "msg":"success" } 例如 { "Moment":{ "code":200, "msg":"success", "count":10 }, "code":200, "msg":"success" }
GETS: 安全/私密获取数据, 用于获取钱包等 对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,其它同GET | 同GET
HEADS: 安全/私密获取数量, 用于获取银行卡数量等 对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,其它同HEAD | 同HEAD
-POST: 新增数据 | base_url/post/ | 单个: { TableName:{ … }, "tag":tag } {…}中id由后端生成,不能传 例如当前登录用户 38710 发布一个新 Comment: { "Comment":{ "momentId":12, "content":"APIJSON,let interfaces and documents go to hell !" }, "tag":"Comment" } 后端校验通过后自动解析为 SQL 并执行: `INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')` 批量: { TableName\[]:\[{ … }, { … } … ], "tag":tag } {…}中id由后端生成,不能传 例如当前登录用户 82001 发布 2 个 Comment: { "Comment\[]":\[{ "momentId":12, "content":"APIJSON,let interfaces and documents go to hell !" }, { "momentId":15, "content":"APIJSON is a JSON transmision protocol." }], "tag":"Comment:[]" } 后端校验通过后自动解析为 SQL 并执行: `INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')` `INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个: { TableName:{ "code":200, "msg":"success", "id":38710 }, "code":200, "msg":"success" } 例如 { "Comment":{ "code":200, "msg":"success", "id":120 }, "code":200, "msg":"success" } 批量: { TableName:{ "code":200, "msg":"success", "count":5, "id[]":[1, 2, 3, 4, 5] }, "code":200, "msg":"success" } 例如 { "Comment":{ "code":200, "msg":"success", "count":2, "id[]":\[1, 2] }, "code":200, "msg":"success" }
-PUT: 修改数据, 只修改所传的字段 | base_url/put/ | { TableName:{ "id":id, … }, "tag":tag } {…} 中 id 或 id{} 至少传一个 例如当前登录用户 82001 修改 id = 235 的 Moment 的 content: { "Moment":{ "id":235, "content":"APIJSON,let interfaces and documents go to hell !" }, "tag":"Moment" } 后端校验通过后自动解析为 SQL 并执行: `UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1` 批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。 "tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置; "tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST
-DELETE: 删除数据 | base_url/delete/ | { TableName:{ "id":id }, "tag":tag } {…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{} 例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment: { "Comment":{ "id{}":[100,110,120] }, "tag":"Comment[]" } 后端校验通过后自动解析为 SQL 并执行: `DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | { TableName:{ "code":200, "msg":"success", "id[]":[100,110,120] "count":3 }, "code":200, "msg":"success" } 例如 { "Comment":{ "code":200, "msg":"success", "id[]":[100,110,120], "count":3 }, "code":200, "msg":"success" }
+POST: 新增数据 | base_url/post/ | 单个: { TableName:{ … }, "tag":tag } {…}中id由后端生成,不能传 例如当前登录用户 38710 发布一个新 Comment: [{ "Comment":{ "momentId":12, "content":"APIJSON,let interfaces and documents go to hell !" }, "tag":"Comment" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Comment"}) 后端校验通过后自动解析为 SQL 并执行: `INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')` 批量: { TableName\[]:\[{ … }, { … } … ], "tag":tag } {…}中id由后端生成,不能传 例如当前登录用户 82001 发布 2 个 Comment: [{ "Comment[]":[{ "momentId":12, "content":"APIJSON,let interfaces and documents go to hell !" }, { "momentId":15, "content":"APIJSON is a JSON transmision protocol." }], "tag":"Comment:[]" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment[]":[{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"}) 后端校验通过后自动解析为 SQL 并执行: `INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')` `INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个: { TableName:{ "code":200, "msg":"success", "id":38710 }, "code":200, "msg":"success" } 例如 { "Comment":{ "code":200, "msg":"success", "id":120 }, "code":200, "msg":"success" } 批量: { TableName:{ "code":200, "msg":"success", "count":5, "id[]":[1, 2, 3, 4, 5] }, "code":200, "msg":"success" } 例如 { "Comment":{ "code":200, "msg":"success", "count":2, "id[]":\[1, 2] }, "code":200, "msg":"success" }
+PUT: 修改数据, 只修改所传的字段 | base_url/put/ | { TableName:{ "id":id, … }, "tag":tag } {…} 中 id 或 id{} 至少传一个 例如当前登录用户 82001 修改 id = 235 的 Moment 的 content: [{ "Moment":{ "id":235, "content":"APIJSON,let interfaces and documents go to hell !" }, "tag":"Moment" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"Moment":{"id":235,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Moment"}) 后端校验通过后自动解析为 SQL 并执行: `UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1` 批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。 "tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置; "tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST
+DELETE: 删除数据 | base_url/delete/ | { TableName:{ "id":id }, "tag":tag } {…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{} 例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment: [{ "Comment":{ "id{}":[100,110,120] }, "tag":"Comment[]" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fdelete&type=JSON&json={"Comment":{"id{}":[100,110,120]},"tag":"Comment[]"}) 后端校验通过后自动解析为 SQL 并执行: `DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | { TableName:{ "code":200, "msg":"success", "id[]":[100,110,120] "count":3 }, "code":200, "msg":"success" } 例如 { "Comment":{ "code":200, "msg":"success", "id[]":[100,110,120], "count":3 }, "code":200, "msg":"success" }
1.TableName指要查询的数据库表Table的名称字符串。第一个字符为大写字母,剩下的字符要符合英语字母、数字、下划线中的任何一种。对应的值的类型为JSONObject,结构是 {...},里面放的是Table的字段(列名)。下同。
From d2a3b8ff7d43b0a42a553408801c16fc13a6da28 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 27 Sep 2021 17:40:46 +0800
Subject: [PATCH 092/754] Update Document.md
---
Document.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Document.md b/Document.md
index 27eff492d..30e7aa914 100644
--- a/Document.md
+++ b/Document.md
@@ -338,8 +338,8 @@ https://github.com/Tencent/APIJSON
------------ | ------------ | ------------ | ------------
GET: 普通获取数据, 可用浏览器调试 | base_url/get/ | { TableName:{ … } } {…}内为限制条件 例如获取一个 id = 235 的 Moment: [{ "Moment":{ "id":235 } }](url=http%3A%2F%2Flocalhost%3A8080%2Fget&type=JSON&json={"Moment":{"id":235}}) 后端校验通过后自动解析为 SQL 并执行: `SELECT * FROM Moment WHERE id=235 LIMIT 1` | { TableName:{ ... }, "code":200, "msg":"success" } 例如 { "Moment":{ "id":235, "userId":38710, "content":"APIJSON,let interfaces and documents go to hell !" }, "code":200, "msg":"success" }
HEAD: 普通获取数量, 可用浏览器调试 | base_url/head/ | { TableName:{ … } } {…}内为限制条件 例如获取一个 id = 38710 的 User 所发布的 Moment 总数: [{ "Moment":{ "userId":38710 } }](url=http%3A%2F%2Flocalhost%3A8080%2Fhead&type=JSON&json={"Moment":{"userId":38710}}) 后端校验通过后自动解析为 SQL 并执行: `SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | { TableName:{ "code":200, "msg":"success", "count":10 }, "code":200, "msg":"success" } 例如 { "Moment":{ "code":200, "msg":"success", "count":10 }, "code":200, "msg":"success" }
-GETS: 安全/私密获取数据, 用于获取钱包等 对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,其它同GET | 同GET
-HEADS: 安全/私密获取数量, 用于获取银行卡数量等 对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,其它同HEAD | 同HEAD
+GETS: 安全/私密获取数据, 用于获取钱包等 对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,例如 ["tag":"Privacy"](url=http%3A%2F%2Flocalhost%3A8080%2Fgets&type=JSON&json={"Privacy":{"id":82001}}),其它同GET | 同GET
+HEADS: 安全/私密获取数量, 用于获取银行卡数量等 对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,例如 ["tag":"Verify"](url=http%3A%2F%2Flocalhost%3A8080%2Fheads&type=JSON&json={"Verify":{"phone":13000082001}}),其它同HEAD | 同HEAD
POST: 新增数据 | base_url/post/ | 单个: { TableName:{ … }, "tag":tag } {…}中id由后端生成,不能传 例如当前登录用户 38710 发布一个新 Comment: [{ "Comment":{ "momentId":12, "content":"APIJSON,let interfaces and documents go to hell !" }, "tag":"Comment" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Comment"}) 后端校验通过后自动解析为 SQL 并执行: `INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')` 批量: { TableName\[]:\[{ … }, { … } … ], "tag":tag } {…}中id由后端生成,不能传 例如当前登录用户 82001 发布 2 个 Comment: [{ "Comment[]":[{ "momentId":12, "content":"APIJSON,let interfaces and documents go to hell !" }, { "momentId":15, "content":"APIJSON is a JSON transmision protocol." }], "tag":"Comment:[]" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment[]":[{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"}) 后端校验通过后自动解析为 SQL 并执行: `INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')` `INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个: { TableName:{ "code":200, "msg":"success", "id":38710 }, "code":200, "msg":"success" } 例如 { "Comment":{ "code":200, "msg":"success", "id":120 }, "code":200, "msg":"success" } 批量: { TableName:{ "code":200, "msg":"success", "count":5, "id[]":[1, 2, 3, 4, 5] }, "code":200, "msg":"success" } 例如 { "Comment":{ "code":200, "msg":"success", "count":2, "id[]":\[1, 2] }, "code":200, "msg":"success" }
PUT: 修改数据, 只修改所传的字段 | base_url/put/ | { TableName:{ "id":id, … }, "tag":tag } {…} 中 id 或 id{} 至少传一个 例如当前登录用户 82001 修改 id = 235 的 Moment 的 content: [{ "Moment":{ "id":235, "content":"APIJSON,let interfaces and documents go to hell !" }, "tag":"Moment" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"Moment":{"id":235,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Moment"}) 后端校验通过后自动解析为 SQL 并执行: `UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1` 批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。 "tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置; "tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST
DELETE: 删除数据 | base_url/delete/ | { TableName:{ "id":id }, "tag":tag } {…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{} 例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment: [{ "Comment":{ "id{}":[100,110,120] }, "tag":"Comment[]" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fdelete&type=JSON&json={"Comment":{"id{}":[100,110,120]},"tag":"Comment[]"}) 后端校验通过后自动解析为 SQL 并执行: `DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | { TableName:{ "code":200, "msg":"success", "id[]":[100,110,120] "count":3 }, "code":200, "msg":"success" } 例如 { "Comment":{ "code":200, "msg":"success", "id[]":[100,110,120], "count":3 }, "code":200, "msg":"success" }
From 1e7b901c0cf7f9e3ad257086a65da51fc457ba9f Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 27 Sep 2021 17:49:34 +0800
Subject: [PATCH 093/754] Update Document.md
---
Document.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Document.md b/Document.md
index 30e7aa914..4fca78c97 100644
--- a/Document.md
+++ b/Document.md
@@ -336,10 +336,10 @@ https://github.com/Tencent/APIJSON
方法及说明 | URL | Request | Response
------------ | ------------ | ------------ | ------------
-GET: 普通获取数据, 可用浏览器调试 | base_url/get/ | { TableName:{ … } } {…}内为限制条件 例如获取一个 id = 235 的 Moment: [{ "Moment":{ "id":235 } }](url=http%3A%2F%2Flocalhost%3A8080%2Fget&type=JSON&json={"Moment":{"id":235}}) 后端校验通过后自动解析为 SQL 并执行: `SELECT * FROM Moment WHERE id=235 LIMIT 1` | { TableName:{ ... }, "code":200, "msg":"success" } 例如 { "Moment":{ "id":235, "userId":38710, "content":"APIJSON,let interfaces and documents go to hell !" }, "code":200, "msg":"success" }
-HEAD: 普通获取数量, 可用浏览器调试 | base_url/head/ | { TableName:{ … } } {…}内为限制条件 例如获取一个 id = 38710 的 User 所发布的 Moment 总数: [{ "Moment":{ "userId":38710 } }](url=http%3A%2F%2Flocalhost%3A8080%2Fhead&type=JSON&json={"Moment":{"userId":38710}}) 后端校验通过后自动解析为 SQL 并执行: `SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | { TableName:{ "code":200, "msg":"success", "count":10 }, "code":200, "msg":"success" } 例如 { "Moment":{ "code":200, "msg":"success", "count":10 }, "code":200, "msg":"success" }
-GETS: 安全/私密获取数据, 用于获取钱包等 对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,例如 ["tag":"Privacy"](url=http%3A%2F%2Flocalhost%3A8080%2Fgets&type=JSON&json={"Privacy":{"id":82001}}),其它同GET | 同GET
-HEADS: 安全/私密获取数量, 用于获取银行卡数量等 对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,例如 ["tag":"Verify"](url=http%3A%2F%2Flocalhost%3A8080%2Fheads&type=JSON&json={"Verify":{"phone":13000082001}}),其它同HEAD | 同HEAD
+GET: 普通获取数据, 可用浏览器调试 | base_url/get/ | { TableName:{ … } } {…}内为限制条件 例如获取一个 id = 235 的 Moment: [{ "Moment":{ "id":235 } }](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fget&type=JSON&json={"Moment"%3A{"id"%3A235}}) 后端校验通过后自动解析为 SQL 并执行: `SELECT * FROM Moment WHERE id=235 LIMIT 1` | { TableName:{ ... }, "code":200, "msg":"success" } 例如 { "Moment":{ "id":235, "userId":38710, "content":"APIJSON,let interfaces and documents go to hell !" }, "code":200, "msg":"success" }
+HEAD: 普通获取数量, 可用浏览器调试 | base_url/head/ | { TableName:{ … } } {…}内为限制条件 例如获取一个 id = 38710 的 User 所发布的 Moment 总数: [{ "Moment":{ "userId":38710 } }](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fhead&type=JSON&json={"Moment"%3A{"userId"%3A38710}}) 后端校验通过后自动解析为 SQL 并执行: `SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | { TableName:{ "code":200, "msg":"success", "count":10 }, "code":200, "msg":"success" } 例如 { "Moment":{ "code":200, "msg":"success", "count":10 }, "code":200, "msg":"success" }
+GETS: 安全/私密获取数据, 用于获取钱包等 对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,例如 ["tag":"Privacy"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}} ),其它同GET | 同GET
+HEADS: 安全/私密获取数量, 用于获取银行卡数量等 对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,例如 ["tag":"Verify"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fheads&type=JSON&json={"tag"%3A"Verify","Verify"%3A{"phone"%3A13000082001}}),其它同HEAD | 同HEAD
POST: 新增数据 | base_url/post/ | 单个: { TableName:{ … }, "tag":tag } {…}中id由后端生成,不能传 例如当前登录用户 38710 发布一个新 Comment: [{ "Comment":{ "momentId":12, "content":"APIJSON,let interfaces and documents go to hell !" }, "tag":"Comment" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Comment"}) 后端校验通过后自动解析为 SQL 并执行: `INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')` 批量: { TableName\[]:\[{ … }, { … } … ], "tag":tag } {…}中id由后端生成,不能传 例如当前登录用户 82001 发布 2 个 Comment: [{ "Comment[]":[{ "momentId":12, "content":"APIJSON,let interfaces and documents go to hell !" }, { "momentId":15, "content":"APIJSON is a JSON transmision protocol." }], "tag":"Comment:[]" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment[]":[{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"}) 后端校验通过后自动解析为 SQL 并执行: `INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')` `INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个: { TableName:{ "code":200, "msg":"success", "id":38710 }, "code":200, "msg":"success" } 例如 { "Comment":{ "code":200, "msg":"success", "id":120 }, "code":200, "msg":"success" } 批量: { TableName:{ "code":200, "msg":"success", "count":5, "id[]":[1, 2, 3, 4, 5] }, "code":200, "msg":"success" } 例如 { "Comment":{ "code":200, "msg":"success", "count":2, "id[]":\[1, 2] }, "code":200, "msg":"success" }
PUT: 修改数据, 只修改所传的字段 | base_url/put/ | { TableName:{ "id":id, … }, "tag":tag } {…} 中 id 或 id{} 至少传一个 例如当前登录用户 82001 修改 id = 235 的 Moment 的 content: [{ "Moment":{ "id":235, "content":"APIJSON,let interfaces and documents go to hell !" }, "tag":"Moment" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"Moment":{"id":235,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Moment"}) 后端校验通过后自动解析为 SQL 并执行: `UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1` 批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。 "tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置; "tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST
DELETE: 删除数据 | base_url/delete/ | { TableName:{ "id":id }, "tag":tag } {…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{} 例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment: [{ "Comment":{ "id{}":[100,110,120] }, "tag":"Comment[]" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fdelete&type=JSON&json={"Comment":{"id{}":[100,110,120]},"tag":"Comment[]"}) 后端校验通过后自动解析为 SQL 并执行: `DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | { TableName:{ "code":200, "msg":"success", "id[]":[100,110,120] "count":3 }, "code":200, "msg":"success" } 例如 { "Comment":{ "code":200, "msg":"success", "id[]":[100,110,120], "count":3 }, "code":200, "msg":"success" }
From 778b1bcd0e8ff567ddda69a2caf8e4b9bdc2970a Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 27 Sep 2021 19:02:36 +0800
Subject: [PATCH 094/754] Update Document.md
---
Document.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Document.md b/Document.md
index 4fca78c97..729b6c4d1 100644
--- a/Document.md
+++ b/Document.md
@@ -338,12 +338,12 @@ https://github.com/Tencent/APIJSON
------------ | ------------ | ------------ | ------------
GET: 普通获取数据, 可用浏览器调试 | base_url/get/ | { TableName:{ … } } {…}内为限制条件 例如获取一个 id = 235 的 Moment: [{ "Moment":{ "id":235 } }](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fget&type=JSON&json={"Moment"%3A{"id"%3A235}}) 后端校验通过后自动解析为 SQL 并执行: `SELECT * FROM Moment WHERE id=235 LIMIT 1` | { TableName:{ ... }, "code":200, "msg":"success" } 例如 { "Moment":{ "id":235, "userId":38710, "content":"APIJSON,let interfaces and documents go to hell !" }, "code":200, "msg":"success" }
HEAD: 普通获取数量, 可用浏览器调试 | base_url/head/ | { TableName:{ … } } {…}内为限制条件 例如获取一个 id = 38710 的 User 所发布的 Moment 总数: [{ "Moment":{ "userId":38710 } }](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fhead&type=JSON&json={"Moment"%3A{"userId"%3A38710}}) 后端校验通过后自动解析为 SQL 并执行: `SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | { TableName:{ "code":200, "msg":"success", "count":10 }, "code":200, "msg":"success" } 例如 { "Moment":{ "code":200, "msg":"success", "count":10 }, "code":200, "msg":"success" }
-GETS: 安全/私密获取数据, 用于获取钱包等 对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,例如 ["tag":"Privacy"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}} ),其它同GET | 同GET
+GETS: 安全/私密获取数据, 用于获取钱包等 对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,例如 ["tag":"Privacy"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}}),其它同GET | 同GET
HEADS: 安全/私密获取数量, 用于获取银行卡数量等 对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,例如 ["tag":"Verify"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fheads&type=JSON&json={"tag"%3A"Verify","Verify"%3A{"phone"%3A13000082001}}),其它同HEAD | 同HEAD
POST: 新增数据 | base_url/post/ | 单个: { TableName:{ … }, "tag":tag } {…}中id由后端生成,不能传 例如当前登录用户 38710 发布一个新 Comment: [{ "Comment":{ "momentId":12, "content":"APIJSON,let interfaces and documents go to hell !" }, "tag":"Comment" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Comment"}) 后端校验通过后自动解析为 SQL 并执行: `INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')` 批量: { TableName\[]:\[{ … }, { … } … ], "tag":tag } {…}中id由后端生成,不能传 例如当前登录用户 82001 发布 2 个 Comment: [{ "Comment[]":[{ "momentId":12, "content":"APIJSON,let interfaces and documents go to hell !" }, { "momentId":15, "content":"APIJSON is a JSON transmision protocol." }], "tag":"Comment:[]" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment[]":[{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"}) 后端校验通过后自动解析为 SQL 并执行: `INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')` `INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个: { TableName:{ "code":200, "msg":"success", "id":38710 }, "code":200, "msg":"success" } 例如 { "Comment":{ "code":200, "msg":"success", "id":120 }, "code":200, "msg":"success" } 批量: { TableName:{ "code":200, "msg":"success", "count":5, "id[]":[1, 2, 3, 4, 5] }, "code":200, "msg":"success" } 例如 { "Comment":{ "code":200, "msg":"success", "count":2, "id[]":\[1, 2] }, "code":200, "msg":"success" }
PUT: 修改数据, 只修改所传的字段 | base_url/put/ | { TableName:{ "id":id, … }, "tag":tag } {…} 中 id 或 id{} 至少传一个 例如当前登录用户 82001 修改 id = 235 的 Moment 的 content: [{ "Moment":{ "id":235, "content":"APIJSON,let interfaces and documents go to hell !" }, "tag":"Moment" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"Moment":{"id":235,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Moment"}) 后端校验通过后自动解析为 SQL 并执行: `UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1` 批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。 "tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置; "tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST
DELETE: 删除数据 | base_url/delete/ | { TableName:{ "id":id }, "tag":tag } {…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{} 例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment: [{ "Comment":{ "id{}":[100,110,120] }, "tag":"Comment[]" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fdelete&type=JSON&json={"Comment":{"id{}":[100,110,120]},"tag":"Comment[]"}) 后端校验通过后自动解析为 SQL 并执行: `DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | { TableName:{ "code":200, "msg":"success", "id[]":[100,110,120] "count":3 }, "code":200, "msg":"success" } 例如 { "Comment":{ "code":200, "msg":"success", "id[]":[100,110,120], "count":3 }, "code":200, "msg":"success" }
-
+以上接口的简单形式: base_url/{method}/{tag} | GET: 普通获取数据 base_url/get/{tag} HEAD: 普通获取数量 base_url/head/{tag} GETS: 安全/私密获取数据 base_url/gets/{tag} HEADS: 安全/私密获取数量 base_url/heads/{tag} POST: 新增数据 base_url/post/{tag} PUT: 修改数据 base_url/put/{tag} DELETE: 删除数据 base_url/delete/{tag} | 例如安全/私密获取一个 id = 82001 的 Privacy: [base_url/gets/Privacy/ {"id":82001}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets%2FPrivacy&type=JSON&json={"id"%3A82001}) 相当于 [base_url/gets/ {"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}}) 例如批量修改 id = 114, 124 的 Comment 的 content: [base_url/put/Comemnt[]/ { "id{}":[114,124], "content":"test multi put" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput%2FComment[]&type=JSON&json={"id{}"%3A[114,124],"content"%3A"test%20multi%20put"}) 相当于 [base_url/put/ { "tag":"Comment[]", "Comment":{ "id{}":[114,124], "content":"test multi put" } }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"tag"%3A"Comment[]","Comment"%3A{"id{}"%3A[114,124],"content"%3A"test%20multi%20put"}}) | 同以上对应的方法
1.TableName指要查询的数据库表Table的名称字符串。第一个字符为大写字母,剩下的字符要符合英语字母、数字、下划线中的任何一种。对应的值的类型为JSONObject,结构是 {...},里面放的是Table的字段(列名)。下同。
2."tag":tag 后面的tag是非GET、HEAD请求中匹配请求的JSON结构的标识,一般是要查询的Table的名称,由后端Request表中指定。下同。
From f3494d79aa31653a6ea45add58dcca89784b4d03 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 27 Sep 2021 19:03:39 +0800
Subject: [PATCH 095/754] Update Document.md
---
Document.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Document.md b/Document.md
index 729b6c4d1..0426f6b8f 100644
--- a/Document.md
+++ b/Document.md
@@ -343,7 +343,7 @@ HEADS: 安全/私密获取数量, 用于获取银行卡数量等 新增数据 | base_url/post/ | 单个: { TableName:{ … }, "tag":tag } {…}中id由后端生成,不能传 例如当前登录用户 38710 发布一个新 Comment: [{ "Comment":{ "momentId":12, "content":"APIJSON,let interfaces and documents go to hell !" }, "tag":"Comment" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Comment"}) 后端校验通过后自动解析为 SQL 并执行: `INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')` 批量: { TableName\[]:\[{ … }, { … } … ], "tag":tag } {…}中id由后端生成,不能传 例如当前登录用户 82001 发布 2 个 Comment: [{ "Comment[]":[{ "momentId":12, "content":"APIJSON,let interfaces and documents go to hell !" }, { "momentId":15, "content":"APIJSON is a JSON transmision protocol." }], "tag":"Comment:[]" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment[]":[{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"}) 后端校验通过后自动解析为 SQL 并执行: `INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')` `INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个: { TableName:{ "code":200, "msg":"success", "id":38710 }, "code":200, "msg":"success" } 例如 { "Comment":{ "code":200, "msg":"success", "id":120 }, "code":200, "msg":"success" } 批量: { TableName:{ "code":200, "msg":"success", "count":5, "id[]":[1, 2, 3, 4, 5] }, "code":200, "msg":"success" } 例如 { "Comment":{ "code":200, "msg":"success", "count":2, "id[]":\[1, 2] }, "code":200, "msg":"success" }
PUT: 修改数据, 只修改所传的字段 | base_url/put/ | { TableName:{ "id":id, … }, "tag":tag } {…} 中 id 或 id{} 至少传一个 例如当前登录用户 82001 修改 id = 235 的 Moment 的 content: [{ "Moment":{ "id":235, "content":"APIJSON,let interfaces and documents go to hell !" }, "tag":"Moment" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"Moment":{"id":235,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Moment"}) 后端校验通过后自动解析为 SQL 并执行: `UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1` 批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。 "tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置; "tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST
DELETE: 删除数据 | base_url/delete/ | { TableName:{ "id":id }, "tag":tag } {…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{} 例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment: [{ "Comment":{ "id{}":[100,110,120] }, "tag":"Comment[]" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fdelete&type=JSON&json={"Comment":{"id{}":[100,110,120]},"tag":"Comment[]"}) 后端校验通过后自动解析为 SQL 并执行: `DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | { TableName:{ "code":200, "msg":"success", "id[]":[100,110,120] "count":3 }, "code":200, "msg":"success" } 例如 { "Comment":{ "code":200, "msg":"success", "id[]":[100,110,120], "count":3 }, "code":200, "msg":"success" }
-以上接口的简单形式: base_url/{method}/{tag} | GET: 普通获取数据 base_url/get/{tag} HEAD: 普通获取数量 base_url/head/{tag} GETS: 安全/私密获取数据 base_url/gets/{tag} HEADS: 安全/私密获取数量 base_url/heads/{tag} POST: 新增数据 base_url/post/{tag} PUT: 修改数据 base_url/put/{tag} DELETE: 删除数据 base_url/delete/{tag} | 例如安全/私密获取一个 id = 82001 的 Privacy: [base_url/gets/Privacy/ {"id":82001}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets%2FPrivacy&type=JSON&json={"id"%3A82001}) 相当于 [base_url/gets/ {"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}}) 例如批量修改 id = 114, 124 的 Comment 的 content: [base_url/put/Comemnt[]/ { "id{}":[114,124], "content":"test multi put" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput%2FComment[]&type=JSON&json={"id{}"%3A[114,124],"content"%3A"test%20multi%20put"}) 相当于 [base_url/put/ { "tag":"Comment[]", "Comment":{ "id{}":[114,124], "content":"test multi put" } }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"tag"%3A"Comment[]","Comment"%3A{"id{}"%3A[114,124],"content"%3A"test%20multi%20put"}}) | 同以上对应的方法
+以上接口的简单形式: base_url/{method}/{tag} | GET: 普通获取数据 base_url/get/{tag} HEAD: 普通获取数量 base_url/head/{tag} GETS: 安全/私密获取数据 base_url/gets/{tag} HEADS: 安全/私密获取数量 base_url/heads/{tag} POST: 新增数据 base_url/post/{tag} PUT: 修改数据 base_url/put/{tag} DELETE: 删除数据 base_url/delete/{tag} | 例如安全/私密获取一个 id = 82001 的 Privacy: [base_url/gets/Privacy/ {"id":82001}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets%2FPrivacy&type=JSON&json={"id"%3A82001}) 相当于 [base_url/gets/ {"tag":"Privacy", "Privacy":{"id":82001}}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}}) 例如批量修改 id = 114, 124 的 Comment 的 content: [base_url/put/Comemnt[]/ { "id{}":[114,124], "content":"test multi put" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput%2FComment[]&type=JSON&json={"id{}"%3A[114,124],"content"%3A"test%20multi%20put"}) 相当于 [base_url/put/ { "tag":"Comment[]", "Comment":{ "id{}":[114,124], "content":"test multi put" } }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"tag"%3A"Comment[]","Comment"%3A{"id{}"%3A[114,124],"content"%3A"test%20multi%20put"}}) | 同以上对应的方法
1.TableName指要查询的数据库表Table的名称字符串。第一个字符为大写字母,剩下的字符要符合英语字母、数字、下划线中的任何一种。对应的值的类型为JSONObject,结构是 {...},里面放的是Table的字段(列名)。下同。
2."tag":tag 后面的tag是非GET、HEAD请求中匹配请求的JSON结构的标识,一般是要查询的Table的名称,由后端Request表中指定。下同。
From b39c1f06980df44fc88099883335c62fe4a8b0d6 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 27 Sep 2021 19:12:30 +0800
Subject: [PATCH 096/754] Update Document.md
---
Document.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Document.md b/Document.md
index 0426f6b8f..907af5311 100644
--- a/Document.md
+++ b/Document.md
@@ -340,7 +340,7 @@ GET: 普通获取数据, 可用浏览器调试 | base_url/get/ | {<
HEAD: 普通获取数量, 可用浏览器调试 | base_url/head/ | { TableName:{ … } } {…}内为限制条件 例如获取一个 id = 38710 的 User 所发布的 Moment 总数: [{ "Moment":{ "userId":38710 } }](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fhead&type=JSON&json={"Moment"%3A{"userId"%3A38710}}) 后端校验通过后自动解析为 SQL 并执行: `SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | { TableName:{ "code":200, "msg":"success", "count":10 }, "code":200, "msg":"success" } 例如 { "Moment":{ "code":200, "msg":"success", "count":10 }, "code":200, "msg":"success" }
GETS: 安全/私密获取数据, 用于获取钱包等 对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,例如 ["tag":"Privacy"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}}),其它同GET | 同GET
HEADS: 安全/私密获取数量, 用于获取银行卡数量等 对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,例如 ["tag":"Verify"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fheads&type=JSON&json={"tag"%3A"Verify","Verify"%3A{"phone"%3A13000082001}}),其它同HEAD | 同HEAD
-POST: 新增数据 | base_url/post/ | 单个: { TableName:{ … }, "tag":tag } {…}中id由后端生成,不能传 例如当前登录用户 38710 发布一个新 Comment: [{ "Comment":{ "momentId":12, "content":"APIJSON,let interfaces and documents go to hell !" }, "tag":"Comment" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Comment"}) 后端校验通过后自动解析为 SQL 并执行: `INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')` 批量: { TableName\[]:\[{ … }, { … } … ], "tag":tag } {…}中id由后端生成,不能传 例如当前登录用户 82001 发布 2 个 Comment: [{ "Comment[]":[{ "momentId":12, "content":"APIJSON,let interfaces and documents go to hell !" }, { "momentId":15, "content":"APIJSON is a JSON transmision protocol." }], "tag":"Comment:[]" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment[]":[{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"}) 后端校验通过后自动解析为 SQL 并执行: `INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')` `INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个: { TableName:{ "code":200, "msg":"success", "id":38710 }, "code":200, "msg":"success" } 例如 { "Comment":{ "code":200, "msg":"success", "id":120 }, "code":200, "msg":"success" } 批量: { TableName:{ "code":200, "msg":"success", "count":5, "id[]":[1, 2, 3, 4, 5] }, "code":200, "msg":"success" } 例如 { "Comment":{ "code":200, "msg":"success", "count":2, "id[]":\[1, 2] }, "code":200, "msg":"success" }
+POST: 新增数据 | base_url/post/ | 单个: { TableName:{ … }, "tag":tag } {…}中id由后端生成,不能传 例如当前登录用户 38710 发布一个新 Comment: [{ "Comment":{ "momentId":12, "content":"APIJSON,let interfaces and documents go to hell !" }, "tag":"Comment" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Comment"}) 后端校验通过后自动解析为 SQL 并执行: `INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')` 批量: { TableName\[]:\[{ … }, { … } … ], "tag":tag } {…}中id由后端生成,不能传 例如当前登录用户 82001 发布 2 个 Comment: [{ "Comment[]":[{ "momentId":12, "content":"APIJSON,let interfaces and documents go to hell !" }, { "momentId":15, "content":"APIJSON is a JSON transmision protocol." }], "tag":"Comment:[]" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment[]":[{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"}) 后端校验通过后自动解析为 SQL 并执行: `INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !');` `INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.');` | 单个: { TableName:{ "code":200, "msg":"success", "id":38710 }, "code":200, "msg":"success" } 例如 { "Comment":{ "code":200, "msg":"success", "id":120 }, "code":200, "msg":"success" } 批量: { TableName:{ "code":200, "msg":"success", "count":5, "id[]":[1, 2, 3, 4, 5] }, "code":200, "msg":"success" } 例如 { "Comment":{ "code":200, "msg":"success", "count":2, "id[]":\[1, 2] }, "code":200, "msg":"success" }
PUT: 修改数据, 只修改所传的字段 | base_url/put/ | { TableName:{ "id":id, … }, "tag":tag } {…} 中 id 或 id{} 至少传一个 例如当前登录用户 82001 修改 id = 235 的 Moment 的 content: [{ "Moment":{ "id":235, "content":"APIJSON,let interfaces and documents go to hell !" }, "tag":"Moment" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"Moment":{"id":235,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Moment"}) 后端校验通过后自动解析为 SQL 并执行: `UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1` 批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。 "tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置; "tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST
DELETE: 删除数据 | base_url/delete/ | { TableName:{ "id":id }, "tag":tag } {…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{} 例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment: [{ "Comment":{ "id{}":[100,110,120] }, "tag":"Comment[]" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fdelete&type=JSON&json={"Comment":{"id{}":[100,110,120]},"tag":"Comment[]"}) 后端校验通过后自动解析为 SQL 并执行: `DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | { TableName:{ "code":200, "msg":"success", "id[]":[100,110,120] "count":3 }, "code":200, "msg":"success" } 例如 { "Comment":{ "code":200, "msg":"success", "id[]":[100,110,120], "count":3 }, "code":200, "msg":"success" }
以上接口的简单形式: base_url/{method}/{tag} | GET: 普通获取数据 base_url/get/{tag} HEAD: 普通获取数量 base_url/head/{tag} GETS: 安全/私密获取数据 base_url/gets/{tag} HEADS: 安全/私密获取数量 base_url/heads/{tag} POST: 新增数据 base_url/post/{tag} PUT: 修改数据 base_url/put/{tag} DELETE: 删除数据 base_url/delete/{tag} | 例如安全/私密获取一个 id = 82001 的 Privacy: [base_url/gets/Privacy/ {"id":82001}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets%2FPrivacy&type=JSON&json={"id"%3A82001}) 相当于 [base_url/gets/ {"tag":"Privacy", "Privacy":{"id":82001}}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}}) 例如批量修改 id = 114, 124 的 Comment 的 content: [base_url/put/Comemnt[]/ { "id{}":[114,124], "content":"test multi put" }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput%2FComment[]&type=JSON&json={"id{}"%3A[114,124],"content"%3A"test%20multi%20put"}) 相当于 [base_url/put/ { "tag":"Comment[]", "Comment":{ "id{}":[114,124], "content":"test multi put" } }](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"tag"%3A"Comment[]","Comment"%3A{"id{}"%3A[114,124],"content"%3A"test%20multi%20put"}}) | 同以上对应的方法
From 773c2cb2e5e502a157a50c25d907da547cd06ea1 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 27 Sep 2021 19:15:50 +0800
Subject: [PATCH 097/754] Update Document.md
---
Document.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Document.md b/Document.md
index 907af5311..292153848 100644
--- a/Document.md
+++ b/Document.md
@@ -257,7 +257,7 @@ https://github.com/Tencent/APIJSON
-[在线测试](http://apijson.org/auto)
+[在线测试](http://apijson.cn/api)
From 7cbc85b1603204519c301a54209ae544eed10104 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 27 Sep 2021 22:36:16 +0800
Subject: [PATCH 098/754] Update Document.md
---
Document.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Document.md b/Document.md
index 292153848..1b6129c2d 100644
--- a/Document.md
+++ b/Document.md
@@ -377,6 +377,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",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置 ⑧ "@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}}) ⑧ 将 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, !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",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置 ⑧ "@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}}) ⑧ 将 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 9bea66e37acb0bb641da61943520dde19f2d72d4 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 27 Sep 2021 22:50:10 +0800
Subject: [PATCH 099/754] =?UTF-8?q?=E9=80=9A=E7=94=A8=E6=96=87=E6=A1=A3=20?=
=?UTF-8?q?=203.2=20=E5=8A=9F=E8=83=BD=E7=AC=A6=20=E5=AF=B9=E8=B1=A1?=
=?UTF-8?q?=E5=85=B3=E9=94=AE=E8=AF=8D=20=E6=96=B0=E5=A2=9E=20"@datasource?=
=?UTF-8?q?":"DRUID"=20=E8=B7=A8=E6=95=B0=E6=8D=AE=E6=BA=90?=
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 1b6129c2d..282fa1ff4 100644
--- a/Document.md
+++ b/Document.md
@@ -377,6 +377,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",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置 ⑧ "@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}}) ⑧ 将 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, !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)"}})
全局关键词 | 为最外层对象 {} 内的关键词。其中 @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 22a41c09fefb4e9878fd76c5a4715c11388360a0 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Tue, 28 Sep 2021 01:40:05 +0800
Subject: [PATCH 100/754] Update Document.md
---
Document.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Document.md b/Document.md
index 282fa1ff4..5da8589ae 100644
--- a/Document.md
+++ b/Document.md
@@ -376,7 +376,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为 "[]":{} 中{}内的关键词,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)"}})
全局关键词 | 为最外层对象 {} 内的关键词。其中 @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 bbf0fc33a9380bbfad0b465d29cb781d907188c5 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Tue, 28 Sep 2021 02:14:26 +0800
Subject: [PATCH 101/754] =?UTF-8?q?=E5=89=8D=E7=AB=AF=E4=BC=A0=20SQL=20?=
=?UTF-8?q?=E5=85=B3=E9=94=AE=E8=AF=8D=20OVER=20=E5=92=8C=20AGAINST=20?=
=?UTF-8?q?=E5=8E=BB=E6=8E=89=E5=A4=9A=E4=BD=99=E7=9A=84=E7=A9=BA=E6=A0=BC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../main/java/apijson/orm/AbstractSQLConfig.java | 13 +++++++------
.../src/main/java/apijson/orm/AbstractVerifier.java | 2 +-
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
index fc3bb3ba5..8204a84b2 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
@@ -1562,10 +1562,10 @@ public String getColumnPrase(String expression, boolean containRaw) {
// 全文索引 math(name,tag) AGAINST ('a b +c -d' IN NATURALE LANGUAGE MODE) // IN BOOLEAN MODE
//有函数,但不是窗口函数
- int overIndex = expression.indexOf(") OVER (");
- int againstIndex = expression.indexOf(") AGAINST (");
- boolean containOver = overIndex > 0 && overIndex < expression.length() - ") OVER (".length();
- boolean containAgainst = againstIndex > 0 && againstIndex < expression.length() - ") AGAINST (".length();
+ int overIndex = expression.indexOf(")OVER("); // 传参不传空格,拼接带空格 ") OVER (");
+ int againstIndex = expression.indexOf(")AGAINST("); // 传参不传空格,拼接带空格 ") AGAINST (");
+ boolean containOver = overIndex > 0 && overIndex < expression.length() - ")OVER(".length();
+ boolean containAgainst = againstIndex > 0 && againstIndex < expression.length() - ")AGAINST(".length();
if (containOver && containAgainst) {
throw new IllegalArgumentException("字符 " + expression + " 不合法!"
@@ -1658,8 +1658,9 @@ public String getColumnPrase(String expression, boolean containRaw) {
// 别名
String alias = s2.lastIndexOf(":") < s2.lastIndexOf(")") ? null : s2.substring(s2.lastIndexOf(":") + 1);
// 获取后半部分的参数解析 (agr0 agr1 ...)
- String argsString2[] = parseArgsSplitWithComma(argString2, false, containRaw);
- expression = fun + "(" + StringUtil.getString(agrsString1) + (containOver ? ") OVER (" : ") AGAINST (") + StringUtil.getString(argsString2) + ")" + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); }
+ String argsString2[] = parseArgsSplitWithComma(argString2, false, containRaw);
+ expression = fun + "(" + StringUtil.getString(agrsString1) + (containOver ? ") OVER (" : ") AGAINST (") // 传参不传空格,拼接带空格
+ + StringUtil.getString(argsString2) + ")" + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); }
}
return expression;
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java
index 5527f17c4..4cab85683 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java
@@ -961,7 +961,7 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name,
//不在target内的 key:{}
if (rk.startsWith("@") == false && objKeySet.contains(rk) == false) {
if (rv instanceof JSONObject) {
- throw new UnsupportedOperationException(method + " 请求," +name + " 里面不允许传 " + rk + ":{} !");
+ throw new UnsupportedOperationException(method + " 请求," + name + " 里面不允许传 " + rk + ":{} !");
}
if ((method == RequestMethod.POST || method == RequestMethod.PUT) && rv instanceof JSONArray && JSONRequest.isArrayKey(rk)) {
throw new UnsupportedOperationException(method + " 请求," + name + " 里面不允许 " + rk + ":[] 等未定义的 Table[]:[{}] 批量操作键值对!");
From 406ab54e67a682b28e8edeebc63ca6dbfae70939 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Tue, 28 Sep 2021 02:41:52 +0800
Subject: [PATCH 102/754] =?UTF-8?q?=E4=B8=BB=E9=A1=B9=E7=9B=AE=E8=B4=A1?=
=?UTF-8?q?=E7=8C=AE=E8=80=85=E6=96=B0=E5=A2=9E=201=20=E4=BA=BA=EF=BC=8C?=
=?UTF-8?q?=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE=E8=B4=A1=E7=8C=AE=E8=80=85?=
=?UTF-8?q?=E6=96=B0=E5=A2=9E=207=20=E4=BA=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/README.md b/README.md
index 0bfb971a6..79d4209ef 100644
--- a/README.md
+++ b/README.md
@@ -283,10 +283,11 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
+
-生态周边项目的作者们(2 个腾讯工程师、1 个字节跳动工程师 等):
+生态周边项目的作者们(2 个腾讯工程师、1 个 BAT 技术专家、1 个字节跳动工程师 等):
https://github.com/search?o=desc&q=apijson&s=stars&type=Repositories
https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count
@@ -436,12 +444,12 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
[FfApiJson](https://gitee.com/own_3_0/ff-api-json) 用 JSON 格式直接生成 SQL,借鉴 APIJSON 支持多数据源
-[apijson-practice](https://github.com/vcoolwind/apijson-practice) 实践一下apijson,对做管理平台还是能有不少提效的
-
[APIJSON-ToDo-Demo](https://github.com/jerrylususu/apijson_todo_demo) 一个简单的 todo 示例项目,精简数据,简化上手流程,带自定义鉴权逻辑
[apijson-learn](https://github.com/rainboy-learn/apijson-learn) APIJSON 学习笔记和源码解析
+[apijson-practice](https://github.com/vcoolwind/apijson-practice) 实践一下 apijson,对做管理平台还是能有不少提效的
+
[apijson-sample](https://gitee.com/greyzeng/apijson-sample) APIJSON 简单使用 Demo 及教程
[apijson-examples](https://gitee.com/drone/apijson-examples) APIJSON 的前端、业务后端、管理后端 Demo
@@ -462,7 +470,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
[APIJSON-Android-RxJava](https://github.com/TommyLemon/APIJSON-Android-RxJava) 仿微信朋友圈动态实战项目,ZBLibrary(UI) + APIJSON(HTTP) + RxJava(Data)
-[Android-ZBLibrary](https://github.com/TommyLemon/Android-ZBLibrary) Android MVP快速开发框架,Demo全面,注释详细,使用简单,代码严谨
+[Android-ZBLibrary](https://github.com/TommyLemon/Android-ZBLibrary) Android MVP 快速开发框架,Demo 全面,注释详细,使用简单,代码严谨
感谢热心的作者们的贡献,点 ⭐Star 支持下他们吧。
From 47a83330e6e149c68bbb2b4bbcda87ebc8ec7201 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Tue, 28 Sep 2021 02:44:50 +0800
Subject: [PATCH 103/754] =?UTF-8?q?=E8=A1=A5=E5=85=85=20-=20=E4=B8=BB?=
=?UTF-8?q?=E9=A1=B9=E7=9B=AE=E8=B4=A1=E7=8C=AE=E8=80=85=E6=96=B0=E5=A2=9E?=
=?UTF-8?q?=201=20=E4=BA=BA=EF=BC=8C=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE?=
=?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E6=96=B0=E5=A2=9E=207=20=E4=BA=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 79d4209ef..9a8c4aa3f 100644
--- a/README.md
+++ b/README.md
@@ -310,7 +310,8 @@ https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count
-
+
+
From d6cb2e14fb878cd83d0bb4f6972935fd8c3aa7e9 Mon Sep 17 00:00:00 2001
From: tianzhenyu
Date: Tue, 28 Sep 2021 16:30:39 +0800
Subject: [PATCH 104/754] =?UTF-8?q?fix=20PG=E7=9A=84=E9=87=8D=E5=A4=8D?=
=?UTF-8?q?=E8=AE=BE=E7=BD=AE=E4=BA=8B=E5=8A=A1=E7=AD=89=E7=BA=A7=E7=9A=84?=
=?UTF-8?q?bug?=
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, 6 insertions(+), 1 deletion(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
index 760781c3a..89a672aac 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
@@ -764,13 +764,18 @@ public void setTransactionIsolation(int transactionIsolation) {
}
@Override
+ private boolean setIsolationStatus = false; //设置事务等级
public void begin(int transactionIsolation) throws SQLException {
Log.d("\n\n" + TAG, "<<<<<<<<<<<<<< TRANSACTION begin transactionIsolation = " + transactionIsolation + " >>>>>>>>>>>>>>>>>>>>>>> \n\n");
//不做判断,如果掩盖了问题,调用层都不知道为啥事务没有提交成功
// if (connection == null || connection.isClosed()) {
// return;
// }
- connection.setTransactionIsolation(transactionIsolation);
+ connection.setAutoCommit(false);
+ if(! this.setIsolationStatus){ //只设置一次Isolation等级 PG重复设置事务等级会报错
+ connection.setTransactionIsolation(transactionIsolation);
+ }
+ this.setIsolationStatus=true;
connection.setAutoCommit(false); //java.sql.SQLException: Can''t call commit when autocommit=true
}
@Override
From 0ca192ab5d3e5960842752d3ef61b9596f93fd6d Mon Sep 17 00:00:00 2001
From: tianzhenyu
Date: Tue, 28 Sep 2021 16:35:25 +0800
Subject: [PATCH 105/754] =?UTF-8?q?fix=20PG=E7=9A=84=E9=87=8D=E5=A4=8D?=
=?UTF-8?q?=E8=AE=BE=E7=BD=AE=E4=BA=8B=E5=8A=A1=E7=AD=89=E7=BA=A7=E7=9A=84?=
=?UTF-8?q?bug?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java | 1 -
1 file changed, 1 deletion(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
index 89a672aac..c9f2d0dc4 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
@@ -771,7 +771,6 @@ public void begin(int transactionIsolation) throws SQLException {
// if (connection == null || connection.isClosed()) {
// return;
// }
- connection.setAutoCommit(false);
if(! this.setIsolationStatus){ //只设置一次Isolation等级 PG重复设置事务等级会报错
connection.setTransactionIsolation(transactionIsolation);
}
From fe3d7f6c82b3257028e722803056c2383d968a5d Mon Sep 17 00:00:00 2001
From: yeyuezhishou <626732147@qq.com>
Date: Thu, 30 Sep 2021 13:30:47 +0800
Subject: [PATCH 106/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BD=BF=E7=94=A8?=
=?UTF-8?q?=E7=99=BB=E5=BD=95=EF=BC=9A=E5=9C=86=E9=80=9A=E7=A7=91=E6=8A=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
公司:圆通科技
链接:https://www.yto.net.cn/
场景:大数据应用APP内部接口
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 7c7f2a34a..086ef296e 100644
--- a/README.md
+++ b/README.md
@@ -237,6 +237,7 @@ https://github.com/Tencent/APIJSON/issues/187
+
From 08780c77d03ec1989871ea68614889da7918b3ca Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 30 Sep 2021 14:06:33 +0800
Subject: [PATCH 107/754] =?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/orm/AbstractSQLExecutor.java | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
index c9f2d0dc4..b7cf2d0f9 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
@@ -763,18 +763,18 @@ public void setTransactionIsolation(int transactionIsolation) {
this.transactionIsolation = transactionIsolation;
}
+ private boolean isIsolationStatusSet = false; //已设置事务等级
@Override
- private boolean setIsolationStatus = false; //设置事务等级
public void begin(int transactionIsolation) throws SQLException {
Log.d("\n\n" + TAG, "<<<<<<<<<<<<<< TRANSACTION begin transactionIsolation = " + transactionIsolation + " >>>>>>>>>>>>>>>>>>>>>>> \n\n");
//不做判断,如果掩盖了问题,调用层都不知道为啥事务没有提交成功
// if (connection == null || connection.isClosed()) {
// return;
// }
- if(! this.setIsolationStatus){ //只设置一次Isolation等级 PG重复设置事务等级会报错
+ if (! isIsolationStatusSet) { //只设置一次Isolation等级 PG重复设置事务等级会报错
+ isIsolationStatusSet = true;
connection.setTransactionIsolation(transactionIsolation);
}
- this.setIsolationStatus=true;
connection.setAutoCommit(false); //java.sql.SQLException: Can''t call commit when autocommit=true
}
@Override
From 310f611477f8d94e129748dc83cdbd1340820c76 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 30 Sep 2021 15:57:33 +0800
Subject: [PATCH 108/754] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?=
=?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
index 8a4d7f7c6..fd436f605 100644
--- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
+++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
@@ -684,7 +684,7 @@ MOMENT表示动态,类似微信朋友圈、QQ空间的动态,每一条动态
{
"[]": {
"Sale":{
- "@column":"store_id,sum(amt):totAmt",
+ "@column":"store_id;sum(amt):totAmt",
"@group":"store_id"
}
}
From 57b45f16efdd5e0435570411a33ba3d450766d44 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 30 Sep 2021 16:02:32 +0800
Subject: [PATCH 109/754] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?=
=?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...64\346\230\216\346\226\207\346\241\243.md" | 35 +++++++++++++------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
index fd436f605..96b4bfc20 100644
--- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
+++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
@@ -43,7 +43,7 @@
### B1.下载项目
```bash
-git clone https://github.com/TommyLemon/APIJSON.git
+git clone https://github.com/Tencent/APIJSON.git
```
或者,直接下载ZIP打包好的项目文件。
@@ -101,25 +101,38 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs
那么需要在`DemoSQLConfig`,40-61行,改为自己数据库对应的链接
```java
+ static {
+ DEFAULT_DATABASE = DATABASE_MYSQL; // TODO 默认数据库类型,改成你自己的
+ DEFAULT_SCHEMA = "sys"; // TODO 默认数据库名/模式,改成你自己的,默认情况是 MySQL: sys, PostgreSQL: public, SQL Server: dbo, Oracle:
+ }
+
+ @Override
+ public String getDBVersion() {
+ return "5.7.22"; // "8.0.11"; // TODO 改成你自己的 MySQL 或 PostgreSQL 数据库版本号 // MYSQL 8 和 7 使用的 JDBC 配置不一样
+ }
+
+ @JSONField(serialize = false) // 不在日志打印 账号/密码 等敏感信息
@Override
public String getDBUri() {
- //TODO 改成你自己的
- return DATABASE_POSTGRESQL.equalsIgnoreCase(getDatabase()) ? "jdbc:postgresql://localhost:5432/postgres" : "jdbc:mysql://192.168.71.146:3306/";
+ // 这个是 MySQL 8.0 及以上,要加 userSSL=false return "jdbc:mysql://localhost:3306?userSSL=false&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8";
+ // 以下是 MySQL 5.7 及以下
+ return "jdbc:mysql://localhost:3306?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8"; //TODO 改成你自己的,TiDB 可以当成 MySQL 使用,默认端口为 4000
}
+
+ @JSONField(serialize = false) // 不在日志打印 账号/密码 等敏感信息
@Override
public String getDBAccount() {
- return DATABASE_POSTGRESQL.equalsIgnoreCase(getDatabase()) ? "postgres" : "root"; //TODO 改成你自己的
+ return "root"; // TODO 改成你自己的
}
+
+ @JSONField(serialize = false) // 不在日志打印 账号/密码 等敏感信息
@Override
public String getDBPassword() {
- return DATABASE_POSTGRESQL.equalsIgnoreCase(getDatabase()) ? null : "root"; //TODO 改成你自己的
- }
- @Override
- public String getSchema() {
- String s = super.getSchema();
- return StringUtil.isEmpty(s, true) ? "thea" : s; //TODO 改成你自己的。例如:将"thea"替换成你自己的“数据库名字”
+ return "apijson"; // TODO 改成你自己的,TiDB 可以当成 MySQL 使用, 默认密码为空字符串 ""
}
```
+具体见源码
+https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONDemo/src/main/java/apijson/demo/DemoSQLConfig.java
#### C-1-2.导入表
@@ -142,7 +155,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs
为了方便测试,我这里使用的Chrome浏览器的Restlet Client插件,大家可以根据自己的喜好使用不同的工具测试。
-使用` http://localhost:8080/get`测试结果。(请注意 APIJSONApplication.java 中使用 Tomcat 默认的 8080 端口,如果不小心开着PC端的“微信”,8080端口会被占用,建议改成 8081, 9090 等其它应用程序未占用的端口。)
+使用` http://localhost:8080/get`测试结果。(请注意 DemoApplication.java 中使用 Tomcat 默认的 8080 端口,如果不小心开着PC端的“微信”,8080端口会被占用,建议改成 8081, 9090 等其它应用程序未占用的端口。)
随便找一个表,比如`Moment`表,我们取其中ID为12的一条出来看看
From 5d99841ccd9003f4bb1c89c58360e0759a52196d Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 30 Sep 2021 16:10:07 +0800
Subject: [PATCH 110/754] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?=
=?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...257\264\346\230\216\346\226\207\346\241\243.md" | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
index 96b4bfc20..57bfd5379 100644
--- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
+++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
@@ -1,16 +1,16 @@
# APIJSON
-[TOC]
-
+可以先看更清晰直观的视频教程
+https://search.bilibili.com/all?keyword=APIJSON&from_source=webtop_search&spm_id_from=333.851
+
## A.介绍
### A1.关于接口开发
-首先是看名字`APIJSON`,API是说这个项目是属于接口开发的项目,JSON是指传输数据格式是JSON格式。介于各位看官的水平高低不齐,这里就先为没有项目经验朋友啰嗦两句接口开发的内容。有经验的朋友可以跳到`A2`继续查看。
-
-(此处内容以后会有的)
+首先是看名字`APIJSON`,API是说这个项目是属于接口开发的项目,JSON是指传输数据格式是JSON格式。介于各位看官的水平高低不齐,这里就先为没有项目经验朋友啰嗦两句接口开发的内容。有经验的朋友可以跳到`A2`继续查看。完整的详细介绍见项目首页
+https://github.com/Tencent/APIJSON#--apijson
### A2.功能说明
@@ -43,12 +43,12 @@
### B1.下载项目
```bash
-git clone https://github.com/Tencent/APIJSON.git
+git clone https://github.com/APIJSON/APIJSON-Demo.git
```
或者,直接下载ZIP打包好的项目文件。
-
+
From 9bc895b53759cb294878588f13c86558adc8a717 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 30 Sep 2021 16:12:40 +0800
Subject: [PATCH 111/754] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?=
=?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
index 57bfd5379..d6f8b6979 100644
--- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
+++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
@@ -66,7 +66,7 @@ Eclipse导入:
为了方便修改源代码,你可以像我一样不添加`libs/apijson-orm-3.5.1.jar`文件到`Build Path`中。而是`libs/apijson-orm-3.5.1.jar`的源码,复制到当前项目里。
-源代码在`APIJSON-Master/APIJSON-Java-Server/APIJSONORM`项目中。
+源代码在`https://github.com/Tencent/APIJSON/tree/master/APIJSONORM`项目中。
### B3. pom.xml的错误修改。
有可能这时候pom.xml中报错,例如:
From 10c3916887209b8896bc4dedb7833af805e2a8fa Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 30 Sep 2021 16:13:34 +0800
Subject: [PATCH 112/754] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?=
=?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...2\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
index d6f8b6979..971653b4e 100644
--- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
+++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
@@ -64,9 +64,10 @@ Eclipse导入:

-为了方便修改源代码,你可以像我一样不添加`libs/apijson-orm-3.5.1.jar`文件到`Build Path`中。而是`libs/apijson-orm-3.5.1.jar`的源码,复制到当前项目里。
+为了方便修改源代码,你可以像我一样不添加`libs/apijson-orm.jar`文件到`Build Path`中。而是`libs/apijson-orm.jar`的源码,复制到当前项目里。
-源代码在`https://github.com/Tencent/APIJSON/tree/master/APIJSONORM`项目中。
+源代码在
+https://github.com/Tencent/APIJSON/tree/master/APIJSONORM
### B3. pom.xml的错误修改。
有可能这时候pom.xml中报错,例如:
From de915017c437671fe21f160501e7f0537f962c8d Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 30 Sep 2021 16:16:34 +0800
Subject: [PATCH 113/754] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?=
=?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...32\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
index 971653b4e..a2ab977be 100644
--- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
+++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
@@ -58,7 +58,7 @@ Eclipse导入:
顶部菜单File > Import > Maven > Existing Maven Projects > Next > Browse
-`APIJSON-Master/APIJSON-Java-Server/APIJSONBoot`
+[APIJSON-Demo-Master/APIJSON-Java-Server/APIJSONDemo](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server/APIJSONDemo)
报依赖错误的时候,同目录下的`lib`里面的`jar`添加到`Build Path`中。
@@ -137,7 +137,7 @@ https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSOND
#### C-1-2.导入表
-在`APIJSON-Master/MySQL`目录下有一批SQL脚本,他们看起来是这样的
+在 [APIJSON-Demo-Master/MySQL](https://github.com/APIJSON/APIJSON-Demo/tree/master/MySQL) 目录下有一批SQL脚本,他们看起来是这样的

From 724cf362308526b606a7af5e509678da410293c0 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 30 Sep 2021 16:18:39 +0800
Subject: [PATCH 114/754] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?=
=?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...32\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
index a2ab977be..b38c2e6ca 100644
--- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
+++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
@@ -4,6 +4,8 @@
https://search.bilibili.com/all?keyword=APIJSON&from_source=webtop_search&spm_id_from=333.851

+其它各种官方和第三方文档见首页相关推荐
+https://github.com/Tencent/APIJSON#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90
## A.介绍
@@ -924,7 +926,7 @@ static { //注册权限
"INSERT": { "@role": "OWNER" } //如果没传@role就自动添加
"UPDATE": { "id@": "User/id" } //强制放入键值对
```
-全部操作符见 [Operation.java](https://github.com/APIJSON/APIJSON/blob/master/APIJSON-Java-Server/APIJSONORM/src/main/java/apijson/orm/Operation.java) 的注释
+全部操作符见 [Operation.java](https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/Operation.java) 的注释
From 77a07af56c5c7a4951b6cc221be694f93c4d5f6c Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 30 Sep 2021 16:19:07 +0800
Subject: [PATCH 115/754] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?=
=?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
index b38c2e6ca..216311ea4 100644
--- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
+++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
@@ -1,4 +1,4 @@
-# APIJSON
+# APIJSON 入门教程
可以先看更清晰直观的视频教程
https://search.bilibili.com/all?keyword=APIJSON&from_source=webtop_search&spm_id_from=333.851
From 6d6de899c8d30ed08c84af34dd6b8e670782b57d Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 30 Sep 2021 16:29:48 +0800
Subject: [PATCH 116/754] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?=
=?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
index 216311ea4..7ebab3f40 100644
--- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
+++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
@@ -934,6 +934,6 @@ static { //注册权限
:first_quarter_moon_with_face:此处的介绍都只是简要介绍,只是为了引导刚刚接触APIJSON的道友快速了解APIJSON,并不代表APIJSON只有这些功能,具体功能详情参考下列图表
#### 4. 完整功能图表
-https://github.com/TommyLemon/APIJSON/blob/master/Document.md#3
+https://github.com/Tencent/APIJSON/blob/master/Document.md#3
From 29129cf5bf4d5e9f8928b68f40e6cab58a804a3e Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 30 Sep 2021 16:41:54 +0800
Subject: [PATCH 117/754] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=99=BB=E8=AE=B0?=
=?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=9C=86=E9=80=9A=E5=85=AC=E5=8F=B8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index c1ccf4582..cae9e4f50 100644
--- a/README.md
+++ b/README.md
@@ -238,7 +238,7 @@ https://github.com/Tencent/APIJSON/issues/187
-
+
* [腾讯科技有限公司](https://www.tencent.com)
From 52a2b64e28c61de95fa3898de7e73dded1bdea71 Mon Sep 17 00:00:00 2001
From: WaizLee <91610687+WaizLee@users.noreply.github.com>
Date: Mon, 18 Oct 2021 14:38:12 +0800
Subject: [PATCH 118/754] =?UTF-8?q?list=E7=B1=BB=E5=9E=8B=E8=AF=B7?=
=?UTF-8?q?=E6=B1=82=E5=8F=82=E6=95=B0=E9=80=9A=E8=BF=87put=E8=AF=B7?=
=?UTF-8?q?=E6=B1=82=E5=88=B0=E8=BF=9C=E7=A8=8B=E5=87=BD=E6=95=B0=E5=90=8E?=
=?UTF-8?q?=E4=B8=A2=E5=A4=B1=E7=9A=84bug?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
index fa4da155d..537eea16f 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
@@ -549,6 +549,7 @@ public JSON onChildParse(int index, String key, JSONObject value) throws Excepti
@Override
public void onPUTArrayParse(@NotNull String key, @NotNull JSONArray array) throws Exception {
if (isTable == false || array.isEmpty()) {
+ sqlRequest.put(key, array);
Log.e(TAG, "onPUTArrayParse isTable == false || array == null || array.isEmpty() >> return;");
return;
}
From bf1457276e52e2bf7f0bb5022f4883900e1bfb9b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=B2=88=E5=94=81?= <52o@qq52o.cn>
Date: Thu, 21 Oct 2021 14:05:42 +0800
Subject: [PATCH 119/754] Update README.md
---
README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index cae9e4f50..1a7274f76 100644
--- a/README.md
+++ b/README.md
@@ -226,12 +226,12 @@ https://github.com/Tencent/APIJSON/issues/187
-
-
+
+
-
+
From 9b25ee37ab7e3f1355dea452edb96d4b3c4bc71f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=B2=88=E5=94=81?= <52o@qq52o.cn>
Date: Thu, 21 Oct 2021 14:06:48 +0800
Subject: [PATCH 120/754] Update README-English.md
---
README-English.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/README-English.md b/README-English.md
index cef9f1c02..c945ce8c2 100644
--- a/README-English.md
+++ b/README-English.md
@@ -314,12 +314,12 @@ https://github.com/Tencent/APIJSON/issues/187
-
-
+
+
-
+
From 3868e8e308e527f50f2b599897e86651cd98a4e8 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 25 Oct 2021 14:11:44 +0800
Subject: [PATCH 121/754] =?UTF-8?q?=20=E8=A7=A3=E5=86=B3=E6=A0=A1=E9=AA=8C?=
=?UTF-8?q?=20CIRCLE=20=E8=A7=92=E8=89=B2=E6=97=B6=E6=B2=A1=E6=9C=89?=
=?UTF-8?q?=E7=AE=97=E5=BD=93=E5=89=8D=E7=94=A8=E6=88=B7?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
APIJSONORM/pom.xml | 2 +-
APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml
index 00b542ab0..f1fdcd63a 100755
--- a/APIJSONORM/pom.xml
+++ b/APIJSONORM/pom.xml
@@ -5,7 +5,7 @@
apijson.orm
apijson-orm
- 4.7.2
+ 4.8.0
jar
APIJSONORM
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java
index 4cab85683..cf8dd8954 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java
@@ -282,7 +282,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception {
//不能在Visitor内null -> [] ! 否则会导致某些查询加上不需要的条件!
List list = visitor.getContactIdList() == null
? new ArrayList() : new ArrayList(visitor.getContactIdList());
- if (role == CIRCLE) {
+ if (CIRCLE.equals(role)) {
list.add(visitorId);
}
From 4327d6a3a34622db5a5ba28ca63e53be6bf4eb16 Mon Sep 17 00:00:00 2001
From: sy-records <52o@qq52o.cn>
Date: Mon, 25 Oct 2021 14:30:35 +0800
Subject: [PATCH 122/754] Update README
---
CONTRIBUTING.md | 1 +
README-English.md | 26 ++++++++++++++++++--------
README.md | 17 +++++++++--------
3 files changed, 28 insertions(+), 16 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index de776b2b9..703b9224b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -42,6 +42,7 @@
- [jerrylususu](https://github.com/jerrylususu)(还开源了 apijson_todo_demo 和 apijson_role_extend)
- [Dalezee](https://github.com/Dalezee)(还开源了 apijson_camp)
- [aaronlinv](https://github.com/aaronlinv)
+- [sy-records](https://github.com/sy-records)
#### 其中特别致谢:
justinfengchen 提交的 6 个 Commits, 对 APIJSON 做出了 3,130 增加和 0 处删减(截止 2020/11/04 日);
diff --git a/README-English.md b/README-English.md
index c945ce8c2..331ed25b2 100644
--- a/README-English.md
+++ b/README-English.md
@@ -316,16 +316,16 @@ https://github.com/Tencent/APIJSON/issues/187
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
[More APIJSON Users](https://github.com/Tencent/APIJSON/issues/73)
@@ -362,6 +362,16 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
+
+
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
index 1a7274f76..ffd4d057f 100644
--- a/README.md
+++ b/README.md
@@ -228,16 +228,16 @@ https://github.com/Tencent/APIJSON/issues/187
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
@@ -285,6 +285,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
+
From d662ca3f3540d731d4b5d38d1aa5a1715305e6cf Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 25 Oct 2021 16:02:24 +0800
Subject: [PATCH 123/754] =?UTF-8?q?=E5=B0=86=E9=9A=90=E8=97=8F=E5=AD=97?=
=?UTF-8?q?=E6=AE=B5=E5=8A=9F=E8=83=BD=E5=8D=95=E7=8B=AC=E6=8A=BD=E5=8F=96?=
=?UTF-8?q?=E6=96=B9=E6=B3=95=20isHideColumn=EF=BC=8C=E6=96=B9=E4=BE=BF?=
=?UTF-8?q?=E9=87=8D=E5=86=99=E6=9D=A5=E8=87=AA=E5=AE=9A=E4=B9=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../java/apijson/orm/AbstractSQLExecutor.java | 21 ++++++++++++++++---
1 file changed, 18 insertions(+), 3 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
index b7cf2d0f9..c0fce49f7 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
@@ -514,9 +514,8 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map
protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd
, final int tablePosition, @NotNull JSONObject table, final int columnIndex, Map childMap) throws Exception {
- if (rsmd.getColumnName(columnIndex).startsWith("_")) {
- Log.i(TAG, "select while (rs.next()){ ..."
- + " >> rsmd.getColumnName(i).startsWith(_) >> continue;");
+ if (isHideColumn(config, rs, rsmd, tablePosition, table, columnIndex, childMap)) {
+ Log.i(TAG, "onPutColumn isHideColumn(config, rs, rsmd, tablePosition, table, columnIndex, childMap) >> return table;");
return table;
}
@@ -572,6 +571,22 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r
return table;
}
+
+ /**如果不需要这个功能,在子类重写并直接 return false; 来提高性能
+ * @param config
+ * @param rs
+ * @param rsmd
+ * @param tablePosition
+ * @param table
+ * @param columnIndex
+ * @param childMap
+ * @return
+ * @throws SQLException
+ */
+ protected boolean isHideColumn(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd
+ , final int tablePosition, @NotNull JSONObject table, final int columnIndex, Map childMap) throws SQLException {
+ return rsmd.getColumnName(columnIndex).startsWith("_");
+ }
/**resultList.put(position, table);
* @param config
From 5a2ab0f2bcf81c20a20c86fa5431206e03aaf520 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 25 Oct 2021 16:05:11 +0800
Subject: [PATCH 124/754] =?UTF-8?q?AbstractSQLConfig.getValue=20=E4=BF=AE?=
=?UTF-8?q?=E9=A5=B0=E7=AC=A6=20private=20=E6=94=B9=E4=B8=BA=20protected?=
=?UTF-8?q?=20=E6=96=B9=E4=BE=BF=E5=AD=90=E7=B1=BB=E9=87=8D=E5=86=99?=
=?UTF-8?q?=E6=9D=A5=E5=AE=9E=E7=8E=B0=E5=85=BC=E5=AE=B9=20Oracle=20DATETI?=
=?UTF-8?q?ME=20=E7=AD=89=E7=B1=BB=E5=9E=8B=EF=BC=8C=E5=AF=B9=E5=BA=94=20P?=
=?UTF-8?q?OST/PUT=20=20to=5Fdate(=3F,'yyyy-mm-dd=20hh24:mi:ss')?=
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 8204a84b2..11651c9fe 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
@@ -2503,7 +2503,7 @@ public String getSQLKey(String key) {
* 使用prepareStatement预编译,值为 ? ,后续动态set进去
*/
private List preparedValueList = new ArrayList<>();
- private Object getValue(@NotNull Object value) {
+ protected Object getValue(@NotNull Object value) {
if (isPrepared()) {
preparedValueList.add(value);
return "?";
From bac5eab40d8ce8d7b378c1cf47962de51beadb6e Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 25 Oct 2021 16:41:14 +0800
Subject: [PATCH 125/754] =?UTF-8?q?AbstractSQLConfig.preparedValueList=20?=
=?UTF-8?q?=E4=BF=AE=E9=A5=B0=E7=AC=A6=E6=94=B9=E4=B8=BA=20protected=20?=
=?UTF-8?q?=E6=96=B9=E4=BE=BF=E5=AD=90=E7=B1=BB=E9=87=8D=E5=86=99=E6=9D=A5?=
=?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=85=BC=E5=AE=B9=20Oracle=20DATETIME,TIMEST?=
=?UTF-8?q?AMP=20=E7=AD=89=E6=97=A5=E6=9C=9F=E6=97=B6=E9=97=B4=E7=B1=BB?=
=?UTF-8?q?=E5=9E=8B=EF=BC=8C=E5=AF=B9=E5=BA=94=20POST/PUT=20to=5Fdate(=3F?=
=?UTF-8?q?,'yyyy-mm-dd=20hh24:mi:ss')?=
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 11651c9fe..442e9eb4a 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
@@ -2502,7 +2502,7 @@ public String getSQLKey(String key) {
/**
* 使用prepareStatement预编译,值为 ? ,后续动态set进去
*/
- private List preparedValueList = new ArrayList<>();
+ protected List preparedValueList = new ArrayList<>();
protected Object getValue(@NotNull Object value) {
if (isPrepared()) {
preparedValueList.add(value);
From ad412fc4fe80c75b4a809834e29e76b08c25d10d Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 25 Oct 2021 17:25:27 +0800
Subject: [PATCH 126/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81=20?=
=?UTF-8?q?@column:"`key`"=20=E5=8F=8D=E5=BC=95=E5=8F=B7=E6=8C=87=E5=AE=9A?=
=?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=90=8D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../java/apijson/orm/AbstractSQLConfig.java | 51 ++++++++++++++-----
1 file changed, 37 insertions(+), 14 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
index 442e9eb4a..af5199b04 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
@@ -145,9 +145,11 @@ public abstract class AbstractSQLConfig implements SQLConfig {
RAW_MAP.put("DISTINCT", "");
//时间
- RAW_MAP.put("DATE", "");
RAW_MAP.put("now()", "");
+ RAW_MAP.put("DATE", "");
+ RAW_MAP.put("TIME", "");
RAW_MAP.put("DATETIME", "");
+ RAW_MAP.put("TIMESTAMP", "");
RAW_MAP.put("DateTime", "");
RAW_MAP.put("SECOND", "");
RAW_MAP.put("MINUTE", "");
@@ -157,17 +159,33 @@ 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", "");
RAW_MAP.put("SIGNED", "");
RAW_MAP.put("DECIMAL", "");
+ RAW_MAP.put("DOUBLE", "");
+ RAW_MAP.put("FLOAT", "");
+ RAW_MAP.put("BOOLEAN", "");
+ RAW_MAP.put("ENUM", "");
+ RAW_MAP.put("SET", "");
+ RAW_MAP.put("POINT", "");
+ RAW_MAP.put("BLOB", "");
+ RAW_MAP.put("LONGBLOB", "");
RAW_MAP.put("BINARY", "");
RAW_MAP.put("UNSIGNED", "");
+ RAW_MAP.put("BIT", "");
+ RAW_MAP.put("TINYINT", "");
+ RAW_MAP.put("SMALLINT", "");
+ RAW_MAP.put("INT", "");
+ RAW_MAP.put("BIGINT", "");
RAW_MAP.put("CHAR", "");
- RAW_MAP.put("TIME", "");
+ RAW_MAP.put("VARCHAR", "");
+ RAW_MAP.put("TEXT", "");
+ RAW_MAP.put("LONGTEXT", "");
+ RAW_MAP.put("JSON", "");
//窗口函数关键字
RAW_MAP.put("OVER", "");
@@ -1686,28 +1704,33 @@ private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean
String ck = ckeys[i];
// 如果参数包含 "'" ,解析字符串
- if (ck.contains("'")) {
- int count = 0;
- for (int j = 0; j < ck.length(); j++) {
- if (ck.charAt(j) == '\'') count++;
+ if (ck.startsWith("`") && ck.endsWith("`")) {
+ origin = ck.substring(1, ck.length() - 1);
+ //sql 注入判断 判断
+ if (StringUtil.isName(origin) == false) {
+ throw new IllegalArgumentException("字符 " + ck + " 不合法!"
+ + "预编译模式下 @column:\"`column0`,`column1`:alias;function0(arg0,arg1,...);function1(...):alias...\""
+ + " 中所有字符串 column 都必须必须为1个单词 !");
}
- // FIXME 把 `column` 和 '2 values with [ / : ] ..' 按引号位置分割才能满足全文索引、窗口函数的需要
- // 排除字符串中参数中包含 ' 的情况和不以' 开头和结尾的情况,同时排除 cast('s' as ...) 以空格分隔的参数中包含字符串的情况
- if (count != 2 || !(ck.startsWith("'") && ck.endsWith("'"))) {
+
+ ckeys[i] = getKey(origin).toString();
+ }
+ else if (ck.startsWith("'") && ck.endsWith("'")) {
+ origin = ck.substring(1, ck.length() - 1);
+ if (origin.contains("'")) {
throw new IllegalArgumentException("字符串 " + ck + " 不合法!"
+ "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\""
+ " 中字符串参数不合法,必须以 ' 开头, ' 结尾,字符串中不能包含 ' ");
}
//sql 注入判断 判断
- origin = (ck.substring(1, ck.length() - 1));
if (origin.contains("--") || PATTERN_STRING.matcher(origin).matches() == true) {
throw new IllegalArgumentException("字符 " + ck + " 不合法!"
+ "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\""
+ " 中所有字符串 arg 都必须不符合正则表达式 " + PATTERN_STRING + " 且不包含连续减号 -- !");
}
-
+
// 1.字符串不是字段也没有别名,所以不解析别名 2. 是字符串,进行预编译,使用getValue() ,对字符串进行截取
- ckeys[i] = getValue(ck.substring(1, ck.length() - 1)).toString();
+ ckeys[i] = getValue(origin).toString();
}
else {
// 参数不包含",",即不是字符串
From b1522c6e7b4c79ba6101f5592f222a405b0a33ed Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 25 Oct 2021 18:07:30 +0800
Subject: [PATCH 127/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81=20?=
=?UTF-8?q?@column:"cast(`date`=20AS=20TIME)"=20=E8=BF=99=E7=A7=8D?=
=?UTF-8?q?=E5=9C=A8=E5=87=BD=E6=95=B0=E5=86=85=20`key`=20=E4=B8=8E?=
=?UTF-8?q?=E5=85=B3=E9=94=AE=E8=AF=8D=E7=AD=89=E7=BB=84=E5=90=88=E7=9A=84?=
=?UTF-8?q?=E6=A0=BC=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 | 2 --
1 file changed, 2 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
index af5199b04..97ee35a25 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
@@ -65,7 +65,6 @@
import apijson.orm.model.PgAttribute;
import apijson.orm.model.PgClass;
import apijson.orm.model.Request;
-import apijson.orm.model.Response;
import apijson.orm.model.SysColumn;
import apijson.orm.model.SysTable;
import apijson.orm.model.Table;
@@ -115,7 +114,6 @@ public abstract class AbstractSQLConfig implements SQLConfig {
CONFIG_TABLE_LIST = new ArrayList<>(); // Table, Column 等是系统表 AbstractVerifier.SYSTEM_ACCESS_MAP.keySet());
CONFIG_TABLE_LIST.add(Function.class.getSimpleName());
CONFIG_TABLE_LIST.add(Request.class.getSimpleName());
- CONFIG_TABLE_LIST.add(Response.class.getSimpleName());
CONFIG_TABLE_LIST.add(Access.class.getSimpleName());
CONFIG_TABLE_LIST.add(Document.class.getSimpleName());
CONFIG_TABLE_LIST.add(TestRecord.class.getSimpleName());
From 8b00c69caa05b2157d1734d86d1f794ff9209779 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 25 Oct 2021 18:50:50 +0800
Subject: [PATCH 128/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81=20?=
=?UTF-8?q?CASE=20WHEN=EF=BC=8C=E4=BE=8B=E5=A6=82=20(CASE=20WHEN=20sex=20*?=
=?UTF-8?q?=201=20=3D=200=20THEN=20'=E7=94=B7'=20WHEN=20sex=20>=3D=201=20T?=
=?UTF-8?q?HEN=20'=E5=A5=B3'=20ELSE=20'=E5=85=B6=E5=AE=83'=20END)=EF=BC=9B?=
=?UTF-8?q?=E8=A7=A3=E5=86=B3=E9=80=9A=E8=BF=87=20`=5Fkey`=20=E7=BB=95?=
=?UTF-8?q?=E8=BF=87=E9=9A=90=E8=97=8F=E5=AD=97=E6=AE=B5=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 | 60 +++++++++++++++----
1 file changed, 48 insertions(+), 12 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
index 97ee35a25..5e5a59917 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
@@ -129,6 +129,20 @@ public abstract class AbstractSQLConfig implements SQLConfig {
RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况
+
+ RAW_MAP.put("+", "");
+ RAW_MAP.put("-", "");
+ RAW_MAP.put("*", "");
+ RAW_MAP.put("/", "");
+ RAW_MAP.put("=", "");
+ RAW_MAP.put("!=", "");
+ RAW_MAP.put(">", "");
+ RAW_MAP.put(">=", "");
+ RAW_MAP.put("<", "");
+ RAW_MAP.put("<=", "");
+ RAW_MAP.put("%", "");
+ RAW_MAP.put("(", "");
+ RAW_MAP.put(")", "");
// MySQL 关键字
RAW_MAP.put("AS", "");
@@ -141,6 +155,11 @@ public abstract class AbstractSQLConfig implements SQLConfig {
RAW_MAP.put("NOT", "");
RAW_MAP.put("VALUE", "");
RAW_MAP.put("DISTINCT", "");
+ RAW_MAP.put("CASE", "");
+ RAW_MAP.put("WHEN", "");
+ RAW_MAP.put("THEN", "");
+ RAW_MAP.put("ELSE", "");
+ RAW_MAP.put("END", "");
//时间
RAW_MAP.put("now()", "");
@@ -1705,7 +1724,7 @@ private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean
if (ck.startsWith("`") && ck.endsWith("`")) {
origin = ck.substring(1, ck.length() - 1);
//sql 注入判断 判断
- if (StringUtil.isName(origin) == false) {
+ if (origin.startsWith("_") || StringUtil.isName(origin) == false) {
throw new IllegalArgumentException("字符 " + ck + " 不合法!"
+ "预编译模式下 @column:\"`column0`,`column1`:alias;function0(arg0,arg1,...);function1(...):alias...\""
+ " 中所有字符串 column 都必须必须为1个单词 !");
@@ -1720,12 +1739,6 @@ else if (ck.startsWith("'") && ck.endsWith("'")) {
+ "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\""
+ " 中字符串参数不合法,必须以 ' 开头, ' 结尾,字符串中不能包含 ' ");
}
- //sql 注入判断 判断
- if (origin.contains("--") || PATTERN_STRING.matcher(origin).matches() == true) {
- throw new IllegalArgumentException("字符 " + ck + " 不合法!"
- + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\""
- + " 中所有字符串 arg 都必须不符合正则表达式 " + PATTERN_STRING + " 且不包含连续减号 -- !");
- }
// 1.字符串不是字段也没有别名,所以不解析别名 2. 是字符串,进行预编译,使用getValue() ,对字符串进行截取
ckeys[i] = getValue(origin).toString();
@@ -1745,7 +1758,7 @@ else if (ck.startsWith("'") && ck.endsWith("'")) {
+ "关键字必须全大写,且以空格分隔的参数,空格必须只有 1 个!其它情况不允许空格!");
}
} else {
- if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) {
+ 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 个空格!其它情况不允许空格!");
@@ -1818,12 +1831,35 @@ private String praseArgsSplitWithSpace(String mkes[]) {
}
//这里为什么还要做一次判断 是因为解析窗口函数调用的时候会判断一次
- if (isPrepared()) {
- if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) {
- throw new IllegalArgumentException("字符 " + origin + " 不合法!"
+ String ck = origin;
+ // 如果参数包含 "`" 或 "'" ,解析字符串
+ if (ck.startsWith("`") && ck.endsWith("`")) {
+ origin = ck.substring(1, ck.length() - 1);
+ if (origin.startsWith("_") || StringUtil.isName(origin) == false) {
+ throw new IllegalArgumentException("字符 " + ck + " 不合法!"
+ + "预编译模式下 @column:\"`column0`,`column1`:alias;function0(arg0,arg1,...);function1(...):alias...\""
+ + " 中所有字符串 column 都必须必须为1个单词 !");
+ }
+
+ mkes[j] = getKey(origin).toString();
+ continue;
+ }
+ else if (ck.startsWith("'") && ck.endsWith("'")) {
+ origin = ck.substring(1, ck.length() - 1);
+ if (origin.contains("'")) {
+ throw new IllegalArgumentException("字符串 " + ck + " 不合法!"
+ "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\""
- + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!");
+ + " 中字符串参数不合法,必须以 ' 开头, ' 结尾,字符串中不能包含 ' ");
}
+
+ // 1.字符串不是字段也没有别名,所以不解析别名 2. 是字符串,进行预编译,使用getValue() ,对字符串进行截取
+ mkes[j] = getValue(origin).toString();
+ continue;
+ }
+ else if (ck.contains("`") || ck.contains("'") || origin.startsWith("_") || origin.contains("--")) { // || PATTERN_FUNCTION.matcher(origin).matches() == false) {
+ throw new IllegalArgumentException("字符 " + origin + " 不合法!"
+ + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\""
+ + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!");
}
boolean isName = false;
From 0ca17e631fb851090adb6a8e8376973839871dd1 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 25 Oct 2021 23:06:51 +0800
Subject: [PATCH 129/754] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=A4=9A=E4=BD=99?=
=?UTF-8?q?=E4=B8=94=E6=9C=AA=E5=AE=9E=E9=99=85=E7=94=A8=E4=B8=8A=E7=9A=84?=
=?UTF-8?q?=E7=9A=84=20Response.java?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../main/java/apijson/orm/AbstractVerifier.java | 2 --
.../src/main/java/apijson/orm/model/Response.java | 15 ---------------
2 files changed, 17 deletions(-)
delete mode 100755 APIJSONORM/src/main/java/apijson/orm/model/Response.java
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java
index cf8dd8954..d983da6ae 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java
@@ -63,7 +63,6 @@
import apijson.orm.model.PgAttribute;
import apijson.orm.model.PgClass;
import apijson.orm.model.Request;
-import apijson.orm.model.Response;
import apijson.orm.model.SysColumn;
import apijson.orm.model.SysTable;
import apijson.orm.model.Table;
@@ -149,7 +148,6 @@ public abstract class AbstractVerifier implements Verifier, IdCallback {
SYSTEM_ACCESS_MAP.put(Access.class.getSimpleName(), getAccessMap(Access.class.getAnnotation(MethodAccess.class)));
SYSTEM_ACCESS_MAP.put(Function.class.getSimpleName(), getAccessMap(Function.class.getAnnotation(MethodAccess.class)));
SYSTEM_ACCESS_MAP.put(Request.class.getSimpleName(), getAccessMap(Request.class.getAnnotation(MethodAccess.class)));
- SYSTEM_ACCESS_MAP.put(Response.class.getSimpleName(), getAccessMap(Response.class.getAnnotation(MethodAccess.class)));
if (Log.DEBUG) {
SYSTEM_ACCESS_MAP.put(Table.class.getSimpleName(), getAccessMap(Table.class.getAnnotation(MethodAccess.class)));
diff --git a/APIJSONORM/src/main/java/apijson/orm/model/Response.java b/APIJSONORM/src/main/java/apijson/orm/model/Response.java
deleted file mode 100755
index f5dbc3926..000000000
--- a/APIJSONORM/src/main/java/apijson/orm/model/Response.java
+++ /dev/null
@@ -1,15 +0,0 @@
-/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
-
-This source code is licensed under the Apache License Version 2.0.*/
-
-
-package apijson.orm.model;
-
-import apijson.MethodAccess;
-
-/**结果处理
- * @author Lemon
- */
-@MethodAccess(POST = {}, PUT = {}, DELETE = {})
-public class Response {
-}
From 3c92e777b54ea2a0ffb126dd40cb47685ae1a9eb Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Tue, 26 Oct 2021 00:02:40 +0800
Subject: [PATCH 130/754] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index ffd4d057f..6588a07cb 100644
--- a/README.md
+++ b/README.md
@@ -451,7 +451,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
[apijson-learn](https://github.com/rainboy-learn/apijson-learn) APIJSON 学习笔记和源码解析
-[apijson-practice](https://github.com/vcoolwind/apijson-practice) 实践一下 apijson,对做管理平台还是能有不少提效的
+[apijson-practice](https://github.com/vcoolwind/apijson-practice) BAT 技术专家开源的 APIJSON 参数校验注解 Library 及相关 Demo
[apijson-sample](https://gitee.com/greyzeng/apijson-sample) APIJSON 简单使用 Demo 及教程
From aaf38e10e2cc8795eaed46ad91a9eb31eacb50b1 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Tue, 26 Oct 2021 00:16:45 +0800
Subject: [PATCH 131/754] =?UTF-8?q?=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE?=
=?UTF-8?q?=E6=96=B0=E5=A2=9E=20apijson-db2=20=E5=92=8C=20ClickHouse=20Dem?=
=?UTF-8?q?o=20APIJSONDemo?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
APIJSON 接入 IBM 数据库 DB2 的 Demo: https://github.com/andream7/apijson-db2
APIJSON 接入 ClickHouse 的 Demo:https://github.com/qiujunlin/APIJSONDemo
---
README.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/README.md b/README.md
index 6588a07cb..a0669b22e 100644
--- a/README.md
+++ b/README.md
@@ -465,6 +465,10 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
[ApiJsonByJFinal](https://gitee.com/zhiyuexin/ApiJsonByJFinal) 整合 APIJSON 和 JFinal 的 Demo
+[apijson-db2](https://github.com/andream7/apijson-db2) APIJSON 接入 IBM 数据库 DB2 的 Demo
+
+[APIJSONDemo](https://github.com/qiujunlin/APIJSONDemo) APIJSON 接入 ClickHouse 的 Demo
+
[APIJSONDemo_ClickHouse](https://github.com/chenyanlann/APIJSONDemo_ClickHouse) APIJSON + SpringBoot 连接 ClickHouse 使用的 Demo
[apijson-builder](https://github.com/pengxianggui/apijson-builder) 一个方便为 APIJSON 构建 RESTful 请求的 JavaScript 库
From b4de2c2ee3de4436602b1c90196e83aa0bf54e3e Mon Sep 17 00:00:00 2001
From: abigeater
Date: Tue, 2 Nov 2021 17:57:39 +0800
Subject: [PATCH 132/754] =?UTF-8?q?add=20=E5=A2=9E=E5=8A=A0=E7=94=9F?=
=?UTF-8?q?=E6=80=81=E9=A1=B9=E7=9B=AEapijson-hyperf?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/README.md b/README.md
index a0669b22e..a3465b33d 100644
--- a/README.md
+++ b/README.md
@@ -439,6 +439,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
[apijson-php](https://github.com/qq547057827/apijson-php) PHP 版 APIJSON,基于 ThinkPHP,支持 MySQL, PostgreSQL, SQL Server, Oracle 等
+[apijson-hyperf](https://github.com/kvnZero/hyperf-APIJSON.git) PHP 版 APIJSON,基于 Hyperf 支持 MySQL
+
[apijson-node](https://github.com/kevinaskin/apijson-node) Node.ts 版 APIJSON,提供 nestjs 和 typeorm 的 Demo,由字节跳动工程师开发
[uliweb-apijson](https://github.com/zhangchunlin/uliweb-apijson) Python 版 APIJSON,支持 MySQL, PostgreSQL, SQL Server, Oracle, SQLite 等
From 147cb7a3c8c0e677a66cacf3f9000c24f1d7ccbf Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Tue, 2 Nov 2021 21:19:42 +0800
Subject: [PATCH 133/754] =?UTF-8?q?=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE?=
=?UTF-8?q?=E6=96=B0=E5=A2=9E=20PHP=20=E7=89=88=20APIJSON=20=E5=8F=AB=20ap?=
=?UTF-8?q?ijson-hyperf=EF=BC=8C=E6=84=9F=E8=B0=A2=E4=BD=9C=E8=80=85?=
=?UTF-8?q?=E7=9A=84=E8=B4=A1=E7=8C=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
https://github.com/kvnZero/hyperf-APIJSON
---
README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index a3465b33d..dd8d36af8 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ This source code is licensed under the Apache License Version 2.0
-
+
@@ -435,12 +435,12 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
[apijson-go](https://gitee.com/tiangao/apijson-go) Go 版 APIJSON ,支持单表查询、数组查询、多表一对一关联查询、多表一对多关联查询 等
+[apijson-hyperf](https://github.com/kvnZero/hyperf-APIJSON.git) PHP 版 APIJSON,基于 Hyperf 支持 MySQL
+
[APIJSON-php](https://github.com/xianglong111/APIJSON-php) PHP 版 APIJSON,基于 ThinkPHP,支持 MySQL, PostgreSQL, SQL Server, Oracle 等
[apijson-php](https://github.com/qq547057827/apijson-php) PHP 版 APIJSON,基于 ThinkPHP,支持 MySQL, PostgreSQL, SQL Server, Oracle 等
-[apijson-hyperf](https://github.com/kvnZero/hyperf-APIJSON.git) PHP 版 APIJSON,基于 Hyperf 支持 MySQL
-
[apijson-node](https://github.com/kevinaskin/apijson-node) Node.ts 版 APIJSON,提供 nestjs 和 typeorm 的 Demo,由字节跳动工程师开发
[uliweb-apijson](https://github.com/zhangchunlin/uliweb-apijson) Python 版 APIJSON,支持 MySQL, PostgreSQL, SQL Server, Oracle, SQLite 等
From 914e22dc5e54e6ba245798437866b8057284016f Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 11 Nov 2021 19:57:36 +0800
Subject: [PATCH 134/754] =?UTF-8?q?=E6=8A=A5=E9=94=99=E4=BF=A1=E6=81=AF?=
=?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=90=9C=E7=B4=A2=E9=93=BE=E6=8E=A5=E5=8F=8A?=
=?UTF-8?q?=E5=B8=A6=E7=B3=BB=E7=BB=9F=E4=BF=A1=E6=81=AF=E7=9A=84=E6=8F=90?=
=?UTF-8?q?=E4=BA=A4=E9=97=AE=E9=A2=98=E6=A8=A1=E6=9D=BF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
APIJSONORM/src/main/java/apijson/Log.java | 3 +
.../apijson/orm/AbstractObjectParser.java | 50 ++++++++++++--
.../main/java/apijson/orm/AbstractParser.java | 66 ++++++++++++++++++-
3 files changed, 111 insertions(+), 8 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/Log.java b/APIJSONORM/src/main/java/apijson/Log.java
index e3f64f86d..d67f19a71 100755
--- a/APIJSONORM/src/main/java/apijson/Log.java
+++ b/APIJSONORM/src/main/java/apijson/Log.java
@@ -13,6 +13,9 @@
public class Log {
public static boolean DEBUG = true;
+
+ public static final String VERSION = "4.8.0";
+ public static final String KEY_SYSTEM_INFO_DIVIDER = "---|-----APIJSON SYSTEM INFO-----|---";
//默认的时间格式
public static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS");
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
index 537eea16f..b35d0c5a4 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
@@ -116,7 +116,7 @@ public AbstractObjectParser(@NotNull JSONObject request, String parentPath, SQLC
public String getParentPath() {
return parentPath;
}
-
+
@Override
public AbstractObjectParser setParentPath(String parentPath) {
this.parentPath = parentPath;
@@ -284,6 +284,42 @@ else if (method == PUT && value instanceof JSONArray && (whereList == null || wh
}
} catch (Exception e) {
if (tri == false) {
+ if (Log.DEBUG && sqlConfig != null && e.getMessage().contains(Log.KEY_SYSTEM_INFO_DIVIDER) == false) {
+ try {
+ String db = sqlConfig.getDatabase();
+ if (db == null) {
+ if (sqlConfig.isPostgreSQL()) {
+ db = SQLConfig.DATABASE_POSTGRESQL;
+ }
+ else if (sqlConfig.isSQLServer()) {
+ db = SQLConfig.DATABASE_SQLSERVER;
+ }
+ else if (sqlConfig.isOracle()) {
+ db = SQLConfig.DATABASE_ORACLE;
+ }
+ else if (sqlConfig.isDb2()) {
+ db = SQLConfig.DATABASE_DB2;
+ }
+ else if (sqlConfig.isClickHouse()) {
+ db = SQLConfig.DATABASE_CLICKHOUSE;
+ }
+ else {
+ db = SQLConfig.DATABASE_MYSQL;
+ }
+ }
+
+ Class extends Exception> clazz = e.getClass();
+ e = clazz.getConstructor(String.class).newInstance(
+ e.getMessage()
+ + " " + Log.KEY_SYSTEM_INFO_DIVIDER + " \n**环境信息** "
+ + "\n系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version")
+ + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch")
+ + "\n数据库: " + db + " " + sqlConfig.getDBVersion()
+ + "\nAPIJSON: " + Log.VERSION
+ );
+ } catch (Throwable e2) {}
+ }
+
throw e; // 不忽略错误,抛异常
}
invalidate(); // 忽略错误,还原request
@@ -860,14 +896,14 @@ public JSONObject onSQLExecute() throws Exception {
boolean isSimpleArray = false;
List rawList = null;
-
+
if (isArrayMainTable && position == 0 && result != null) {
-
+
isSimpleArray = (functionMap == null || functionMap.isEmpty())
&& (customMap == null || customMap.isEmpty())
&& (childMap == null || childMap.isEmpty())
&& (table.equals(arrayTable));
-
+
// 提取并缓存数组主表的列表数据
rawList = (List) result.remove(SQLExecutor.KEY_RAW_LIST);
if (rawList != null) {
@@ -875,7 +911,7 @@ public JSONObject onSQLExecute() throws Exception {
if (isSimpleArray == false) {
long startTime = System.currentTimeMillis();
-
+
for (int i = 1; i < rawList.size(); i++) { // 从 1 开始,0 已经处理过
JSONObject obj = rawList.get(i);
@@ -883,7 +919,7 @@ public JSONObject onSQLExecute() throws Exception {
parser.putQueryResult(arrayPath + "/" + i + "/" + name, obj); // 解决获取关联数据时requestObject里不存在需要的关联数据
}
}
-
+
long endTime = System.currentTimeMillis(); // 3ms - 8ms
Log.e(TAG, "\n onSQLExecute <<<<<<<<<<<<<<<<<<<<<<<<<<<<\n for (int i = 1; i < list.size(); i++) startTime = " + startTime
+ "; endTime = " + endTime + "; duration = " + (endTime - startTime) + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n ");
@@ -895,7 +931,7 @@ public JSONObject onSQLExecute() throws Exception {
if (isSubquery == false && result != null) {
parser.putQueryResult(path, result); // 解决获取关联数据时requestObject里不存在需要的关联数据
-
+
if (isSimpleArray && rawList != null) {
result.put(SQLExecutor.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 b4de3e7c1..5210e58b6 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
@@ -9,6 +9,7 @@
import static apijson.RequestMethod.GET;
import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Savepoint;
@@ -641,8 +642,35 @@ public static JSONObject newSuccessResult() {
* @return
*/
public static JSONObject extendErrorResult(JSONObject object, Exception e) {
+ String msg = e.getMessage();
+
+ if (Log.DEBUG) {
+ try {
+ int index = msg.lastIndexOf(Log.KEY_SYSTEM_INFO_DIVIDER);
+ String info = index >= 0 ? msg.substring(index + Log.KEY_SYSTEM_INFO_DIVIDER.length()).trim()
+ : "\n**环境信息** "
+ + "\n系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version")
+ + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch")
+ + "\n数据库: "
+ + "\nAPIJSON: " + Log.VERSION;
+
+ msg = index < 0 ? msg : msg.substring(0, index).trim();
+ String encodedMsg = URLEncoder.encode(msg, "UTF-8");
+
+ msg += " \n\n\n浏览器打开以下链接搜索答案\nGitHub: https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+" + encodedMsg
+ + " \n\nGoogle:https://www.google.com/search?q=" + encodedMsg
+ + " \n\nBaidu:https://www.baidu.com/s?ie=UTF-8&wd=" + encodedMsg
+ + " \n\n都没找到答案?打开这个链接 https://github.com/Tencent/APIJSON/issues/new?assignees=&labels=&template=--bug.md "
+ + "\n然后提交问题,推荐用以下模板修改,注意要换行保持清晰可读。"
+ + "\n【标题】:" + msg
+ + "\n【内容】:" + info + "\n\n**问题描述**\n" + msg;
+ } catch (Throwable e2) {
+ e2.printStackTrace();
+ }
+ }
+
JSONObject error = newErrorResult(e);
- return extendResult(object, error.getIntValue(JSONResponse.KEY_CODE), error.getString(JSONResponse.KEY_MSG));
+ return extendResult(object, error.getIntValue(JSONResponse.KEY_CODE), msg);
}
/**新建错误状态内容
* @param e
@@ -1682,6 +1710,42 @@ public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Except
return result;
}
catch (Exception e) {
+ if (Log.DEBUG && e.getMessage().contains(Log.KEY_SYSTEM_INFO_DIVIDER) == false) {
+ try {
+ String db = config.getDatabase();
+ if (db == null) {
+ if (config.isPostgreSQL()) {
+ db = SQLConfig.DATABASE_POSTGRESQL;
+ }
+ else if (config.isSQLServer()) {
+ db = SQLConfig.DATABASE_SQLSERVER;
+ }
+ else if (config.isOracle()) {
+ db = SQLConfig.DATABASE_ORACLE;
+ }
+ else if (config.isDb2()) {
+ db = SQLConfig.DATABASE_DB2;
+ }
+ else if (config.isClickHouse()) {
+ db = SQLConfig.DATABASE_CLICKHOUSE;
+ }
+ else {
+ db = SQLConfig.DATABASE_MYSQL;
+ }
+ }
+
+ Class extends Exception> clazz = e.getClass();
+ e = clazz.getConstructor(String.class).newInstance(
+ e.getMessage()
+ + " " + Log.KEY_SYSTEM_INFO_DIVIDER + " \n**环境信息** "
+ + "\n系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version")
+ + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch")
+ + "\n数据库: " + db + " " + config.getDBVersion()
+ + "\nAPIJSON: " + Log.VERSION
+ );
+ } catch (Throwable e2) {}
+ }
+
if (Log.DEBUG == false && e instanceof SQLException) {
throw new SQLException("数据库驱动执行异常SQLException,非 Log.DEBUG 模式下不显示详情,避免泄漏真实模式名、表名等隐私信息", e);
}
From c397c82584daede3ce1237618174ca2ad744514a Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 11 Nov 2021 23:01:35 +0800
Subject: [PATCH 135/754] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=8A=A5=E9=94=99?=
=?UTF-8?q?=E6=8F=90=E7=A4=BA=EF=BC=8C=E5=BC=95=E5=AF=BC=E7=94=A8=E6=88=B7?=
=?UTF-8?q?=E8=87=AA=E8=A1=8C=E8=A7=A3=E5=86=B3=E5=8F=8A=E6=8F=90=E4=BA=A4?=
=?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=9B=E8=A7=A3=E5=86=B3=20AbstractVerifie?=
=?UTF-8?q?r.verifyAccess=20=E5=8F=AA=E5=85=81=E8=AE=B8=20Number=20?=
=?UTF-8?q?=E7=B1=BB=E5=9E=8B=E7=9A=84=20id=EF=BC=8C=E5=B9=B6=E4=BC=98?=
=?UTF-8?q?=E5=8C=96=E5=8F=98=E9=87=8F=E5=90=8D=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../apijson/orm/AbstractObjectParser.java | 2 +-
.../main/java/apijson/orm/AbstractParser.java | 125 +++++++++++++-----
.../java/apijson/orm/AbstractVerifier.java | 22 +--
3 files changed, 107 insertions(+), 42 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
index b35d0c5a4..453db9c5f 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
@@ -313,8 +313,8 @@ else if (sqlConfig.isClickHouse()) {
e.getMessage()
+ " " + Log.KEY_SYSTEM_INFO_DIVIDER + " \n**环境信息** "
+ "\n系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version")
- + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch")
+ "\n数据库: " + db + " " + sqlConfig.getDBVersion()
+ + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch")
+ "\nAPIJSON: " + Log.VERSION
);
} catch (Throwable e2) {}
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
index 5210e58b6..65b79cea9 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
@@ -9,6 +9,8 @@
import static apijson.RequestMethod.GET;
import java.io.UnsupportedEncodingException;
+import java.lang.management.ManagementFactory;
+import java.net.InetAddress;
import java.net.URLEncoder;
import java.sql.Connection;
import java.sql.SQLException;
@@ -26,6 +28,9 @@
import java.util.concurrent.TimeoutException;
import javax.activation.UnsupportedDataTypeException;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.management.Query;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
@@ -153,6 +158,15 @@ public AbstractParser setTag(String tag) {
return this;
}
+ protected String requestURL;
+ public String getRequestURL() {
+ return requestURL;
+ }
+ public AbstractParser setRequestURL(String requestURL) {
+ this.requestURL = requestURL;
+ return this;
+ }
+
protected JSONObject requestObject;
@Override
public JSONObject getRequest() {
@@ -354,7 +368,7 @@ public JSONObject parseResponse(JSONObject request) {
onVerifyContent();
}
} catch (Exception e) {
- return extendErrorResult(requestObject, e);
+ return extendErrorResult(requestObject, e, requestMethod, getRequestURL());
}
}
@@ -364,7 +378,7 @@ public JSONObject parseResponse(JSONObject request) {
setGlobleRole(requestObject.getString(JSONRequest.KEY_ROLE));
requestObject.remove(JSONRequest.KEY_ROLE);
} catch (Exception e) {
- return extendErrorResult(requestObject, e);
+ return extendErrorResult(requestObject, e, requestMethod, getRequestURL());
}
}
@@ -383,7 +397,7 @@ public JSONObject parseResponse(JSONObject request) {
requestObject.remove(JSONRequest.KEY_EXPLAIN);
requestObject.remove(JSONRequest.KEY_CACHE);
} catch (Exception e) {
- return extendErrorResult(requestObject, e);
+ return extendErrorResult(requestObject, e, requestMethod, getRequestURL());
}
final String requestString = JSON.toJSONString(request);//request传进去解析后已经变了
@@ -407,7 +421,7 @@ public JSONObject parseResponse(JSONObject request) {
onRollback();
}
- requestObject = error == null ? extendSuccessResult(requestObject) : extendErrorResult(requestObject, error);
+ requestObject = error == null ? extendSuccessResult(requestObject) : extendErrorResult(requestObject, error, requestMethod, getRequestURL());
JSONObject res = (globleFormat != null && globleFormat) && JSONResponse.isSuccess(requestObject) ? new JSONResponse(requestObject) : requestObject;
@@ -608,8 +622,9 @@ public static JSONObject extendResult(JSONObject object, int code, String msg) {
if (object == null) {
object = new JSONObject(true);
}
+ boolean isOk = JSONResponse.isSuccess(code);
if (object.containsKey(JSONResponse.KEY_OK) == false) {
- object.put(JSONResponse.KEY_OK, JSONResponse.isSuccess(code));
+ object.put(JSONResponse.KEY_OK, isOk);
}
if (object.containsKey(JSONResponse.KEY_CODE) == false) {
object.put(JSONResponse.KEY_CODE, code);
@@ -619,6 +634,7 @@ public static JSONObject extendResult(JSONObject object, int code, String msg) {
if (m.isEmpty() == false) {
msg = m + " ;\n " + StringUtil.getString(msg);
}
+
object.put(JSONResponse.KEY_MSG, msg);
return object;
}
@@ -642,36 +658,84 @@ public static JSONObject newSuccessResult() {
* @return
*/
public static JSONObject extendErrorResult(JSONObject object, Exception e) {
+ return extendErrorResult(object, e, null, null);
+ }
+ /**添加请求成功的状态内容
+ * @param object
+ * @return
+ */
+ public static JSONObject extendErrorResult(JSONObject object, Exception e, RequestMethod requestMethod, String url) {
String msg = e.getMessage();
-
+
if (Log.DEBUG) {
try {
int index = msg.lastIndexOf(Log.KEY_SYSTEM_INFO_DIVIDER);
String info = index >= 0 ? msg.substring(index + Log.KEY_SYSTEM_INFO_DIVIDER.length()).trim()
: "\n**环境信息** "
+ "\n系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version")
- + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch")
+ "\n数据库: "
+ + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch")
+ "\nAPIJSON: " + Log.VERSION;
msg = index < 0 ? msg : msg.substring(0, index).trim();
String encodedMsg = URLEncoder.encode(msg, "UTF-8");
+
+ if (StringUtil.isEmpty(url, true)) {
+ String host = "localhost";
+ try {
+ host = InetAddress.getLocalHost().getHostAddress();
+ } catch (Throwable e2) {}
+
+ String port = "8080";
+ try {
+ MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
+
+ Set objectNames = beanServer.queryNames(
+ new ObjectName("*:type=Connector,*"),
+ Query.match(Query.attr("protocol"), Query.value("HTTP/1.1"))
+ );
+ String p = objectNames.iterator().next().getKeyProperty("port");
+ port = StringUtil.isEmpty(p, true) ? port : p;
+ } catch (Throwable e2) {}
+
+ url = "http://" + host + ":" + port + "/" + (requestMethod == null ? RequestMethod.GET : requestMethod).name().toLowerCase();
+ }
+
+ String req = JSON.toJSONString(object);
+ try {
+ req = URLEncoder.encode(req, "UTF-8");
+ } catch (Throwable e2) {}
+
+
+ boolean isSQLException = e instanceof SQLException; // SQL 报错一般都是通用问题,优先搜索引擎
+ String apiatuoAndGitHubLink = "\n【APIAuto】: \n http://apijson.cn/api?type=JSON&url=" + URLEncoder.encode(url, "UTF-8") + "&json=" + req
+ + " \n\n【GitHub】: \n https://www.google.com/search?q=site%3Agithub.com%2FTencent%2FAPIJSON+++" + encodedMsg;
- msg += " \n\n\n浏览器打开以下链接搜索答案\nGitHub: https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+" + encodedMsg
- + " \n\nGoogle:https://www.google.com/search?q=" + encodedMsg
- + " \n\nBaidu:https://www.baidu.com/s?ie=UTF-8&wd=" + encodedMsg
- + " \n\n都没找到答案?打开这个链接 https://github.com/Tencent/APIJSON/issues/new?assignees=&labels=&template=--bug.md "
+ msg += " \n\n\n浏览器打开以下链接查看解答"
+ + (isSQLException ? "" : apiatuoAndGitHubLink)
+ // GitHub Issue 搜索貌似是精准包含,不易找到答案 + " \n\nGitHub: \n https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+" + encodedMsg
+ + " \n\n【Google】:\n https://www.google.com/search?q=" + encodedMsg
+ + " \n\n【百度】:\n https://www.baidu.com/s?ie=UTF-8&wd=" + encodedMsg
+ + (isSQLException ? apiatuoAndGitHubLink : "")
+ + " \n\n都没找到答案?打开这个链接 \n https://github.com/Tencent/APIJSON/issues/new?assignees=&labels=&template=--bug.md "
+ "\n然后提交问题,推荐用以下模板修改,注意要换行保持清晰可读。"
+ "\n【标题】:" + msg
- + "\n【内容】:" + info + "\n\n**问题描述**\n" + msg;
- } catch (Throwable e2) {
- e2.printStackTrace();
- }
+ + "\n【内容】:" + info + "\n\n**问题描述**\n" + msg
+ + "\n\n"
+ + "\n\nPOST " + url
+ + "\n请求 Request JSON:\n ```js"
+ + "\n 请填写,例如 { \"Users\":{} }"
+ + "\n```"
+ + "\n\n返回结果 Response JSON:\n ```js"
+ + "\n 请填写,例如 { \"Users\": {}, \"code\": 401, \"msg\": \"Users 不允许 UNKNOWN 用户的 GET 请求!\" }"
+ + "\n```";
+ } catch (Throwable e2) {}
}
-
+
JSONObject error = newErrorResult(e);
return extendResult(object, error.getIntValue(JSONResponse.KEY_CODE), msg);
}
+
/**新建错误状态内容
* @param e
* @return
@@ -1000,7 +1064,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 Integer count = request.getInteger(JSONRequest.KEY_COUNT); //TODO 如果不想用默认数量可以改成 getIntValue(JSONRequest.KEY_COUNT);
- final int page = request.getIntValue(JSONRequest.KEY_PAGE);
+ final Integer page = request.getInteger(JSONRequest.KEY_PAGE);
final Object join = request.get(JSONRequest.KEY_JOIN);
int query2;
@@ -1026,8 +1090,9 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name
}
}
+ int page2 = page == null ? 0 : page;
int maxPage = getMaxQueryPage();
- if (page < 0 || page > maxPage) {
+ if (page2 < 0 || page2 > maxPage) {
throw new IllegalArgumentException(path + "/" + JSONRequest.KEY_PAGE + ":value 中 value 的值不合法!必须在 0-" + maxPage + " 内 !");
}
@@ -1052,8 +1117,8 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name
JSONArray response = null;
try {
- int size = count2 == 0 ? max : count2;//count为每页数量,size为第page页实际数量,max(size) = count
- Log.d(TAG, "onArrayParse size = " + size + "; page = " + page);
+ int size = count2 == 0 ? max : count2; //count为每页数量,size为第page页实际数量,max(size) = count
+ Log.d(TAG, "onArrayParse size = " + size + "; page = " + page2);
//key[]:{Table:{}}中key equals Table时 提取Table
@@ -1076,13 +1141,13 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { //
SQLConfig config = createSQLConfig()
.setMethod(requestMethod)
.setCount(size)
- .setPage(page)
+ .setPage(page2)
.setQuery(query2)
.setTable(arrTableKey)
.setJoinList(onJoinParse(join, request));
JSONObject parent;
-
+
boolean isExtract = true;
//生成size个
@@ -1091,7 +1156,7 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { //
if (parent == null || parent.isEmpty()) {
break;
}
-
+
long startTime = System.currentTimeMillis();
/* 这里优化了 Table[]: { Table:{} } 这种情况下的性能
@@ -1101,13 +1166,13 @@ 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);
-
+
if (list != null && list.isEmpty() == false) {
isExtract = false;
-
+
list.set(0, fo); // 不知道为啥第 0 项也加了 @RAW@LIST
response.addAll(list); // List cannot match List response = new JSONArray(list);
-
+
long endTime = System.currentTimeMillis(); // 0ms
Log.d(TAG, "\n onArrayParse <<<<<<<<<<<<<<<<<<<<<<<<<<<<\n for (int i = 0; i < (isSubquery ? 1 : size); i++) "
+ " startTime = " + startTime + "; endTime = " + endTime + "; duration = " + (endTime - startTime) + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
@@ -1117,7 +1182,7 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { //
//key[]:{Table:{}}中key equals Table时 提取Table
response.add(getValue(parent, childKeys)); //null有意义
}
-
+
//Table>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
@@ -1143,7 +1208,7 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { //
if (fo instanceof Boolean || fo instanceof Number || fo instanceof String) { //[{}] 和 [[]] 都没意义
putQueryResult(path, response);
}
-
+
long endTime = System.currentTimeMillis();
Log.d(TAG, "\n onArrayParse <<<<<<<<<<<<<<<<<<<<<<<<<<<<\n isExtract >> putQueryResult "
+ " startTime = " + startTime + "; endTime = " + endTime + "; duration = " + (endTime - startTime) + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
@@ -1739,13 +1804,13 @@ else if (config.isClickHouse()) {
e.getMessage()
+ " " + Log.KEY_SYSTEM_INFO_DIVIDER + " \n**环境信息** "
+ "\n系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version")
- + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch")
+ "\n数据库: " + db + " " + config.getDBVersion()
+ + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch")
+ "\nAPIJSON: " + Log.VERSION
);
} catch (Throwable e2) {}
}
-
+
if (Log.DEBUG == false && e instanceof SQLException) {
throw new SQLException("数据库驱动执行异常SQLException,非 Log.DEBUG 模式下不显示详情,避免泄漏真实模式名、表名等隐私信息", e);
}
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java
index d983da6ae..1f366d0c0 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java
@@ -268,7 +268,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception {
//验证角色,假定真实强制匹配<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
- String visitorIdkey = getVisitorIdKey(config);
+ String visitorIdKey = getVisitorIdKey(config);
Object requestId;
switch (role) {
@@ -285,9 +285,9 @@ public boolean verifyAccess(SQLConfig config) throws Exception {
}
//key!{}:[] 或 其它没有明确id的条件 等 可以和key{}:list组合。类型错误就报错
- requestId = (Number) config.getWhere(visitorIdkey, true);//JSON里数值不能保证是Long,可能是Integer
+ requestId = config.getWhere(visitorIdKey, true);//JSON里数值不能保证是Long,可能是Integer
@SuppressWarnings("unchecked")
- Collection requestIdArray = (Collection) config.getWhere(visitorIdkey + "{}", true);//不能是 &{}, |{} 不要传,直接{}
+ Collection requestIdArray = (Collection) config.getWhere(visitorIdKey + "{}", true);//不能是 &{}, |{} 不要传,直接{}
if (requestId != null) {
if (requestIdArray == null) {
requestIdArray = new JSONArray();
@@ -296,7 +296,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception {
}
if (requestIdArray == null) {//可能是@得到 || requestIdArray.isEmpty()) {//请求未声明key:id或key{}:[...]条件,自动补全
- config.putWhere(visitorIdkey+"{}", JSON.parseArray(list), true); //key{}:[]有效,SQLConfig里throw NotExistException
+ config.putWhere(visitorIdKey+"{}", JSON.parseArray(list), true); //key{}:[]有效,SQLConfig里throw NotExistException
}
else {//请求已声明key:id或key{}:[]条件,直接验证
for (Object id : requestIdArray) {
@@ -307,7 +307,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception {
throw new UnsupportedDataTypeException(table + ".id类型错误,id类型必须是Long!");
}
if (list.contains(Long.valueOf("" + id)) == false) {//Integer等转为Long才能正确判断。强转崩溃
- throw new IllegalAccessException(visitorIdkey + " = " + id + " 的 " + table
+ throw new IllegalAccessException(visitorIdKey + " = " + id + " 的 " + table
+ " 不允许 " + role + " 用户的 " + method.name() + " 请求!");
}
}
@@ -321,20 +321,20 @@ public boolean verifyAccess(SQLConfig config) throws Exception {
throw new IllegalArgumentException("POST 请求必须在Table内设置要保存的 key:value !");
}
- int index = c.indexOf(visitorIdkey);
+ int index = c.indexOf(visitorIdKey);
if (index >= 0) {
Object oid;
for (List ovl : ovs) {
oid = ovl == null || index >= ovl.size() ? null : ovl.get(index);
if (oid == null || StringUtil.getString(oid).equals("" + visitorId) == false) {
- throw new IllegalAccessException(visitorIdkey + " = " + oid + " 的 " + table
+ throw new IllegalAccessException(visitorIdKey + " = " + oid + " 的 " + table
+ " 不允许 " + role + " 用户的 " + method.name() + " 请求!");
}
}
}
else {
List nc = new ArrayList<>(c);
- nc.add(visitorIdkey);
+ nc.add(visitorIdKey);
config.setColumn(nc);
List> nvs = new ArrayList<>();
@@ -349,13 +349,13 @@ public boolean verifyAccess(SQLConfig config) throws Exception {
}
}
else {
- requestId = config.getWhere(visitorIdkey, true);//JSON里数值不能保证是Long,可能是Integer
+ requestId = config.getWhere(visitorIdKey, true);//JSON里数值不能保证是Long,可能是Integer
if (requestId != null && StringUtil.getString(requestId).equals(StringUtil.getString(visitorId)) == false) {
- throw new IllegalAccessException(visitorIdkey + " = " + requestId + " 的 " + table
+ throw new IllegalAccessException(visitorIdKey + " = " + requestId + " 的 " + table
+ " 不允许 " + role + " 用户的 " + method.name() + " 请求!");
}
- config.putWhere(visitorIdkey, visitorId, true);
+ config.putWhere(visitorIdKey, visitorId, true);
}
break;
case ADMIN://这里不好做,在特定接口内部判。 可以是 /get/admin + 固定秘钥 Parser#needVerify,之后全局跳过验证
From 0ffe78c9d9c60c0144a5d8bf99a3763382e62616 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 12 Nov 2021 05:58:05 +0800
Subject: [PATCH 136/754] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?=
=?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
index 7ebab3f40..93279022b 100644
--- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
+++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
@@ -148,7 +148,7 @@ https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSOND
### C-1-2-1.更多测试用例
如果需要更多测试用例,请按照以下步骤获取:
-1、在浏览器中输入 apijson.org;
+1、在浏览器中输入 apijson.cn;
2、点击右上角的“登录”按钮登录;
3、点击“测试账号”按钮左边第二个按钮。(也就是“-”左边的第一个)获取各种测试用例
4、欢迎大家踊跃共享自己的测试用例;
From 0a5b950dbca681aef80f4bff5d0b1a3d3507ef2d Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 15 Nov 2021 00:39:08 +0800
Subject: [PATCH 137/754] 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 331ed25b2..eeceb8783 100644
--- a/README-English.md
+++ b/README-English.md
@@ -331,7 +331,7 @@ https://github.com/Tencent/APIJSON/issues/187
[More APIJSON Users](https://github.com/Tencent/APIJSON/issues/73)
### Contributers of APIJSON:
-Contributers for the APIJSON core project(6 Tencent engineers、1 Zhihu architect、1 YTO Express engineer, etc.):
+Contributers for the APIJSON core project(6 Tencent engineers, 1 Zhihu architect, 1 YTO Express engineer, etc.):
https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
-Authors of other projects for ecosystem of APIJSON(2 Tencent engineers、1 Bytedance(TikTok) engineer, etc.):
+Authors of other projects for ecosystem of APIJSON(2 Tencent engineers, 1 BAT(Baidu/Alibaba/Tencent) specialist, 1 Bytedance(TikTok) engineer, etc.):
https://github.com/search?o=desc&q=apijson&s=stars&type=Repositories
https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count
From f044d5f903e6a109014ab438b23a36689340b705 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 15 Nov 2021 00:41:42 +0800
Subject: [PATCH 138/754] Update Document.md
---
Document.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Document.md b/Document.md
index 5da8589ae..73f262b15 100644
--- a/Document.md
+++ b/Document.md
@@ -5,7 +5,7 @@ https://github.com/Tencent/APIJSON

-后端开发者可以先看 [图文入门教程1](https://vincentcheng.github.io/apijson-doc/zh) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (都非官方,和本文档有出入的点以本文档为准。例如正则匹配 key? 已废弃,用 key~ 替代;例如 "@column":"store_id,sum(amt):totAmt" 中逗号 , 有误,应该用分号 ; 隔开 SQL 函数,改为 "@column":"store_id;sum(amt):totAmt")
+后端开发者可以先看 [图文入门教程1](http://apijson.cn/doc/zh/) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (和本文档有出入的点以本文档为准。例如正则匹配 key? 已废弃,用 key~ 替代;例如 "@column":"store_id,sum(amt):totAmt" 中逗号 , 有误,应该用分号 ; 隔开 SQL 函数,改为 "@column":"store_id;sum(amt):totAmt")
* ### [1.示例](#1)
* ### [2.对比传统方式](#2)
From 7e60828122961f7cd50fa29deec64c1814e04a85 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 15 Nov 2021 00:52:57 +0800
Subject: [PATCH 139/754] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index dd8d36af8..7b1908260 100644
--- a/README.md
+++ b/README.md
@@ -353,7 +353,7 @@ https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count
-[DB2](https://www.ibm.com/support/knowledgecenter/SSEPGG_11.1.0/com.ibm.db2.luw.sql.ref.doc/doc/r0059224.html), [Elasticsearch](https://www.elastic.co/cn/what-is/elasticsearch-sql), [ClickHouse](https://clickhouse.tech/docs/zh/sql-reference/syntax/), [OceanBase](https://www.oceanbase.com/docs/oceanbase/V2.2.50/ss-sr-select_daur3l), [Presto](https://prestodb.io/docs/current/admin/function-namespace-managers.html), [Spark](http://spark.apache.org/sql/), [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select)(延伸支持 Hadoop, Spark), [Phoenix](http://phoenix.apache.org/language/index.html#select)(延伸支持 HBase), [Presto/Trino](https://prestodb.io/docs/current/sql/select.html)(延伸支持 Redis, Hive, Kafka, Elasticsearch, Thrift, Cassandra, MySQL, PostgreSQL, Oracle, MongoDB...)
+[Elasticsearch](https://www.elastic.co/cn/what-is/elasticsearch-sql), [OceanBase](https://www.oceanbase.com/docs/oceanbase/V2.2.50/ss-sr-select_daur3l), [Presto](https://prestodb.io/docs/current/admin/function-namespace-managers.html), [Spark](http://spark.apache.org/sql/), [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select)(延伸支持 Hadoop, Spark), [Phoenix](http://phoenix.apache.org/language/index.html#select)(延伸支持 HBase), [Presto/Trino](https://prestodb.io/docs/current/sql/select.html)(延伸支持 Redis, Hive, Kafka, Elasticsearch, Thrift, Cassandra, MySQL, PostgreSQL, Oracle, MongoDB...)
### 我要赞赏
如果你喜欢 APIJSON,感觉 APIJSON 帮助到了你,可以点右上角 ⭐Star 支持一下,谢谢 ^_^
From 76c8885a9d47198fb07aee314944f4b39c91d0f0 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 15 Nov 2021 00:53:45 +0800
Subject: [PATCH 140/754] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 7b1908260..61e4709bb 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ This source code is licensed under the Apache License Version 2.0
-
+
From b85aed18b27e3ff130b6a6c6b707ceb80d666b23 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 15 Nov 2021 00:58:35 +0800
Subject: [PATCH 141/754] Update Roadmap.md
---
Roadmap.md | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/Roadmap.md b/Roadmap.md
index 74c3b4ca4..572fa41c1 100644
--- a/Roadmap.md
+++ b/Roadmap.md
@@ -7,7 +7,7 @@
### 新增功能
部分功能描述可在 [APIAuto](https://github.com/TommyLemon/APIAuto) 上查看
账号 13000002020 密码 123456
-http://apijson.org:8000/auto/
+http://apijson.cn:8000/api
##### 基本原则
1.一定要有相关的应用场景,不能是伪需求,最好举例说明
@@ -35,6 +35,7 @@ POST: 用不上,不处理
@having! 必须性不大,可通过反转内部条件来实现,但如果实现简单、且不影响原来的功能,则可以顺便加上。
#### 新增支持 @column!
+【更新:已提供字段插件 [apijson-column](https://github.com/APIJSON/apijson-column),支持 字段名映射 和 !key 反选字段。】
这个只在 [apijson-framework](https://github.com/APIJSON/apijson-framework) 支持,需要配置每个接口版本、每张表所拥有的全部字段,然后排除掉 @column! 的。
可新增一个 VersionedColumn 表记录来代替 HashMap 代码配置。
@@ -191,7 +192,8 @@ https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/
### 提高性能
-20200205 更新:最近的两次大幅提升性能相关优化及 Release
+20200205 更新:最近的及次大幅提升性能相关优化及 Release
+[4.8.0【性能】大幅提升提升单表数组查询性能](https://github.com/Tencent/APIJSON/releases/tag/4.8.0)
[4.6.0【性能】大幅提升数组内主表查询性能](https://github.com/Tencent/APIJSON/releases/tag/4.6.0)
[4.4.5【性能】大幅提升增删改的性能](https://github.com/Tencent/APIJSON/releases/tag/4.4.5)
From 1d9f0e24998e2c4bea2234bb214c694a7bb46905 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 15 Nov 2021 01:03:20 +0800
Subject: [PATCH 142/754] Update Roadmap.md
---
Roadmap.md | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/Roadmap.md b/Roadmap.md
index 572fa41c1..4a479cd2e 100644
--- a/Roadmap.md
+++ b/Roadmap.md
@@ -7,7 +7,7 @@
### 新增功能
部分功能描述可在 [APIAuto](https://github.com/TommyLemon/APIAuto) 上查看
账号 13000002020 密码 123456
-http://apijson.cn:8000/api
+http://apijson.cn/api
##### 基本原则
1.一定要有相关的应用场景,不能是伪需求,最好举例说明
@@ -35,7 +35,7 @@ POST: 用不上,不处理
@having! 必须性不大,可通过反转内部条件来实现,但如果实现简单、且不影响原来的功能,则可以顺便加上。
#### 新增支持 @column!
-【更新:已提供字段插件 [apijson-column](https://github.com/APIJSON/apijson-column),支持 字段名映射 和 !key 反选字段。】
+20210415 更新:已提供字段插件 [apijson-column](https://github.com/APIJSON/apijson-column),支持 字段名映射 和 !key 反选字段。
这个只在 [apijson-framework](https://github.com/APIJSON/apijson-framework) 支持,需要配置每个接口版本、每张表所拥有的全部字段,然后排除掉 @column! 的。
可新增一个 VersionedColumn 表记录来代替 HashMap 代码配置。
@@ -193,7 +193,6 @@ https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/
### 提高性能
20200205 更新:最近的及次大幅提升性能相关优化及 Release
-[4.8.0【性能】大幅提升提升单表数组查询性能](https://github.com/Tencent/APIJSON/releases/tag/4.8.0)
[4.6.0【性能】大幅提升数组内主表查询性能](https://github.com/Tencent/APIJSON/releases/tag/4.6.0)
[4.4.5【性能】大幅提升增删改的性能](https://github.com/Tencent/APIJSON/releases/tag/4.4.5)
@@ -231,7 +230,7 @@ https://github.com/Tencent/APIJSON/issues/created_by/QiAnXinCodeSafe
##### [APIAuto](https://github.com/TommyLemon/APIAuto) 上统计的 bug
账号 13000002000 密码 123456
-http://apijson.org:8000/auto/
+http://apijson.cn/api
##### 其它发现的 Bug
https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aopen+label%3Abug
@@ -239,13 +238,14 @@ https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aopen+label%3Abug
+http://apijson.cn/api
-##### 接入 UnitAuto-机器学习自动化单元测试平台,每次启动都自动测试所有可测方法并输出报告
-https://gitee.com/TommyLemon/UnitAuto
+##### 在 UnitAuto-机器学习自动化单元测试平台 上传更多、更全面、更细致的测试用例、动态参数等
+http://apijson.cn/unit
### 完善文档
+20211112 更新:已在官网部署文档 http://apijson.cn/doc/zh
20200205 更新:最近完善及更新了通用文档、上手文档、图文入门文档等,还对首页引导文档加了导航目录
https://github.com/Tencent/APIJSON/blob/master/Navigation.md
From d970eeda31912621bc1c01f7732d7900b825b58a Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 15 Nov 2021 01:11:42 +0800
Subject: [PATCH 143/754] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?=
=?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 3 +++
1 file changed, 3 insertions(+)
diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
index 93279022b..4a25253a3 100644
--- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
+++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md"
@@ -4,6 +4,9 @@
https://search.bilibili.com/all?keyword=APIJSON&from_source=webtop_search&spm_id_from=333.851

+本文档已部署到官网,浏览和检索体验更好
+http://apijson.cn/doc/zh/
+
其它各种官方和第三方文档见首页相关推荐
https://github.com/Tencent/APIJSON#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90
From fb26ccfc5af79270865f1a81489d09222c06550f Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 25 Nov 2021 21:20:59 +0800
Subject: [PATCH 144/754] =?UTF-8?q?=E8=B7=AF=E7=BA=BF=E8=A7=84=E5=88=92?=
=?UTF-8?q?=EF=BC=9A=E6=96=B0=E5=A2=9E=204.8.0=20=E5=A4=A7=E5=B9=85?=
=?UTF-8?q?=E6=8F=90=E5=8D=87=E5=8D=95=E8=BE=B9=E6=95=B0=E7=BB=84=E6=9F=A5?=
=?UTF-8?q?=E8=AF=A2=E6=80=A7=E8=83=BD=E7=9A=84=E8=AF=B4=E6=98=8E?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
https://github.com/Tencent/APIJSON/releases/tag/4.8.0
---
Roadmap.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Roadmap.md b/Roadmap.md
index 4a479cd2e..dd8fcea9e 100644
--- a/Roadmap.md
+++ b/Roadmap.md
@@ -193,6 +193,7 @@ https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/
### 提高性能
20200205 更新:最近的及次大幅提升性能相关优化及 Release
+[新增支持 ClickHouse、窗口函数 OVER、反引号 `key`、单引号 'value';大幅提升单表数组查询性能](https://github.com/Tencent/APIJSON/releases/tag/4.8.0)
[4.6.0【性能】大幅提升数组内主表查询性能](https://github.com/Tencent/APIJSON/releases/tag/4.6.0)
[4.4.5【性能】大幅提升增删改的性能](https://github.com/Tencent/APIJSON/releases/tag/4.4.5)
@@ -279,7 +280,7 @@ https://github.com/APIJSON/APIJSON#%E7%94%9F%E6%80%81%E9%A1%B9%E7%9B%AE
JavaScript 前端,TypeScript 前端,微信等小程序,
Android 客户端,iOS 客户端,C# 游戏客户端等。
-Java, C#, Node, Python 等后端 Demo 及数据。
+Java, C#, PHP, Node, Python 等后端 Demo 及数据。
https://github.com/APIJSON/APIJSON-Demo
#### 新增扩展
From 12cdd0f5848fe58a56ca6c0799e69cead5553f94 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Sat, 27 Nov 2021 02:24:37 +0800
Subject: [PATCH 145/754] Update README.md
---
README.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index 61e4709bb..282325e22 100644
--- a/README.md
+++ b/README.md
@@ -147,13 +147,13 @@ https://www.bilibili.com/video/BV1yv411p7Y4
### 为什么选择 APIJSON?
-前后端 关于接口的 开发、文档、联调 等 10 个痛点解析
+前后端 关于接口的 开发、文档、联调 等 10 大痛点解析
https://github.com/Tencent/APIJSON/wiki
-* **解决十个痛点** (APIJSON 可帮助用户 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等)
+* **解决十大痛点** (APIJSON 可帮助用户 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等)
* **开发提速很大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上)
* **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告)
-* **社区影响力大** (GitHub 1W+ Star 在 350W Java 项目中排名前 140,远超 FLAG, BAT 等国内外绝大部分开源项目)
+* **社区影响力大** (GitHub 1W+ Star 在 350W Java 项目中排名前 120,远超 FLAG, BAT 等国内外绝大部分开源项目)
* **多样用户案例** (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等)
* **适用场景广泛** (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目)
* **周边生态丰富** (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等)
@@ -164,7 +164,7 @@ https://github.com/Tencent/APIJSON/wiki
* **高质可靠代码** (代码严谨规范,商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 Bug 率低至 0.15%)
* **兼容各种项目** (协议不限 HTTP,与其它库无冲突,对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo)
* **工程轻量小巧** (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1)
-* **多年持续迭代** (自 2016 年开源至今已连续维护 4 年,累计 2000+ Commits、70+ Releases,不断更新迭代中...)
+* **多年持续迭代** (自 2016 年开源至今已连续维护 5 年,累计 2000+ Commits、80+ Releases,不断更新迭代中...)
### 常见问题
From 128ca8e76a2d56a849ce63e717c2b8f3932311e7 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Sat, 27 Nov 2021 02:25:22 +0800
Subject: [PATCH 146/754] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 282325e22..7704d2e5e 100644
--- a/README.md
+++ b/README.md
@@ -162,7 +162,7 @@ https://github.com/Tencent/APIJSON/wiki
* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动控制权限、自动校验参数、自动防SQL注入等)
* **灵活定制业务** (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理)
* **高质可靠代码** (代码严谨规范,商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 Bug 率低至 0.15%)
-* **兼容各种项目** (协议不限 HTTP,与其它库无冲突,对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo)
+* **兼容各种项目** (协议不限 HTTP,与其它库无冲突,对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的示例)
* **工程轻量小巧** (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1)
* **多年持续迭代** (自 2016 年开源至今已连续维护 5 年,累计 2000+ Commits、80+ Releases,不断更新迭代中...)
From 44917295296fa4dd6479ea08f9618ccb84238774 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Wed, 1 Dec 2021 01:37:04 +0800
Subject: [PATCH 147/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=207=20=E7=AF=87?=
=?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=88=86=E6=9E=90=E7=9B=B8=E5=85=B3=E6=96=87?=
=?UTF-8?q?=E7=AB=A0=EF=BC=8C=E5=9F=BA=E6=9C=AC=E9=83=BD=E6=98=AF=2027=20?=
=?UTF-8?q?=E7=AF=87=E4=B8=AD=E7=9A=84=E5=BC=80=E7=AF=87=EF=BC=8C=E6=84=9F?=
=?UTF-8?q?=E8=B0=A2=203=20=E4=B8=AA=E5=8D=9A=E4=B8=BB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
https://github.com/Tencent/APIJSON/edit/master/README.md#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90
---
README.md | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/README.md b/README.md
index 7704d2e5e..bb6639c11 100644
--- a/README.md
+++ b/README.md
@@ -411,6 +411,21 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
[全国行政区划数据抓取与处理](https://www.xlongwei.com/detail/21032616)
[新手搭建 APIJSON 项目指北](https://github.com/jerrylususu/apijson_todo_demo/blob/master/FULLTEXT.md)
+
+[APIJSON(一:综述)](https://blog.csdn.net/qq_50861917/article/details/120556168)
+
+[APIJSON 代码分析(三:demo主体代码)](https://blog.csdn.net/qq_50861917/article/details/120751630)
+
+[APIJSON 代码分析(二)AbstractParser类(解析器)](https://blog.csdn.net/weixin_45767055/article/details/120815927)
+
+[APIJSON 代码分析(四:AbstractObjectParser源码阅读)](https://blog.csdn.net/qq_50861917/article/details/120896381)
+
+[APIJSON 代码分析 AbstractSQLConfig 第二篇](https://blog.csdn.net/csascscascd/article/details/120684889)
+
+[APIJSON 代码分析(六)APIJSON—Verifier检查类](https://blog.csdn.net/weixin_45767055/article/details/121321731)
+
+[APIJSON 代码分析(四)AbstractSQLExecutor—SQL执行器](https://blog.csdn.net/weixin_45767055/article/details/121069887)
+
### 生态项目
[APIJSON-Demo](https://github.com/APIJSON/APIJSON-Demo) APIJSON 各种语言、各种框架 的 使用示例项目、上手文档、测试数据 SQL 文件 等
@@ -483,6 +498,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
感谢热心的作者们的贡献,点 ⭐Star 支持下他们吧。
+
### 腾讯犀牛鸟开源人才培养计划
https://github.com/Tencent/APIJSON/issues/229
From e114eff51ed1c9c2c9fc2776a3a5462c4a311ed2 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Wed, 1 Dec 2021 01:38:36 +0800
Subject: [PATCH 148/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=207=20=E7=AF=87?=
=?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=88=86=E6=9E=90=E7=9B=B8=E5=85=B3=E6=96=87?=
=?UTF-8?q?=E7=AB=A0=EF=BC=8C=E5=9F=BA=E6=9C=AC=E9=83=BD=E6=98=AF=2027=20?=
=?UTF-8?q?=E7=AF=87=E4=B8=AD=E7=9A=84=E5=BC=80=E7=AF=87=EF=BC=8C=E6=84=9F?=
=?UTF-8?q?=E8=B0=A2=203=20=E4=B8=AA=E5=8D=9A=E4=B8=BB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
https://github.com/Tencent/APIJSON#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index bb6639c11..376bb2a2f 100644
--- a/README.md
+++ b/README.md
@@ -412,6 +412,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
[新手搭建 APIJSON 项目指北](https://github.com/jerrylususu/apijson_todo_demo/blob/master/FULLTEXT.md)
+
[APIJSON(一:综述)](https://blog.csdn.net/qq_50861917/article/details/120556168)
[APIJSON 代码分析(三:demo主体代码)](https://blog.csdn.net/qq_50861917/article/details/120751630)
From 7a0c85d961116c191b73fd919335b7e66a90db22 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 2 Dec 2021 20:20:59 +0800
Subject: [PATCH 149/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=96=87=E7=AB=A0=20?=
=?UTF-8?q?=E4=BD=BF=E7=94=A8APIJSON=E5=86=99=E4=BD=8E=E4=BB=A3=E7=A0=81Cr?=
=?UTF-8?q?ud=E6=8E=A5=E5=8F=A3=EF=BC=8C=E6=84=9F=E8=B0=A2=E5=8D=9A?=
=?UTF-8?q?=E4=B8=BB=E8=B4=A1=E7=8C=AE~?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
https://blog.csdn.net/weixin_42375862/article/details/121654264
位于相关推荐的多篇代码分析博文上方
https://github.com/Tencent/APIJSON#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 376bb2a2f..7388173da 100644
--- a/README.md
+++ b/README.md
@@ -412,6 +412,7 @@ 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/qq_50861917/article/details/120556168)
From 6023bc0fba0ad9a6d4101e361d9fc987d4548139 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Sun, 5 Dec 2021 01:24:56 +0800
Subject: [PATCH 150/754] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E6=9F=90=E4=B8=AA?=
=?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=80=BC=E4=B8=BA=20null=20=E5=AF=BC?=
=?UTF-8?q?=E8=87=B4=E4=B8=AD=E6=96=AD=E5=90=8E=E7=BB=AD=E6=AD=A3=E5=B8=B8?=
=?UTF-8?q?=E8=BF=94=E5=9B=9E=E5=80=BC=EF=BC=9B=E8=A7=A3=E5=86=B3=20LEFT/R?=
=?UTF-8?q?IGHT=20JOIN=20=E5=89=AF=E8=A1=A8=E5=85=B3=E8=81=94=E4=B8=BB?=
=?UTF-8?q?=E8=A1=A8=E5=A4=96=E9=94=AE=E7=9A=84=E5=AD=97=E6=AE=B5=E5=8F=96?=
=?UTF-8?q?=E5=88=AB=E5=90=8D=E5=AF=BC=E8=87=B4=20SQL=20=E6=8A=A5=E9=94=99?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../main/java/apijson/orm/AbstractParser.java | 1 +
.../java/apijson/orm/AbstractSQLConfig.java | 45 +++++++++++--------
.../java/apijson/orm/AbstractSQLExecutor.java | 15 ++++---
3 files changed, 35 insertions(+), 26 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
index 65b79cea9..9be40b0d2 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
@@ -432,6 +432,7 @@ public JSONObject parseResponse(JSONObject request) {
requestObject.put("sql:generate|cache|execute|maxExecute", getSQLExecutor().getGeneratedSQLCount() + "|" + getSQLExecutor().getCachedSQLCount() + "|" + getSQLExecutor().getExecutedSQLCount() + "|" + getMaxSQLCount());
requestObject.put("depth:count|max", queryDepth + "|" + getMaxQueryDepth());
requestObject.put("time:start|duration|end", startTime + "|" + duration + "|" + endTime);
+// TODO 放在 msg 中的调试和提示信息应该单独放一个字段,避免 APIAuto 异常分支不显示提示语或太长,以及 DEBUG 和非 DEBUG 模式下提示语不一致 requestObject.put("debug", debugStr);
if (error != null) {
requestObject.put("throw", error.getClass().getName());
requestObject.put("trace", error.getStackTrace());
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
index 5e5a59917..dc6a33647 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
@@ -83,7 +83,7 @@ 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;
+ private static final Pattern PATTERN_STRING;
/**
* 表名映射,隐藏真实表名,对安全要求很高的表可以这么做
@@ -1490,9 +1490,8 @@ public String getColumnString(boolean inSQLJoin) throws Exception {
return "(" + s + ")";
case GET:
case GETS:
- boolean isQuery = RequestMethod.isQueryMethod(method); //TODO 这个有啥用?上面应是 getMethod 的值 GET 和 GETS 了。
String joinColumn = "";
- if (isQuery && joinList != null) {
+ if (joinList != null) {
SQLConfig ecfg;
SQLConfig cfg;
String c;
@@ -1501,23 +1500,31 @@ public String getColumnString(boolean inSQLJoin) throws Exception {
if (j.isAppJoin()) {
continue;
}
-
- 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;
+
+ if (j.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;
+ }
}
inSQLJoin = true;
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
index c0fce49f7..49703a2a1 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
@@ -542,7 +542,8 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r
if (joinList != null) {
for (Join j : joinList) {
childConfig = j.isAppJoin() ? null : j.getCacheConfig(); //这里用config改了getSQL后再还原很麻烦,所以提前给一个config2更好
-
+
+ // FIXME 副表的 SQL 函数,甚至普通字段都可能从 rsmd.getTableName(columnIndex) 拿到 ""
if (childConfig != null && childTable.equalsIgnoreCase(childConfig.getSQLTable())) {
childConfig.putWhere(j.getKey(), table.get(j.getTargetKey()), true);
@@ -561,13 +562,13 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r
}
Object value = getValue(config, rs, rsmd, tablePosition, table, columnIndex, lable, childMap);
- if (value != null) {
- if (finalTable == null) {
- finalTable = new JSONObject(true);
- childMap.put(childSql, finalTable);
- }
- finalTable.put(lable, value);
+ // 必须 put 进去,否则某个字段为 null 可能导致中断后续正常返回值 if (value != null) {
+ if (finalTable == null) {
+ finalTable = new JSONObject(true);
+ childMap.put(childSql, finalTable);
}
+ finalTable.put(lable, value);
+ // }
return table;
}
From 00dae1b6bfa0de09b3e2465f62319c278524f375 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Tue, 7 Dec 2021 03:49:49 +0800
Subject: [PATCH 151/754] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20JOIN=20=E5=89=AF?=
=?UTF-8?q?=E8=A1=A8=E5=8C=85=E5=90=AB=20SQL=20=E5=87=BD=E6=95=B0=E6=97=B6?=
=?UTF-8?q?=E6=B2=A1=E6=9C=89=E8=BF=94=E5=9B=9E=20SQL=20=E5=87=BD=E6=95=B0?=
=?UTF-8?q?=E7=9A=84=E6=89=A7=E8=A1=8C=E7=BB=93=E6=9E=9C=E4=BB=A5=E5=8F=8A?=
=?UTF-8?q?=E6=9C=AA=E7=94=A8=E4=B8=8A=20SQL=20=E7=BC=93=E5=AD=98=E5=AF=BC?=
=?UTF-8?q?=E8=87=B4=E5=86=97=E4=BD=99=20SQL=20=E6=9F=A5=E8=AF=A2=20#341?=
=?UTF-8?q?=EF=BC=9B=E6=8F=90=E5=8D=87=20JOIN=20=E5=B0=81=E8=A3=85?=
=?UTF-8?q?=E7=BB=93=E6=9E=9C=E7=9A=84=E6=80=A7=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../java/apijson/orm/AbstractSQLExecutor.java | 183 ++++++++++++++----
1 file changed, 146 insertions(+), 37 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
index 49703a2a1..72065302f 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
@@ -25,6 +25,7 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Objects;
import java.util.Set;
import com.alibaba.fastjson.JSON;
@@ -267,17 +268,29 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws
//
childMap = new HashMap<>(); //要存到cacheMap
+// Map columnIndexAndJoinMap = new HashMap<>(length);
+ String lastTableName = null; // 默认就是主表 config.getTable();
+ int lastViceTableStart = 0;
+ int lastViceColumnStart = 0;
+ Join lastJoin = null;
+ // TODO String[] columnIndexAndTableMap = new String[length];
// WHERE id = ? AND ... 或 WHERE ... AND id = ? 强制排序 remove 再 put,还是重新 getSQL吧
- boolean hasJoin = config.hasJoin();
- int viceColumnStart = length + 1; //第一个副表字段的index
+ List joinList = config.getJoinList();
+ boolean hasJoin = config.hasJoin() && joinList != null && ! joinList.isEmpty();
+
+ // 直接用数组存取更快 Map columnIndexAndJoinMap = isExplain || ! hasJoin ? null : new HashMap<>(length);
+ Join[] columnIndexAndJoinMap = isExplain || ! hasJoin ? null : new Join[length];
+
+// int viceColumnStart = length + 1; //第一个副表字段的index
while (rs.next()) {
index ++;
Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n execute while (rs.next()){ index = " + index + "\n\n");
JSONObject item = new JSONObject(true);
-
+ boolean isMain = true;
+
for (int i = 1; i <= length; i++) {
// if (hasJoin && viceColumnStart > length && config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) {
@@ -285,21 +298,117 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws
// }
// bugfix-修复非常规数据库字段,获取表名失败导致输出异常
- if (isExplain == false && hasJoin && viceColumnStart > length) {
- List column = config.getColumn();
- String sqlTable = rsmd.getTableName(i);
- if (config.isClickHouse()&&(sqlTable.startsWith("`")||sqlTable.startsWith("\""))){
- sqlTable = sqlTable.substring(1,sqlTable.length()-1);
+ Join curJoin = columnIndexAndJoinMap == null ? null : columnIndexAndJoinMap[i - 1]; // columnIndexAndJoinMap.get(i);
+
+ // 为什么 isExplain == false 不用判断?因为所有字段都在一张 Query Plan 表
+ if (index <= 0 && hasJoin && ! isExplain) { // && viceColumnStart > length) {
+
+ SQLConfig curConfig = curJoin == null || ! curJoin.isSQLJoin() ? null : curJoin.getCacheConfig();
+ List curColumn = curConfig == null ? null : curConfig.getColumn();
+ String sqlTable = curConfig == null ? null : curConfig.getSQLTable();
+
+ List column = config.getColumn();
+ int mainColumnSize = column == null ? 0 : column.size();
+ boolean toFindJoin = mainColumnSize <= 0 || i > mainColumnSize; // 主表就不用找 JOIN 配置
+
+ if (StringUtil.isEmpty(sqlTable , true)) {
+ if (toFindJoin) { // 在主表字段数量内的都归属主表
+ sqlTable = rsmd.getTableName(i); // SQL 函数甚至部分字段都不返回表名,当然如果没传 @column 生成的 Table.* 则返回的所有字段都会带表名
+
+ if (StringUtil.isEmpty(sqlTable, true)) { // hasJoin 已包含这个判断 && joinList != null) {
+
+ int nextViceColumnStart = lastViceColumnStart; // 主表没有 @column 时会偏小 lastViceColumnStart
+ for (int j = lastViceTableStart; j < joinList.size(); j++) { // 查找副表 @column,定位字段所在表
+ Join join = joinList.get(j);
+ SQLConfig cfg = join == null || ! join.isSQLJoin() ? null : join.getJoinConfig();
+ List c = cfg == null ? null : cfg.getColumn();
+
+ nextViceColumnStart += (
+ c != null && ! c.isEmpty() ? c.size()
+ : (Objects.equals(sqlTable, lastTableName) || sqlTable.equalsIgnoreCase(lastTableName) ? 1 : 0)
+ );
+ if (i < nextViceColumnStart) {
+ sqlTable = cfg.getSQLTable();
+ lastViceTableStart = j; // 避免后面的空 @column 表内字段被放到之前的空 @column 表
+
+ curJoin = join;
+ curConfig = cfg;
+ curColumn = c;
+
+ toFindJoin = false;
+ isMain = false;
+ break;
+ }
+ }
+ }
+
+ // 没有 @column,仍然定位不了,用前一个 table 名。FIXME 如果刚好某个表内第一个字段是就是 SQL 函数?
+ if (StringUtil.isEmpty(sqlTable, true)) {
+ sqlTable = lastTableName;
+ toFindJoin = false;
+ }
+ }
}
- if (column != null && column.isEmpty() == false) {
- viceColumnStart = column.size() + 1;
+ else if (config.isClickHouse() && (sqlTable.startsWith("`") || sqlTable.startsWith("\""))){
+ sqlTable = sqlTable.substring(1, sqlTable.length() - 1);
}
- else if (config.getSQLTable().equalsIgnoreCase(sqlTable) == false) {
- viceColumnStart = i;
+
+ if ((sqlTable == null && lastTableName != null) || (sqlTable != null && ! sqlTable.equalsIgnoreCase(lastTableName))) {
+ lastTableName = sqlTable;
+ lastViceColumnStart = i;
+
+ if (toFindJoin) { // 找到对应的副表 JOIN 配置
+ for (int j = lastViceTableStart; j < joinList.size(); j++) { // 查找副表 @column,定位字段所在表
+ Join join = joinList.get(j);
+ SQLConfig cfg = join == null || ! join.isSQLJoin() ? null : join.getJoinConfig();
+
+ if (cfg != null && sqlTable != null && sqlTable.equalsIgnoreCase(cfg.getSQLTable())) {
+ lastViceTableStart = j; // 避免后面的空 @column 表内字段被放到之前的空 @column 表
+
+ curJoin = join;
+ curConfig = cfg;
+ curColumn = curConfig == null ? null : curConfig.getColumn();
+
+ isMain = false;
+ break;
+ }
+ }
+ }
}
+
+ if (isMain) {
+ lastViceColumnStart ++;
+ }
+ else {
+ if (curJoin == null) {
+ curJoin = lastJoin;
+ }
+ else {
+ lastJoin = curJoin;
+ }
+
+ if (curColumn == null) {
+ curConfig = curJoin == null || ! curJoin.isSQLJoin() ? null : curJoin.getJoinConfig();
+ curColumn = curConfig == null ? null : curConfig.getColumn();
+ }
+
+ // 解决后面的表内 SQL 函数被放到之前的空 @column 表
+ if (curColumn == null || curColumn.isEmpty()) {
+ lastViceColumnStart ++;
+ }
+ }
+
+ columnIndexAndJoinMap[i - 1] = curJoin; // columnIndexAndJoinMap.put(i, curJoin); // TODO columnIndexAndTableMap[i] = sqlTable 提速?
+
+// if (column != null && column.isEmpty() == false) {
+// viceColumnStart = column.size() + 1;
+// }
+// else if (config.getSQLTable().equalsIgnoreCase(sqlTable) == false) {
+// viceColumnStart = i;
+// }
}
- item = onPutColumn(config, rs, rsmd, index, item, i, isExplain == false && hasJoin && i >= viceColumnStart ? childMap : null);
+ item = onPutColumn(config, rs, rsmd, index, item, i, curJoin, childMap); // isExplain == false && hasJoin && i >= viceColumnStart ? childMap : null);
}
resultList = onPutTable(config, rs, rsmd, resultList, index, item);
@@ -391,13 +500,13 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map
SQLConfig jc;
SQLConfig cc;
- for (Join j : joinList) {
- if (j.isAppJoin() == false) {
+ for (Join join : joinList) {
+ if (join.isAppJoin() == false) {
Log.i(TAG, "executeAppJoin for (Join j : joinList) >> j.isAppJoin() == false >> continue;");
continue;
}
- cc = j.getCacheConfig(); //这里用config改了getSQL后再还原很麻烦,所以提前给一个config2更好
+ cc = join.getCacheConfig(); //这里用config改了getSQL后再还原很麻烦,所以提前给一个config2更好
if (cc == null) {
if (Log.DEBUG) {
throw new NullPointerException("服务器内部错误, executeAppJoin cc == null ! 导致不能缓存 @ APP JOIN 的副表数据!");
@@ -405,7 +514,7 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map
continue;
}
- jc = j.getJoinConfig();
+ jc = join.getJoinConfig();
//取出 "id@": "@/User/userId" 中所有 userId 的值
List targetValueList = new ArrayList<>();
@@ -414,7 +523,7 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map
for (int i = 0; i < resultList.size(); i++) {
mainTable = resultList.get(i);
- targetValue = mainTable == null ? null : mainTable.get(j.getTargetKey());
+ targetValue = mainTable == null ? null : mainTable.get(join.getTargetKey());
if (targetValue != null && targetValueList.contains(targetValue) == false) {
targetValueList.add(targetValue);
@@ -423,8 +532,8 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map
//替换为 "id{}": [userId1, userId2, userId3...]
- jc.putWhere(j.getOriginKey(), null, false); // remove orginKey
- jc.putWhere(j.getKey() + "{}", targetValueList, true); // add orginKey{}
+ jc.putWhere(join.getOriginKey(), null, false); // remove orginKey
+ jc.putWhere(join.getKey() + "{}", targetValueList, true); // add orginKey{}
jc.setMain(true).setPreparedValueList(new ArrayList<>());
@@ -462,7 +571,7 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map
for (int i = 1; i <= length; i++) {
- result = onPutColumn(jc, rs, rsmd, index, result, i, null);
+ result = onPutColumn(jc, rs, rsmd, index, result, i, null, null);
}
//每个 result 都要用新的 SQL 来存 childResultMap = onPutTable(config, rs, rsmd, childResultMap, index, result);
@@ -471,7 +580,7 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map
+ "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n");
//缓存到 childMap
- cc.putWhere(j.getKey(), result.get(j.getKey()), true);
+ cc.putWhere(join.getKey(), result.get(join.getKey()), true);
cacheSql = cc.getSQL(false);
childMap.put(cacheSql, result);
@@ -512,7 +621,7 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map
* @throws Exception
*/
protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd
- , final int tablePosition, @NotNull JSONObject table, final int columnIndex, Map childMap) throws Exception {
+ , final int tablePosition, @NotNull JSONObject table, final int columnIndex, Join join, Map childMap) throws Exception {
if (isHideColumn(config, rs, rsmd, tablePosition, table, columnIndex, childMap)) {
Log.i(TAG, "onPutColumn isHideColumn(config, rs, rsmd, tablePosition, table, columnIndex, childMap) >> return table;");
@@ -524,13 +633,13 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r
// int dotIndex = lable.indexOf(".");
String lable = getKey(config, rs, rsmd, tablePosition, table, columnIndex, childMap);
- String childTable = childMap == null ? null : rsmd.getTableName(columnIndex); //dotIndex < 0 ? null : lable.substring(0, dotIndex);
+// String childTable = childMap == null ? null : sqlTableName; // rsmd.getTableName(columnIndex); //dotIndex < 0 ? null : lable.substring(0, dotIndex);
JSONObject finalTable = null;
String childSql = null;
- SQLConfig childConfig = null;
-
- if (childTable == null) {
+
+ SQLConfig childConfig = join == null || join.isSQLJoin() == false ? null : join.getCacheConfig();
+ if (childConfig == null) {
finalTable = table;
}
else {
@@ -538,15 +647,15 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r
//
- List joinList = config.getJoinList();
- if (joinList != null) {
- for (Join j : joinList) {
- childConfig = j.isAppJoin() ? null : j.getCacheConfig(); //这里用config改了getSQL后再还原很麻烦,所以提前给一个config2更好
+// List joinList = config.getJoinList();
+// if (joinList != null) {
+// for (Join j : joinList) {
+// childConfig = j.isAppJoin() ? null : j.getCacheConfig(); //这里用config改了getSQL后再还原很麻烦,所以提前给一个config2更好
// FIXME 副表的 SQL 函数,甚至普通字段都可能从 rsmd.getTableName(columnIndex) 拿到 ""
- if (childConfig != null && childTable.equalsIgnoreCase(childConfig.getSQLTable())) {
+// if (childConfig != null && childTable.equalsIgnoreCase(childConfig.getSQLTable())) {
- childConfig.putWhere(j.getKey(), table.get(j.getTargetKey()), true);
+ childConfig.putWhere(join.getKey(), table.get(join.getTargetKey()), true);
childSql = childConfig.getSQL(false);
if (StringUtil.isEmpty(childSql, true)) {
@@ -554,10 +663,10 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r
}
finalTable = (JSONObject) childMap.get(childSql);
- break;
- }
- }
- }
+// break;
+// }
+// }
+// }
}
From c496f016d810d30d96a00b21c3a097f2e118daa5 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 20 Dec 2021 22:06:39 +0800
Subject: [PATCH 152/754] =?UTF-8?q?=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE?=
=?UTF-8?q?=E6=96=B0=E5=A2=9E=20=20apijson=5Ftemplate=20=E5=92=8C=20api-js?=
=?UTF-8?q?on-demo=EF=BC=8C=E6=84=9F=E8=B0=A2=E4=B8=A4=E4=B8=AA=E4=BD=9C?=
=?UTF-8?q?=E8=80=85=E7=9A=84=E8=B4=A1=E7=8C=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
基于 APIJSON,实现低代码写 CURD 代码,代替传统 ORM 框架,适配 Oracle 事务:
https://gitee.com/hxdwd/api-json-demo
apijson java 模版,使用 gradle 管理依赖和构建应用:
https://github.com/abliger/apijson_template
---
README.md | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/README.md b/README.md
index 7388173da..af6520919 100644
--- a/README.md
+++ b/README.md
@@ -467,11 +467,17 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
[FfApiJson](https://gitee.com/own_3_0/ff-api-json) 用 JSON 格式直接生成 SQL,借鉴 APIJSON 支持多数据源
[APIJSON-ToDo-Demo](https://github.com/jerrylususu/apijson_todo_demo) 一个简单的 todo 示例项目,精简数据,简化上手流程,带自定义鉴权逻辑
-
+
[apijson-learn](https://github.com/rainboy-learn/apijson-learn) APIJSON 学习笔记和源码解析
[apijson-practice](https://github.com/vcoolwind/apijson-practice) BAT 技术专家开源的 APIJSON 参数校验注解 Library 及相关 Demo
+[apijson-db2](https://github.com/andream7/apijson-db2) APIJSON 接入 IBM 数据库 DB2 的 Demo
+
+[APIJSONDemo](https://github.com/qiujunlin/APIJSONDemo) APIJSON 接入 ClickHouse 的 Demo
+
+[APIJSONDemo_ClickHouse](https://github.com/chenyanlann/APIJSONDemo_ClickHouse) APIJSON + SpringBoot 连接 ClickHouse 使用的 Demo
+
[apijson-sample](https://gitee.com/greyzeng/apijson-sample) APIJSON 简单使用 Demo 及教程
[apijson-examples](https://gitee.com/drone/apijson-examples) APIJSON 的前端、业务后端、管理后端 Demo
@@ -480,16 +486,12 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
[SpringServer1.2-APIJSON](https://github.com/Airforce-1/SpringServer1.2-APIJSON) 智慧党建服务器端,提供 上传 和 下载 文件的接口
-[apijson-examples](https://gitee.com/drone/apijson-examples) APIJSON 的前端、业务后端、管理后端 Demo
+[apijson_template](https://github.com/abliger/apijson_template) apijson java 模版,使用 gradle 管理依赖和构建应用
+
+[api-json-demo](https://gitee.com/hxdwd/api-json-demo) 基于 APIJSON,实现低代码写 CURD 代码,代替传统 ORM 框架,适配 Oracle 事务
[ApiJsonByJFinal](https://gitee.com/zhiyuexin/ApiJsonByJFinal) 整合 APIJSON 和 JFinal 的 Demo
-[apijson-db2](https://github.com/andream7/apijson-db2) APIJSON 接入 IBM 数据库 DB2 的 Demo
-
-[APIJSONDemo](https://github.com/qiujunlin/APIJSONDemo) APIJSON 接入 ClickHouse 的 Demo
-
-[APIJSONDemo_ClickHouse](https://github.com/chenyanlann/APIJSONDemo_ClickHouse) APIJSON + SpringBoot 连接 ClickHouse 使用的 Demo
-
[apijson-builder](https://github.com/pengxianggui/apijson-builder) 一个方便为 APIJSON 构建 RESTful 请求的 JavaScript 库
[AbsGrade](https://github.com/APIJSON/AbsGrade) 列表级联算法,支持微信朋友圈单层评论、QQ空间双层评论、百度网盘多层(无限层)文件夹等
From ed05ae37afdc6274bb7763b2792bf1311a16b662 Mon Sep 17 00:00:00 2001
From: chenyanlann <32511cyl@gmail.com>
Date: Fri, 31 Dec 2021 17:53:06 +0800
Subject: [PATCH 153/754] Add:ORM's support for Hive
---
.../main/java/apijson/orm/AbstractSQLConfig.java | 11 +++++++++++
.../main/java/apijson/orm/AbstractSQLExecutor.java | 13 ++++++++++++-
APIJSONORM/src/main/java/apijson/orm/SQLConfig.java | 2 ++
3 files changed, 25 insertions(+), 1 deletion(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
index dc6a33647..57deaa44e 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
@@ -126,6 +126,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
DATABASE_LIST.add(DATABASE_ORACLE);
DATABASE_LIST.add(DATABASE_DB2);
DATABASE_LIST.add(DATABASE_CLICKHOUSE);
+ DATABASE_LIST.add(DATABASE_HIVE);
RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况
@@ -903,6 +904,13 @@ public boolean isClickHouse() {
public static boolean isClickHouse(String db) {
return DATABASE_CLICKHOUSE.equals(db);
}
+ @Override
+ public boolean isHive() {
+ return isHive(getSQLDatabase());
+ }
+ public static boolean isHive(String db) {
+ return DATABASE_HIVE.equals(db);
+ }
@Override
public String getQuote() {
@@ -2727,6 +2735,9 @@ public String getRegExpString(String key, String value, boolean ignoreCase) {
if (isClickHouse()) {
return "match(" + (ignoreCase ? "lower(" : "") + getKey(key) + (ignoreCase ? ")" : "") + ", " + (ignoreCase ? "lower(" : "") + getValue(value) + (ignoreCase ? ")" : "") + ")";
}
+ if (isHive()) {
+ return (ignoreCase ? "lower(" : "") + getKey(key) + (ignoreCase ? ")" : "") + " REGEXP " + (ignoreCase ? "lower(" : "") + getValue(value) + (ignoreCase ? ")" : "");
+ }
return getKey(key) + " REGEXP " + (ignoreCase ? "" : "BINARY ") + getValue(value);
}
//~ regexp >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
index 72065302f..1f1ddcb1e 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
@@ -27,6 +27,7 @@
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
+import java.util.regex.Pattern;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
@@ -36,6 +37,7 @@
import apijson.NotNull;
import apijson.RequestMethod;
import apijson.StringUtil;
+import apijson.orm.AbstractSQLConfig;
/**executor for query(read) or update(write) MySQL database
* @author Lemon
@@ -718,7 +720,15 @@ protected List onPutTable(@NotNull SQLConfig config, @NotNull Result
protected String getKey(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd
, final int tablePosition, @NotNull JSONObject table, final int columnIndex, Map childMap) throws Exception {
- return rsmd.getColumnLabel(columnIndex); // dotIndex < 0 ? lable : lable.substring(dotIndex + 1);
+ String key = rsmd.getColumnLabel(columnIndex);// dotIndex < 0 ? lable : lable.substring(dotIndex + 1);
+ if (config.isHive()) {
+ String table_name = config.getTable();
+ if(AbstractSQLConfig.TABLE_KEY_MAP.containsKey(table_name)) table_name = AbstractSQLConfig.TABLE_KEY_MAP.get(table_name);
+ String pattern = "^" + table_name + "\\." + "[a-zA-Z]+$";
+ boolean isMatch = Pattern.matches(pattern, key);
+ if(isMatch) key = key.split("\\.")[1];
+ }
+ return key;
}
protected Object getValue(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd
@@ -974,6 +984,7 @@ public ResultSet executeQuery(@NotNull SQLConfig config) throws Exception {
public int executeUpdate(@NotNull SQLConfig config) throws Exception {
PreparedStatement s = getStatement(config);
int count = s.executeUpdate(); //PreparedStatement 不用传 SQL
+ if (config.isHive() && count==0) count = 1;
if (config.getMethod() == RequestMethod.POST && config.getId() == null) { //自增id
ResultSet rs = s.getGeneratedKeys();
diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java
index ab8e2d254..83eddb301 100755
--- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java
@@ -22,6 +22,7 @@ public interface SQLConfig {
String DATABASE_ORACLE = "ORACLE";
String DATABASE_DB2 = "DB2";
String DATABASE_CLICKHOUSE = "CLICKHOUSE";
+ String DATABASE_HIVE = "HIVE";
String SCHEMA_INFORMATION = "information_schema"; //MySQL, PostgreSQL, SQL Server 都有的系统模式
String SCHEMA_SYS = "sys"; //SQL Server 系统模式
@@ -38,6 +39,7 @@ public interface SQLConfig {
boolean isOracle();
boolean isDb2();
boolean isClickHouse();
+ boolean isHive();
//暂时只兼容以上 5 种
// boolean isSQL();
// boolean isTSQL();
From a8bad69e007e76ca5bc85634053363fc010c58c9 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Sat, 1 Jan 2022 04:16:18 +0800
Subject: [PATCH 154/754] =?UTF-8?q?SQL=20JOIN=EF=BC=9A=E8=A7=A3=E5=86=B3?=
=?UTF-8?q?=E5=89=AF=E8=A1=A8=E9=99=A4=E4=BA=86=E5=BC=95=E7=94=A8=E8=B5=8B?=
=?UTF-8?q?=E5=80=BC=E9=94=AE=E5=80=BC=E5=AF=B9=E8=BF=98=E6=9C=89=E5=85=B6?=
=?UTF-8?q?=E5=AE=83=E6=9D=A1=E4=BB=B6=E9=94=AE=E5=80=BC=E5=AF=B9=E6=97=B6?=
=?UTF-8?q?=E4=B8=8D=E8=83=BD=E5=91=BD=E4=B8=AD=E7=BC=93=E5=AD=98=EF=BC=8C?=
=?UTF-8?q?=E5=AF=BC=E8=87=B4=20=E4=B8=80=E5=AF=B9=E5=A4=9A=E3=80=81?=
=?UTF-8?q?=E5=A4=9A=E5=AF=B9=E5=A4=9A=E5=89=AF=E8=A1=A8=E6=95=B0=E6=8D=AE?=
=?UTF-8?q?=E9=87=8D=E5=A4=8D=20=E4=BB=A5=E5=8F=8A=20=E4=B8=80=E5=AF=B9?=
=?UTF-8?q?=E4=B8=80=E3=80=81=E5=A4=9A=E5=AF=B9=E4=B8=80=20=E6=9F=A5?=
=?UTF-8?q?=E8=AF=A2=E6=80=A7=E8=83=BD=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../java/apijson/orm/AbstractSQLConfig.java | 41 +++++++++++++++----
1 file changed, 33 insertions(+), 8 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
index dc6a33647..6798fc0ad 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
@@ -2197,18 +2197,42 @@ else if (prior && andList.isEmpty() == false) {
String userIdKey = getUserIdKey();
String userIdInKey = userIdKey + "{}";
- if (andList.contains(idKey)) {
- i ++;
+ int lastIndex;
+ if (key.equals(idKey)) {
+ lastIndex = -1;
}
- if (andList.contains(idInKey)) {
- i ++;
+ else if (key.equals(idInKey)) {
+ lastIndex = andList.lastIndexOf(idKey);
}
- if (andList.contains(userIdKey)) {
- i ++;
+ else if (key.equals(userIdKey)) {
+ lastIndex = andList.lastIndexOf(idInKey);
+ if (lastIndex < 0) {
+ lastIndex = andList.lastIndexOf(idKey);
+ }
}
- if (andList.contains(userIdInKey)) {
- i ++;
+ else if (key.equals(userIdInKey)) {
+ lastIndex = andList.lastIndexOf(userIdKey);
+ if (lastIndex < 0) {
+ lastIndex = andList.lastIndexOf(idInKey);
+ }
+ if (lastIndex < 0) {
+ lastIndex = andList.lastIndexOf(idKey);
+ }
+ }
+ else {
+ lastIndex = andList.lastIndexOf(userIdInKey);
+ if (lastIndex < 0) {
+ lastIndex = andList.lastIndexOf(userIdKey);
+ }
+ if (lastIndex < 0) {
+ lastIndex = andList.lastIndexOf(idInKey);
+ }
+ if (lastIndex < 0) {
+ lastIndex = andList.lastIndexOf(idKey);
+ }
}
+
+ i = lastIndex + 1;
}
if (prior) {
@@ -2219,6 +2243,7 @@ else if (prior && andList.isEmpty() == false) {
}
combine.put("&", andList);
}
+
return this;
}
From 61883bc24b1dcc3abe0ba60c57e6c057fae8e552 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Sat, 1 Jan 2022 04:18:29 +0800
Subject: [PATCH 155/754] =?UTF-8?q?=E4=BC=98=E5=8C=96=20Response=20JSON=20?=
=?UTF-8?q?=E4=B8=AD=E7=9A=84=20debug:info|help,=20trace:stack=20=E7=AD=89?=
=?UTF-8?q?=E8=B0=83=E8=AF=95=E5=AD=97=E6=AE=B5=E5=8F=8A=E7=9B=B8=E5=85=B3?=
=?UTF-8?q?=E6=8F=90=E7=A4=BA=E8=AF=AD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../apijson/orm/AbstractObjectParser.java | 17 +-
.../main/java/apijson/orm/AbstractParser.java | 192 ++++++++++++------
2 files changed, 145 insertions(+), 64 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
index 453db9c5f..590e016dc 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
@@ -288,7 +288,10 @@ else if (method == PUT && value instanceof JSONArray && (whereList == null || wh
try {
String db = sqlConfig.getDatabase();
if (db == null) {
- if (sqlConfig.isPostgreSQL()) {
+ if (sqlConfig.isMySQL()) {
+ db = SQLConfig.DATABASE_MYSQL;
+ }
+ else if (sqlConfig.isPostgreSQL()) {
db = SQLConfig.DATABASE_POSTGRESQL;
}
else if (sqlConfig.isSQLServer()) {
@@ -304,18 +307,18 @@ else if (sqlConfig.isClickHouse()) {
db = SQLConfig.DATABASE_CLICKHOUSE;
}
else {
- db = SQLConfig.DATABASE_MYSQL;
+ db = AbstractSQLConfig.DEFAULT_DATABASE;
}
}
Class extends Exception> clazz = e.getClass();
e = clazz.getConstructor(String.class).newInstance(
e.getMessage()
- + " " + Log.KEY_SYSTEM_INFO_DIVIDER + " \n**环境信息** "
- + "\n系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version")
- + "\n数据库: " + db + " " + sqlConfig.getDBVersion()
- + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch")
- + "\nAPIJSON: " + Log.VERSION
+ + " " + Log.KEY_SYSTEM_INFO_DIVIDER + " \n **环境信息** "
+ + " \n 系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version")
+ + " \n 数据库: " + db + " " + sqlConfig.getDBVersion()
+ + " \n JDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch")
+ + " \n APIJSON: " + Log.VERSION
);
} catch (Throwable e2) {}
}
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
index 9be40b0d2..b9c37a1a6 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
@@ -94,6 +94,16 @@ public AbstractParser(RequestMethod method, boolean needVerify) {
setMethod(method);
setNeedVerify(needVerify);
}
+
+ protected boolean isRoot = true;
+ public boolean isRoot() {
+ return isRoot;
+ }
+ public AbstractParser setRoot(boolean isRoot) {
+ this.isRoot = isRoot;
+ return this;
+ }
+
@NotNull
protected Visitor visitor;
@@ -336,7 +346,7 @@ public JSONObject parseResponse(String request) {
try {
requestObject = parseRequest(request);
} catch (Exception e) {
- return newErrorResult(e);
+ return newErrorResult(e, isRoot);
}
return parseResponse(requestObject);
@@ -368,7 +378,7 @@ public JSONObject parseResponse(JSONObject request) {
onVerifyContent();
}
} catch (Exception e) {
- return extendErrorResult(requestObject, e, requestMethod, getRequestURL());
+ return extendErrorResult(requestObject, e, requestMethod, getRequestURL(), isRoot);
}
}
@@ -378,7 +388,7 @@ public JSONObject parseResponse(JSONObject request) {
setGlobleRole(requestObject.getString(JSONRequest.KEY_ROLE));
requestObject.remove(JSONRequest.KEY_ROLE);
} catch (Exception e) {
- return extendErrorResult(requestObject, e, requestMethod, getRequestURL());
+ return extendErrorResult(requestObject, e, requestMethod, getRequestURL(), isRoot);
}
}
@@ -397,7 +407,7 @@ public JSONObject parseResponse(JSONObject request) {
requestObject.remove(JSONRequest.KEY_EXPLAIN);
requestObject.remove(JSONRequest.KEY_CACHE);
} catch (Exception e) {
- return extendErrorResult(requestObject, e, requestMethod, getRequestURL());
+ return extendErrorResult(requestObject, e, requestMethod, getRequestURL(), isRoot);
}
final String requestString = JSON.toJSONString(request);//request传进去解析后已经变了
@@ -421,7 +431,7 @@ public JSONObject parseResponse(JSONObject request) {
onRollback();
}
- requestObject = error == null ? extendSuccessResult(requestObject) : extendErrorResult(requestObject, error, requestMethod, getRequestURL());
+ requestObject = error == null ? extendSuccessResult(requestObject, isRoot) : extendErrorResult(requestObject, error, requestMethod, getRequestURL(), isRoot);
JSONObject res = (globleFormat != null && globleFormat) && JSONResponse.isSuccess(requestObject) ? new JSONResponse(requestObject) : requestObject;
@@ -432,26 +442,26 @@ public JSONObject parseResponse(JSONObject request) {
requestObject.put("sql:generate|cache|execute|maxExecute", getSQLExecutor().getGeneratedSQLCount() + "|" + getSQLExecutor().getCachedSQLCount() + "|" + getSQLExecutor().getExecutedSQLCount() + "|" + getMaxSQLCount());
requestObject.put("depth:count|max", queryDepth + "|" + getMaxQueryDepth());
requestObject.put("time:start|duration|end", startTime + "|" + duration + "|" + endTime);
-// TODO 放在 msg 中的调试和提示信息应该单独放一个字段,避免 APIAuto 异常分支不显示提示语或太长,以及 DEBUG 和非 DEBUG 模式下提示语不一致 requestObject.put("debug", debugStr);
+
if (error != null) {
- requestObject.put("throw", error.getClass().getName());
- requestObject.put("trace", error.getStackTrace());
+ requestObject.put("trace:throw", error.getClass().getName());
+ requestObject.put("trace:stack", error.getStackTrace());
}
}
onClose();
//CS304 Issue link: https://github.com/Tencent/APIJSON/issues/232
- if (IS_PRINT_REQUEST_STRING_LOG||Log.DEBUG||error != null) {
- Log.sl("\n\n\n",'<',"");
- Log.fd(TAG , requestMethod + "/parseResponse request = \n" + requestString + "\n\n");
+ if (IS_PRINT_REQUEST_STRING_LOG || Log.DEBUG || error != null) {
+ Log.sl("\n\n\n", '<', "");
+ Log.fd(TAG, requestMethod + "/parseResponse request = \n" + requestString + "\n\n");
}
- if (IS_PRINT_BIG_LOG||Log.DEBUG||error != null) { // 日志仅存服务器,所以不太敏感,而且这些日志虽然量大但非常重要,对排查 bug 很关键
- Log.fd(TAG,requestMethod + "/parseResponse return response = \n" + JSON.toJSONString(requestObject) + "\n\n");
+ if (IS_PRINT_BIG_LOG || Log.DEBUG || error != null) { // 日志仅存服务器,所以不太敏感,而且这些日志虽然量大但非常重要,对排查 bug 很关键
+ Log.fd(TAG, requestMethod + "/parseResponse return response = \n" + JSON.toJSONString(requestObject) + "\n\n");
}
- if (IS_PRINT_REQUEST_ENDTIME_LOG||Log.DEBUG||error != null) {
- Log.fd(TAG , requestMethod + "/parseResponse endTime = " + endTime + "; duration = " + duration);
- Log.sl("",'>',"\n\n\n");
+ if (IS_PRINT_REQUEST_ENDTIME_LOG || Log.DEBUG || error != null) {
+ Log.fd(TAG, requestMethod + "/parseResponse endTime = " + endTime + "; duration = " + duration);
+ Log.sl("", '>', "\n\n\n");
}
return res;
}
@@ -611,21 +621,44 @@ else if (target.containsKey(key) == false) {
* @return
*/
public static JSONObject newResult(int code, String msg) {
- return extendResult(null, code, msg);
+ return newResult(code, msg, false);
}
+ /**新建带状态内容的JSONObject
+ * @param code
+ * @param msg
+ * @param isRoot
+ * @return
+ */
+ public static JSONObject newResult(int code, String msg, boolean isRoot) {
+ return extendResult(null, code, msg, isRoot);
+ }
+
/**添加JSONObject的状态内容,一般用于错误提示结果
* @param object
* @param code
* @param msg
* @return
*/
- public static JSONObject extendResult(JSONObject object, int code, String msg) {
+ 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 系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version")
+ + " \n 数据库: DEFAULT_DATABASE = " + AbstractSQLConfig.DEFAULT_DATABASE
+ + " \n JDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch")
+ + " \n APIJSON: " + Log.VERSION
+ + " \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);
}
- boolean isOk = JSONResponse.isSuccess(code);
+
if (object.containsKey(JSONResponse.KEY_OK) == false) {
- object.put(JSONResponse.KEY_OK, isOk);
+ object.put(JSONResponse.KEY_OK, JSONResponse.isSuccess(code));
}
if (object.containsKey(JSONResponse.KEY_CODE) == false) {
object.put(JSONResponse.KEY_CODE, code);
@@ -635,8 +668,12 @@ public static JSONObject extendResult(JSONObject object, int code, String msg) {
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;
}
@@ -646,37 +683,64 @@ public static JSONObject extendResult(JSONObject object, int code, String msg) {
* @return
*/
public static JSONObject extendSuccessResult(JSONObject object) {
- return extendResult(object, JSONResponse.CODE_SUCCESS, JSONResponse.MSG_SUCCEED);
+ return extendSuccessResult(object, false);
+ }
+ /**添加请求成功的状态内容
+ * @param object
+ * @param isRoot
+ * @return
+ */
+ public static JSONObject extendSuccessResult(JSONObject object, boolean isRoot) {
+ return extendResult(object, JSONResponse.CODE_SUCCESS, JSONResponse.MSG_SUCCEED, isRoot);
}
/**获取请求成功的状态内容
* @return
*/
public static JSONObject newSuccessResult() {
- return newResult(JSONResponse.CODE_SUCCESS, JSONResponse.MSG_SUCCEED);
+ return newSuccessResult(false);
+ }
+ /**获取请求成功的状态内容
+ * @param isRoot
+ * @return
+ */
+ public static JSONObject newSuccessResult(boolean isRoot) {
+ return newResult(JSONResponse.CODE_SUCCESS, JSONResponse.MSG_SUCCEED, isRoot);
}
+
/**添加请求成功的状态内容
* @param object
+ * @param e
+ * @param isRoot
* @return
*/
public static JSONObject extendErrorResult(JSONObject object, Exception e) {
- return extendErrorResult(object, e, null, null);
+ return extendErrorResult(object, e, false);
+ }
+ /**添加请求成功的状态内容
+ * @param object
+ * @param e
+ * @param isRoot
+ * @return
+ */
+ public static JSONObject extendErrorResult(JSONObject object, Exception e, boolean isRoot) {
+ return extendErrorResult(object, e, null, null, isRoot);
}
/**添加请求成功的状态内容
* @param object
* @return
*/
- public static JSONObject extendErrorResult(JSONObject object, Exception e, RequestMethod requestMethod, String url) {
+ public static JSONObject extendErrorResult(JSONObject object, Exception e, RequestMethod requestMethod, String url, boolean isRoot) {
String msg = e.getMessage();
- if (Log.DEBUG) {
+ if (Log.DEBUG && isRoot) {
try {
int index = msg.lastIndexOf(Log.KEY_SYSTEM_INFO_DIVIDER);
String info = index >= 0 ? msg.substring(index + Log.KEY_SYSTEM_INFO_DIVIDER.length()).trim()
- : "\n**环境信息** "
- + "\n系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version")
- + "\n数据库: "
- + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch")
- + "\nAPIJSON: " + Log.VERSION;
+ : " \n **环境信息** "
+ + " \n 系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version")
+ + " \n 数据库: "
+ + " \n JDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch")
+ + " \n APIJSON: " + Log.VERSION;
msg = index < 0 ? msg : msg.substring(0, index).trim();
String encodedMsg = URLEncoder.encode(msg, "UTF-8");
@@ -711,30 +775,30 @@ public static JSONObject extendErrorResult(JSONObject object, Exception e, Reque
boolean isSQLException = e instanceof SQLException; // SQL 报错一般都是通用问题,优先搜索引擎
String apiatuoAndGitHubLink = "\n【APIAuto】: \n http://apijson.cn/api?type=JSON&url=" + URLEncoder.encode(url, "UTF-8") + "&json=" + req
+ " \n\n【GitHub】: \n https://www.google.com/search?q=site%3Agithub.com%2FTencent%2FAPIJSON+++" + encodedMsg;
-
- msg += " \n\n\n浏览器打开以下链接查看解答"
+
+ msg += Log.KEY_SYSTEM_INFO_DIVIDER + " \n | \n 浏览器打开以下链接查看解答"
+ (isSQLException ? "" : apiatuoAndGitHubLink)
// GitHub Issue 搜索貌似是精准包含,不易找到答案 + " \n\nGitHub: \n https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+" + encodedMsg
+ " \n\n【Google】:\n https://www.google.com/search?q=" + encodedMsg
+ " \n\n【百度】:\n https://www.baidu.com/s?ie=UTF-8&wd=" + encodedMsg
+ (isSQLException ? apiatuoAndGitHubLink : "")
+ " \n\n都没找到答案?打开这个链接 \n https://github.com/Tencent/APIJSON/issues/new?assignees=&labels=&template=--bug.md "
- + "\n然后提交问题,推荐用以下模板修改,注意要换行保持清晰可读。"
- + "\n【标题】:" + msg
- + "\n【内容】:" + info + "\n\n**问题描述**\n" + msg
- + "\n\n"
- + "\n\nPOST " + url
- + "\n请求 Request JSON:\n ```js"
- + "\n 请填写,例如 { \"Users\":{} }"
- + "\n```"
- + "\n\n返回结果 Response JSON:\n ```js"
- + "\n 请填写,例如 { \"Users\": {}, \"code\": 401, \"msg\": \"Users 不允许 UNKNOWN 用户的 GET 请求!\" }"
- + "\n```";
+ + " \n然后提交问题,推荐用以下模板修改,注意要换行保持清晰可读。"
+ + " \n【标题】:" + msg
+ + " \n【内容】:" + info + "\n\n**问题描述**\n" + msg
+ + " \n\n"
+ + " \n\nPOST " + url
+ + " \n请求 Request JSON:\n ```js"
+ + " \n 请填写,例如 { \"Users\":{} }"
+ + " \n```"
+ + " \n\n返回结果 Response JSON:\n ```js"
+ + " \n 请填写,例如 { \"Users\": {}, \"code\": 401, \"msg\": \"Users 不允许 UNKNOWN 用户的 GET 请求!\" }"
+ + " \n```";
} catch (Throwable e2) {}
}
- JSONObject error = newErrorResult(e);
- return extendResult(object, error.getIntValue(JSONResponse.KEY_CODE), msg);
+ JSONObject error = newErrorResult(e, isRoot);
+ return extendResult(object, error.getIntValue(JSONResponse.KEY_CODE), msg, isRoot);
}
/**新建错误状态内容
@@ -742,6 +806,14 @@ public static JSONObject extendErrorResult(JSONObject object, Exception e, Reque
* @return
*/
public static JSONObject newErrorResult(Exception e) {
+ return newErrorResult(e, false);
+ }
+ /**新建错误状态内容
+ * @param e
+ * @param isRoot
+ * @return
+ */
+ public static JSONObject newErrorResult(Exception e, boolean isRoot) {
if (e != null) {
e.printStackTrace();
@@ -786,10 +858,10 @@ else if (e instanceof NullPointerException) {
code = JSONResponse.CODE_SERVER_ERROR;
}
- return newResult(code, e.getMessage());
+ return newResult(code, e.getMessage(), isRoot);
}
- return newResult(JSONResponse.CODE_SERVER_ERROR, JSONResponse.MSG_SERVER_ERROR);
+ return newResult(JSONResponse.CODE_SERVER_ERROR, JSONResponse.MSG_SERVER_ERROR, isRoot);
}
@@ -1261,7 +1333,6 @@ else if (join != null){
}
-
List joinList = new ArrayList<>();
@@ -1375,6 +1446,9 @@ else if (join != null){
tableObj = newTableObj;
request.put(tableKey, tableObj);
+
+// tableObj.clear();
+// tableObj.putAll(newTableObj);
}
// 保证和 SQLExcecutor 缓存的 Config 里 where 顺序一致,生成的 SQL 也就一致 >>>>>>>>>
@@ -1763,7 +1837,8 @@ public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Except
result.put(KEY_EXPLAIN, explainResult);
result.putAll(res);
}
- }else{//如果是更新请求,不执行explain,但可以返回sql
+ }
+ else {//如果是更新请求,不执行explain,但可以返回sql
result = new JSONObject(true);
result.put(KEY_SQL, config.getSQL(false));
result.putAll(res);
@@ -1780,7 +1855,10 @@ public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Except
try {
String db = config.getDatabase();
if (db == null) {
- if (config.isPostgreSQL()) {
+ if (config.isMySQL()) {
+ db = SQLConfig.DATABASE_MYSQL;
+ }
+ else if (config.isPostgreSQL()) {
db = SQLConfig.DATABASE_POSTGRESQL;
}
else if (config.isSQLServer()) {
@@ -1796,18 +1874,18 @@ else if (config.isClickHouse()) {
db = SQLConfig.DATABASE_CLICKHOUSE;
}
else {
- db = SQLConfig.DATABASE_MYSQL;
+ db = AbstractSQLConfig.DEFAULT_DATABASE;
}
}
Class extends Exception> clazz = e.getClass();
e = clazz.getConstructor(String.class).newInstance(
e.getMessage()
- + " " + Log.KEY_SYSTEM_INFO_DIVIDER + " \n**环境信息** "
- + "\n系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version")
- + "\n数据库: " + db + " " + config.getDBVersion()
- + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch")
- + "\nAPIJSON: " + Log.VERSION
+ + " " + Log.KEY_SYSTEM_INFO_DIVIDER + " \n **环境信息** "
+ + " \n 系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version")
+ + " \n 数据库: " + db + " " + config.getDBVersion()
+ + " \n JDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch")
+ + " \n APIJSON: " + Log.VERSION
);
} catch (Throwable e2) {}
}
From 5d59b3529274d1542bdef90dc78e888c7af8d0f1 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 3 Jan 2022 00:48:35 +0800
Subject: [PATCH 156/754] =?UTF-8?q?=E4=BC=98=E5=8C=96=20JOIN=20=E5=89=AF?=
=?UTF-8?q?=E8=A1=A8=E8=A7=A3=E6=9E=90=E7=BB=93=E6=9E=9C=E9=9B=86=20Result?=
=?UTF-8?q?Set=20=E7=9A=84=E6=80=A7=E8=83=BD=EF=BC=88=E5=87=8F=E5=B0=91?=
=?UTF-8?q?=E5=90=8C=E5=89=AF=E8=A1=A8=E5=AD=97=E6=AE=B5=E7=9A=84=E9=87=8D?=
=?UTF-8?q?=E5=A4=8D=E9=80=BB=E8=BE=91=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/main/java/apijson/StringUtil.java | 15 ++
.../main/java/apijson/orm/AbstractParser.java | 5 +-
.../java/apijson/orm/AbstractSQLExecutor.java | 148 ++++++++----------
.../src/main/java/apijson/orm/Join.java | 7 +-
4 files changed, 90 insertions(+), 85 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java
index bd3b87f7b..0d473a6b0 100755
--- a/APIJSONORM/src/main/java/apijson/StringUtil.java
+++ b/APIJSONORM/src/main/java/apijson/StringUtil.java
@@ -8,6 +8,7 @@
import java.io.File;
import java.math.BigDecimal;
import java.text.DecimalFormat;
+import java.util.Objects;
import java.util.regex.Pattern;
/**通用字符串(String)相关类,为null时返回""
@@ -891,4 +892,18 @@ public static String concat(String left, String right, String split, boolean tri
//校正(自动补全等)字符串>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+ public static boolean equals(Object s1, Object s2) {
+ return Objects.equals(s1, s2);
+ }
+ public static boolean equalsIgnoreCase(String s1, String s2) {
+ if (s1 == s2) {
+ return true;
+ }
+ if (s1 == null || s2 == null) {
+ return false;
+ }
+ return s1.equalsIgnoreCase(s2);
+ }
+
}
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
index b9c37a1a6..1bc1bacfb 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
@@ -1851,7 +1851,8 @@ public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Except
return result;
}
catch (Exception e) {
- if (Log.DEBUG && e.getMessage().contains(Log.KEY_SYSTEM_INFO_DIVIDER) == false) {
+ String msg = e.getMessage();
+ if (Log.DEBUG && msg != null && msg.contains(Log.KEY_SYSTEM_INFO_DIVIDER) == false) {
try {
String db = config.getDatabase();
if (db == null) {
@@ -1880,7 +1881,7 @@ else if (config.isClickHouse()) {
Class extends Exception> clazz = e.getClass();
e = clazz.getConstructor(String.class).newInstance(
- e.getMessage()
+ msg
+ " " + Log.KEY_SYSTEM_INFO_DIVIDER + " \n **环境信息** "
+ " \n 系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version")
+ " \n 数据库: " + db + " " + config.getDBVersion()
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
index 72065302f..8400e429e 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
@@ -78,7 +78,7 @@ public int getExecutedSQLCount() {
* @param isStatic
*/
@Override
- public synchronized void putCache(String sql, List list, int type) {
+ public void putCache(String sql, List list, int type) {
if (sql == null || list == null) { //空map有效,说明查询过sql了 || list.isEmpty()) {
Log.i(TAG, "saveList sql == null || list == null >> return;");
return;
@@ -90,7 +90,7 @@ public synchronized void putCache(String sql, List list, int type) {
* @param isStatic
*/
@Override
- public synchronized void removeCache(String sql, int type) {
+ public void removeCache(String sql, int type) {
if (sql == null) {
Log.i(TAG, "removeList sql == null >> return;");
return;
@@ -115,7 +115,8 @@ public JSONObject getCacheItem(String sql, int position, int type) {
//只要map不为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();
}
@@ -124,20 +125,14 @@ public JSONObject getCacheItem(String sql, int position, int type) {
@Override
public ResultSet executeQuery(@NotNull Statement statement, String sql) throws Exception {
- executedSQLCount ++;
-
return statement.executeQuery(sql);
}
@Override
public int executeUpdate(@NotNull Statement statement, String sql) throws Exception {
- executedSQLCount ++;
-
return statement.executeUpdate(sql);
}
@Override
public ResultSet execute(@NotNull Statement statement, String sql) throws Exception {
- executedSQLCount ++;
-
statement.execute(sql);
return statement.getResultSet();
}
@@ -186,6 +181,10 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws
try {
if (unknowType) {
Statement statement = getStatement(config);
+
+ if (isExplain == false) { //只有 SELECT 才能 EXPLAIN
+ executedSQLCount ++;
+ }
rs = execute(statement, sql);
result = new JSONObject(true);
@@ -199,8 +198,9 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws
case POST:
case PUT:
case DELETE:
- executedSQLCount ++;
-
+ if (isExplain == false) { //只有 SELECT 才能 EXPLAIN
+ executedSQLCount ++;
+ }
int updateCount = executeUpdate(config);
if (updateCount <= 0) {
throw new IllegalAccessException("没权限访问或对象不存在!"); // NotExistException 会被 catch 转为成功状态
@@ -222,7 +222,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws
case GETS:
case HEAD:
case HEADS:
- result = isHead ? null : getCacheItem(sql, position, config.getCache());
+ result = isHead || isExplain ? null : getCacheItem(sql, position, config.getCache());
Log.i(TAG, ">>> execute result = getCache('" + sql + "', " + position + ") = " + result);
if (result != null) {
cachedSQLCount ++;
@@ -231,11 +231,10 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws
return result;
}
- rs = executeQuery(config); //FIXME SQL Server 是一次返回两个结果集,包括查询结果和执行计划,需要 moreResults
-
if (isExplain == false) { //只有 SELECT 才能 EXPLAIN
executedSQLCount ++;
}
+ rs = executeQuery(config); //FIXME SQL Server 是一次返回两个结果集,包括查询结果和执行计划,需要 moreResults
break;
default://OPTIONS, TRACE等
@@ -270,6 +269,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws
childMap = new HashMap<>(); //要存到cacheMap
// Map columnIndexAndJoinMap = new HashMap<>(length);
String lastTableName = null; // 默认就是主表 config.getTable();
+ String lastAliasName = null; // 默认就是主表 config.getAlias();
int lastViceTableStart = 0;
int lastViceColumnStart = 0;
Join lastJoin = null;
@@ -289,6 +289,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws
Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n execute while (rs.next()){ index = " + index + "\n\n");
JSONObject item = new JSONObject(true);
+ JSONObject curItem = item;
boolean isMain = true;
for (int i = 1; i <= length; i++) {
@@ -301,17 +302,19 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws
Join curJoin = columnIndexAndJoinMap == null ? null : columnIndexAndJoinMap[i - 1]; // columnIndexAndJoinMap.get(i);
// 为什么 isExplain == false 不用判断?因为所有字段都在一张 Query Plan 表
- if (index <= 0 && hasJoin && ! isExplain) { // && viceColumnStart > length) {
+ if (index <= 0 && columnIndexAndJoinMap != null) { // && viceColumnStart > length) {
SQLConfig curConfig = curJoin == null || ! curJoin.isSQLJoin() ? null : curJoin.getCacheConfig();
List curColumn = curConfig == null ? null : curConfig.getColumn();
String sqlTable = curConfig == null ? null : curConfig.getSQLTable();
+ String sqlAlias = curConfig == null ? null : curConfig.getAlias();
List column = config.getColumn();
int mainColumnSize = column == null ? 0 : column.size();
+ // FIXME 主副表同名导致主表数据当成副表数据 { "[]": { "join": "": 0 }, "Comment:to": { "@column": "id,content", "id@": "/Comment/toId" } }, "@explain": true }
boolean toFindJoin = mainColumnSize <= 0 || i > mainColumnSize; // 主表就不用找 JOIN 配置
- if (StringUtil.isEmpty(sqlTable , true)) {
+ if (StringUtil.isEmpty(sqlTable, true)) {
if (toFindJoin) { // 在主表字段数量内的都归属主表
sqlTable = rsmd.getTableName(i); // SQL 函数甚至部分字段都不返回表名,当然如果没传 @column 生成的 Table.* 则返回的所有字段都会带表名
@@ -323,12 +326,15 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws
SQLConfig cfg = join == null || ! join.isSQLJoin() ? null : join.getJoinConfig();
List c = cfg == null ? null : cfg.getColumn();
- nextViceColumnStart += (
- c != null && ! c.isEmpty() ? c.size()
- : (Objects.equals(sqlTable, lastTableName) || sqlTable.equalsIgnoreCase(lastTableName) ? 1 : 0)
+ nextViceColumnStart += (c != null && ! c.isEmpty() ?
+ c.size() : (
+ StringUtil.equalsIgnoreCase(sqlTable, lastTableName)
+ && StringUtil.equals(sqlAlias, lastAliasName) ? 1 : 0
+ )
);
if (i < nextViceColumnStart) {
sqlTable = cfg.getSQLTable();
+ sqlAlias = cfg.getAlias();
lastViceTableStart = j; // 避免后面的空 @column 表内字段被放到之前的空 @column 表
curJoin = join;
@@ -345,6 +351,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws
// 没有 @column,仍然定位不了,用前一个 table 名。FIXME 如果刚好某个表内第一个字段是就是 SQL 函数?
if (StringUtil.isEmpty(sqlTable, true)) {
sqlTable = lastTableName;
+ sqlAlias = lastAliasName;
toFindJoin = false;
}
}
@@ -353,8 +360,9 @@ else if (config.isClickHouse() && (sqlTable.startsWith("`") || sqlTable.startsWi
sqlTable = sqlTable.substring(1, sqlTable.length() - 1);
}
- if ((sqlTable == null && lastTableName != null) || (sqlTable != null && ! sqlTable.equalsIgnoreCase(lastTableName))) {
+ if (StringUtil.equalsIgnoreCase(sqlTable, lastTableName) == false || StringUtil.equals(sqlAlias, lastAliasName) == false) {
lastTableName = sqlTable;
+ lastAliasName = sqlAlias;
lastViceColumnStart = i;
if (toFindJoin) { // 找到对应的副表 JOIN 配置
@@ -362,7 +370,8 @@ else if (config.isClickHouse() && (sqlTable.startsWith("`") || sqlTable.startsWi
Join join = joinList.get(j);
SQLConfig cfg = join == null || ! join.isSQLJoin() ? null : join.getJoinConfig();
- if (cfg != null && sqlTable != null && sqlTable.equalsIgnoreCase(cfg.getSQLTable())) {
+ if (cfg != null && StringUtil.equalsIgnoreCase(sqlTable, cfg.getSQLTable())
+ ) { // FIXME 导致副表字段错放到主表 && StringUtil.equals(sqlAlias, cfg.getAlias())) {
lastViceTableStart = j; // 避免后面的空 @column 表内字段被放到之前的空 @column 表
curJoin = join;
@@ -378,7 +387,7 @@ else if (config.isClickHouse() && (sqlTable.startsWith("`") || sqlTable.startsWi
if (isMain) {
lastViceColumnStart ++;
- }
+ }
else {
if (curJoin == null) {
curJoin = lastJoin;
@@ -398,23 +407,42 @@ else if (config.isClickHouse() && (sqlTable.startsWith("`") || sqlTable.startsWi
}
}
- columnIndexAndJoinMap[i - 1] = curJoin; // columnIndexAndJoinMap.put(i, curJoin); // TODO columnIndexAndTableMap[i] = sqlTable 提速?
-
-// if (column != null && column.isEmpty() == false) {
-// viceColumnStart = column.size() + 1;
-// }
-// else if (config.getSQLTable().equalsIgnoreCase(sqlTable) == false) {
-// viceColumnStart = i;
-// }
+ columnIndexAndJoinMap[i - 1] = curJoin;
}
+
+ // 如果是主表则直接用主表对应的 item,否则缓存副表数据到 childMap
+ Join prevJoin = columnIndexAndJoinMap == null || i < 2 ? null : columnIndexAndJoinMap[i - 2];
+ 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);
+ }
+ String viceSql = viceConfig == null ? null : viceConfig.getSQL(false); //TODO 在 SQLConfig 缓存 SQL,减少大量的重复生成
- item = onPutColumn(config, rs, rsmd, index, item, i, curJoin, childMap); // isExplain == false && hasJoin && i >= viceColumnStart ? childMap : null);
+ if (StringUtil.isEmpty(viceSql, true)) {
+ Log.i(TAG, "execute StringUtil.isEmpty(viceSql, true) >> item = null; >> ");
+ curItem = null;
+ }
+ else if (curJoin.isOuterJoin() || curJoin.isAntiJoin()) {
+ Log.i(TAG, "execute curJoin.isOuterJoin() || curJoin.isAntiJoin() >> item = null; >> ");
+ curItem = null; // 肯定没有数据,缓存也无意义
+ // 副表是按常规条件查询,缓存会导致其它同表同条件对象查询结果集为空 childMap.put(viceSql, new JSONObject()); // 缓存固定空数据,避免后续多余查询
+ }
+ else {
+ curItem = (JSONObject) childMap.get(viceSql);
+ if (curItem == null) {
+ curItem = new JSONObject(true);
+ childMap.put(viceSql, curItem);
+ }
+ }
+ }
+
+ curItem = onPutColumn(config, rs, rsmd, index, curItem, i, curJoin, childMap); // isExplain == false && hasJoin && i >= viceColumnStart ? childMap : null);
}
resultList = onPutTable(config, rs, rsmd, resultList, index, item);
- Log.d(TAG, "\n execute while (rs.next()) { resultList.put( " + index + ", result); "
- + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n");
+ Log.d(TAG, "execute while (rs.next()) { resultList.put( " + index + ", result); " + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n");
}
}
}
@@ -622,61 +650,21 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map
*/
protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd
, final int tablePosition, @NotNull JSONObject table, final int columnIndex, Join join, Map childMap) throws Exception {
-
+ if (table == null) { // 对应副表 viceSql 不能生成正常 SQL, 或者是 ! - Outer, ( - ANTI JOIN 的副表这种不需要缓存及返回的数据
+ Log.i(TAG, "onPutColumn table == null >> return table;");
+ return table;
+ }
+
if (isHideColumn(config, rs, rsmd, tablePosition, table, columnIndex, childMap)) {
Log.i(TAG, "onPutColumn isHideColumn(config, rs, rsmd, tablePosition, table, columnIndex, childMap) >> return table;");
return table;
}
- //已改为 rsmd.getTableName(columnIndex) 支持副表不传 @column , 但如何判断是副表?childMap != null
- // String lable = rsmd.getColumnLabel(columnIndex);
- // int dotIndex = lable.indexOf(".");
String lable = getKey(config, rs, rsmd, tablePosition, table, columnIndex, childMap);
-
-// String childTable = childMap == null ? null : sqlTableName; // rsmd.getTableName(columnIndex); //dotIndex < 0 ? null : lable.substring(0, dotIndex);
-
- JSONObject finalTable = null;
- String childSql = null;
-
- SQLConfig childConfig = join == null || join.isSQLJoin() == false ? null : join.getCacheConfig();
- if (childConfig == null) {
- finalTable = table;
- }
- else {
- // lable = column;
-
- //
-
-// List joinList = config.getJoinList();
-// if (joinList != null) {
-// for (Join j : joinList) {
-// childConfig = j.isAppJoin() ? null : j.getCacheConfig(); //这里用config改了getSQL后再还原很麻烦,所以提前给一个config2更好
-
- // FIXME 副表的 SQL 函数,甚至普通字段都可能从 rsmd.getTableName(columnIndex) 拿到 ""
-// if (childConfig != null && childTable.equalsIgnoreCase(childConfig.getSQLTable())) {
-
- childConfig.putWhere(join.getKey(), table.get(join.getTargetKey()), true);
- childSql = childConfig.getSQL(false);
-
- if (StringUtil.isEmpty(childSql, true)) {
- return table;
- }
-
- finalTable = (JSONObject) childMap.get(childSql);
-// break;
-// }
-// }
-// }
-
- }
-
Object value = getValue(config, rs, rsmd, tablePosition, table, columnIndex, lable, childMap);
+
// 必须 put 进去,否则某个字段为 null 可能导致中断后续正常返回值 if (value != null) {
- if (finalTable == null) {
- finalTable = new JSONObject(true);
- childMap.put(childSql, finalTable);
- }
- finalTable.put(lable, value);
+ table.put(lable, value);
// }
return table;
diff --git a/APIJSONORM/src/main/java/apijson/orm/Join.java b/APIJSONORM/src/main/java/apijson/orm/Join.java
index 70c4401b9..7d8707393 100644
--- a/APIJSONORM/src/main/java/apijson/orm/Join.java
+++ b/APIJSONORM/src/main/java/apijson/orm/Join.java
@@ -198,12 +198,13 @@ public boolean isLeftOrRightJoin() {
String jt = getJoinType();
return "<".equals(jt) || ">".equals(jt);
}
-
+
public boolean canCacheViceTable() {
String jt = getJoinType();
return "@".equals(jt) || "<".equals(jt) || ">".equals(jt) || "&".equals(jt) || "*".equals(jt) || ")".equals(jt);
+ // 副表是按常规条件查询,缓存会导致其它同表同条件对象查询结果集为空 return ! isFullJoin(); // ! - OUTER, ( - FOREIGN 都需要缓存空副表数据,避免多余的查询
}
-
+
public boolean isSQLJoin() {
return ! isAppJoin();
}
@@ -215,7 +216,7 @@ public static boolean isSQLJoin(Join j) {
public static boolean isAppJoin(Join j) {
return j != null && j.isAppJoin();
}
-
+
public static boolean isLeftOrRightJoin(Join j) {
return j != null && j.isLeftOrRightJoin();
}
From d41f2a49a6836506ca09b33548477dc5f02df285 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Wed, 5 Jan 2022 01:32:20 +0800
Subject: [PATCH 157/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81?=
=?UTF-8?q?=E5=AF=B9=E6=8E=A5=20Hive=20=E5=92=8C=20Hadoop=20=E7=9A=84=20De?=
=?UTF-8?q?mo=EF=BC=8C=E6=84=9F=E8=B0=A2=20chenyanlann=20=E7=9A=84?=
=?UTF-8?q?=E8=B4=A1=E7=8C=AE~?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
可以点 Star 支持下作者哦
https://github.com/chenyanlann/APIJSONBoot_Hive
---
README.md | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index af6520919..33e7ec713 100644
--- a/README.md
+++ b/README.md
@@ -17,10 +17,12 @@ This source code is licensed under the Apache License Version 2.0
+
+
-
+
@@ -478,6 +480,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
[APIJSONDemo_ClickHouse](https://github.com/chenyanlann/APIJSONDemo_ClickHouse) APIJSON + SpringBoot 连接 ClickHouse 使用的 Demo
+[APIJSONBoot_Hive](https://github.com/chenyanlann/APIJSONBoot_Hive) APIJSON + SpringBoot 连接 Hive 使用的 Demo
+
[apijson-sample](https://gitee.com/greyzeng/apijson-sample) APIJSON 简单使用 Demo 及教程
[apijson-examples](https://gitee.com/drone/apijson-examples) APIJSON 的前端、业务后端、管理后端 Demo
From 27d7e0154877e2973dda13aba33a2ab03be965c6 Mon Sep 17 00:00:00 2001
From: chenyanlann <62465397+chenyanlann@users.noreply.github.com>
Date: Tue, 11 Jan 2022 20:30:55 +0800
Subject: [PATCH 158/754] Update AbstractSQLExecutor.java
fix code format
---
APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
index bc9547e82..54a77bca1 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
@@ -711,10 +711,10 @@ protected String getKey(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNu
String key = rsmd.getColumnLabel(columnIndex);// dotIndex < 0 ? lable : lable.substring(dotIndex + 1);
if (config.isHive()) {
String table_name = config.getTable();
- if(AbstractSQLConfig.TABLE_KEY_MAP.containsKey(table_name)) table_name = AbstractSQLConfig.TABLE_KEY_MAP.get(table_name);
+ if (AbstractSQLConfig.TABLE_KEY_MAP.containsKey(table_name)) table_name = AbstractSQLConfig.TABLE_KEY_MAP.get(table_name);
String pattern = "^" + table_name + "\\." + "[a-zA-Z]+$";
boolean isMatch = Pattern.matches(pattern, key);
- if(isMatch) key = key.split("\\.")[1];
+ if (isMatch) key = key.split("\\.")[1];
}
return key;
}
From 8d16e66b7668761fcd98e545b6f21779ab7d0f20 Mon Sep 17 00:00:00 2001
From: chenyanlann <62465397+chenyanlann@users.noreply.github.com>
Date: Tue, 11 Jan 2022 21:21:42 +0800
Subject: [PATCH 159/754] Update AbstractSQLExecutor.java
fix code format
---
.../main/java/apijson/orm/AbstractSQLExecutor.java | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
index 54a77bca1..2a8f6d7f8 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
@@ -711,10 +711,14 @@ protected String getKey(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNu
String key = rsmd.getColumnLabel(columnIndex);// dotIndex < 0 ? lable : lable.substring(dotIndex + 1);
if (config.isHive()) {
String table_name = config.getTable();
- if (AbstractSQLConfig.TABLE_KEY_MAP.containsKey(table_name)) table_name = AbstractSQLConfig.TABLE_KEY_MAP.get(table_name);
+ if (AbstractSQLConfig.TABLE_KEY_MAP.containsKey(table_name)) {
+ table_name = AbstractSQLConfig.TABLE_KEY_MAP.get(table_name);
+ }
String pattern = "^" + table_name + "\\." + "[a-zA-Z]+$";
boolean isMatch = Pattern.matches(pattern, key);
- if (isMatch) key = key.split("\\.")[1];
+ if (isMatch) {
+ key = key.split("\\.")[1];
+ }
}
return key;
}
@@ -972,7 +976,9 @@ public ResultSet executeQuery(@NotNull SQLConfig config) throws Exception {
public int executeUpdate(@NotNull SQLConfig config) throws Exception {
PreparedStatement s = getStatement(config);
int count = s.executeUpdate(); //PreparedStatement 不用传 SQL
- if (config.isHive() && count==0) count = 1;
+ if (config.isHive() && count==0) {
+ count = 1;
+ }
if (config.getMethod() == RequestMethod.POST && config.getId() == null) { //自增id
ResultSet rs = s.getGeneratedKeys();
From dae5ac9709abde6e749fce49cfacbd9b805a8e48 Mon Sep 17 00:00:00 2001
From: chenyanlann <62465397+chenyanlann@users.noreply.github.com>
Date: Wed, 12 Jan 2022 00:00:46 +0800
Subject: [PATCH 160/754] Update AbstractSQLExecutor.java
---
.../src/main/java/apijson/orm/AbstractSQLExecutor.java | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
index 2a8f6d7f8..3c7ddc2a7 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
@@ -710,11 +710,11 @@ protected String getKey(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNu
, final int tablePosition, @NotNull JSONObject table, final int columnIndex, Map childMap) throws Exception {
String key = rsmd.getColumnLabel(columnIndex);// dotIndex < 0 ? lable : lable.substring(dotIndex + 1);
if (config.isHive()) {
- String table_name = config.getTable();
- if (AbstractSQLConfig.TABLE_KEY_MAP.containsKey(table_name)) {
- table_name = AbstractSQLConfig.TABLE_KEY_MAP.get(table_name);
+ String tableName = config.getTable();
+ if (AbstractSQLConfig.TABLE_KEY_MAP.containsKey(tableName)) {
+ tableName = AbstractSQLConfig.TABLE_KEY_MAP.get(tableName);
}
- String pattern = "^" + table_name + "\\." + "[a-zA-Z]+$";
+ String pattern = "^" + tableName + "\\." + "[a-zA-Z]+$";
boolean isMatch = Pattern.matches(pattern, key);
if (isMatch) {
key = key.split("\\.")[1];
@@ -976,7 +976,7 @@ public ResultSet executeQuery(@NotNull SQLConfig config) throws Exception {
public int executeUpdate(@NotNull SQLConfig config) throws Exception {
PreparedStatement s = getStatement(config);
int count = s.executeUpdate(); //PreparedStatement 不用传 SQL
- if (config.isHive() && count==0) {
+ if (config.isHive() && count == 0) {
count = 1;
}
From bb58a25354478b917204663aee9c1af21064893c Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Mon, 17 Jan 2022 00:39:41 +0800
Subject: [PATCH 161/754] =?UTF-8?q?=E8=B0=83=E8=AF=95=E6=97=B6=E9=97=B4?=
=?UTF-8?q?=E5=AD=97=E6=AE=B5=E6=96=B0=E5=A2=9E=20parse=20=E5=92=8C=20sql?=
=?UTF-8?q?=20=E4=B8=A4=E4=B8=AA=E6=97=B6=E9=95=BF=EF=BC=8C=E4=BE=8B?=
=?UTF-8?q?=E5=A6=82=20"time:start|duration|end|parse|sql":=20"16417510485?=
=?UTF-8?q?73|145|1641751048718|50|95"?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
APIJSONORM/src/main/java/apijson/Log.java | 2 +-
.../main/java/apijson/orm/AbstractParser.java | 13 +-
.../java/apijson/orm/AbstractSQLExecutor.java | 134 +++++++++++++++---
.../main/java/apijson/orm/SQLExecutor.java | 5 +-
4 files changed, 130 insertions(+), 24 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/Log.java b/APIJSONORM/src/main/java/apijson/Log.java
index d67f19a71..6dfc96caf 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.8.0";
+ public static final String VERSION = "4.8.5";
public static final String KEY_SYSTEM_INFO_DIVIDER = "---|-----APIJSON SYSTEM INFO-----|---";
//默认的时间格式
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
index 1bc1bacfb..529f4025f 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
@@ -353,6 +353,7 @@ public JSONObject parseResponse(String request) {
}
private int queryDepth;
+ private long executedSQLDuration;
/**解析请求json并获取对应结果
* @param request
@@ -420,6 +421,8 @@ public JSONObject parseResponse(JSONObject request) {
onBegin();
try {
queryDepth = 0;
+ executedSQLDuration = 0;
+
requestObject = onObjectParse(request, null, null, null, false);
onCommit();
@@ -441,7 +444,10 @@ public JSONObject parseResponse(JSONObject request) {
if (Log.DEBUG) {
requestObject.put("sql:generate|cache|execute|maxExecute", getSQLExecutor().getGeneratedSQLCount() + "|" + getSQLExecutor().getCachedSQLCount() + "|" + getSQLExecutor().getExecutedSQLCount() + "|" + getMaxSQLCount());
requestObject.put("depth:count|max", queryDepth + "|" + getMaxQueryDepth());
- requestObject.put("time:start|duration|end", startTime + "|" + duration + "|" + endTime);
+
+ executedSQLDuration += sqlExecutor.getExecutedSQLDuration() + sqlExecutor.getSqlResultDuration();
+ long parseDuration = duration - executedSQLDuration;
+ requestObject.put("time:start|duration|end|parse|sql", startTime + "|" + duration + "|" + endTime + "|" + parseDuration + "|" + executedSQLDuration);
if (error != null) {
requestObject.put("trace:throw", error.getClass().getName());
@@ -1845,7 +1851,10 @@ public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Except
}
}
else {
- result = getSQLExecutor().execute(config, false);
+ sqlExecutor = getSQLExecutor();
+ result = sqlExecutor.execute(config, false);
+ // FIXME 改为直接在 sqlExecutor 内加好,最后 Parser 取结果,可以解决并发执行导致内部计算出错
+// executedSQLDuration += sqlExecutor.getExecutedSQLDuration() + sqlExecutor.getSqlResultDuration();
}
return result;
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
index bc9547e82..e7f959a93 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
@@ -25,7 +25,6 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
-import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
@@ -37,7 +36,6 @@
import apijson.NotNull;
import apijson.RequestMethod;
import apijson.StringUtil;
-import apijson.orm.AbstractSQLConfig;
/**executor for query(read) or update(write) MySQL database
* @author Lemon
@@ -67,6 +65,33 @@ public int getCachedSQLCount() {
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;
+ }
+ @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;
+ }
+
+ @Override
+ public long getSqlResultDuration() {
+ return sqlResultDuration;
+ }
/**
* 缓存map
@@ -127,16 +152,25 @@ public JSONObject getCacheItem(String sql, int position, int type) {
@Override
public ResultSet executeQuery(@NotNull Statement statement, String sql) throws Exception {
- return statement.executeQuery(sql);
+// executedSQLStartTime = System.currentTimeMillis();
+ ResultSet rs = statement.executeQuery(sql);
+// executedSQLEndTime = System.currentTimeMillis();
+ return rs;
}
@Override
public int executeUpdate(@NotNull Statement statement, String sql) throws Exception {
- return statement.executeUpdate(sql);
+// 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);
- return statement.getResultSet();
+ ResultSet rs = statement.getResultSet();
+// executedSQLEndTime = System.currentTimeMillis();
+ return rs;
}
/**执行SQL
@@ -146,6 +180,11 @@ 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;
+
boolean isPrepared = config.isPrepared();
final String sql = config.getSQL(false);
@@ -182,15 +221,19 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws
try {
if (unknowType) {
- Statement statement = getStatement(config);
-
if (isExplain == false) { //只有 SELECT 才能 EXPLAIN
executedSQLCount ++;
+ executedSQLStartTime = System.currentTimeMillis();
}
+ Statement statement = getStatement(config);
rs = execute(statement, sql);
-
- result = new JSONObject(true);
int updateCount = statement.getUpdateCount();
+ if (isExplain == false) {
+ executedSQLEndTime = System.currentTimeMillis();
+ executedSQLDuration += executedSQLEndTime - executedSQLStartTime;
+ }
+
+ result = new JSONObject(true);
result.put(JSONResponse.KEY_COUNT, updateCount);
result.put("update", updateCount >= 0);
//导致后面 rs.getMetaData() 报错 Operation not allowed after ResultSet closed result.put("moreResults", statement.getMoreResults());
@@ -202,8 +245,14 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws
case DELETE:
if (isExplain == false) { //只有 SELECT 才能 EXPLAIN
executedSQLCount ++;
+ executedSQLStartTime = System.currentTimeMillis();
}
int updateCount = executeUpdate(config);
+ if (isExplain == false) {
+ executedSQLEndTime = System.currentTimeMillis();
+ executedSQLDuration += executedSQLEndTime - executedSQLStartTime;
+ }
+
if (updateCount <= 0) {
throw new IllegalAccessException("没权限访问或对象不存在!"); // NotExistException 会被 catch 转为成功状态
}
@@ -235,8 +284,13 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws
if (isExplain == false) { //只有 SELECT 才能 EXPLAIN
executedSQLCount ++;
+ executedSQLStartTime = System.currentTimeMillis();
}
rs = executeQuery(config); //FIXME SQL Server 是一次返回两个结果集,包括查询结果和执行计划,需要 moreResults
+ if (isExplain == false) {
+ executedSQLEndTime = System.currentTimeMillis();
+ executedSQLDuration += executedSQLEndTime - executedSQLStartTime;
+ }
break;
default://OPTIONS, TRACE等
@@ -264,8 +318,10 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws
int index = -1;
+ long startTime2 = System.currentTimeMillis();
ResultSetMetaData rsmd = rs.getMetaData();
final int length = rsmd.getColumnCount();
+ sqlResultDuration += System.currentTimeMillis() - startTime2;
//
childMap = new HashMap<>(); //要存到cacheMap
@@ -286,7 +342,14 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws
Join[] columnIndexAndJoinMap = isExplain || ! hasJoin ? null : new Join[length];
// int viceColumnStart = length + 1; //第一个副表字段的index
+
+// FIXME 统计游标查找的时长?可能 ResultSet.next() 及 getTableName, getColumnName, getObject 比较耗时,因为不是一次加载到内存,而是边读边发
+
+ long lastCursorTime = System.currentTimeMillis();
while (rs.next()) {
+ sqlResultDuration += System.currentTimeMillis() - lastCursorTime;
+ lastCursorTime = System.currentTimeMillis();
+
index ++;
Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n execute while (rs.next()){ index = " + index + "\n\n");
@@ -318,8 +381,10 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws
if (StringUtil.isEmpty(sqlTable, true)) {
if (toFindJoin) { // 在主表字段数量内的都归属主表
+ long startTime3 = System.currentTimeMillis();
sqlTable = rsmd.getTableName(i); // SQL 函数甚至部分字段都不返回表名,当然如果没传 @column 生成的 Table.* 则返回的所有字段都会带表名
-
+ sqlResultDuration += System.currentTimeMillis() - startTime3;
+
if (StringUtil.isEmpty(sqlTable, true)) { // hasJoin 已包含这个判断 && joinList != null) {
int nextViceColumnStart = lastViceColumnStart; // 主表没有 @column 时会偏小 lastViceColumnStart
@@ -588,12 +653,19 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map
int index = -1;
+ long startTime2 = System.currentTimeMillis();
ResultSetMetaData rsmd = rs.getMetaData();
final int length = rsmd.getColumnCount();
-
+ sqlResultDuration += System.currentTimeMillis() - startTime2;
+
JSONObject result;
String cacheSql;
+
+ long lastCursorTime = System.currentTimeMillis();
while (rs.next()) { //FIXME 同时有 @ APP JOIN 和 < 等 SQL JOIN 时,next = false 总是无法进入循环,导致缓存失效,可能是连接池或线程问题
+ sqlResultDuration += System.currentTimeMillis() - lastCursorTime;
+ lastCursorTime = System.currentTimeMillis();
+
index ++;
Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n executeAppJoin while (rs.next()){ index = " + index + "\n\n");
@@ -708,13 +780,20 @@ protected List onPutTable(@NotNull SQLConfig config, @NotNull Result
protected String getKey(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd
, final int tablePosition, @NotNull JSONObject table, final int columnIndex, Map childMap) throws Exception {
- String key = rsmd.getColumnLabel(columnIndex);// dotIndex < 0 ? lable : lable.substring(dotIndex + 1);
+ long startTime = System.currentTimeMillis();
+ String key = rsmd.getColumnLabel(columnIndex); // dotIndex < 0 ? lable : lable.substring(dotIndex + 1);
+ sqlResultDuration += System.currentTimeMillis() - startTime;
+
if (config.isHive()) {
String table_name = config.getTable();
- if(AbstractSQLConfig.TABLE_KEY_MAP.containsKey(table_name)) table_name = AbstractSQLConfig.TABLE_KEY_MAP.get(table_name);
+ if (AbstractSQLConfig.TABLE_KEY_MAP.containsKey(table_name)) {
+ table_name = AbstractSQLConfig.TABLE_KEY_MAP.get(table_name);
+ }
String pattern = "^" + table_name + "\\." + "[a-zA-Z]+$";
boolean isMatch = Pattern.matches(pattern, key);
- if(isMatch) key = key.split("\\.")[1];
+ if (isMatch) {
+ key = key.split("\\.")[1];
+ }
}
return key;
}
@@ -722,7 +801,10 @@ protected String getKey(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNu
protected Object getValue(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd
, final int tablePosition, @NotNull JSONObject table, final int columnIndex, String lable, Map childMap) throws Exception {
+ long startTime = System.currentTimeMillis();
Object value = rs.getObject(columnIndex);
+ sqlResultDuration += System.currentTimeMillis() - startTime;
+
// Log.d(TAG, "name:" + rsmd.getColumnName(i));
// Log.d(TAG, "lable:" + rsmd.getColumnLabel(i));
// Log.d(TAG, "type:" + rsmd.getColumnType(i));
@@ -799,7 +881,10 @@ else if (value instanceof Clob) { //SQL Server TEXT 类型 居然走这个
@Override
public boolean isJSONType(@NotNull SQLConfig config, ResultSetMetaData rsmd, int position, String lable) {
try {
+ long startTime = System.currentTimeMillis();
String column = rsmd.getColumnTypeName(position);
+ sqlResultDuration += System.currentTimeMillis() - startTime;
+
//TODO CHAR和JSON类型的字段,getColumnType返回值都是1 ,如果不用CHAR,改用VARCHAR,则可以用上面这行来提高性能。
//return rsmd.getColumnType(position) == 1;
@@ -965,19 +1050,28 @@ public void close() {
@Override
public ResultSet executeQuery(@NotNull SQLConfig config) throws Exception {
- return getStatement(config).executeQuery(); //PreparedStatement 不用传 SQL
+ PreparedStatement s = getStatement(config);
+// 不准,getStatement 有时比 execute sql 更耗时 executedSQLStartTime = System.currentTimeMillis();
+ ResultSet rs = s.executeQuery(); //PreparedStatement 不用传 SQL
+// executedSQLEndTime = System.currentTimeMillis();
+ return rs;
}
@Override
public int executeUpdate(@NotNull SQLConfig config) throws Exception {
PreparedStatement s = getStatement(config);
- int count = s.executeUpdate(); //PreparedStatement 不用传 SQL
- if (config.isHive() && count==0) count = 1;
-
- if (config.getMethod() == RequestMethod.POST && config.getId() == null) { //自增id
+// 不准,getStatement 有时比 execute sql 更耗时 executedSQLStartTime = System.currentTimeMillis();
+ int count = s.executeUpdate(); // PreparedStatement 不用传 SQL
+// executedSQLEndTime = System.currentTimeMillis();
+
+ if (count <= 0 && config.isHive()) {
+ count = 1;
+ }
+
+ if (config.getId() == null && config.getMethod() == RequestMethod.POST) { // 自增id
ResultSet rs = s.getGeneratedKeys();
if (rs != null && rs.next()) {
- config.setId(rs.getLong(1));//返回插入的主键id
+ config.setId(rs.getLong(1)); //返回插入的主键id FIXME Oracle 拿不到
}
}
diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java
index 9fe5917a7..3cb2bf60a 100755
--- a/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java
@@ -115,5 +115,8 @@ public interface SQLExecutor {
int getExecutedSQLCount();
-
+ long getExecutedSQLDuration();
+
+ long getSqlResultDuration();
+
}
From a6ac4b726a649f277384f525e0d9f339b7f59862 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Sun, 30 Jan 2022 18:44:50 +0800
Subject: [PATCH 162/754] Update README.md
---
README.md | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 33e7ec713..3a6f0cf6a 100644
--- a/README.md
+++ b/README.md
@@ -244,7 +244,11 @@ https://github.com/Tencent/APIJSON/issues/187
* [腾讯科技有限公司](https://www.tencent.com)
-
+ * [腾讯音乐娱乐集团](https://www.tencentmusic.com)
+ * [华能贵成信托有限公司](https://www.hngtrust.com)
+ * [投投科技](https://www.toutou.com.cn)
+ * [圆通科技](https://www.tencentmusic.com)
+ * [乐拼科技](https://www.lepinyongche.com)
### 贡献者们
主项目 APIJSON 的贡献者们(6 个腾讯工程师、1 个知乎基础研发架构师、1 个圆通工程师 等):
From ff8efebd33eb89eeb997bc8555d527ad4ee449f0 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Sun, 30 Jan 2022 18:46:49 +0800
Subject: [PATCH 163/754] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 3a6f0cf6a..eaf7445e4 100644
--- a/README.md
+++ b/README.md
@@ -245,7 +245,7 @@ https://github.com/Tencent/APIJSON/issues/187
* [腾讯科技有限公司](https://www.tencent.com)
* [腾讯音乐娱乐集团](https://www.tencentmusic.com)
- * [华能贵成信托有限公司](https://www.hngtrust.com)
+ * [华能贵诚信托有限公司](https://www.hngtrust.com)
* [投投科技](https://www.toutou.com.cn)
* [圆通科技](https://www.tencentmusic.com)
* [乐拼科技](https://www.lepinyongche.com)
From bfe4c9d8a1c0b4c1cef510e9ddb3e273ef473378 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Sun, 30 Jan 2022 18:50:12 +0800
Subject: [PATCH 164/754] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index eaf7445e4..f63435b13 100644
--- a/README.md
+++ b/README.md
@@ -247,7 +247,7 @@ https://github.com/Tencent/APIJSON/issues/187
* [腾讯音乐娱乐集团](https://www.tencentmusic.com)
* [华能贵诚信托有限公司](https://www.hngtrust.com)
* [投投科技](https://www.toutou.com.cn)
- * [圆通科技](https://www.tencentmusic.com)
+ * [圆通科技](https://www.yto.net.cn)
* [乐拼科技](https://www.lepinyongche.com)
### 贡献者们
From 6ef55cb4ca8f01a15f690fa31295fb87e1fd2c33 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Sun, 30 Jan 2022 18:52:21 +0800
Subject: [PATCH 165/754] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index f63435b13..d367d6109 100644
--- a/README.md
+++ b/README.md
@@ -247,7 +247,7 @@ https://github.com/Tencent/APIJSON/issues/187
* [腾讯音乐娱乐集团](https://www.tencentmusic.com)
* [华能贵诚信托有限公司](https://www.hngtrust.com)
* [投投科技](https://www.toutou.com.cn)
- * [圆通科技](https://www.yto.net.cn)
+ * [圆通速递](https://www.yto.net.cn)
* [乐拼科技](https://www.lepinyongche.com)
### 贡献者们
From 2da22e618585f4009145c69965092ab3987004b3 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Sun, 6 Feb 2022 21:08:59 +0800
Subject: [PATCH 166/754] =?UTF-8?q?=E6=8F=90=E5=8D=87=E7=89=88=E6=9C=AC?=
=?UTF-8?q?=E5=8F=B7=E4=B8=BA=204.9.0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
APIJSONORM/pom.xml | 2 +-
APIJSONORM/src/main/java/apijson/Log.java | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml
index f1fdcd63a..377ecc644 100755
--- a/APIJSONORM/pom.xml
+++ b/APIJSONORM/pom.xml
@@ -5,7 +5,7 @@
apijson.orm
apijson-orm
- 4.8.0
+ 4.9.0
jar
APIJSONORM
diff --git a/APIJSONORM/src/main/java/apijson/Log.java b/APIJSONORM/src/main/java/apijson/Log.java
index 6dfc96caf..f97fae342 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.8.5";
+ public static final String VERSION = "4.9.0";
public static final String KEY_SYSTEM_INFO_DIVIDER = "---|-----APIJSON SYSTEM INFO-----|---";
//默认的时间格式
From d6bd9dd4e4725c35acdcddfca5feb7347dd7ef12 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Sun, 6 Feb 2022 21:15:24 +0800
Subject: [PATCH 167/754] Update README.md
---
README.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/README.md b/README.md
index d367d6109..c5f4f9069 100644
--- a/README.md
+++ b/README.md
@@ -245,6 +245,8 @@ https://github.com/Tencent/APIJSON/issues/187
* [腾讯科技有限公司](https://www.tencent.com)
* [腾讯音乐娱乐集团](https://www.tencentmusic.com)
+ * [深圳市传音通讯有限公司](http://www.transsion.com)
+ * [社宝信息科技(上海)有限公司](http://shebaochina.com)
* [华能贵诚信托有限公司](https://www.hngtrust.com)
* [投投科技](https://www.toutou.com.cn)
* [圆通速递](https://www.yto.net.cn)
From 7531e2e6602a52c51472d0c6722ca7a3bf04c99f Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Sun, 6 Feb 2022 21:18:14 +0800
Subject: [PATCH 168/754] Update README.md
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index c5f4f9069..7fea8ebd9 100644
--- a/README.md
+++ b/README.md
@@ -245,8 +245,8 @@ https://github.com/Tencent/APIJSON/issues/187
* [腾讯科技有限公司](https://www.tencent.com)
* [腾讯音乐娱乐集团](https://www.tencentmusic.com)
- * [深圳市传音通讯有限公司](http://www.transsion.com)
- * [社宝信息科技(上海)有限公司](http://shebaochina.com)
+ * [深圳市传音通讯有限公司](https://www.transsion.com)
+ * [社宝信息科技(上海)有限公司](https://shebaochina.com)
* [华能贵诚信托有限公司](https://www.hngtrust.com)
* [投投科技](https://www.toutou.com.cn)
* [圆通速递](https://www.yto.net.cn)
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 169/754] =?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 网络传输协议 以及 基于这

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

+
+
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 170/754] =?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
}
+
+
#### 获取用户列表
@@ -95,6 +97,8 @@ https://github.com/Tencent/APIJSON
}
+
+
#### 获取动态及发布者用户
@@ -134,6 +138,8 @@ https://github.com/Tencent/APIJSON
"msg":"success"
}
+
+
@@ -254,6 +260,29 @@ https://github.com/Tencent/APIJSON
}
+
+
+
+ APIJSON 各种 JOIN:< LEFT, > RIGHT, & INNER 等
+
+
+
+
+
+
+
+ APIJSON 各种子查询:@from@ 数据源, key@ =, key{}@ IN, key<>@ CONTAINS, key}{@ EXISTS 等
+
+
+
+
+
+
+
+ APIJSON 部分功能演示集合,由浅入深、由简单到复杂
+
+
+
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 171/754] =?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 各种单表对象查询:简单查询、统计、分组、排序、聚合、比较、筛选字段、字段别名 等
+
+

@@ -97,6 +101,10 @@ https://github.com/Tencent/APIJSON
}
+
+ APIJSON 各种单表数组查询:简单查询、统计、分组、排序、聚合、分页、比较、搜索、正则、条件组合 等
+
+

@@ -139,8 +147,6 @@ https://github.com/Tencent/APIJSON
}
-
-
#### 获取类似微信朋友圈的动态列表
@@ -260,20 +266,26 @@ https://github.com/Tencent/APIJSON
}
+
+ APIJSON 各种多表关联查询:一对一、一对多、多对一、各种条件 等
+
+
+
+
- APIJSON 各种 JOIN:< LEFT, > RIGHT, & INNER 等
+ APIJSON 各种 JOIN:< LEFT JOIN, & INNER JOIN 等
-
+

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

@@ -281,7 +293,7 @@ https://github.com/Tencent/APIJSON
APIJSON 部分功能演示集合,由浅入深、由简单到复杂
-
+

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 172/754] 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 173/754] 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 174/754] =?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 175/754] =?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 176/754] =?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 177/754] =?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 178/754] =?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 179/754] 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

@@ -53,7 +53,7 @@ https://github.com/Tencent/APIJSON
- APIJSON 各种单表对象查询:简单查询、统计、分组、排序、聚合、比较、筛选字段、字段别名 等
+ [GIF] APIJSON 各种单表对象查询:简单查询、统计、分组、排序、聚合、比较、筛选字段、字段别名 等

@@ -102,7 +102,7 @@ https://github.com/Tencent/APIJSON
- APIJSON 各种单表数组查询:简单查询、统计、分组、排序、聚合、分页、比较、搜索、正则、条件组合 等
+ [GIF] APIJSON 各种单表数组查询:简单查询、统计、分组、排序、聚合、分页、比较、搜索、正则、条件组合 等

@@ -267,7 +267,7 @@ https://github.com/Tencent/APIJSON
- APIJSON 各种多表关联查询:一对一、一对多、多对一、各种条件 等
+ [GIF] APIJSON 各种多表关联查询:一对一、一对多、多对一、各种条件 等

@@ -275,7 +275,7 @@ https://github.com/Tencent/APIJSON
- APIJSON 各种 JOIN:< LEFT JOIN, & INNER JOIN 等
+ [GIF] APIJSON 各种 JOIN:< LEFT JOIN, & INNER JOIN 等

@@ -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 等

@@ -291,7 +291,7 @@ https://github.com/Tencent/APIJSON
- APIJSON 部分功能演示集合,由浅入深、由简单到复杂
+ [GIF] APIJSON 部分功能演示集合,由浅入深、由简单到复杂

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 180/754] 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 181/754] =?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 182/754] =?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 183/754] =?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 184/754] =?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 185/754] =?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 186/754] =?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 187/754] =?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 188/754] =?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 189/754] =?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 190/754] =?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 191/754] =?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=
=?UTF-8?q?,=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 192/754] =?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 193/754] 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 194/754] =?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 195/754] =?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 196/754] =?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 197/754] =?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 198/754] =?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 199/754] =?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 200/754] =?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 201/754] =?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 202/754] 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 203/754] =?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