From 6ab88d13d17265bf637c81eda5f1b95239f68dcb Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Wed, 8 Jun 2022 21:16:21 +0800
Subject: [PATCH 001/355] Update README.md
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index f37bea63..5b7be059 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,7 @@ Tencent is pleased to support the open source community by making APIJSON availa
Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
This source code is licensed under the Apache License Version 2.0
+[APIJSON 已加入 腾源会开源摘星计划(WeOpen Star),该计划提供奖励以鼓励你加入我们的社区](https://github.com/weopenprojects/WeOpen-Star/issues/79)
APIJSON
--
Gitee
From 51f94299189a58e1971913274ffb7769cca216b7 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Wed, 8 Jun 2022 21:24:40 +0800
Subject: [PATCH 002/355] Update CONTRIBUTING.md
---
CONTRIBUTING.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0cb35f97..e424b705 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -50,9 +50,9 @@ ruoranw 提交的 18 个 Commits, 对 APIJSON 做出了 328 增加和 520 处删
Zerounary 提交的 6 个 Commits, 对 APIJSON 做出了 1,104 增加和 1 处删减(截止 2020/11/04 日)。
-APIJSON 持续招募贡献者,即使是在 Issue 中回答问题,或者做一些简单的 Bug Fix ,也会给 APIJSON 带来很大的帮助。
-APIJSON 已开发近 4 年,在此感谢所有开发者对于 APIJSON 的喜欢和支持,希望你能够成为 APIJSON 的核心贡献者,
-加入 APIJSON ,共同打造一个更棒的零代码、全自动、强安全 ORM 库!🍾🎉
+APIJSON 持续招募贡献者,新增功能、修复 Bug、完善文档、修正错误、宣传推广、回答问题等,都能帮助项目及广大用户。
+APIJSON 已开发近 6 年,在此感谢所有开发者对于 APIJSON 的喜欢和支持,希望你能够成为 APIJSON 的核心贡献者,
+加入 APIJSON ,共同打造一个更棒的零代码、全功能、强安全 ORM 库,造福更多前后端开发者!🍾🎉
### 为什么一定要贡献代码?
APIJSON 作为腾讯开源的知名热门项目,贡献代码除了可以给简历加亮点、为面试加分,还可以避免你碰到以下麻烦:
--
Gitee
From f8a3c6706b9dec303e8802ccb8caaf740a3ebc5d Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Wed, 8 Jun 2022 21:25:42 +0800
Subject: [PATCH 003/355] Update CONTRIBUTING.md
---
CONTRIBUTING.md | 31 ++++++++++++++++---------------
1 file changed, 16 insertions(+), 15 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e424b705..939328aa 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -61,21 +61,6 @@ APIJSON 作为腾讯开源的知名热门项目,贡献代码除了可以给简
3.你需要自己维护你的代码,每次升级 APIJSON 版本时,你都需要下载 APIJSON 新代码再合并你自己的更改
#### 所以为了让你自己的更改始终能跟上项目版本,得到他人给予的可靠且持续的维护,强烈建议 [提交 Pull Request](/CONTRIBUTING.md#pull-request) 来贡献代码。
-
-
-## Issue 提交
-
-#### 对于贡献者
-
-在提 Issue 前请确保满足一下条件:
-
-- 必须是一个 Bug 或者功能新增。
-- 必须是 APIJSON 相关问题。
-- 已经在 Issue 中搜索过,并且没有找到相似的 Issue 或者解决方案。
-- 完善下面模板中的信息
-
-如果已经满足以上条件,我们提供了 Issue 的标准模版,请按照模板填写。
-
## Pull Request
@@ -145,3 +130,19 @@ https://www.jianshu.com/p/00cf29d2d66c
如何在 Github 上给别人的项目贡献代码
https://git-scm.com/book/zh/v2/GitHub-%E5%AF%B9%E9%A1%B9%E7%9B%AE%E5%81%9A%E5%87%BA%E8%B4%A1%E7%8C%AE
+
+
+
+
+## Issue 提交
+
+#### 对于贡献者
+
+在提 Issue 前请确保满足一下条件:
+
+- 必须是一个 Bug 或者功能新增。
+- 必须是 APIJSON 相关问题。
+- 已经在 Issue 中搜索过,并且没有找到相似的 Issue 或者解决方案。
+- 完善下面模板中的信息
+
+如果已经满足以上条件,我们提供了 Issue 的标准模版,请按照模板填写。
--
Gitee
From 9fb6c885f0f5d493bf160e8f60762266823e838c Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 10 Jun 2022 17:24:11 +0800
Subject: [PATCH 004/355] Update README.md
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 5b7be059..a9f52add 100644
--- a/README.md
+++ b/README.md
@@ -219,8 +219,8 @@ https://github.com/Tencent/APIJSON/issues/36
### 注意事项
-请求参数 JSON 中表名、字段名、关键词及对应的值都是大小写敏感、逗号敏感、分号敏感、空格敏感、换行敏感,
-大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 [设计规范](https://github.com/Tencent/APIJSON/blob/master/Document.md#3) 来调用 API
+**请求参数 JSON 中表名、字段名、关键词及对应的值都是大小写敏感、逗号敏感、分号敏感、空格敏感、换行敏感,
+大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 [设计规范](https://github.com/Tencent/APIJSON/blob/master/Document.md#3) 来调用 API !**
[#181](https://github.com/Tencent/APIJSON/issues/181)
--
Gitee
From ed935c1c4ad32157cf35ab91a0752985c14bfc73 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 10 Jun 2022 17:27:56 +0800
Subject: [PATCH 005/355] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index a9f52add..17777f3a 100644
--- a/README.md
+++ b/README.md
@@ -88,7 +88,7 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这
#### APIAuto 展示 APIJSON
-使用 APIAuto-机器学习接口工具 来管理和测试 HTTP API 可大幅提升接口联调效率
+**使用 APIAuto-机器学习接口工具 来管理和测试 HTTP API 可大幅 减少传参错误、提升联调效率**
(注意网页工具界面是 APIAuto,里面的 URL+JSON 才是 APIJSON 的 HTTP API):
--
Gitee
From 0600a8e93436b0ac60ca8f18c9c02176287b6434 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 10 Jun 2022 18:50:31 +0800
Subject: [PATCH 006/355] Update --bug.md
---
.github/ISSUE_TEMPLATE/--bug.md | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md
index 3683b0f3..35df8dcc 100755
--- a/.github/ISSUE_TEMPLATE/--bug.md
+++ b/.github/ISSUE_TEMPLATE/--bug.md
@@ -4,6 +4,12 @@ about: Create a report to help us improve
---
+如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献谢谢,开源要大家参与贡献才会更美好。
+开发者也是人,也需要工作和休息,养活自己和家人,也会有心情不好和身体病痛,往往没有额外的时间精力顾及一些小问题,请理解和支持~
+https://github.com/Tencent/APIJSON/issues/406
+
+_________________________________
+
**提 bug 请发请求和响应的【完整截屏】,没图的自行解决!
开发者有限的时间和精力主要放在【维护项目源码和文档】上!
【描述不详细】 或 【文档/常见问题 已有答案】 的问题可能会被忽略!!
--
Gitee
From e2826242b89291ecd0505efe7aa61eb8f1eef1d5 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 10 Jun 2022 18:57:52 +0800
Subject: [PATCH 007/355] Update --bug.md
---
.github/ISSUE_TEMPLATE/--bug.md | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md
index 35df8dcc..193baa05 100755
--- a/.github/ISSUE_TEMPLATE/--bug.md
+++ b/.github/ISSUE_TEMPLATE/--bug.md
@@ -4,8 +4,9 @@ about: Create a report to help us improve
---
-如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献谢谢,开源要大家参与贡献才会更美好。
-开发者也是人,也需要工作和休息,养活自己和家人,也会有心情不好和身体病痛,往往没有额外的时间精力顾及一些小问题,请理解和支持~
+如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。
+开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛,
+往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~
https://github.com/Tencent/APIJSON/issues/406
_________________________________
--
Gitee
From bd3dc264f234f4834c3296c3f3bfd327d5e2165e Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 10 Jun 2022 18:58:19 +0800
Subject: [PATCH 008/355] Update --bug.md
---
.github/ISSUE_TEMPLATE/--bug.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md
index 193baa05..c752ac3d 100755
--- a/.github/ISSUE_TEMPLATE/--bug.md
+++ b/.github/ISSUE_TEMPLATE/--bug.md
@@ -4,9 +4,9 @@ about: Create a report to help us improve
---
-如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。
-开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛,
-往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~
+如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。
+开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛,
+往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~
https://github.com/Tencent/APIJSON/issues/406
_________________________________
--
Gitee
From d95a1b90c8ead2e6a5d2b0f219b5795b706532e2 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 10 Jun 2022 18:58:59 +0800
Subject: [PATCH 009/355] Update --bug.md
---
.github/ISSUE_TEMPLATE/--bug.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md
index c752ac3d..1bb8e026 100755
--- a/.github/ISSUE_TEMPLATE/--bug.md
+++ b/.github/ISSUE_TEMPLATE/--bug.md
@@ -4,9 +4,9 @@ about: Create a report to help us improve
---
-如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。
-开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛,
-往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~
+如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。
+开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛,
+往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~
https://github.com/Tencent/APIJSON/issues/406
_________________________________
--
Gitee
From 88d895d0f06911693e1b6289a00a8a1ca51f3341 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 10 Jun 2022 19:00:54 +0800
Subject: [PATCH 010/355] Update --bug.md
---
.github/ISSUE_TEMPLATE/--bug.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md
index 1bb8e026..545c06b9 100755
--- a/.github/ISSUE_TEMPLATE/--bug.md
+++ b/.github/ISSUE_TEMPLATE/--bug.md
@@ -4,9 +4,9 @@ about: Create a report to help us improve
---
-如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。
-开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛,
-往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~
+如果你已经知道问题所在、怎么解决,请直接 提交 Pull Request 为社区做贡献,非常感谢。
+开发者也是人,也需要工作、休息、恋爱、陪伴家人、走亲访友等,也有心情不好和身体病痛,
+往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~
https://github.com/Tencent/APIJSON/issues/406
_________________________________
--
Gitee
From f4d63d1799f160c32390b462ea4e68ee2c7ac422 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 10 Jun 2022 19:08:23 +0800
Subject: [PATCH 011/355] Update --bug.md
---
.github/ISSUE_TEMPLATE/--bug.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md
index 545c06b9..ec35b18d 100755
--- a/.github/ISSUE_TEMPLATE/--bug.md
+++ b/.github/ISSUE_TEMPLATE/--bug.md
@@ -7,6 +7,7 @@ about: Create a report to help us improve
如果你已经知道问题所在、怎么解决,请直接 提交 Pull Request 为社区做贡献,非常感谢。
开发者也是人,也需要工作、休息、恋爱、陪伴家人、走亲访友等,也有心情不好和身体病痛,
往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~
+少数个人的热情终有被耗尽的一天,只有大家共同建设和繁荣社区,才能让开源可持续发展!
https://github.com/Tencent/APIJSON/issues/406
_________________________________
--
Gitee
From 76c91a9557c34a9b187fee1befedc5a8c36b0c6a Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 10 Jun 2022 19:09:12 +0800
Subject: [PATCH 012/355] Update --bug.md
---
.github/ISSUE_TEMPLATE/--bug.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md
index ec35b18d..072aba60 100755
--- a/.github/ISSUE_TEMPLATE/--bug.md
+++ b/.github/ISSUE_TEMPLATE/--bug.md
@@ -5,7 +5,7 @@ about: Create a report to help us improve
---
如果你已经知道问题所在、怎么解决,请直接 提交 Pull Request 为社区做贡献,非常感谢。
-开发者也是人,也需要工作、休息、恋爱、陪伴家人、走亲访友等,也有心情不好和身体病痛,
+开发者也是人,也需要工作、休息、恋爱、陪伴家人、走亲会友等,也有心情不好和身体病痛,
往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~
少数个人的热情终有被耗尽的一天,只有大家共同建设和繁荣社区,才能让开源可持续发展!
https://github.com/Tencent/APIJSON/issues/406
--
Gitee
From 0a764540fdaf5cd80ec79728604080087d29bb78 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 23 Jun 2022 23:45:26 +0800
Subject: [PATCH 013/355] Update Document.md
---
Document.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Document.md b/Document.md
index fefcd5ea..afc131f5 100644
--- a/Document.md
+++ b/Document.md
@@ -409,7 +409,7 @@ DELETE: 删除数据 | base_url/delete/ | { TableName:{<
存储过程 | "@key()":"SQL函数表达式",函数表达式为 function(key0,key1...) 会调用后端数据库对应的存储过程 SQL函数 function(String key0, String key1...) 除了参数会提前赋值,其它和 远程函数 一致 | ["@limit":10, "@offset":0, "@procedure()":"getCommentByUserId(id,@limit,@offset)"](http://apijson.cn:8080/get/{"User":{"@limit":10,"@offset":0,"@procedure()":"getCommentByUserId(id,@limit,@offset)"}}) 会转为 `getCommentByUserId(38710,10,0)` 来调用存储过程 SQL 函数 `getCommentByUserId(IN id bigint, IN limit int, IN offset int)` 然后变为 "procedure":{ "count":-1, "update":false, "list":[] } 其中 count 是指写操作影响记录行数,-1 表示不是写操作;update 是指是否为写操作(增删改);list 为返回结果集
引用赋值 | "key@":"key0/key1/.../refKey",引用路径为用/分隔的字符串。以/开头的是缺省引用路径,从声明key所处容器的父容器路径开始;其它是完整引用路径,从最外层开始。 被引用的refKey必须在声明key的上面。如果对refKey的容器指定了返回字段,则被引用的refKey必须写在@column对应的值内,例如 "@column":"refKey,key1,..." | ["Moment":{ "userId":38710 }, "User":{ "id@":"/Moment/userId" }](http://apijson.cn:8080/get/{"Moment":{"userId":38710},"User":{"id@":"%252FMoment%252FuserId"}}) User内的id引用了与User同级的Moment内的userId, 即User.id = Moment.userId,请求完成后 "id@":"/Moment/userId" 会变成 "id":38710
子查询 | "key@":{ "range":"ALL", "from":"Table", "Table":{ ... } } 其中: range 可为 ALL,ANY; from 为目标表 Table 的名称; @ 后面的对象类似数组对象,可使用 count 和 join 等功能。 | ["id@":{ "from":"Comment", "Comment":{ "@column":"min(userId)" } }](http://apijson.cn:8080/get/{"User":{"id@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}}) WHERE id=(SELECT min(userId) FROM Comment)
- 模糊搜索 | "key$":"SQL搜索表达式" => "key$":["SQL搜索表达式"],任意SQL搜索表达式字符串,如 %key%(包含key), key%(以key开始), %k%e%y%(包含字母k,e,y) 等,%表示任意字符 | ["name$":"%m%"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"name$":"%2525m%2525"}}}),对应SQL是`name LIKE '%m%'`,查询name包含"m"的一个User数组
+ 模糊搜索 | `"key$":"SQL搜索表达式"` => `"key$":["SQL搜索表达式"]`,任意SQL搜索表达式字符串,如 %key%(包含key), key%(以key开始), %k%e%y%(包含字母k,e,y) 等,%表示任意字符 | ["name$":"%m%"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"name$":"%2525m%2525"}}}),对应SQL是`name LIKE '%m%'`,查询name包含"m"的一个User数组
正则匹配 | "key~":"正则表达式" => "key~":["正则表达式"],任意正则表达式字符串,如 ^[0-9]+$ ,*~ 忽略大小写,可用于高级搜索 | ["name~":"^[0-9]+$"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"name~":"^[0-9]%252B$"}}}),对应SQL是`name REGEXP '^[0-9]+$'`,查询name中字符全为数字的一个User数组
连续范围 | "key%":"start,end" => "key%":["start,end"],其中 start 和 end 都只能为 Boolean, Number, String 中的一种,如 "2017-01-01,2019-01-01" ,["1,90000", "82001,100000"] ,可用于连续范围内的筛选 | ["date%":"2017-10-01,2018-10-01"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"date%2525":"2017-10-01,2018-10-01"}}}),对应SQL是`date BETWEEN '2017-10-01' AND '2018-10-01'`,查询在2017-10-01和2018-10-01期间注册的用户的一个User数组
新建别名 | "name:alias",name映射为alias,用alias替代name。可用于 column,Table,SQL函数 等。只用于GET类型、HEAD类型的请求 | ["@column":"toId:parentId"](http://apijson.cn:8080/get/{"Comment":{"@column":"id,toId:parentId","id":51}}),对应SQL是`toId AS parentId`,将查询的字段toId变为parentId返回
--
Gitee
From 4dfd9d4d4fa1b5a524634580cc8beed60027bdf9 Mon Sep 17 00:00:00 2001
From: huangcanjia
Date: Wed, 6 Jul 2022 18:51:20 +0800
Subject: [PATCH 014/355] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=B7=A8?=
=?UTF-8?q?=E5=B1=82=E7=BA=A7app=20join?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
优化app join模式下,一对多join表查询时的1+N性能问题
- 支持客户端join字段,path的多层路径指定
- Join类增加count字段,以支持在生成副表sql时,按照指定count数量生成
- 处理App Join的查询结果时,将'一条条缓存'调整为'攒一起再缓存',防止错误替换
---
.../main/java/apijson/orm/AbstractParser.java | 18 +++++++++----
.../java/apijson/orm/AbstractSQLConfig.java | 2 +-
.../java/apijson/orm/AbstractSQLExecutor.java | 26 +++++++++++--------
.../src/main/java/apijson/orm/Join.java | 8 ++++++
4 files changed, 37 insertions(+), 17 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
index 5135a727..c472ceb8 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
@@ -1457,10 +1457,11 @@ public abstract class AbstractParser implements Parser, Par
// }
path = path.substring(index + 1);
- index = path.indexOf("/");
+ index = path.lastIndexOf("/");
String tableKey = index < 0 ? path : path.substring(0, index); // User:owner
apijson.orm.Entry entry = Pair.parseEntry(tableKey, true);
- String table = entry.getKey(); // User
+ String[] tablePath = entry.getKey().split("/"); // User
+ String table = tableKey = tablePath[tablePath.length - 1]; // path最后一级为真实table;如:@/A/b/id@,b为目录最后一级
if (StringUtil.isName(table) == false) {
throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 Table 值 " + table + " 不合法!"
+ "必须为 &/Table0, implements Parser, Par
}
// 取出Table对应的JSONObject,及内部引用赋值 key:value
- JSONObject tableObj;
+ JSONObject tableObj = request;
+ JSONObject parentPathObj = null; // 保留
try {
- tableObj = request.getJSONObject(tableKey);
+ for (String tableKeyPath : tablePath) {
+ parentPathObj = tableObj;
+ tableObj = tableObj.getJSONObject(tableKeyPath);
+ }
if (tableObj == null) {
throw new NullPointerException("tableObj == null");
}
@@ -1580,6 +1585,9 @@ public abstract class AbstractParser implements Parser, Par
j.setAlias(alias);
j.setOuter((JSONObject) outer);
j.setRequest(requestObj);
+ if (parentPathObj != null) {
+ j.setCount(parentPathObj.getInteger("count") != null ? parentPathObj.getInteger("count") : 1);
+ }
List onList = new ArrayList<>();
for (Entry refEntry : refSet) {
@@ -1656,7 +1664,7 @@ public abstract class AbstractParser implements Parser, Par
if (refObj.size() != tableObj.size()) { // 把 key 强制放最前,AbstractSQLExcecutor 中 config.putWhere 也是放尽可能最前
refObj.putAll(tableObj);
- request.put(tableKey, refObj);
+ parentPathObj.put(tableKey, refObj);
// tableObj.clear();
// tableObj.putAll(refObj);
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
index c9c9c862..cfe4df81 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
@@ -5008,7 +5008,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
alias = j.getAlias();
//JOIN子查询不能设置LIMIT,因为ON关系是在子查询后处理的,会导致结果会错误
SQLConfig joinConfig = newSQLConfig(method, table, alias, j.getRequest(), null, false, callback);
- SQLConfig cacheConfig = j.canCacheViceTable() == false ? null : newSQLConfig(method, table, alias, j.getRequest(), null, false, callback).setCount(1);
+ SQLConfig cacheConfig = j.canCacheViceTable() == false ? null : newSQLConfig(method, table, alias, j.getRequest(), null, false, callback).setCount(j.getCount());
if (j.isAppJoin() == false) { //除了 @ APP JOIN,其它都是 SQL JOIN,则副表要这样配置
if (joinConfig.getDatabase() == null) {
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
index 1f1d4bd9..fb5713be 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
@@ -260,7 +260,9 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
Log.i(TAG, ">>> execute result = getCache('" + sql + "', " + position + ") = " + result);
if (result != null) {
cachedSQLCount ++;
-
+ if (getCache(sql,config).size() > 1) {
+ result.put(KEY_RAW_LIST, getCache(sql,config));
+ }
Log.d(TAG, "\n\n execute result != null >> return result;" + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n");
return result;
}
@@ -589,19 +591,17 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
if (isHead == false) {
// @ APP JOIN 查询副表并缓存到 childMap <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
-
- executeAppJoin(config, resultList, childMap);
+ Map> appJoinChildMap = new HashMap<>();
+ executeAppJoin(config, resultList, appJoinChildMap);
// @ APP JOIN 查询副表并缓存到 childMap >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//子查询 SELECT Moment.*, Comment.id 中的 Comment 内字段
- Set> set = childMap.entrySet();
+ Set>> set = appJoinChildMap.entrySet();
//
- for (Entry entry : set) {
- List l = new ArrayList<>();
- l.add(entry.getValue());
- putCache(entry.getKey(), l, null);
+ for (Entry> entry : set) {
+ putCache(entry.getKey(), entry.getValue(), null);
}
putCache(sql, resultList, config);
@@ -633,7 +633,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
* @param childMap
* @throws Exception
*/
- protected void executeAppJoin(SQLConfig config, List resultList, Map childMap) throws Exception {
+ protected void executeAppJoin(SQLConfig config, List resultList, Map> childMap) throws Exception {
List joinList = config.getJoinList();
if (joinList != null) {
@@ -737,8 +737,12 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
}
}
cacheSql = cc.getSQL(false);
- childMap.put(cacheSql, result);
-
+ List results = childMap.get(cacheSql);
+ if (results == null) {
+ results = new ArrayList<>();
+ childMap.put(cacheSql,results);
+ }
+ results.add(result);
Log.d(TAG, ">>> executeAppJoin childMap.put('" + cacheSql + "', result); childMap.size() = " + childMap.size());
}
}
diff --git a/APIJSONORM/src/main/java/apijson/orm/Join.java b/APIJSONORM/src/main/java/apijson/orm/Join.java
index 4fb34b0a..af3c2979 100644
--- a/APIJSONORM/src/main/java/apijson/orm/Join.java
+++ b/APIJSONORM/src/main/java/apijson/orm/Join.java
@@ -22,6 +22,7 @@ public class Join {
private String joinType; // "@" - APP, "<" - LEFT, ">" - RIGHT, "*" - CROSS, "&" - INNER, "|" - FULL, "!" - OUTER, "^" - SIDE, "(" - ANTI, ")" - FOREIGN
private String table; // User
private String alias; // owner
+ private int count = 1; // 当app join子表,需要返回子表的行数,默认1行;
private List onList; // ON User.id = Moment.userId AND ...
private JSONObject request; // { "id@":"/Moment/userId" }
@@ -39,6 +40,13 @@ public class Join {
this.path = path;
}
+ public int getCount() {
+ return count;
+ }
+ public void setCount(int count) {
+ this.count = count;
+ }
+
public String getJoinType() {
return joinType;
}
--
Gitee
From 5525eab38d5c3886a93d082102835b08ad338979 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 7 Jul 2022 05:52:44 +0800
Subject: [PATCH 015/355] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=AF=B9=20APP=20JOI?=
=?UTF-8?q?N=20=E5=90=8C=E5=B1=82=E5=92=8C=E8=B7=A8=E5=B1=82=E7=9A=84?=
=?UTF-8?q?=E6=A0=A1=E9=AA=8C=EF=BC=9B=E5=AE=8C=E5=96=84=E5=AF=B9=20APP=20?=
=?UTF-8?q?JOIN=20=E7=9A=84=20SQL=20=E6=89=A7=E8=A1=8C=E4=B8=8E=E7=BC=93?=
=?UTF-8?q?=E5=AD=98=E6=AC=A1=E6=95=B0=E7=BB=9F=E8=AE=A1=EF=BC=9B=E8=A7=A3?=
=?UTF-8?q?=E5=86=B3=E5=90=8C=E5=B1=82=20JOIN=20=E6=96=B0=E5=A2=9E?=
=?UTF-8?q?=E7=9A=84=E6=8A=A5=E9=94=99=20bug=EF=BC=9B=E8=A7=A3=E5=86=B3=20?=
=?UTF-8?q?APP=20JOIN=20=E5=89=AF=E8=A1=A8=E8=BF=94=E5=9B=9E=E5=86=85?=
=?UTF-8?q?=E9=83=A8=E5=AD=97=E6=AE=B5=20@RAW@LIST=EF=BC=9Bfastjson2=20?=
=?UTF-8?q?=E6=94=B9=E4=B8=BA=201.2.79?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
APIJSONORM/pom.xml | 2 +-
APIJSONORM/src/main/java/apijson/JSON.java | 9 +-
.../apijson/orm/AbstractObjectParser.java | 20 +--
.../main/java/apijson/orm/AbstractParser.java | 160 +++++++++++-------
.../java/apijson/orm/AbstractSQLExecutor.java | 159 +++++++++--------
5 files changed, 202 insertions(+), 148 deletions(-)
diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml
index 3f776fb6..fcc4ffa6 100755
--- a/APIJSONORM/pom.xml
+++ b/APIJSONORM/pom.xml
@@ -21,7 +21,7 @@
com.alibaba
fastjson
- 2.0.4
+ 1.2.79
javax.activation
diff --git a/APIJSONORM/src/main/java/apijson/JSON.java b/APIJSONORM/src/main/java/apijson/JSON.java
index b610cfc5..40ef46f9 100755
--- a/APIJSONORM/src/main/java/apijson/JSON.java
+++ b/APIJSONORM/src/main/java/apijson/JSON.java
@@ -6,8 +6,9 @@ package apijson;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson.JSONReader;
import java.util.List;
@@ -64,10 +65,10 @@ public class JSON {
* @param json
* @return
*/
- private static final JSONReader.Feature[] DEFAULT_FASTJSON_FEATURES = {JSONReader.Feature.FieldBased, JSONReader.Feature.UseBigDecimalForDoubles};
+ private static final Feature[] DEFAULT_FASTJSON_FEATURES = {Feature.OrderedField, Feature.UseBigDecimal};
public static Object parse(Object obj) {
try {
- return com.alibaba.fastjson2.JSON.parse(obj instanceof String ? (String) obj : toJSONString(obj), DEFAULT_FASTJSON_FEATURES);
+ return com.alibaba.fastjson.JSON.parse(obj instanceof String ? (String) obj : toJSONString(obj), DEFAULT_FASTJSON_FEATURES);
} catch (Exception e) {
Log.i(TAG, "parse catch \n" + e.getMessage());
}
@@ -101,7 +102,7 @@ public class JSON {
Log.e(TAG, "parseObject clazz == null >> return null;");
} else {
try {
- return com.alibaba.fastjson2.JSON.parseObject(getCorrectJson(json), clazz, DEFAULT_FASTJSON_FEATURES);
+ return com.alibaba.fastjson.JSON.parseObject(getCorrectJson(json), clazz, DEFAULT_FASTJSON_FEATURES);
} catch (Exception e) {
Log.i(TAG, "parseObject catch \n" + e.getMessage());
}
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
index e571cd8b..68b31a87 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
@@ -71,7 +71,7 @@ public abstract class AbstractObjectParser implements ObjectParser {
* @param parentPath
* @param request
* @param name
- * @throws Exception
+ * @throws Exception
*/
public AbstractObjectParser(@NotNull JSONObject request, String parentPath, SQLConfig arrayConfig
, boolean isSubquery, boolean isTable, boolean isArrayMainTable) throws Exception {
@@ -400,7 +400,7 @@ public abstract class AbstractObjectParser implements ObjectParser {
if (arrObj == null) {
throw new IllegalArgumentException("子查询 " + path + "/" + key + ":{ from:value } 中 value 对应的主表对象 " + from + ":{} 不存在!");
}
- //
+ //
SQLConfig cfg = (SQLConfig) arrObj.get(AbstractParser.KEY_CONFIG);
if (cfg == null) {
throw new NotExistException(TAG + ".onParse cfg == null");
@@ -453,7 +453,7 @@ public abstract class AbstractObjectParser implements ObjectParser {
Log.d(TAG, "onParse isTable(table) == false >> return true;");
return true;//舍去,对Table无影响
}
- }
+ }
//直接替换原来的key@:path为key:target
Log.i(TAG, "onParse >> key = replaceKey; value = target;");
@@ -517,7 +517,7 @@ public abstract class AbstractObjectParser implements ObjectParser {
/**
* @param key
* @param value
- * @param isFirst
+ * @param isFirst
* @return
* @throws Exception
*/
@@ -553,7 +553,7 @@ public abstract class AbstractObjectParser implements ObjectParser {
+ "数组 []:{} 中每个 key:{} 都必须是表 TableKey:{} 或 数组 arrayKey[]:{} !");
}
- if ( //避免使用 "test":{"Test":{}} 绕过限制,实现查询爆炸 isTableKey &&
+ if ( //避免使用 "test":{"Test":{}} 绕过限制,实现查询爆炸 isTableKey &&
(arrayConfig == null || arrayConfig.getPosition() == 0)) {
objectCount ++;
int maxObjectCount = parser.getMaxObjectCount();
@@ -577,7 +577,7 @@ public abstract class AbstractObjectParser implements ObjectParser {
- //TODO 改用 MySQL json_add,json_remove,json_contains 等函数!
+ //TODO 改用 MySQL json_add,json_remove,json_contains 等函数!
/**PUT key:[]
* @param key
* @param array
@@ -757,7 +757,7 @@ public abstract class AbstractObjectParser implements ObjectParser {
//执行SQL操作数据库
if (isTable == false) {//提高性能
sqlReponse = new JSONObject(sqlRequest);
- }
+ }
else {
try {
sqlReponse = onSQLExecute();
@@ -896,7 +896,8 @@ public abstract class AbstractObjectParser implements ObjectParser {
result = parser.executeSQL(sqlConfig, isSubquery);
boolean isSimpleArray = false;
- List rawList = null;
+ // 提取并缓存数组主表的列表数据
+ List rawList = (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST);
if (isArrayMainTable && position == 0 && result != null) {
@@ -905,8 +906,7 @@ public abstract class AbstractObjectParser implements ObjectParser {
&& (childMap == null || childMap.isEmpty())
&& (table.equals(arrayTable));
- // 提取并缓存数组主表的列表数据
- rawList = (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST);
+ // APP JOIN 副表时副表返回了这个字段 rawList = (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST);
if (rawList != null) {
String arrayPath = parentPath.substring(0, parentPath.lastIndexOf("[]") + 2);
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
index c472ceb8..3f598df1 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
@@ -6,6 +6,7 @@ This source code is licensed under the Apache License Version 2.0.*/
package apijson.orm;
import static apijson.JSONObject.KEY_EXPLAIN;
+import static apijson.JSONObject.KEY_JSON;
import static apijson.RequestMethod.GET;
import java.io.UnsupportedEncodingException;
@@ -80,7 +81,7 @@ public abstract class AbstractParser implements Parser, Par
public static int MAX_OBJECT_COUNT = 5;
public static int MAX_ARRAY_COUNT = 5;
public static int MAX_QUERY_DEPTH = 5;
-
+
@Override
public int getDefaultQueryCount() {
return DEFAULT_QUERY_COUNT;
@@ -122,13 +123,13 @@ public abstract class AbstractParser implements Parser, Par
this(null);
}
/**needVerify = true
- * @param requestMethod null ? requestMethod = GET
+ * @param method null ? requestMethod = GET
*/
public AbstractParser(RequestMethod method) {
this(method, true);
}
/**
- * @param requestMethod null ? requestMethod = GET
+ * @param method null ? requestMethod = GET
* @param needVerify 仅限于为服务端提供方法免验证特权,普通请求不要设置为 false ! 如果对应Table有权限也建议用默认值 true,保持和客户端权限一致
*/
public AbstractParser(RequestMethod method, boolean needVerify) {
@@ -136,7 +137,7 @@ public abstract class AbstractParser implements Parser, Par
setMethod(method);
setNeedVerify(needVerify);
}
-
+
protected boolean isRoot = true;
public boolean isRoot() {
return isRoot;
@@ -145,7 +146,7 @@ public abstract class AbstractParser implements Parser, Par
this.isRoot = isRoot;
return this;
}
-
+
@NotNull
protected Visitor visitor;
@@ -464,7 +465,7 @@ public abstract class AbstractParser implements Parser, Par
try {
queryDepth = 0;
executedSQLDuration = 0;
-
+
requestObject = onObjectParse(request, null, null, null, false);
onCommit();
@@ -486,7 +487,7 @@ public abstract class AbstractParser implements Parser, Par
if (Log.DEBUG) {
res.put("sql:generate|cache|execute|maxExecute", getSQLExecutor().getGeneratedSQLCount() + "|" + getSQLExecutor().getCachedSQLCount() + "|" + getSQLExecutor().getExecutedSQLCount() + "|" + getMaxSQLCount());
res.put("depth:count|max", queryDepth + "|" + getMaxQueryDepth());
-
+
executedSQLDuration += sqlExecutor.getExecutedSQLDuration() + sqlExecutor.getSqlResultDuration();
long parseDuration = duration - executedSQLDuration;
res.put("time:start|duration|end|parse|sql", startTime + "|" + duration + "|" + endTime + "|" + parseDuration + "|" + executedSQLDuration);
@@ -551,7 +552,7 @@ public abstract class AbstractParser implements Parser, Par
/**解析请求JSONObject
* @param request => URLDecoder.decode(request, UTF_8);
* @return
- * @throws Exception
+ * @throws Exception
*/
@NotNull
public static JSONObject parseRequest(String request) throws Exception {
@@ -624,7 +625,7 @@ public abstract class AbstractParser implements Parser, Par
String arrKey = key + "[]";
if (target.containsKey(arrKey) == false) {
- target.put(arrKey, new JSONArray());
+ target.put(arrKey, new JSONArray());
}
try {
@@ -680,7 +681,7 @@ public abstract class AbstractParser implements Parser, Par
public static JSONObject newResult(int code, String msg, boolean isRoot) {
return extendResult(null, code, msg, isRoot);
}
-
+
/**添加JSONObject的状态内容,一般用于错误提示结果
* @param object
* @param code
@@ -702,13 +703,13 @@ public abstract class AbstractParser implements Parser, Par
+ " \n | \n 常见问题:https://github.com/Tencent/APIJSON/issues/36"
+ " \n 通用文档:https://github.com/Tencent/APIJSON/blob/master/Document.md"
+ " \n 视频教程:https://search.bilibili.com/all?keyword=APIJSON");
-
+
msg = index >= 0 ? msg.substring(0, index) : msg;
-
+
if (object == null) {
object = new JSONObject(true);
}
-
+
if (object.containsKey(JSONResponse.KEY_OK) == false) {
object.put(JSONResponse.KEY_OK, JSONResponse.isSuccess(code));
}
@@ -720,12 +721,12 @@ public abstract class AbstractParser implements Parser, Par
if (m.isEmpty() == false) {
msg = m + " ;\n " + StringUtil.getString(msg);
}
-
+
object.put(JSONResponse.KEY_MSG, msg);
if (debug != null) {
object.put("debug:info|help", debug);
}
-
+
return object;
}
@@ -758,7 +759,7 @@ public abstract class AbstractParser implements Parser, Par
public static JSONObject newSuccessResult(boolean isRoot) {
return newResult(JSONResponse.CODE_SUCCESS, JSONResponse.MSG_SUCCEED, isRoot);
}
-
+
/**添加请求成功的状态内容
* @param object
* @param e
@@ -872,7 +873,7 @@ public abstract class AbstractParser implements Parser, Par
int code;
if (e instanceof UnsupportedEncodingException) {
code = JSONResponse.CODE_UNSUPPORTED_ENCODING;
- }
+ }
else if (e instanceof IllegalAccessException) {
code = JSONResponse.CODE_ILLEGAL_ACCESS;
}
@@ -890,7 +891,7 @@ public abstract class AbstractParser implements Parser, Par
}
else if (e instanceof TimeoutException) {
code = JSONResponse.CODE_TIME_OUT;
- }
+ }
else if (e instanceof ConflictException) {
code = JSONResponse.CODE_CONFLICT;
}
@@ -921,10 +922,8 @@ public abstract class AbstractParser implements Parser, Par
//TODO 启动时一次性加载Request所有内容,作为初始化。
/**获取正确的请求,非GET请求必须是服务器指定的
- * @param method
- * @param request
* @return
- * @throws Exception
+ * @throws Exception
*/
@Override
public JSONObject parseCorrectRequest() throws Exception {
@@ -1027,13 +1026,14 @@ public abstract class AbstractParser implements Parser, Par
// protected SQLConfig itemConfig;
/**获取单个对象,该对象处于parentObject内
- * @param parentPath parentObject的路径
- * @param name parentObject的key
- * @param request parentObject的value
- * @param config for array item
- * @return
- * @throws Exception
- */
+ * @param request parentObject 的 value
+ * @param parentPath parentObject 的路径
+ * @param name parentObject 的 key
+ * @param arrayConfig config for array item
+ * @param isSubquery 是否为子查询
+ * @return
+ * @throws Exception
+ */
@Override
public JSONObject onObjectParse(final JSONObject request
, String parentPath, String name, final SQLConfig arrayConfig, boolean isSubquery) throws Exception {
@@ -1093,7 +1093,7 @@ public abstract class AbstractParser implements Parser, Par
if (type == SQLConfig.TYPE_ITEM_CHILD_0 && query != JSONRequest.QUERY_TABLE && position == 0) {
//TODO 应在这里判断 @column 中是否有聚合函数,而不是 AbstractSQLConfig.getColumnString
-
+
JSONObject rp;
Boolean compat = arrayConfig.getCompat();
if (compat != null && compat) {
@@ -1192,7 +1192,7 @@ public abstract class AbstractParser implements Parser, Par
* @param parentPath parentObject的路径
* @param name parentObject的key
* @param request parentObject的value
- * @return
+ * @return
* @throws Exception
*/
@Override
@@ -1283,7 +1283,7 @@ public abstract class AbstractParser implements Parser, Par
String[] childKeys = StringUtil.split(childPath, "-", false);
if (childKeys == null || childKeys.length <= 0 || request.containsKey(childKeys[0]) == false) {
childKeys = null;
- }
+ }
else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // 可能无需提取,直接返回 rawList 即可
arrTableKey = childKeys[0];
}
@@ -1402,12 +1402,12 @@ public abstract class AbstractParser implements Parser, Par
JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_ORDER);
JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_RAW);
}
-
+
/**JOIN 多表同时筛选
* @param join "&/User,0"}
* @param request
- * @return
- * @throws Exception
+ * @return
+ * @throws Exception
*/
private List onJoinParse(Object join, JSONObject request) throws Exception {
JSONObject joinMap = null;
@@ -1440,7 +1440,7 @@ public abstract class AbstractParser implements Parser, Par
// 分割 /Table/key
String path = e == null ? null : e.getKey();
Object outer = path == null ? null : e.getValue();
-
+
if (outer instanceof JSONObject == false) {
throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中value不合法!"
+ "必须为 &/Table0/key0, implements Parser, Par
index = path.lastIndexOf("/");
String tableKey = index < 0 ? path : path.substring(0, index); // User:owner
- apijson.orm.Entry entry = Pair.parseEntry(tableKey, true);
- String[] tablePath = entry.getKey().split("/"); // User
- String table = tableKey = tablePath[tablePath.length - 1]; // path最后一级为真实table;如:@/A/b/id@,b为目录最后一级
+ int index2 = tableKey.lastIndexOf("/");
+ String arrKey = index2 < 0 ? null : tableKey.substring(0, index2);
+ if (arrKey != null && JSONRequest.isArrayKey(arrKey) == false) {
+ throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 对应的 " + arrKey + " 不是合法的数组 key[] !" +
+ "@ APP JOIN 最多允许跨 1 层,只能是子数组,且数组对象中不能有 join: value 键值对!");
+ }
+
+ tableKey = index2 < 0 ? tableKey : tableKey.substring(index2+1);
+
+ apijson.orm.Entry entry = Pair.parseEntry(tableKey, true);
+ String table = entry.getKey(); // User
if (StringUtil.isName(table) == false) {
throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 Table 值 " + table + " 不合法!"
+ "必须为 &/Table0, implements Parser, Par
}
// 取出Table对应的JSONObject,及内部引用赋值 key:value
- JSONObject tableObj = request;
- JSONObject parentPathObj = null; // 保留
+ JSONObject tableObj;
+ JSONObject parentPathObj; // 保留
try {
- for (String tableKeyPath : tablePath) {
- parentPathObj = tableObj;
- tableObj = tableObj.getJSONObject(tableKeyPath);
- }
- if (tableObj == null) {
+ parentPathObj = arrKey == null ? request : request.getJSONObject(arrKey); // 保留
+ tableObj = parentPathObj == null ? null : parentPathObj.getJSONObject(tableKey);
+ if (tableObj == null) {
throw new NullPointerException("tableObj == null");
}
}
catch (Exception e2) {
- throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 对应的 " + tableKey + ":value 中 value 类型不合法!必须是 {} 这种 JSONObject 格式!" + e2.getMessage());
+ throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 对应的 " + tableKey + ":value 中 value 类型不合法!" +
+ "必须是 {} 这种 JSONObject 格式!" + e2.getMessage());
}
-
+
+ if (arrKey != null) {
+ if (parentPathObj.get(JSONRequest.KEY_JOIN) != null) {
+ throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 对应的 " + arrKey + ":{ join: value } 中 value 不合法!" +
+ "@ APP JOIN 最多允许跨 1 层,只能是子数组,且数组对象中不能有 join: value 键值对!");
+ }
+
+ Integer subPage = parentPathObj.getInteger(JSONRequest.KEY_PAGE);
+ if (subPage != null && subPage != 0) {
+ throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 对应的 " + arrKey + ":{ page: value } 中 value 不合法!" +
+ "@ APP JOIN 最多允许跨 1 层,只能是子数组,且数组对象中 page 值只能为 null 或 0 !");
+ }
+ }
+
+ boolean isAppJoin = "@".equals(joinType);
JSONObject refObj = new JSONObject(tableObj.size(), true);
@@ -1501,15 +1522,21 @@ public abstract class AbstractParser implements Parser, Par
+ "必须为 &/Table0,> tableSet = tableObj.entrySet();
// 取出所有 join 条件
JSONObject requestObj = new JSONObject(true); // (JSONObject) obj.clone();
@@ -1538,7 +1565,19 @@ public abstract class AbstractParser implements Parser, Par
apijson.orm.Entry te = tk == null || p.substring(ind2 + 1).indexOf("/") >= 0 ? null : Pair.parseEntry(tk, true);
if (te != null && JSONRequest.isTableKey(te.getKey()) && request.get(tk) instanceof JSONObject) {
- refObj.put(k, v);
+ if (isAppJoin) {
+ if (refObj.size() >= 1) {
+ throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":" + e.getKey() + " 中 " + k + " 不合法!"
+ + "@ APP JOIN 必须有且只有一个引用赋值键值对!");
+ }
+
+ if (StringUtil.isName(k.substring(0, k.length() - 1)) == false) {
+ throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 中 " + k + " 不合法 !" +
+ "@ APP JOIN 只允许 key@:/Table/refKey 这种 = 等价连接!");
+ }
+ }
+
+ refObj.put(k, v);
continue;
}
}
@@ -1585,8 +1624,9 @@ public abstract class AbstractParser implements Parser, Par
j.setAlias(alias);
j.setOuter((JSONObject) outer);
j.setRequest(requestObj);
- if (parentPathObj != null) {
- j.setCount(parentPathObj.getInteger("count") != null ? parentPathObj.getInteger("count") : 1);
+ if (arrKey != null) {
+ Integer count = parentPathObj.getInteger(JSONRequest.KEY_COUNT);
+ j.setCount(count == null ? getDefaultQueryCount() : count);
}
List onList = new ArrayList<>();
@@ -1598,7 +1638,7 @@ public abstract class AbstractParser implements Parser, Par
throw new IllegalArgumentException(e.getKey() + ":value 中 value 值 " + targetPath + " 不合法!必须为引用赋值的路径 '/targetTable/targetKey' !");
}
- // 取出引用赋值路径 targetPath 对应的 Table 和 key
+ // 取出引用赋值路径 targetPath 对应的 Table 和 key
index = targetPath.lastIndexOf("/");
String targetKey = index < 0 ? null : targetPath.substring(index + 1);
if (StringUtil.isName(targetKey) == false) {
@@ -1638,24 +1678,24 @@ public abstract class AbstractParser implements Parser, Par
if (targetObj == null) {
throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中路径对应的对象 '" + targetTableKey + "':{} 不存在或值为 null !必须是 {} 这种 JSONObject 格式!");
}
-
+
Join.On on = new Join.On();
on.setKeyAndType(j.getJoinType(), j.getTable(), originKey);
if (StringUtil.isName(on.getKey()) == false) {
throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 key@ 中 key 值 " + on.getKey() + " 不合法!必须满足英文单词变量名格式!");
}
-
+
on.setOriginKey(originKey);
on.setOriginValue((String) refEntry.getValue());
on.setTargetTable(targetTable);
on.setTargetAlias(targetAlias);
on.setTargetKey(targetKey);
-
+
onList.add(on);
}
-
+
j.setOnList(onList);
-
+
joinList.add(j);
// onList.add(table + "." + key + " = " + targetTable + "." + targetKey); // ON User.id = Moment.userId
@@ -1762,7 +1802,7 @@ public abstract class AbstractParser implements Parser, Par
pos = ps[i+1].contains("/") == false ? ps[i+1]
: ps[i+1].substring(0, ps[i+1].indexOf("/"));
if (
- //StringUtil.isNumer(pos) &&
+ //StringUtil.isNumer(pos) &&
vs[i+1].startsWith(pos + "/") == false) {
vs[i+1] = pos + "/" + vs[i+1];
}
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
index fb5713be..1dcbb754 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
@@ -65,14 +65,14 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
public int getExecutedSQLCount() {
return executedSQLCount;
}
-
+
private long executedSQLDuration = 0;
private long sqlResultDuration = 0;
@Override
public long getExecutedSQLDuration() {
return executedSQLDuration;
}
-
+
@Override
public long getSqlResultDuration() {
return sqlResultDuration;
@@ -95,7 +95,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
Log.i(TAG, "saveList sql == null || list == null >> return;");
return;
}
-
+
cacheMap.put(sql, list);
}
@@ -165,7 +165,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
@Override
public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws Exception {
long executedSQLStartTime = System.currentTimeMillis();
-
+
boolean isPrepared = config.isPrepared();
final String sql = config.getSQL(false);
@@ -212,7 +212,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
if (isExplain == false) {
executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime;
}
-
+
result = new JSONObject(true);
result.put(JSONResponse.KEY_COUNT, updateCount);
result.put("update", updateCount >= 0);
@@ -241,7 +241,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
//id,id{}至少一个会有,一定会返回,不用抛异常来阻止关联写操作时前面错误导致后面无条件执行!
result.put(JSONResponse.KEY_COUNT, updateCount);//返回修改的记录数
-
+
String idKey = config.getIdKey();
if (config.getId() != null) {
result.put(idKey, config.getId());
@@ -249,7 +249,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
if (config.getIdIn() != null) {
result.put(idKey + "[]", config.getIdIn());
}
-
+
return result;
case GET:
@@ -271,7 +271,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
executedSQLCount ++;
executedSQLStartTime = System.currentTimeMillis();
}
- rs = executeQuery(config); //FIXME SQL Server 是一次返回两个结果集,包括查询结果和执行计划,需要 moreResults
+ rs = executeQuery(config); //FIXME SQL Server 是一次返回两个结果集,包括查询结果和执行计划,需要 moreResults
if (isExplain == false) {
executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime;
}
@@ -318,23 +318,23 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
if (capacity > 100) {
// 有 WHERE 条件,条件越多过滤数据越多,暂时不考虑 @combine:"a | (b & !c)" 里面 | OR 和 ! NOT 条件,太复杂也不是很必要
Map> combine = config.getCombineMap();
-
+
List andList = combine == null ? null : combine.get("&");
int andCondCount = andList == null ? (config.getWhere() == null ? 0 : config.getWhere().size()) : andList.size();
-
+
List orList = combine == null ? null : combine.get("|");
int orCondCount = orList == null ? 0 : orList.size();
-
+
List notList = combine == null ? null : combine.get("!");
int notCondCount = notList == null ? 0 : notList.size();
-
+
// 有 GROUP BY 分组,字段越少过滤数据越多
String[] group = StringUtil.split(config.getGroup());
int groupCount = group == null ? 0 : group.length;
if (groupCount > 0 && Arrays.asList(group).contains(config.getIdKey())) {
groupCount = 0;
}
-
+
// 有 HAVING 聚合函数,字段越多过滤数据越多,暂时不考虑 @combine:"a | (b & !c)" 里面 | OR 和 ! NOT 条件,太复杂也不是很必要
Map having = config.getHaving();
int havingCount = having == null ? 0 : having.size();
@@ -350,7 +350,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
}
}
}
-
+
resultList = new ArrayList<>(capacity);
}
@@ -363,7 +363,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
//
childMap = new HashMap<>(); //要存到cacheMap
-// Map columnIndexAndJoinMap = new HashMap<>(length);
+// Map columnIndexAndJoinMap = new HashMap<>(length);
String lastTableName = null; // 默认就是主表 config.getTable();
String lastAliasName = null; // 默认就是主表 config.getAlias();
int lastViceTableStart = 0;
@@ -375,26 +375,26 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
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];
-
+ 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");
JSONObject item = new JSONObject(true);
JSONObject curItem = item;
boolean isMain = true;
-
+
for (int i = 1; i <= length; i++) {
// if (hasJoin && viceColumnStart > length && config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) {
@@ -403,34 +403,34 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
// bugfix-修复非常规数据库字段,获取表名失败导致输出异常
Join curJoin = columnIndexAndJoinMap == null ? null : columnIndexAndJoinMap[i - 1]; // columnIndexAndJoinMap.get(i);
-
+
// 为什么 isExplain == false 不用判断?因为所有字段都在一张 Query Plan 表
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();
+
+ 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 (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
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() : (
StringUtil.equalsIgnoreCase(sqlTable, lastTableName)
@@ -441,11 +441,11 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
sqlTable = cfg.getSQLTable();
sqlAlias = cfg.getAlias();
lastViceTableStart = j; // 避免后面的空 @column 表内字段被放到之前的空 @column 表
-
+
curJoin = join;
curConfig = cfg;
curColumn = c;
-
+
toFindJoin = false;
isMain = false;
break;
@@ -464,12 +464,12 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
else if (config.isClickHouse() && (sqlTable.startsWith("`") || sqlTable.startsWith("\""))){
sqlTable = sqlTable.substring(1, sqlTable.length() - 1);
}
-
+
if (StringUtil.equalsIgnoreCase(sqlTable, lastTableName) == false || StringUtil.equals(sqlAlias, lastAliasName) == false) {
lastTableName = sqlTable;
lastAliasName = sqlAlias;
lastViceColumnStart = i;
-
+
if (toFindJoin) { // 找到对应的副表 JOIN 配置
for (int j = lastViceTableStart; j < joinList.size(); j++) { // 查找副表 @column,定位字段所在表
Join join = joinList.get(j);
@@ -478,43 +478,43 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
if (cfg != null && StringUtil.equalsIgnoreCase(sqlTable, cfg.getSQLTable())
) { // FIXME 导致副表字段错放到主表 && StringUtil.equals(sqlAlias, cfg.getAlias())) {
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[i - 1] = curJoin;
}
-
+
// 如果是主表则直接用主表对应的 item,否则缓存副表数据到 childMap
Join prevJoin = columnIndexAndJoinMap == null || i < 2 ? null : columnIndexAndJoinMap[i - 2];
if (curJoin != prevJoin) { // 前后字段不在同一个表对象,即便后面出现 null,也不该是主表数据,而是逻辑 bug 导致
@@ -564,7 +564,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
if (rs != null) {
try {
rs.close();
- }
+ }
catch (Exception e) {
e.printStackTrace();
}
@@ -610,7 +610,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
// 数组主表对象额外一次返回全部,方便 Parser 缓存来提高性能
result = position >= resultList.size() ? new JSONObject() : resultList.get(position);
- if (position == 0 && resultList.size() > 1 && result != null && result.isEmpty() == false) {
+ if (position == 0 && resultList.size() > 1 && result != null && result.isEmpty() == false) {
// 不是 main 不会直接执行,count=1 返回的不会超过 1 && config.isMain() && config.getCount() != 1
Log.i(TAG, ">>> execute position == 0 && resultList.size() > 1 && result != null && result.isEmpty() == false"
+ " >> result = new JSONObject(result); result.put(KEY_RAW_LIST, resultList);");
@@ -631,7 +631,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
* @param config
* @param resultList
* @param childMap
- * @throws Exception
+ * @throws Exception
*/
protected void executeAppJoin(SQLConfig config, List resultList, Map> childMap) throws Exception {
List joinList = config.getJoinList();
@@ -687,6 +687,11 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
throw new NullPointerException(TAG + ".executeAppJoin StringUtil.isEmpty(sql, true) >> return null;");
}
+ boolean isExplain = jc.isExplain();
+ if (isExplain == false) {
+ generatedSQLCount ++;
+ }
+
long startTime = System.currentTimeMillis();
Log.d(TAG, "\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
+ "\n executeAppJoin startTime = " + startTime
@@ -696,7 +701,15 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
//执行副表的批量查询 并 缓存到 childMap
ResultSet rs = null;
try {
+ long executedSQLStartTime = 0;
+ if (isExplain == false) { //只有 SELECT 才能 EXPLAIN
+ executedSQLCount ++;
+ executedSQLStartTime = System.currentTimeMillis();
+ }
rs = executeQuery(jc);
+ if (isExplain == false) {
+ executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime;
+ }
int index = -1;
@@ -704,15 +717,15 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
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");
@@ -740,7 +753,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
List results = childMap.get(cacheSql);
if (results == null) {
results = new ArrayList<>();
- childMap.put(cacheSql,results);
+ childMap.put(cacheSql, results);
}
results.add(result);
Log.d(TAG, ">>> executeAppJoin childMap.put('" + cacheSql + "', result); childMap.size() = " + childMap.size());
@@ -750,7 +763,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
if (rs != null) {
try {
rs.close();
- }
+ }
catch (Exception e) {
e.printStackTrace();
}
@@ -769,13 +782,13 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
/**table.put(rsmd.getColumnLabel(i), rs.getObject(i));
- * @param config
+ * @param config
* @param rs
* @param rsmd
* @param tablePosition 从0开始
* @param table
* @param columnIndex 从1开始
- * @param childMap
+ * @param childMap
* @return result
* @throws Exception
*/
@@ -785,23 +798,23 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
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;
}
- String lable = getKey(config, rs, rsmd, tablePosition, table, columnIndex, childMap);
- Object value = getValue(config, rs, rsmd, tablePosition, table, columnIndex, lable, childMap);
+ String label = getKey(config, rs, rsmd, tablePosition, table, columnIndex, childMap);
+ Object value = getValue(config, rs, rsmd, tablePosition, table, columnIndex, label, childMap);
// 主表必须 put 至少一个 null 进去,否则全部字段为 null 都不 put 会导致中断后续正常返回值
if (value != null || (join == null && table.isEmpty())) {
- table.put(lable, value);
+ table.put(label, value);
}
return table;
}
-
+
/**如果不需要这个功能,在子类重写并直接 return false; 来提高性能
* @param config
* @param rs
@@ -819,7 +832,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
}
/**resultList.put(position, table);
- * @param config
+ * @param config
* @param rs
* @param rsmd
* @param resultList
@@ -841,7 +854,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
long startTime = System.currentTimeMillis();
String key = rsmd.getColumnLabel(columnIndex); // dotIndex < 0 ? lable : lable.substring(dotIndex + 1);
sqlResultDuration += System.currentTimeMillis() - startTime;
-
+
if (config.isHive()) {
String tableName = config.getTable();
String realTableName = AbstractSQLConfig.TABLE_KEY_MAP.get(tableName);
@@ -852,7 +865,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
key = key.split("\\.")[1];
}
}
-
+
return key;
}
@@ -862,7 +875,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
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));
@@ -899,12 +912,12 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
else if (value instanceof Clob) { //SQL Server TEXT 类型 居然走这个
castToJson = true;
- StringBuffer sb = new StringBuffer();
- BufferedReader br = new BufferedReader(((Clob) value).getCharacterStream());
+ StringBuffer sb = new StringBuffer();
+ BufferedReader br = new BufferedReader(((Clob) value).getCharacterStream());
String s = br.readLine();
while (s != null) {
- sb.append(s);
- s = br.readLine();
+ sb.append(s);
+ s = br.readLine();
}
value = sb.toString();
@@ -933,8 +946,8 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
/**判断是否为JSON类型
- * @param config
- * @param lable
+ * @param config
+ * @param lable
* @param rsmd
* @param position
* @return
@@ -945,7 +958,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
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;
@@ -963,7 +976,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
/**
- * @param config
+ * @param config
* @return
* @throws Exception
*/
@@ -979,7 +992,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
else {
statement = getConnection(config).prepareStatement(config.getSQL(config.isPrepared()));
}
-
+
List valueList = config.isPrepared() ? config.getPreparedValueList() : null;
if (valueList != null && valueList.isEmpty() == false) {
@@ -1128,11 +1141,11 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
public int executeUpdate(@NotNull SQLConfig config) throws Exception {
PreparedStatement stt = getStatement(config);
int count = stt.executeUpdate(); // PreparedStatement 不用传 SQL
-
+
if (count <= 0 && config.isHive()) {
count = 1;
}
-
+
if (config.getId() == null && config.getMethod() == RequestMethod.POST) { // 自增id
ResultSet rs = stt.getGeneratedKeys();
if (rs != null && rs.next()) {
--
Gitee
From 7c2443dd91e5ffc3bfb2b6d3d9d93d46b3bd1657 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 6 Jul 2022 21:53:20 +0000
Subject: [PATCH 016/355] Bump fastjson from 1.2.79 to 1.2.83 in /APIJSONORM
Bumps [fastjson](https://github.com/alibaba/fastjson) from 1.2.79 to 1.2.83.
- [Release notes](https://github.com/alibaba/fastjson/releases)
- [Commits](https://github.com/alibaba/fastjson/compare/1.2.79...1.2.83)
---
updated-dependencies:
- dependency-name: com.alibaba:fastjson
dependency-type: direct:production
...
Signed-off-by: dependabot[bot]
---
APIJSONORM/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
mode change 100755 => 100644 APIJSONORM/pom.xml
diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml
old mode 100755
new mode 100644
index fcc4ffa6..3be04ade
--- a/APIJSONORM/pom.xml
+++ b/APIJSONORM/pom.xml
@@ -21,7 +21,7 @@
com.alibaba
fastjson
- 1.2.79
+ 1.2.83
javax.activation
--
Gitee
From 5bb7eecfcafecd609df6d2f2553c2ffb60c52cc4 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 7 Jul 2022 06:27:20 +0800
Subject: [PATCH 017/355] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=88=9A=E5=88=9A?=
=?UTF-8?q?=E6=94=B9=E5=8A=A8=20result.remove("@RAW@LIST")=20=E9=80=BB?=
=?UTF-8?q?=E8=BE=91=E5=90=8E=E6=96=B0=E5=A2=9E=E7=9A=84=20NPE=20bug?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../apijson/orm/AbstractObjectParser.java | 46 +++++++++----------
1 file changed, 22 insertions(+), 24 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
index 68b31a87..0fc5379c 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java
@@ -897,38 +897,36 @@ public abstract class AbstractObjectParser implements ObjectParser {
boolean isSimpleArray = false;
// 提取并缓存数组主表的列表数据
- List rawList = (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST);
+ List rawList = result == null ? null : (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST);
- if (isArrayMainTable && position == 0 && result != null) {
+ if (isArrayMainTable && position == 0 && rawList != null) {
- isSimpleArray = (functionMap == null || functionMap.isEmpty())
- && (customMap == null || customMap.isEmpty())
- && (childMap == null || childMap.isEmpty())
- && (table.equals(arrayTable));
+ isSimpleArray = (functionMap == null || functionMap.isEmpty())
+ && (customMap == null || customMap.isEmpty())
+ && (childMap == null || childMap.isEmpty())
+ && (table.equals(arrayTable));
- // APP JOIN 副表时副表返回了这个字段 rawList = (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST);
- if (rawList != null) {
- String arrayPath = parentPath.substring(0, parentPath.lastIndexOf("[]") + 2);
+ // APP JOIN 副表时副表返回了这个字段 rawList = (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST);
+ String arrayPath = parentPath.substring(0, parentPath.lastIndexOf("[]") + 2);
- if (isSimpleArray == false) {
- long startTime = System.currentTimeMillis();
+ if (isSimpleArray == false) {
+ long startTime = System.currentTimeMillis();
- for (int i = 1; i < rawList.size(); i++) { // 从 1 开始,0 已经处理过
- JSONObject obj = rawList.get(i);
+ 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里不存在需要的关联数据
- }
- }
+ 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(); // 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 ");
+ }
- parser.putArrayMainCache(arrayPath, rawList);
- }
- }
+ parser.putArrayMainCache(arrayPath, rawList);
+ }
if (isSubquery == false && result != null) {
parser.putQueryResult(path, result); // 解决获取关联数据时requestObject里不存在需要的关联数据
--
Gitee
From 0f836600f01f42919163382550ee1b3584b9716b Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Tue, 12 Jul 2022 02:46:13 +0800
Subject: [PATCH 018/355] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20@combine:"(a=20|?=
=?UTF-8?q?=20b)=20&=20(c=20|=20d)"=20=E8=BF=99=E7=A7=8D=E4=BB=BB=E6=84=8F?=
=?UTF-8?q?=E6=9D=A1=E4=BB=B6=E7=BB=84=E5=90=88=E6=83=85=E5=86=B5=E4=B8=8B?=
=?UTF-8?q?=E6=9C=89=E6=97=B6=E9=A2=84=E7=BC=96=E8=AF=91=E5=80=BC=E9=94=99?=
=?UTF-8?q?=E4=BD=8D=E5=AF=BC=E8=87=B4=20SQL=20=E6=8A=A5=E9=94=99=EF=BC=9B?=
=?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81=E5=92=8C=E6=B3=A8=E9=87=8A?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../java/apijson/orm/AbstractSQLConfig.java | 282 ++++++++++--------
.../java/apijson/orm/AbstractSQLExecutor.java | 74 +++--
.../main/java/apijson/orm/SQLExecutor.java | 35 ++-
3 files changed, 216 insertions(+), 175 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
index cfe4df81..b33ae695 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
@@ -990,11 +990,8 @@ public abstract class AbstractSQLConfig implements SQLConfig {
public String getSchema() {
return schema;
}
- /**
- * @param sqlTable
- * @return
- */
- @NotNull
+
+ @NotNull
public String getSQLSchema() {
String table = getTable();
//强制,避免因为全局默认的 @schema 自动填充进来,导致这几个类的 schema 为 sys 等其它值
@@ -1084,7 +1081,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
a = getTable();
}
String q = getQuote();
- //getTable 不能小写,因为Verifier用大小写敏感的名称判断权限
+ //getTable 不能小写,因为Verifier用大小写敏感的名称判断权限
//如果要强制小写,则可在子类重写这个方法再 toLowerCase return q + (DATABASE_POSTGRESQL.equals(getDatabase()) ? a.toLowerCase() : a) + q;
return q + a + q;
}
@@ -1175,7 +1172,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
/**TODO @having 改为默认 | 或连接,且支持 @having: { "key1>": 1, "key{}": "length(key2)>0", "@combine": "key1,key2" }
* @return HAVING conditoin0 AND condition1 OR condition2 ...
- * @throws Exception
+ * @throws Exception
*/
@JSONField(serialize = false)
public String getHavingString(boolean hasPrefix) throws Exception {
@@ -1304,7 +1301,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
// else if (StringUtil.isName(origin)) {
// origin = quote + origin + quote;
// isName = true;
- // }
+ // }
// else {
// origin = getValue(origin).toString();
// }
@@ -1557,7 +1554,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
if (StringUtil.isName(origin) == false) {
int start = origin.indexOf("(");
- if (start < 0 || origin.lastIndexOf(")") <= start) {
+ if (start < 0 || origin.lastIndexOf(")") <= start) {
throw new IllegalArgumentException("HEAD请求: 字符" + origin + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项"
+ " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!");
}
@@ -1721,21 +1718,19 @@ public abstract class AbstractSQLConfig implements SQLConfig {
* @param expression
* @param containRaw
* @param allowAlias
- * @param columnPrefix
* @return
*/
public String parseSQLExpression(String key, String expression, boolean containRaw, boolean allowAlias) {
return parseSQLExpression(key, expression, containRaw, allowAlias, null);
}
/**解析@column 中以“;”分隔的表达式("@column":"expression1;expression2;expression2;....")中的expression
- * @param key
- * @param expression
- * @param containRaw
- * @param allowAlias
- * @param columnPrefix
- * @param example
- * @return
- */
+ * @param key
+ * @param expression
+ * @param containRaw
+ * @param allowAlias
+ * @param example
+ * @return
+ */
public String parseSQLExpression(String key, String expression, boolean containRaw, boolean allowAlias, String example) {
String quote = getQuote();
int start = expression.indexOf('(');
@@ -1844,7 +1839,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
}
}
else if (SQL_FUNCTION_MAP.containsKey(fun) == false) {
- throw new IllegalArgumentException("字符 " + fun + " 不合法!预编译模式下 " + example
+ throw new IllegalArgumentException("字符 " + fun + " 不合法!预编译模式下 " + example
+ " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!");
}
}
@@ -1862,7 +1857,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
+ key + ":value 中 value里面用 ; 分割的每一项"
+ " function(arg0,arg1,...):alias 中 alias 必须是1个单词!并且不要有多余的空格!");
}
-
+
String suffix = s2.substring(end + 1, aliasIndex < 0 ? s2.length() : aliasIndex);
if (suffix.isEmpty() == false && (((String) suffix).contains("--") || ((String) suffix).contains("/*")
|| PATTERN_RANGE.matcher((String) suffix).matches() == false)) {
@@ -1870,9 +1865,9 @@ public abstract class AbstractSQLConfig implements SQLConfig {
+ ":\"column?value;function(arg0,arg1,...)?value...\""
+ " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!");
}
-
+
// 获取后半部分的参数解析 (agr0 agr1 ...)
- String argsString2[] = parseArgsSplitWithComma(argString2, false, containRaw, allowAlias);
+ String argsString2[] = parseArgsSplitWithComma(argString2, false, containRaw, allowAlias);
expression = fun + "(" + StringUtil.getString(agrsString1) + (containOver ? ") OVER (" : ") AGAINST (") // 传参不传空格,拼接带空格
+ StringUtil.getString(argsString2) + ")" + suffix + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); }
}
@@ -1880,12 +1875,12 @@ public abstract class AbstractSQLConfig implements SQLConfig {
return expression;
}
-
+
/**解析函数参数或者字段,此函数对于解析字段 和 函数内参数通用
* @param param
* @param isColumn true:不是函数参数。false:是函数参数
- * @param containRaw
- * @param allowAlias
+ * @param containRaw
+ * @param allowAlias
* @return
*/
private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean containRaw, boolean allowAlias) {
@@ -1900,7 +1895,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
String origin;
String alias;
-
+
// 如果参数包含 "'" ,解析字符串
if (ck.startsWith("`") && ck.endsWith("`")) {
origin = ck.substring(1, ck.length() - 1);
@@ -2280,10 +2275,13 @@ public abstract class AbstractSQLConfig implements SQLConfig {
}
return getLimitString(getPage(), getCount(), isOracle() || isSQLServer() || isDb2(), isOracle());
}
- /**获取限制数量
- * @param limit
- * @return
- */
+ /**获取限制数量及偏移量
+ * @param page
+ * @param count
+ * @param isTSQL
+ * @param isOracle
+ * @return
+ */
public static String getLimitString(int page, int count, boolean isTSQL, boolean isOracle) {
int offset = getOffset(page, count);
@@ -2418,7 +2416,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
public AbstractSQLConfig putWhere(String key, Object value, boolean prior) {
if (key != null) {
if (where == null) {
- where = new LinkedHashMap();
+ where = new LinkedHashMap();
}
if (value == null) {
where.remove(key);
@@ -2501,7 +2499,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
/**获取WHERE
* @return
- * @throws Exception
+ * @throws Exception
*/
@JSONField(serialize = false)
@Override
@@ -2516,7 +2514,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
* @param method
* @param where
* @return
- * @throws Exception
+ * @throws Exception
*/
@JSONField(serialize = false)
public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, String combine, List joinList, boolean verifyName) throws Exception {
@@ -2537,7 +2535,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
* @param quote
* @param table
* @param alias
- * @param conditioinMap where 或 having 对应条件的 Map
+ * @param conditionMap where 或 having 对应条件的 Map
* @param combine
* @param verifyName
* @param containRaw
@@ -2546,7 +2544,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
* @throws Exception
*/
protected String parseCombineExpression(RequestMethod method, String quote, String table, String alias
- , Map conditioinMap, String combine, boolean verifyName, boolean containRaw, boolean isHaving) throws Exception {
+ , Map conditionMap, String combine, boolean verifyName, boolean containRaw, boolean isHaving) throws Exception {
String errPrefix = table + (isHaving ? ":{ @having:{ " : ":{ ") + "@combine:'" + combine + (isHaving ? "' } }" : "' }");
String s = StringUtil.getString(combine);
@@ -2556,10 +2554,10 @@ public abstract class AbstractSQLConfig implements SQLConfig {
+ "逻辑连接符 & | 左右必须各一个相邻空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!");
}
- if (conditioinMap == null) {
- conditioinMap = new HashMap<>();
+ if (conditionMap == null) {
+ conditionMap = new HashMap<>();
}
- int size = conditioinMap.size();
+ int size = conditionMap.size();
int maxCount = isHaving ? getMaxHavingCount() : getMaxWhereCount();
if (maxCount > 0 && size > maxCount) {
@@ -2569,13 +2567,16 @@ public abstract class AbstractSQLConfig implements SQLConfig {
String result = "";
- List prepreadValues = getPreparedValueList();
+ List preparedValues = getPreparedValueList();
+ if (preparedValues == null && isHaving == false) {
+ preparedValues = new ArrayList<>();
+ }
Map usedKeyCountMap = new HashMap<>(size);
int n = s.length();
if (n > 0) {
- if (isHaving == false) {
+ if (isHaving == false) { // 只收集表达式条件值
setPreparedValueList(new ArrayList<>()); // 必须反过来,否则 JOIN ON 内部 @combine 拼接后顺序错误
}
@@ -2626,7 +2627,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
String column = key;
- Object value = conditioinMap.get(column);
+ Object value = conditionMap.get(column);
if (value == null) {
throw new IllegalArgumentException(errPrefix + " 中字符 '" + key
+ "' 对应的条件键值对 " + column + ":value 不存在!");
@@ -2672,7 +2673,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
result += SQL.AND;
lastLogic = c;
i ++;
- }
+ }
else {
key += c;
}
@@ -2734,7 +2735,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
depth ++;
if (depth > maxDepth && maxDepth > 0) {
- throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(0, i + 1)
+ throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(0, i + 1)
+ "' 不合法!括号 (()) 嵌套层级 " + depth + " 已超过最大值,必须在 0-" + maxDepth + " 内!");
}
@@ -2751,7 +2752,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
result += c;
lastLogic = 0;
- }
+ }
else {
key += c;
}
@@ -2766,7 +2767,12 @@ public abstract class AbstractSQLConfig implements SQLConfig {
}
}
- Set> set = conditioinMap.entrySet();
+ List exprPreparedValues = getPreparedValueList();
+ if (isHaving == false) { // 只收集 AND 条件值
+ setPreparedValueList(new ArrayList<>());
+ }
+
+ Set> set = conditionMap.entrySet();
String andCond = "";
boolean isItemFirst = true;
@@ -2786,26 +2792,33 @@ public abstract class AbstractSQLConfig implements SQLConfig {
isItemFirst = false;
}
+ if (isHaving == false) { // 优先存放 AND 条件值
+ preparedValues.addAll(getPreparedValueList());
+ }
+
if (StringUtil.isEmpty(result, true)) {
result = andCond;
}
else if (StringUtil.isNotEmpty(andCond, true)) { // andCond 必须放后面,否则 prepared 值顺序错误
if (isHaving) { // HAVING 前 WHERE 已经有条件 ? 占位,不能反过来,想优化 AND 连接在最前,需要多遍历一次内部的 key,也可以 newSQLConfig 时存到 andList
result = "( " + result + " )" + AND + andCond;
- }
+ }
else {
result = andCond + AND + "( " + result + " )"; // 先暂存之前的 prepared 值,然后反向整合
- if (n > 0) {
- prepreadValues.addAll(getPreparedValueList());
- setPreparedValueList(prepreadValues);
- }
}
}
+ if (isHaving == false) {
+ if (exprPreparedValues != null && exprPreparedValues.isEmpty() == false) {
+ preparedValues.addAll(exprPreparedValues); // 在 AND 条件值后存放表达式内的条件值
+ }
+ setPreparedValueList(preparedValues);
+ }
+
return result;
}
- /**已废弃,最快 6.0 删除,请尽快把前端/客户端 @combine:"a,b" 这种旧方式改为 @combine:"a | b" 这种新方式
+ /**@combine:"a,b" 条件组合。虽然有了 @combine:"a | b" 这种新方式,但为了 Join 多个 On 能保证顺序正确,以及这个性能更好,还是保留这个方式
* @param hasPrefix
* @param method
* @param where
@@ -2815,8 +2828,8 @@ public abstract class AbstractSQLConfig implements SQLConfig {
* @return
* @throws Exception
*/
- @Deprecated
- public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, Map> combine, List joinList, boolean verifyName) throws Exception {
+ 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()) {
Log.w(TAG, "getWhereString combineSet == null || combineSet.isEmpty() >> return \"\";");
@@ -2908,9 +2921,9 @@ public abstract class AbstractSQLConfig implements SQLConfig {
case ">": // RIGHT JOIN
break;
- case "&": // INNER JOIN: A & B
- case "": // FULL JOIN: A | B
- case "|": // FULL JOIN: A | B
+ 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
@@ -2966,7 +2979,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
newWs += AND;
}
- if (isAntiJoin) { // ( ANTI JOIN: A & ! B
+ if (isAntiJoin) { // ( ANTI JOIN: A & ! B
newWs += " ( " + ( isWsEmpty ? "" : ws + AND ) + NOT + " ( " + js + " ) " + " ) ";
}
else if (isForeignJoin) { // ) FOREIGN JOIN: (! A) & B // preparedValueList.add 不好反过来 B & ! A
@@ -2975,7 +2988,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B)
//MySQL 因为 NULL 值处理问题,(A & ! B) | (B & ! A) 与 ! (A & B) 返回结果不一样,后者往往更多
newWs += " ( " + getCondition(
- true,
+ true,
( isWsEmpty ? "" : ws + AND ) + " ( " + js + " ) "
) + " ) ";
}
@@ -2983,7 +2996,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
int logic = Logic.getType(jt);
newWs += " ( "
+ getCondition(
- Logic.isNot(logic),
+ Logic.isNot(logic),
ws
+ ( isWsEmpty ? "" : (Logic.isAnd(logic) ? AND : OR) )
+ " ( " + js + " ) "
@@ -3039,7 +3052,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
int keyType;
if (key.endsWith("$")) {
keyType = 1;
- }
+ }
else if (key.endsWith("~")) {
keyType = key.charAt(key.length() - 2) == '*' ? -2 : 2; //FIXME StringIndexOutOfBoundsException
}
@@ -3071,7 +3084,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
}
String column = getRealKey(method, key, false, true, verifyName);
-
+
// 原始 SQL 片段
String rawSQL = getRawSQL(key, value, keyType != 4 || value instanceof String == false);
@@ -3109,7 +3122,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
throw new IllegalArgumentException(key + ":value 中value不合法!非PUT请求只支持 [Boolean, Number, String] 内的类型 !");
}
- boolean not = column.endsWith("!"); // & | 没有任何意义,写法多了不好控制
+ boolean not = column.endsWith("!"); // & | 没有任何意义,写法多了不好控制
if (not) {
column = column.substring(0, column.length() - 1);
}
@@ -3215,10 +3228,13 @@ public abstract class AbstractSQLConfig implements SQLConfig {
//$ search <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
/**search key match value
- * @param in
- * @return {@link #getSearchString(String, Object[], int)}
- * @throws IllegalArgumentException
- */
+ * @param key
+ * @param column
+ * @param value
+ * @param rawSQL
+ * @return {@link #getSearchString(String, String, Object[], int)}
+ * @throws IllegalArgumentException
+ */
@JSONField(serialize = false)
public String getSearchString(String key, String column, Object value, String rawSQL) throws IllegalArgumentException {
if (rawSQL != null) {
@@ -3239,10 +3255,13 @@ public abstract class AbstractSQLConfig implements SQLConfig {
return getSearchString(key, column, arr.toArray(), logic.getType());
}
/**search key match values
- * @param in
- * @return LOGIC [ key LIKE 'values[i]' ]
- * @throws IllegalArgumentException
- */
+ * @param key
+ * @param column
+ * @param values
+ * @param type
+ * @return LOGIC [ key LIKE 'values[i]' ]
+ * @throws IllegalArgumentException
+ */
@JSONField(serialize = false)
public String getSearchString(String key, String column, Object[] values, int type) throws IllegalArgumentException {
if (values == null || values.length <= 0) {
@@ -3334,9 +3353,9 @@ public abstract class AbstractSQLConfig implements SQLConfig {
* @param key
* @param column
* @param value
- * @param ignoreCase
- * @return {@link #getRegExpString(String, Object[], int, boolean)}
- * @throws IllegalArgumentException
+ * @param ignoreCase
+ * @return {@link #getRegExpString(String, String, Object[], int, boolean)}
+ * @throws IllegalArgumentException
*/
@JSONField(serialize = false)
public String getRegExpString(String key, String column, Object value, boolean ignoreCase, String rawSQL) throws IllegalArgumentException {
@@ -3360,10 +3379,10 @@ public abstract class AbstractSQLConfig implements SQLConfig {
/**search key match RegExp values
* @param key
* @param values
- * @param type
- * @param ignoreCase
+ * @param type
+ * @param ignoreCase
* @return LOGIC [ key REGEXP 'values[i]' ]
- * @throws IllegalArgumentException
+ * @throws IllegalArgumentException
*/
@JSONField(serialize = false)
public String getRegExpString(String key, String column, Object[] values, int type, boolean ignoreCase) throws IllegalArgumentException {
@@ -3416,7 +3435,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
* @param key
* @param value 'start,end'
* @return LOGIC [ key BETWEEN 'start' AND 'end' ]
- * @throws IllegalArgumentException
+ * @throws IllegalArgumentException
*/
@JSONField(serialize = false)
public String getBetweenString(String key, String column, Object value, String rawSQL) throws IllegalArgumentException {
@@ -3440,9 +3459,11 @@ public abstract class AbstractSQLConfig implements SQLConfig {
/**WHERE key BETWEEN 'start' AND 'end'
* @param key
- * @param value 'start,end' TODO 在 '1,2' 和 ['1,2', '3,4'] 基础上新增支持 [1, 2] 和 [[1,2], [3,4]] ?
- * @return LOGIC [ key BETWEEN 'start' AND 'end' ]
- * @throws IllegalArgumentException
+ * @param column
+ * @param values ['start,end'] TODO 在 '1,2' 和 ['1,2', '3,4'] 基础上新增支持 [1, 2] 和 [[1,2], [3,4]] ?
+ * @param type
+ * @return LOGIC [ key BETWEEN 'start' AND 'end' ]
+ * @throws IllegalArgumentException
*/
@JSONField(serialize = false)
public String getBetweenString(String key, String column, Object[] values, int type) throws IllegalArgumentException {
@@ -3469,11 +3490,13 @@ public abstract class AbstractSQLConfig implements SQLConfig {
}
/**WHERE key BETWEEN 'start' AND 'end'
- * @param key
- * @param value 'start,end' TODO 在 '1,2' 和 ['1,2', '3,4'] 基础上新增支持 [1, 2] 和 [[1,2], [3,4]] ?
- * @return key BETWEEN 'start' AND 'end'
- * @throws IllegalArgumentException
- */
+ * @return key
+ * @param column
+ * @param start
+ * @param end
+ * @return LOGIC [ key BETWEEN 'start' AND 'end' ]
+ * @throws IllegalArgumentException
+ */
@JSONField(serialize = false)
public String getBetweenString(String key, String column, Object start, Object end) throws IllegalArgumentException {
if (JSON.isBooleanOrNumberOrString(start) == false || JSON.isBooleanOrNumberOrString(end) == false) {
@@ -3494,7 +3517,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
* @param key
* @param range "condition0,condition1..."
* @return key condition0 AND key condition1 AND ...
- * @throws Exception
+ * @throws Exception
*/
@JSONField(serialize = false)
public String getRangeString(String key, String column, Object range, String rawSQL) throws Exception {
@@ -3535,24 +3558,24 @@ public abstract class AbstractSQLConfig implements SQLConfig {
List raw = getRaw();
boolean containRaw = raw == null ? false : raw.contains(key);
String lk = logic.isAnd() ? AND : OR;
-
+
for (int i = 0; i < cs.length; i++) {//对函数条件length(key)<=5这种不再在开头加key
String expr = cs[i];
-
+
if (expr.length() > 100) {
throw new UnsupportedOperationException(key + ":value 的 value 中字符串 " + expr + " 不合法!"
+ "不允许传超过 100 个字符的函数或表达式!请用 @raw 简化传参!");
}
-
+
int index = expr == null ? -1 : expr.indexOf("(");
if (index >= 0) {
expr = parseSQLExpression(key, expr, containRaw, false, key + ":\"!=null;+3*2<=10;function0(arg0,arg1,...)>1;function1(...)%5<=3...\"");
- }
+ }
else {
String fk = getKey(k) + " ";
String[] ccs = StringUtil.split(expr, false);
expr = "";
-
+
for (int j = 0; j < ccs.length; j++) {
String c = ccs[j];
if ("=null".equals(c)) {
@@ -3565,7 +3588,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
throw new UnsupportedOperationException(key + ":value 的 value 中 " + c + " 不合法!"
+ "预编译模式下 key{}:\"condition\" 中 condition 必须 为 =null 或 !=null 或 符合正则表达式 " + PATTERN_RANGE + " !不允许连续减号 -- !不允许空格!");
}
-
+
expr += (j <= 0 ? "" : lk) + fk + c;
}
}
@@ -3573,7 +3596,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
condition += ((i <= 0 ? "" : lk) + expr);
}
}
-
+
if (condition.isEmpty()) {
return "";
}
@@ -3590,7 +3613,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
/**WHERE key IN ('key0', 'key1', ... )
* @param in
* @return IN ('key0', 'key1', ... )
- * @throws NotExistException
+ * @throws NotExistException
*/
@JSONField(serialize = false)
public String getInString(String key, String column, Object[] in, boolean not) throws NotExistException {
@@ -3641,7 +3664,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
/**WHERE key contains value
* @param key
* @param value
- * @return {@link #getContainString(String, Object[], int)}
+ * @return {@link #getContainString(String, String, Object[], int)}
* @throws NotExistException
*/
@JSONField(serialize = false)
@@ -3662,7 +3685,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
* @param type |, &, !
* @return LOGIC [ ( key LIKE '[" + childs[i] + "]' OR key LIKE '[" + childs[i] + ", %'
* OR key LIKE '%, " + childs[i] + ", %' OR key LIKE '%, " + childs[i] + "]' ) ]
- * @throws IllegalArgumentException
+ * @throws IllegalArgumentException
*/
@JSONField(serialize = false)
public String getContainString(String key, String column, Object[] childs, int type) throws IllegalArgumentException {
@@ -3690,7 +3713,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
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 + "]");
+ 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()) + ")");
@@ -3708,7 +3731,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
if (condition.isEmpty()) {
condition = getKey(column) + SQL.isNull(true) + OR + getLikeString(key, column, "[]"); // key = '[]' 无结果!
- }
+ }
else {
condition = getKey(column) + SQL.isNull(false) + AND + "(" + condition + ")";
}
@@ -3757,7 +3780,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
/**拼接条件
* @param not
* @param condition
- * @param outerBreaket
+ * @param outerBreaket
* @return
*/
public static String getCondition(boolean not, String condition, boolean addOuterBracket) {
@@ -3767,7 +3790,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
/**转为JSONArray
- * @param tv
+ * @param obj
* @return
*/
@NotNull
@@ -3789,7 +3812,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
//SET <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
/**获取SET
* @return
- * @throws Exception
+ * @throws Exception
*/
@JSONField(serialize = false)
public String getSetString() throws Exception {
@@ -3881,7 +3904,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
/**
* @return
- * @throws Exception
+ * @throws Exception
*/
@JSONField(serialize = false)
@Override
@@ -3891,7 +3914,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
/**
* @param config
* @return
- * @throws Exception
+ * @throws Exception
*/
public static String getSQL(AbstractSQLConfig config) throws Exception {
if (config == null) {
@@ -3943,17 +3966,16 @@ public abstract class AbstractSQLConfig implements SQLConfig {
if (StringUtil.isNotEmpty(config.getGroup(),true) && RequestMethod.isHeadMethod(config.getMethod(), true)){
return explain + "SELECT count(*) FROM (SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString();
}
-
+
String sql = "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config);
return explain + config.getOraclePageSql(sql);
}
-
+
return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString();
}
}
/**Oracle的分页获取
- * @param config
* @param sql
* @return
*/
@@ -3961,16 +3983,16 @@ public abstract class AbstractSQLConfig implements SQLConfig {
int count = getCount();
int offset = getOffset(getPage(), count);
String alias = getAliasWithQuote();
-
+
return "SELECT * FROM (SELECT " + alias + ".*, ROWNUM RN FROM (" + sql + ") " + alias + " WHERE ROWNUM <= " + (offset + count) + ") WHERE RN > " + offset;
}
-
+
/**获取条件SQL字符串
* @param column
* @param table
* @param config
* @return
- * @throws Exception
+ * @throws Exception
*/
private static String getConditionString(String column, String table, AbstractSQLConfig config) throws Exception {
String where = config.getWhereString(true);
@@ -4098,9 +4120,9 @@ public abstract class AbstractSQLConfig implements SQLConfig {
changed = true;
break;
- case "&": // INNER JOIN: A & B
- case "": // FULL JOIN: A | B
- case "|": // FULL JOIN: A | B
+ 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
@@ -4282,7 +4304,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
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 ? ") " : "");
+ + " IS NOT NULL AND " + arrKeyPath + " @> " + itemKeyPath) + (isNot ? ") " : "");
}
else if (isOracle()) {
sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath
@@ -4336,7 +4358,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
* @param isProcedure
* @param callback
* @return
- * @throws Exception
+ * @throws Exception
*/
public static SQLConfig newSQLConfig(RequestMethod method, String table, String alias, JSONObject request, List joinList, boolean isProcedure, Callback callback) throws Exception {
if (request == null) { // User:{} 这种空内容在查询时也有效
@@ -4407,7 +4429,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
}
if (id != null) { //null无效
- if (id instanceof Number) {
+ if (id instanceof Number) {
if (((Number) id).longValue() <= 0) { //一定没有值
throw new NotExistException(TAG + ": newSQLConfig " + table + ".id <= 0");
}
@@ -4461,7 +4483,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
Object userId = userIdKey.equals(idKey) ? null : request.get(userIdKey);
if (userId != null) { //null无效
- if (userId instanceof Number) {
+ if (userId instanceof Number) {
if (((Number) userId).longValue() <= 0) { //一定没有值
throw new NotExistException(TAG + ": newSQLConfig " + table + ".userId <= 0");
}
@@ -4596,7 +4618,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
+ " 这种非字段命名 key !必须为 英文字母 开头且只包含 英文字母、数字、下划线的 字段命名!");
}
}
-
+
String[] columns = set.toArray(new String[]{});
Collection valueCollection = request.values();
@@ -4606,7 +4628,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
throw new Exception("服务器内部错误:\n" + TAG
+ " newSQLConfig values == null || values.length != columns.length !");
}
-
+
column = (id == null ? "" : idKey + ",") + (userId == null ? "" : userIdKey + ",") + StringUtil.getString(columns); //set已经判断过不为空
int idCount = id == null ? (userId == null ? 0 : 1) : (userId == null ? 1 : 2);
@@ -4621,14 +4643,14 @@ public abstract class AbstractSQLConfig implements SQLConfig {
}
for (int j = 0; j < values.length; j++) {
- items.add(values[j]); // 从第 1 个开始,允许 "null"
+ items.add(values[j]); // 从第 1 个开始,允许 "null"
}
List> valuess = new ArrayList<>(1);
valuess.add(items);
config.setValues(valuess);
}
- }
+ }
else { //非POST操作
final boolean isWhere = method != PUT; //除了POST,PUT,其它全是条件!!!
@@ -4644,7 +4666,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
List andList = new ArrayList<>();
List orList = new ArrayList<>();
List notList = new ArrayList<>();
-
+
List whereList = new ArrayList<>();
//强制作为条件且放在最前面优化性能
@@ -4857,7 +4879,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
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 +
+ throw new IllegalArgumentException(table + ":{ " + havingKey + ":value } 里的 value 中的第 " + i +
" 个字符 '" + havingsStr + "' 不合法!里面没有包含 SQL 函数!必须为 fun(col1,col2..)?val 格式!");
}
@@ -5039,8 +5061,8 @@ public abstract class AbstractSQLConfig implements SQLConfig {
}
}
- //解决 query: 1/2 查数量时报错
- /* SELECT count(*) AS count FROM sys.Moment AS Moment
+ //解决 query: 1/2 查数量时报错
+ /* 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)) {
List onList = j.getOnList();
@@ -5134,16 +5156,16 @@ public abstract class AbstractSQLConfig implements SQLConfig {
}
else if (key.endsWith("{}")) {//被包含 IN,或者说key对应值处于value的范围内。查询时处理
key = key.substring(0, key.length() - 2);
- }
+ }
else if (key.endsWith("}{")) {//被包含 EXISTS,或者说key对应值处于value的范围内。查询时处理
key = key.substring(0, key.length() - 2);
- }
+ }
else if (key.endsWith("<>")) {//包含 json_contains,或者说value处于key对应值的范围内。查询时处理
key = key.substring(0, key.length() - 2);
- }
+ }
else if (key.endsWith("()")) {//方法,查询完后处理,先用一个Map保存
key = key.substring(0, key.length() - 2);
- }
+ }
else if (key.endsWith("@")) {//引用,引用对象查询完后处理。fillTarget中暂时不用处理,因为非GET请求都是由给定的id确定,不需要引用
key = key.substring(0, key.length() - 1);
}
@@ -5163,7 +5185,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
if (method == PUT) {//不为PUT就抛异常
key = key.substring(0, key.length() - 1);
}
- }
+ }
else if (key.endsWith("-")) {//缩减,PUT查询时处理
if (method == PUT) {//不为PUT就抛异常
key = key.substring(0, key.length() - 1);
@@ -5263,7 +5285,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
id = LAST_ID + 1; // 解决高并发下 id 冲突导致新增记录失败
}
LAST_ID = id;
-
+
return (T) id;
}
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
index 1dcbb754..0c8f7089 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
@@ -6,6 +6,7 @@ This source code is licensed under the Apache License Version 2.0.*/
package apijson.orm;
import java.io.BufferedReader;
+import java.rmi.ServerError;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
@@ -40,6 +41,7 @@ import apijson.NotNull;
import apijson.RequestMethod;
import apijson.StringUtil;
import apijson.orm.Join.On;
+import apijson.orm.exception.NotExistException;
/**executor for query(read) or update(write) MySQL database
* @author Lemon
@@ -163,7 +165,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
* @throws Exception
*/
@Override
- public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws Exception {
+ public JSONObject execute(@NotNull SQLConfig config, boolean unknownType) throws Exception {
long executedSQLStartTime = System.currentTimeMillis();
boolean isPrepared = config.isPrepared();
@@ -201,7 +203,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
Map childMap = null;
try {
- if (unknowType) {
+ if (unknownType) {
if (isExplain == false) { //只有 SELECT 才能 EXPLAIN
executedSQLCount ++;
executedSQLStartTime = System.currentTimeMillis();
@@ -521,14 +523,17 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
SQLConfig viceConfig = curJoin != null && curJoin.isSQLJoin() ? curJoin.getCacheConfig() : null;
if (viceConfig != null) { //FIXME 只有和主表关联才能用 item,否则应该从 childMap 查其它副表数据
List onList = curJoin.getOnList();
- if (onList != null) {
- Collections.reverse(onList);
- for (On on : onList) {
- if (on != null) {
- String ok = on.getOriginKey();
- viceConfig.putWhere(ok.substring(0, ok.length() - 1), item.get(on.getTargetKey()), true);
+ int size = onList == null ? 0 : onList.size();
+ if (size > 0) {
+ for (int j = size - 1; j >= 0; j--) {
+ On on = onList.get(j);
+ String ok = on == null ? null : on.getOriginKey();
+ if (ok == null) {
+ throw new NullPointerException("服务器内部错误,List 中 Join.onList[" + j + (on == null ? "] = null!" : ".getOriginKey() = null!"));
}
- }
+
+ viceConfig.putWhere(ok.substring(0, ok.length() - 1), item.get(on.getTargetKey()), true);
+ }
}
}
String viceSql = viceConfig == null ? null : viceConfig.getSQL(false); //TODO 在 SQLConfig 缓存 SQL,减少大量的重复生成
@@ -575,7 +580,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
return null;
}
- if (unknowType || isExplain) {
+ if (unknownType || isExplain) {
if (isExplain) {
if (result == null) {
result = new JSONObject(true);
@@ -657,25 +662,36 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
jc = join.getJoinConfig();
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++) {
- JSONObject mainTable = resultList.get(i);
- Object targetValue = mainTable == null ? null : mainTable.get(on.getTargetKey());
-
- 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{}
- }
- }
+ int size = onList == null ? 0 : onList.size();
+ if (size > 0) {
+ for (int j = size - 1; j >= 0; j--) {
+ On on = onList.get(j);
+ String ok = on == null ? null : on.getOriginKey();
+ if (ok == null) {
+ throw new NullPointerException("服务器内部错误,List 中 Join.onList[" + j + (on == null ? "] = null!" : ".getOriginKey() = null!"));
+ }
+
+ // 取出 "id@": "@/User/userId" 中所有 userId 的值
+ List targetValueList = new ArrayList<>();
+
+ 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 (targetValueList.isEmpty() && config.isExplain() == false) {
+ throw new NotExistException("targetValueList.isEmpty() && config.isExplain() == false");
+ }
+
+ // 替换为 "id{}": [userId1, userId2, userId3...]
+ jc.putWhere(ok, null, false); // remove orginKey
+ jc.putWhere(on.getKey() + "{}", targetValueList, true); // add orginKey{} }
+ }
+ }
jc.setMain(true).setPreparedValueList(new ArrayList<>());
diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java
index 7e700c26..25bfa467 100755
--- a/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java
@@ -49,41 +49,44 @@ public interface SQLExecutor {
* @param config
*/
void removeCache(String sql, SQLConfig config);
-
+
/**执行SQL
* @param config
+ * @param unknownType
* @return
* @throws Exception
*/
- JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws Exception;
-
+ JSONObject execute(@NotNull SQLConfig config, boolean unknownType) throws Exception;
+
//executeQuery和executeUpdate这两个函数因为返回类型不同,所以不好合并
/**执行查询
- * @param sql
+ * @param config
* @return
* @throws SQLException
*/
ResultSet executeQuery(@NotNull SQLConfig config) throws Exception;
-
+
/**执行增、删、改
- * @param sql
+ * @param config
* @return
* @throws SQLException
*/
int executeUpdate(@NotNull SQLConfig config) throws Exception;
-
+
/**判断是否为JSON类型
- * @param rsmd
- * @param position
- * @return
- */
+ * @param config
+ * @param rsmd
+ * @param position
+ * @param lable
+ * @return
+ */
boolean isJSONType(@NotNull SQLConfig config, ResultSetMetaData rsmd, int position, String lable);
-
+
Connection getConnection(@NotNull SQLConfig config) throws Exception;
Statement getStatement(@NotNull SQLConfig config) throws Exception;
-
+
int getTransactionIsolation();
void setTransactionIsolation(int transactionIsolation);
/**开始事务
@@ -105,11 +108,11 @@ public interface SQLExecutor {
/**关闭连接,释放资源
*/
void close();
-
+
ResultSet executeQuery(@NotNull Statement statement, String sql) throws Exception;
-
+
int executeUpdate(@NotNull Statement statement, String sql) throws Exception;
-
+
ResultSet execute(@NotNull Statement statement, String sql) throws Exception;
int getGeneratedSQLCount();
--
Gitee
From 8415910a52f6f96b5a7d7dea9bbde3e3499bb176 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Tue, 12 Jul 2022 03:10:36 +0800
Subject: [PATCH 019/355] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E7=89=88=E6=9C=AC?=
=?UTF-8?q?=E8=87=B3=205.1.5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 1 +
APIJSONORM/pom.xml | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index 204504dc..f4b1dd87 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,3 +34,4 @@ build/
### VS Code ###
.vscode/
+APIJSONORM/bin
diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml
index 3be04ade..d8b42da7 100644
--- a/APIJSONORM/pom.xml
+++ b/APIJSONORM/pom.xml
@@ -5,7 +5,7 @@
apijson.orm
apijson-orm
- 5.1.0-F2
+ 5.1.5
jar
APIJSONORM
--
Gitee
From f66b571ad0d84dfacd1dbd98151d9781304e5000 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Tue, 12 Jul 2022 18:57:15 +0800
Subject: [PATCH 020/355] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 17777f3a..89207e4e 100644
--- a/README.md
+++ b/README.md
@@ -174,7 +174,7 @@ https://github.com/Tencent/APIJSON/wiki
* **高质可靠代码** (代码严谨规范,商业分析软件源伞 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 年开源起已连续维护近 6 年,累计 2000+ Commits、80+ Releases,不断更新迭代中...)
![image](https://user-images.githubusercontent.com/5738175/167264836-9c5d8f8a-99e1-4e1e-9864-e8f906b8e704.png)
--
Gitee
From 3914cb6c4e8ee14e78b5109c85620cc652f68141 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Tue, 12 Jul 2022 18:57:55 +0800
Subject: [PATCH 021/355] Update README.md
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 89207e4e..ef0f18da 100644
--- a/README.md
+++ b/README.md
@@ -160,7 +160,7 @@ https://www.bilibili.com/video/BV1yv411p7Y4
前后端 关于接口的 开发、文档、联调 等 10 大痛点解析
https://github.com/Tencent/APIJSON/wiki
-* **解决十大痛点** (可帮前后端开发大幅提振开发效率、强力杜绝联调扯皮、巧妙规避文档缺陷、非常节省流量带宽等)
+* **解决十大痛点** (可帮前后端开发大幅提振开发效率、强力杜绝联调扯皮、巧妙规避文档缺陷、非常节省流量带宽)
* **开发提速很大** (CRUD 零代码热更新全自动,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上)
* **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告)
* **社区影响力大** (GitHub 1W+ Star 在 350W Java 项目中排名前 120,远超 FLAG, BAT 等国内外绝大部分开源项目)
@@ -169,7 +169,7 @@ https://github.com/Tencent/APIJSON/wiki
* **周边生态丰富** (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等)
* **文档视频齐全** (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等)
* **功能丰富强大** (增删改查、分页排序、分组聚合、各种条件、各种 JOIN、各种子查询、跨库连表 等零代码实现)
-* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动控制权限、自动校验参数、自动防 SQL 注入等)
+* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动控制权限、自动校验参数、自动防 SQL 注入)
* **灵活定制业务** (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理)
* **高质可靠代码** (代码严谨规范,商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 Bug 率低至 0.15%)
* **兼容各种项目** (协议不限 HTTP,与其它库无冲突,对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的示例)
--
Gitee
From f5f7f3db784d87287f6a68467b92887c6b6856fe Mon Sep 17 00:00:00 2001
From: huangcanjia
Date: Thu, 14 Jul 2022 10:31:48 +0800
Subject: [PATCH 022/355] =?UTF-8?q?fix:=20sql=20join=E7=BC=93=E5=AD=98?=
=?UTF-8?q?=E4=B8=A2=E5=A4=B1=E8=80=8C=E5=87=BA=E7=8E=B01+N=E6=9F=A5?=
=?UTF-8?q?=E8=AF=A2=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
在处理app join时,连接上sql join的结果缓存
- 降childMap放入appJoinChildMap中,一起缓存
---
APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
index 1dcbb754..31514f2e 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
@@ -592,6 +592,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
if (isHead == false) {
// @ APP JOIN 查询副表并缓存到 childMap <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Map> appJoinChildMap = new HashMap<>();
+ childMap.forEach((viceSql,item) -> appJoinChildMap.put(viceSql,Arrays.asList(item)));
executeAppJoin(config, resultList, appJoinChildMap);
// @ APP JOIN 查询副表并缓存到 childMap >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
--
Gitee
From 77db28269d7d8b04245f18c179a56c64d848d34f Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Thu, 14 Jul 2022 23:15:23 +0800
Subject: [PATCH 023/355] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20APP=20JOIN=20?=
=?UTF-8?q?=E4=B8=80=E5=AF=B9=E5=A4=9A=E6=97=B6=E5=AD=90=E6=95=B0=E7=BB=84?=
=?UTF-8?q?=E9=95=BF=E5=BA=A6=E8=B6=85=E8=BF=87=E9=A2=84=E8=AE=BE=E8=8C=83?=
=?UTF-8?q?=E5=9B=B4=EF=BC=9B=E6=8F=90=E5=8D=87=20APP=20JOIN=20=E4=B8=80?=
=?UTF-8?q?=E5=AF=B9=E5=A4=9A=E7=9A=84=E5=AD=90=E6=95=B0=E7=BB=84=E7=BC=93?=
=?UTF-8?q?=E5=AD=98=E6=80=A7=E8=83=BD=EF=BC=9B=E6=96=B0=E5=A2=9E=E5=AF=B9?=
=?UTF-8?q?=20Year,=20Month,=20DayOfWeek=20=E7=9A=84=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
APIJSONORM/pom.xml | 6 +-
.../java/apijson/orm/AbstractSQLExecutor.java | 80 ++++++++++++-------
2 files changed, 52 insertions(+), 34 deletions(-)
diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml
index d8b42da7..6da06a14 100644
--- a/APIJSONORM/pom.xml
+++ b/APIJSONORM/pom.xml
@@ -3,9 +3,9 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
- apijson.orm
- apijson-orm
- 5.1.5
+ com.github.Tencent
+ APIJSON
+ 5.1.6
jar
APIJSONORM
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
index c1a10276..3fa0603b 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
@@ -10,7 +10,7 @@ import java.rmi.ServerError;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
-import java.sql.Date;
+import java.util.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
@@ -20,7 +20,10 @@ import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
+import java.time.DayOfWeek;
import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.Year;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -548,7 +551,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
// 副表是按常规条件查询,缓存会导致其它同表同条件对象查询结果集为空 childMap.put(viceSql, new JSONObject()); // 缓存固定空数据,避免后续多余查询
}
else {
- curItem = (JSONObject) childMap.get(viceSql);
+ curItem = childMap.get(viceSql);
if (curItem == null) {
curItem = new JSONObject(true);
childMap.put(viceSql, curItem);
@@ -596,8 +599,8 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
if (isHead == false) {
// @ APP JOIN 查询副表并缓存到 childMap <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
- Map> appJoinChildMap = new HashMap<>();
- childMap.forEach((viceSql,item) -> appJoinChildMap.put(viceSql,Arrays.asList(item)));
+ Map> appJoinChildMap = new HashMap<>();
+ childMap.forEach((viceSql, item) -> appJoinChildMap.put(viceSql, Arrays.asList(item)));
executeAppJoin(config, resultList, appJoinChildMap);
// @ APP JOIN 查询副表并缓存到 childMap >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
@@ -643,16 +646,13 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
List joinList = config.getJoinList();
if (joinList != null) {
- SQLConfig jc;
- SQLConfig cc;
-
for (Join join : joinList) {
if (join.isAppJoin() == false) {
Log.i(TAG, "executeAppJoin for (Join j : joinList) >> j.isAppJoin() == false >> continue;");
continue;
}
- cc = join.getCacheConfig(); //这里用config改了getSQL后再还原很麻烦,所以提前给一个config2更好
+ SQLConfig cc = join.getCacheConfig(); //这里用config改了getSQL后再还原很麻烦,所以提前给一个config2更好
if (cc == null) {
if (Log.DEBUG) {
throw new NullPointerException("服务器内部错误, executeAppJoin cc == null ! 导致不能缓存 @ APP JOIN 的副表数据!");
@@ -660,7 +660,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
continue;
}
- jc = join.getJoinConfig();
+ SQLConfig jc = join.getJoinConfig();
List onList = join.getOnList();
int size = onList == null ? 0 : onList.size();
@@ -689,8 +689,8 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
}
// 替换为 "id{}": [userId1, userId2, userId3...]
- jc.putWhere(ok, null, false); // remove orginKey
- jc.putWhere(on.getKey() + "{}", targetValueList, true); // add orginKey{} }
+ jc.putWhere(ok, null, false); // remove originKey
+ jc.putWhere(on.getKey() + "{}", targetValueList, true); // add originKey{} }
}
}
@@ -728,6 +728,10 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime;
}
+ int childCount = cc.getCount();
+ int allChildCount = childCount*config.getCount(); // 所有分组子项数量总和
+ int count = 0;
+
int index = -1;
long startTime2 = System.currentTimeMillis();
@@ -735,18 +739,17 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
final int length = rsmd.getColumnCount();
sqlResultDuration += System.currentTimeMillis() - startTime2;
- JSONObject result;
- String cacheSql;
+ Map skipMap = new HashMap<>();
long lastCursorTime = System.currentTimeMillis();
- while (rs.next()) { //FIXME 同时有 @ APP JOIN 和 < 等 SQL JOIN 时,next = false 总是无法进入循环,导致缓存失效,可能是连接池或线程问题
+ while ((allChildCount <= 0 || count < allChildCount) && 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");
- result = new JSONObject(true);
+ JSONObject result = new JSONObject(true);
for (int i = 1; i <= length; i++) {
result = onPutColumn(jc, rs, rsmd, index, result, i, null, null);
@@ -757,24 +760,33 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
Log.d(TAG, "\n executeAppJoin while (rs.next()) { resultList.put( " + index + ", result); "
+ "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n");
- //缓存到 childMap
if (onList != null) {
- for (On on : onList) {
+ for (On on : onList) { // APP JOIN 应该有且只有一个 ON 条件
String ok = on.getOriginKey();
String vk = ok.substring(0, ok.length() - 1);
//TODO 兼容复杂关联
cc.putWhere(on.getKey(), result.get(on.getKey()), true);
}
}
- cacheSql = cc.getSQL(false);
- List results = childMap.get(cacheSql);
- if (results == null) {
- results = new ArrayList<>();
- childMap.put(cacheSql, results);
- }
- results.add(result);
- Log.d(TAG, ">>> executeAppJoin childMap.put('" + cacheSql + "', result); childMap.size() = " + childMap.size());
- }
+
+ String cacheSql = cc.getSQL(false);
+ List results = childMap.get(cacheSql);
+
+ if (results == null || skipMap.get(cacheSql) == null) { // 避免添加重复数据
+ results = new ArrayList<>(childCount);
+ childMap.put(cacheSql, results);
+ skipMap.put(cacheSql, Boolean.TRUE);
+ }
+
+ if (childCount <= 0 || results.size() < childCount) { // 避免超过子数组每页数量
+ // if (count == 1 && results.isEmpty() == false) { // 避免添加重复数据
+ // results.clear();
+ // }
+ results.add(result); //缓存到 childMap
+ count ++;
+ Log.d(TAG, ">>> executeAppJoin childMap.put('" + cacheSql + "', result); childMap.size() = " + childMap.size());
+ }
+ }
}
finally {
if (rs != null) {
@@ -910,14 +922,20 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
else if (value instanceof Timestamp) {
value = ((Timestamp) value).toString();
}
- else if (value instanceof Date) {
+ else if (value instanceof Date) { // java.sql.Date 和 java.sql.Time 都继承 java.util.Date
value = ((Date) value).toString();
}
- else if (value instanceof Time) {
- value = ((Time) value).toString();
+ else if (value instanceof LocalDateTime) {
+ value = ((LocalDateTime) value).toString();
+ }
+ else if (value instanceof Year) {
+ value = ((Year) value).getValue();
+ }
+ else if (value instanceof Month) {
+ value = ((Month) value).getValue();
}
- else if (value instanceof LocalDateTime) {
- value = ((LocalDateTime) value).toString();
+ else if (value instanceof DayOfWeek) {
+ value = ((DayOfWeek) value).getValue();
}
else if (value instanceof String && isJSONType(config, rsmd, columnIndex, lable)) { //json String
castToJson = true;
--
Gitee
From 1e7f38e322ddae409d590a49d8a366a24a8703a7 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 15 Jul 2022 01:57:27 +0800
Subject: [PATCH 024/355] =?UTF-8?q?=E5=A4=A7=E5=B9=85=E6=8F=90=E5=8D=87=20?=
=?UTF-8?q?APP=20JOIN=20=E4=B8=80=E5=AF=B9=E5=A4=9A=E6=97=B6=E5=AD=90?=
=?UTF-8?q?=E6=95=B0=E7=BB=84=E6=9F=A5=E8=AF=A2=E5=92=8C=E7=BC=93=E5=AD=98?=
=?UTF-8?q?=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 | 135 +++++++++++-------
.../src/main/java/apijson/orm/Join.java | 44 +++---
.../main/java/apijson/orm/SQLExecutor.java | 15 +-
3 files changed, 115 insertions(+), 79 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
index 3fa0603b..3b54dbeb 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
@@ -172,9 +172,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
long executedSQLStartTime = System.currentTimeMillis();
boolean isPrepared = config.isPrepared();
-
final String sql = config.getSQL(false);
-
config.setPrepared(isPrepared);
if (StringUtil.isEmpty(sql, true)) {
@@ -265,8 +263,9 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
Log.i(TAG, ">>> execute result = getCache('" + sql + "', " + position + ") = " + result);
if (result != null) {
cachedSQLCount ++;
- if (getCache(sql,config).size() > 1) {
- result.put(KEY_RAW_LIST, getCache(sql,config));
+ List cache = getCache(sql, config);
+ if (cache != null && cache.size() > 1) {
+ result.put(KEY_RAW_LIST, cache);
}
Log.d(TAG, "\n\n execute result != null >> return result;" + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n");
return result;
@@ -282,7 +281,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
}
break;
- default://OPTIONS, TRACE等
+ default: //OPTIONS, TRACE等
Log.e(TAG, "execute sql = " + sql + " ; method = " + config.getMethod() + " >> return null;");
return null;
}
@@ -663,47 +662,79 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
SQLConfig jc = join.getJoinConfig();
List onList = join.getOnList();
- int size = onList == null ? 0 : onList.size();
- if (size > 0) {
- for (int j = size - 1; j >= 0; j--) {
- On on = onList.get(j);
- String ok = on == null ? null : on.getOriginKey();
- if (ok == null) {
- throw new NullPointerException("服务器内部错误,List 中 Join.onList[" + j + (on == null ? "] = null!" : ".getOriginKey() = null!"));
- }
-
- // 取出 "id@": "@/User/userId" 中所有 userId 的值
- List targetValueList = new ArrayList<>();
-
- for (int i = 0; i < resultList.size(); i++) {
- JSONObject mainTable = resultList.get(i);
- Object targetValue = mainTable == null ? null : mainTable.get(on.getTargetKey());
+ On on = onList == null || onList.isEmpty() ? null : onList.get(0); // APP JOIN 应该有且只有一个 ON 条件
+ String originKey = on == null ? null : on.getOriginKey();
+ if (originKey == null) {
+ throw new NullPointerException("服务器内部错误,List 中 Join.onList[0" + (on == null ? "] = null!" : ".getOriginKey() = null!"));
+ }
+ String key = on.getKey();
+ if (key == null) {
+ throw new NullPointerException("服务器内部错误,List 中 Join.onList[0" + (on == null ? "] = null!" : ".getKey() = null!"));
+ }
- if (targetValue != null && targetValueList.contains(targetValue) == false) {
- targetValueList.add(targetValue);
- }
- }
+ // 取出 "id@": "@/User/userId" 中所有 userId 的值
+ List targetValueList = new ArrayList<>();
- if (targetValueList.isEmpty() && config.isExplain() == false) {
- throw new NotExistException("targetValueList.isEmpty() && config.isExplain() == false");
- }
+ for (int i = 0; i < resultList.size(); i++) {
+ JSONObject mainTable = resultList.get(i);
+ Object targetValue = mainTable == null ? null : mainTable.get(on.getTargetKey());
- // 替换为 "id{}": [userId1, userId2, userId3...]
- jc.putWhere(ok, null, false); // remove originKey
- jc.putWhere(on.getKey() + "{}", targetValueList, true); // add originKey{} }
+ if (targetValue != null && targetValueList.contains(targetValue) == false) {
+ targetValueList.add(targetValue);
}
}
+ if (targetValueList.isEmpty() && config.isExplain() == false) {
+ throw new NotExistException("targetValueList.isEmpty() && config.isExplain() == false");
+ }
+
+ // 替换为 "id{}": [userId1, userId2, userId3...]
+ jc.putWhere(originKey, null, false); // remove originKey
+ jc.putWhere(key + "{}", targetValueList, true); // add originKey{} }
+
jc.setMain(true).setPreparedValueList(new ArrayList<>());
+ // 放一块逻辑更清晰,也避免解析 * 等不支持或性能开销
+// String q = jc.getQuote();
+// if (allChildCount > 0 && jc.getCount() <= 0) {
+// List column = jc.getColumn();
+// if (column == null || column.isEmpty()) {
+// column = Arrays.asList("*;row_number()OVER(PARTITION BY " + q + key + q + " ORDER BY " + q + key + q + " ASC):_row_num_");
+// }
+// else {
+// column.add("row_number()OVER(PARTITION BY " + q + key + q + " ORDER BY " + q + key + q + " ASC):_row_num_");
+// }
+// jc.setColumn(column);
+// }
+
boolean prepared = jc.isPrepared();
- final String sql = jc.getSQL(false);
+ String sql = jc.getSQL(false);
jc.setPrepared(prepared);
if (StringUtil.isEmpty(sql, true)) {
throw new NullPointerException(TAG + ".executeAppJoin StringUtil.isEmpty(sql, true) >> return null;");
}
+ int childCount = cc.getCount();
+ int allChildCount = childCount*config.getCount(); // 所有分组子项数量总和
+
+ String sql2 = null;
+ if (childCount > 0 && (childCount != 1 || join.isOne2Many())) { // TODO 判断 MySQL >= 8.0
+ String q = jc.getQuote();
+ sql2 = prepared ? jc.getSQL(true) : sql;
+
+ String prefix = "SELECT * FROM(";
+ String rnStr = ", row_number() OVER (PARTITION BY " + q + key + q + ((AbstractSQLConfig) jc).getOrderString(true) + ") _row_num_ FROM ";
+ String suffix = ") _t WHERE ( (_row_num_ <= " + childCount + ") ) LIMIT " + allChildCount;
+
+ sql2 = prefix
+ // 放一块逻辑更清晰,也避免解析 * 等不支持或性能开销 + sql
+ + sql2.replaceFirst(" FROM ", rnStr) // * 居然只能放在 row_number() 前面,放后面就报错 "SELECT ", rnStr)
+ + suffix;
+
+ sql = prepared ? (prefix + sql.replaceFirst(" FROM ", rnStr) + suffix) : sql2;
+ }
+
boolean isExplain = jc.isExplain();
if (isExplain == false) {
generatedSQLCount ++;
@@ -723,13 +754,11 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
executedSQLCount ++;
executedSQLStartTime = System.currentTimeMillis();
}
- rs = executeQuery(jc);
+ rs = executeQuery(jc, sql2);
if (isExplain == false) {
executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime;
}
- int childCount = cc.getCount();
- int allChildCount = childCount*config.getCount(); // 所有分组子项数量总和
int count = 0;
int index = -1;
@@ -760,15 +789,8 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
Log.d(TAG, "\n executeAppJoin while (rs.next()) { resultList.put( " + index + ", result); "
+ "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n");
- if (onList != null) {
- for (On on : onList) { // APP JOIN 应该有且只有一个 ON 条件
- String ok = on.getOriginKey();
- String vk = ok.substring(0, ok.length() - 1);
- //TODO 兼容复杂关联
- cc.putWhere(on.getKey(), result.get(on.getKey()), true);
- }
- }
-
+ //TODO 兼容复杂关联
+ cc.putWhere(key, result.get(key), true); // APP JOIN 应该有且只有一个 ON 条件
String cacheSql = cc.getSQL(false);
List results = childMap.get(cacheSql);
@@ -1010,22 +1032,25 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
- /**
- * @param config
- * @return
- * @throws Exception
- */
- @Override
+ @Override // 重写是为了返回类型从 Statement 改为 PreparedStatement,避免其它方法出错
public PreparedStatement getStatement(@NotNull SQLConfig config) throws Exception {
+ return getStatement(config, null);
+ }
+ @Override
+ public PreparedStatement getStatement(@NotNull SQLConfig config, String sql) throws Exception {
+ if (StringUtil.isEmpty(sql)) {
+ sql = config.getSQL(config.isPrepared());
+ }
+
PreparedStatement statement; //创建Statement对象
if (config.getMethod() == RequestMethod.POST && config.getId() == null) { //自增id
- statement = getConnection(config).prepareStatement(config.getSQL(config.isPrepared()), Statement.RETURN_GENERATED_KEYS);
+ statement = getConnection(config).prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
}
else if (RequestMethod.isGetMethod(config.getMethod(), true)) {
- statement = getConnection(config).prepareStatement(config.getSQL(config.isPrepared()), ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
+ statement = getConnection(config).prepareStatement(sql, ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
}
else {
- statement = getConnection(config).prepareStatement(config.getSQL(config.isPrepared()));
+ statement = getConnection(config).prepareStatement(sql);
}
List valueList = config.isPrepared() ? config.getPreparedValueList() : null;
@@ -1162,8 +1187,8 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
}
@Override
- public ResultSet executeQuery(@NotNull SQLConfig config) throws Exception {
- PreparedStatement stt = getStatement(config);
+ public ResultSet executeQuery(@NotNull SQLConfig config, String sql) throws Exception {
+ PreparedStatement stt = getStatement(config, sql);
ResultSet rs = stt.executeQuery(); //PreparedStatement 不用传 SQL
// if (config.isExplain() && (config.isSQLServer() || config.isOracle())) {
// FIXME 返回的是 boolean 值 rs = stt.getMoreResults(Statement.CLOSE_CURRENT_RESULT);
@@ -1173,7 +1198,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
}
@Override
- public int executeUpdate(@NotNull SQLConfig config) throws Exception {
+ public int executeUpdate(@NotNull SQLConfig config, String sql) throws Exception {
PreparedStatement stt = getStatement(config);
int count = stt.executeUpdate(); // PreparedStatement 不用传 SQL
diff --git a/APIJSONORM/src/main/java/apijson/orm/Join.java b/APIJSONORM/src/main/java/apijson/orm/Join.java
index af3c2979..3e55637a 100644
--- a/APIJSONORM/src/main/java/apijson/orm/Join.java
+++ b/APIJSONORM/src/main/java/apijson/orm/Join.java
@@ -16,15 +16,15 @@ import apijson.StringUtil;
* @author Lemon
*/
public class Join {
-
- private String path;
+
+ private String path; // /User/id@
private String joinType; // "@" - APP, "<" - LEFT, ">" - RIGHT, "*" - CROSS, "&" - INNER, "|" - FULL, "!" - OUTER, "^" - SIDE, "(" - ANTI, ")" - FOREIGN
private String table; // User
private String alias; // owner
private int count = 1; // 当app join子表,需要返回子表的行数,默认1行;
private List onList; // ON User.id = Moment.userId AND ...
-
+
private JSONObject request; // { "id@":"/Moment/userId" }
private JSONObject outer; // "join": { " onList) {
this.onList = onList;
}
-
+
public JSONObject getRequest() {
return request;
}
@@ -105,6 +105,12 @@ public class Join {
this.outerConfig = outerConfig;
}
+ public boolean isOne2One() {
+ return ! isOne2Many();
+ }
+ public boolean isOne2Many() {
+ return path != null && path.contains("[]"); // TODO 必须保证一对一时不会传包含 [] 的 path
+ }
public boolean isAppJoin() {
return "@".equals(getJoinType());
@@ -165,7 +171,7 @@ public class Join {
return j != null && j.isLeftOrRightJoin();
}
-
+
public static class On {
@@ -230,7 +236,7 @@ public class Join {
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("@")) {
@@ -241,7 +247,7 @@ public class Join {
}
String k;
-
+
if (originKey.endsWith("{}")) {
setRelateType("{}");
k = originKey.substring(0, originKey.length() - 2);
@@ -253,18 +259,18 @@ public class Join {
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);
}
@@ -272,7 +278,7 @@ public class Join {
throw new IllegalArgumentException(originKey + ":value 中字符 " + originKey + " 不合法!key$:value 中不允许只有单独的 '?',必须和 '%', '_' 之一配合使用 !");
}
}
-
+
setRelateType(t);
}
else if (originKey.endsWith("~")) {
@@ -300,27 +306,23 @@ public class Join {
setRelateType("");
k = originKey;
}
-
+
if (k != null && (k.contains("&") || k.contains("|"))) {
throw new UnsupportedOperationException(joinType + "/.../" + table + "/" + originKey + " 中字符 " + k + " 不合法!与或非逻辑符仅支持 '!' 非逻辑符 !");
}
-
+
//TODO if (c3 == '-') { // 表示 key 和 value 顺序反过来: value LIKE key
-
+
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/SQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java
index 25bfa467..ef437b6f 100755
--- a/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java
@@ -64,14 +64,20 @@ public interface SQLExecutor {
* @return
* @throws SQLException
*/
- ResultSet executeQuery(@NotNull SQLConfig config) throws Exception;
+ default ResultSet executeQuery(@NotNull SQLConfig config) throws Exception {
+ return executeQuery(config, null);
+ }
+ ResultSet executeQuery(@NotNull SQLConfig config, String sql) throws Exception;
/**执行增、删、改
* @param config
* @return
* @throws SQLException
*/
- int executeUpdate(@NotNull SQLConfig config) throws Exception;
+ default int executeUpdate(@NotNull SQLConfig config) throws Exception {
+ return executeUpdate(config, null);
+ }
+ int executeUpdate(@NotNull SQLConfig config, String sql) throws Exception;
/**判断是否为JSON类型
@@ -85,7 +91,10 @@ public interface SQLExecutor {
Connection getConnection(@NotNull SQLConfig config) throws Exception;
- Statement getStatement(@NotNull SQLConfig config) throws Exception;
+ default Statement getStatement(@NotNull SQLConfig config) throws Exception {
+ return getStatement(config, null);
+ }
+ Statement getStatement(@NotNull SQLConfig config, String sql) throws Exception;
int getTransactionIsolation();
void setTransactionIsolation(int transactionIsolation);
--
Gitee
From 4efcfd3c172729c79e542f699a1c24432b2e5b1c Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 15 Jul 2022 02:34:16 +0800
Subject: [PATCH 025/355] =?UTF-8?q?=E4=BC=98=E5=8C=96=20Join.isOne2Many=20?=
=?UTF-8?q?=E5=88=A4=E6=96=AD=E6=80=A7=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
APIJSONORM/src/main/java/apijson/orm/Join.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/Join.java b/APIJSONORM/src/main/java/apijson/orm/Join.java
index 3e55637a..65127509 100644
--- a/APIJSONORM/src/main/java/apijson/orm/Join.java
+++ b/APIJSONORM/src/main/java/apijson/orm/Join.java
@@ -109,7 +109,7 @@ public class Join {
return ! isOne2Many();
}
public boolean isOne2Many() {
- return path != null && path.contains("[]"); // TODO 必须保证一对一时不会传包含 [] 的 path
+ return count != 1 || (path != null && path.contains("[]")); // TODO 必须保证一对一时不会传包含 [] 的 path
}
public boolean isAppJoin() {
--
Gitee
From e2fb0a1066205c4c191a232e361f3e713a3a487c Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 15 Jul 2022 03:16:39 +0800
Subject: [PATCH 026/355] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=95=B0=E6=8D=AE?=
=?UTF-8?q?=E5=BA=93=E7=89=88=E6=9C=AC=E5=88=A4=E6=96=AD=EF=BC=8C=E5=85=BC?=
=?UTF-8?q?=E5=AE=B9=20MySQL=208.0=20=E4=BB=A5=E4=B8=8A=E5=92=8C=E4=BB=A5?=
=?UTF-8?q?=E4=B8=8B=E7=89=88=E6=9C=AC=EF=BC=8C=E9=80=82=E9=85=8D=E6=AD=A3?=
=?UTF-8?q?=E5=88=99=E5=8C=B9=E9=85=8D=E3=80=81=E7=AA=97=E5=8F=A3=E5=87=BD?=
=?UTF-8?q?=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 | 16 +++++--
.../java/apijson/orm/AbstractSQLExecutor.java | 2 +-
.../src/main/java/apijson/orm/SQLConfig.java | 44 ++++++++++++++-----
3 files changed, 47 insertions(+), 15 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
index b33ae695..a7a7cdca 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
@@ -728,8 +728,16 @@ public abstract class AbstractSQLConfig implements SQLConfig {
}
+ private int[] dbVersionNums = null;
+ @Override
+ public int[] getDBVersionNums() {
+ if (dbVersionNums == null || dbVersionNums.length <= 0) {
+ dbVersionNums = SQLConfig.super.getDBVersionNums();
+ }
+ return dbVersionNums;
+ }
- @Override
+ @Override
public boolean limitSQLCount() {
return Log.DEBUG == false || AbstractVerifier.SYSTEM_ACCESS_MAP.containsKey(getTable()) == false;
}
@@ -3412,7 +3420,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
if (isPostgreSQL()) {
return getKey(column) + " ~" + (ignoreCase ? "* " : " ") + getValue(key, column, value);
}
- if (isOracle()) {
+ if (isOracle() || (isMySQL() && getDBVersionNums()[0] >= 8)) {
return "regexp_like(" + getKey(column) + ", " + getValue(key, column, value) + (ignoreCase ? ", 'i'" : ", 'c'") + ")";
}
if (isClickHouse()) {
@@ -3425,7 +3433,9 @@ public abstract class AbstractSQLConfig implements SQLConfig {
}
return getKey(column) + " REGEXP " + (ignoreCase ? "" : "BINARY ") + getValue(key, column, value);
}
- //~ regexp >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+
+ //~ regexp >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
index 3b54dbeb..77b48f48 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
@@ -719,7 +719,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
int allChildCount = childCount*config.getCount(); // 所有分组子项数量总和
String sql2 = null;
- if (childCount > 0 && (childCount != 1 || join.isOne2Many())) { // TODO 判断 MySQL >= 8.0
+ if (childCount > 0 && (childCount != 1 || join.isOne2Many()) && (jc.isMySQL() == false || jc.getDBVersionNums()[0] >= 8)) {
String q = jc.getQuote();
sql2 = prepared ? jc.getSQL(true) : sql;
diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java
index 4db5a752..e6c8712b 100755
--- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java
@@ -10,6 +10,7 @@ import java.util.Map;
import apijson.NotNull;
import apijson.RequestMethod;
+import apijson.StringUtil;
/**SQL配置
* @author Lemon
@@ -46,8 +47,8 @@ public interface SQLConfig {
// boolean isPLSQL();
// boolean isAnsiSQL();
- boolean limitSQLCount(); //用来给 Table, Column 等系统属性表来绕过 MAX_SQL_COUNT 等限制
-
+ boolean limitSQLCount(); //用来给 Table, Column 等系统属性表来绕过 MAX_SQL_COUNT 等限制
+
@NotNull
String getIdKey();
@NotNull
@@ -60,6 +61,27 @@ public interface SQLConfig {
*/
String getDBVersion();
+ @NotNull
+ default int[] getDBVersionNums() {
+ String dbVersion = StringUtil.getNoBlankString(getDBVersion());
+ if (dbVersion.isEmpty()) {
+ return new int[]{0};
+ }
+
+ int index = dbVersion.indexOf("-");
+ if (index > 0) {
+ dbVersion = dbVersion.substring(0, index);
+ }
+
+ String[] ss = dbVersion.split("[.]");
+ int[] nums = new int[Math.max(1, ss.length)];
+ for (int i = 0; i < ss.length; i++) {
+ nums[i] = Integer.valueOf(ss[i]);
+ }
+
+ return nums;
+ }
+
/**获取数据库地址
* @return
*/
@@ -116,10 +138,10 @@ public interface SQLConfig {
Object getId();
SQLConfig setId(Object id);
-
+
Object getIdIn();
SQLConfig setIdIn(Object idIn);
-
+
Object getUserId();
SQLConfig setUserId(Object userId);
@@ -137,7 +159,7 @@ public interface SQLConfig {
String getSchema();
SQLConfig setSchema(String schema);
-
+
String getDatasource();
SQLConfig setDatasource(String datasource);
@@ -145,13 +167,13 @@ public interface SQLConfig {
List getJson();
SQLConfig setJson(List json);
-
+
/**请求传进来的Table名
* @return
* @see {@link #getSQLTable()}
*/
String getTable();
-
+
SQLConfig setTable(String table);
/**数据库里的真实Table名
@@ -182,13 +204,13 @@ public interface SQLConfig {
String getCombine();
SQLConfig setCombine(String combine);
-
+
Map getCast();
SQLConfig setCast(Map cast);
-
+
List getNull();
SQLConfig setNull(List nulls);
-
+
Map getWhere();
SQLConfig setWhere(Map where);
@@ -197,7 +219,7 @@ public interface SQLConfig {
Map getHaving();
SQLConfig setHaving(Map having);
-
+
String getHavingCombine();
SQLConfig setHavingCombine(String havingCombine);
--
Gitee
From 880694841e38fcdbbecc27942a569950a0643c50 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 15 Jul 2022 03:46:35 +0800
Subject: [PATCH 027/355] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=B8=80=E5=AF=B9?=
=?UTF-8?q?=E5=A4=9A=20APP=20JOIN=20=E7=9A=84=E7=BC=93=E5=AD=98=E9=80=BB?=
=?UTF-8?q?=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../java/apijson/orm/AbstractSQLExecutor.java | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
index 77b48f48..4a040c24 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
@@ -705,6 +705,14 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
// column.add("row_number()OVER(PARTITION BY " + q + key + q + " ORDER BY " + q + key + q + " ASC):_row_num_");
// }
// jc.setColumn(column);
+// }
+
+ int childCount = cc.getCount();
+ int allChildCount = childCount*config.getCount(); // 所有分组子项数量总和
+ boolean isOne2Many = childCount != 1 || join.isOne2Many();
+ // 一对多会漏副表数据 TODO 似乎一对一走以下优化 row_number() <= childCount 逻辑也没问题
+// if (isOne2Many == false && allChildCount > 0 && jc.getCount() < allChildCount) {
+// jc.setCount(allChildCount);
// }
boolean prepared = jc.isPrepared();
@@ -715,17 +723,14 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
throw new NullPointerException(TAG + ".executeAppJoin StringUtil.isEmpty(sql, true) >> return null;");
}
- int childCount = cc.getCount();
- int allChildCount = childCount*config.getCount(); // 所有分组子项数量总和
-
String sql2 = null;
- if (childCount > 0 && (childCount != 1 || join.isOne2Many()) && (jc.isMySQL() == false || jc.getDBVersionNums()[0] >= 8)) {
+ if (childCount > 0 && isOne2Many && (jc.isMySQL() == false || jc.getDBVersionNums()[0] >= 8)) {
String q = jc.getQuote();
sql2 = prepared ? jc.getSQL(true) : sql;
String prefix = "SELECT * FROM(";
String rnStr = ", row_number() OVER (PARTITION BY " + q + key + q + ((AbstractSQLConfig) jc).getOrderString(true) + ") _row_num_ FROM ";
- String suffix = ") _t WHERE ( (_row_num_ <= " + childCount + ") ) LIMIT " + allChildCount;
+ String suffix = ") _t WHERE ( (_row_num_ <= " + childCount + ") )" + (allChildCount > 0 ? " LIMIT " + allChildCount : "");
sql2 = prefix
// 放一块逻辑更清晰,也避免解析 * 等不支持或性能开销 + sql
@@ -786,7 +791,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
//每个 result 都要用新的 SQL 来存 childResultMap = onPutTable(config, rs, rsmd, childResultMap, index, result);
- Log.d(TAG, "\n executeAppJoin while (rs.next()) { resultList.put( " + index + ", result); "
+ Log.d(TAG, "\n executeAppJoin while (rs.next()) { resultList.put(" + index + ", result); "
+ "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n");
//TODO 兼容复杂关联
--
Gitee
From f224c5d81f6daec4005abe559d6e99a7d8c6e5d1 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 15 Jul 2022 04:03:31 +0800
Subject: [PATCH 028/355] =?UTF-8?q?=E8=AE=B0=E5=BD=95=20SQL=20JOIN=20?=
=?UTF-8?q?=E5=89=AF=E8=A1=A8=E7=9A=84=E8=81=9A=E5=90=88=E5=87=BD=E6=95=B0?=
=?UTF-8?q?=E7=BB=93=E6=9E=9C=E5=92=8C=E4=B9=8B=E5=89=8D=E4=B8=8D=E4=B8=80?=
=?UTF-8?q?=E8=87=B4=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../java/apijson/orm/AbstractSQLExecutor.java | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
index 4a040c24..321b38b4 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
@@ -725,6 +725,24 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
String sql2 = null;
if (childCount > 0 && isOne2Many && (jc.isMySQL() == false || jc.getDBVersionNums()[0] >= 8)) {
+// 加 row_number 字段并不会导致 count 等聚合函数统计出错,结果偏大,SQL JOIN 才会,之前没发现是因为缓存失效 bug
+// boolean noAggrFun = true;
+// List column = jc.getColumn();
+// if (column != null) {
+// for (String c : column) {
+// int start = c == null ? -1 : c.indexOf("(");
+// int end = start <= 0 ? -1 : c.lastIndexOf(")");
+// if (start > 0 && end > start) {
+// String fun = c.substring(0, start);
+// if (AbstractSQLConfig.SQL_AGGREGATE_FUNCTION_MAP.containsKey(fun)) {
+// noAggrFun = false;
+// break;
+// }
+// }
+// }
+// }
+//
+// if (noAggrFun) { // 加 row_number 字段会导致 count 等聚合函数统计出错,结果偏大?
String q = jc.getQuote();
sql2 = prepared ? jc.getSQL(true) : sql;
@@ -738,6 +756,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
+ suffix;
sql = prepared ? (prefix + sql.replaceFirst(" FROM ", rnStr) + suffix) : sql2;
+// }
}
boolean isExplain = jc.isExplain();
--
Gitee
From 38482654020f4ac67281d76a988124227e2336b2 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 15 Jul 2022 13:55:39 +0800
Subject: [PATCH 029/355] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index ef0f18da..7258bcff 100644
--- a/README.md
+++ b/README.md
@@ -68,7 +68,7 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这
#### 对于后端
* 提供万能通用接口,大部分 HTTP API 不用再写
-* 零代码增删改查、各种跨库连表、多层嵌套子查询等
+* 零代码增删改查、各种跨库连表、JOIN 嵌套子查询等
* 自动生成文档,不用再编写和维护,且自动静态检查
* 自动校验权限、自动管理版本、自动防 SQL 注入
* 开放 HTTP API 无需划分版本,始终保持兼容
--
Gitee
From 7937d4c8e2b344cddb220f7db6d18cb53da85aa8 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 15 Jul 2022 18:47:43 +0800
Subject: [PATCH 030/355] Update README.md
---
README.md | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index 7258bcff..a95dabfa 100644
--- a/README.md
+++ b/README.md
@@ -414,14 +414,17 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
QQ 技术群 : 734652054(新)、607020115(旧)
**开发者时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,**
-**不解决 文档/视频/常见问题 已明确说明、描述简陋 或 态度无礼 的问题!**
+**不解决 文档/视频/常见问题 已明确说明、描述简陋 或 态度无礼 的问题!**
+**如果你已经多次得到帮助,却仍然只索取不贡献,那就别指望大家再帮你!**
-如果你 [登记了自己使用 APIJSON 的公司](https://github.com/Tencent/APIJSON/issues/187),可以加专门的 企业用户支持群,作者亲自答疑。
+如果你 [登记了自己使用 APIJSON 的公司](https://github.com/Tencent/APIJSON/issues/187),可以加专门的 **企业用户支持群**,作者亲自且优先答疑,
+只有解答了这个群里的全部问题,才看情况解答其它群里的问题(其它群不保证解答、更不保证及时)。
如果你为 APIJSON 做出了以下任何一个贡献:
[提交了 PR 且被合并](https://github.com/Tencent/APIJSON/pull/92)、[提交了优质 Issue](https://github.com/Tencent/APIJSON/issues/189)、[发表了优质文章](https://blog.csdn.net/qq_41829492/article/details/88670940)、[开发了可用的生态项目](https://github.com/zhangchunlin/uliweb-apijson),
-可以加 贡献者交流群,注意联系 LonelyExplorer,加好友描述中附上贡献链接,谢谢
-
+可以在群里发出贡献链接并附带说明,管理员将设置关注你一段时间,优先答疑解惑。
+一般解答顺序:贡献者 > 帮助他人的群友 > 带企业名昵称 > 带岗位名昵称 > 其他群友。
+
### 相关推荐
[APIJSON, 让接口和文档见鬼去吧!](https://my.oschina.net/tommylemon/blog/805459)
--
Gitee
From 3f5245f475026ff964c5aea66ae1cc24a4f3b57a Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 15 Jul 2022 22:35:35 +0800
Subject: [PATCH 031/355] Update README.md
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index a95dabfa..2de4a80c 100644
--- a/README.md
+++ b/README.md
@@ -252,7 +252,7 @@ https://github.com/Tencent/APIJSON/issues/36
使用 [Apache License 2.0](/LICENSE),对 公司、团队、个人 等 商用、非商用 都自由免费且非常友好,请放心使用和登记
### 使用登记
-如果您在使用 APIJSON,请让我们知道,您的使用对我们非常重要(按登记顺序排列):
+如果您在使用 APIJSON,请让我们知道,您的使用对我们非常重要(新的按登记顺序排列、专群优先答疑解惑):
https://github.com/Tencent/APIJSON/issues/187
@@ -417,7 +417,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
**不解决 文档/视频/常见问题 已明确说明、描述简陋 或 态度无礼 的问题!**
**如果你已经多次得到帮助,却仍然只索取不贡献,那就别指望大家再帮你!**
-如果你 [登记了自己使用 APIJSON 的公司](https://github.com/Tencent/APIJSON/issues/187),可以加专门的 **企业用户支持群**,作者亲自且优先答疑,
+如果你 [提 PR 登记了自己使用 APIJSON 的公司](https://github.com/Tencent/APIJSON/issues/187),可以加 **企业用户支持群**,作者亲自且优先答疑,
只有解答了这个群里的全部问题,才看情况解答其它群里的问题(其它群不保证解答、更不保证及时)。
如果你为 APIJSON 做出了以下任何一个贡献:
--
Gitee
From 3f748f1abd61aa8b3f58b2247d5e81077c18d2f9 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Fri, 15 Jul 2022 22:45:36 +0800
Subject: [PATCH 032/355] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 2de4a80c..d30d3a66 100644
--- a/README.md
+++ b/README.md
@@ -423,7 +423,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
如果你为 APIJSON 做出了以下任何一个贡献:
[提交了 PR 且被合并](https://github.com/Tencent/APIJSON/pull/92)、[提交了优质 Issue](https://github.com/Tencent/APIJSON/issues/189)、[发表了优质文章](https://blog.csdn.net/qq_41829492/article/details/88670940)、[开发了可用的生态项目](https://github.com/zhangchunlin/uliweb-apijson),
可以在群里发出贡献链接并附带说明,管理员将设置关注你一段时间,优先答疑解惑。
-一般解答顺序:贡献者 > 帮助他人的群友 > 带企业名昵称 > 带岗位名昵称 > 其他群友。
+其它群一般解答顺序:贡献者 > 帮助他人的群友 > 带企业名昵称 > 带岗位名昵称 > 其他群友。
### 相关推荐
--
Gitee
From 4886ee923659826ff980cbd55ab36e5adf570d63 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Sat, 16 Jul 2022 04:49:58 +0800
Subject: [PATCH 033/355] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81?=
=?UTF-8?q?=E7=89=A9=E8=81=94=E7=BD=91=E6=97=B6=E5=BA=8F=E6=95=B0=E6=8D=AE?=
=?UTF-8?q?=E5=BA=93=20TDengine?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../java/apijson/orm/AbstractSQLConfig.java | 10 +++++++-
.../java/apijson/orm/AbstractSQLExecutor.java | 25 ++++++++++++++++---
.../src/main/java/apijson/orm/SQLConfig.java | 14 +++++++++--
3 files changed, 42 insertions(+), 7 deletions(-)
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
index a7a7cdca..0dfe0e34 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
@@ -148,6 +148,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
DATABASE_LIST.add(DATABASE_DB2);
DATABASE_LIST.add(DATABASE_CLICKHOUSE);
DATABASE_LIST.add(DATABASE_HIVE);
+ DATABASE_LIST.add(DATABASE_TDENGINE);
RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况
@@ -726,6 +727,13 @@ public abstract class AbstractSQLConfig implements SQLConfig {
SQL_FUNCTION_MAP.put("bitAnd", ""); //bitAnd(a,b)
SQL_FUNCTION_MAP.put("bitOr", ""); //bitOr(a,b)
+ // PostgreSQL 表结构相关 SQL 函数
+ SQL_FUNCTION_MAP.put("obj_description", "");
+ SQL_FUNCTION_MAP.put("col_description", "");
+
+ // SQLServer 相关 SQL 函数
+ SQL_FUNCTION_MAP.put("datalength", "");
+
}
private int[] dbVersionNums = null;
@@ -991,7 +999,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
@Override
public String getQuote() {
- return isMySQL()||isClickHouse() ? "`" : "\"";
+ return isMySQL() || isClickHouse() || isTDengine() ? "`" : "\"";
}
@Override
diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
index 321b38b4..02cbad74 100755
--- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
+++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
@@ -1114,7 +1114,8 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
connectionMap.put(connectionKey, connection);
}
- int ti = getTransactionIsolation();
+ // TDengine 驱动内部事务处理方法都是空实现,手动 commit 无效
+ int ti = config.isTDengine() ? Connection.TRANSACTION_NONE : getTransactionIsolation();
if (ti != Connection.TRANSACTION_NONE) { //java.sql.SQLException: Transaction isolation level NONE not supported by MySQL
begin(ti);
}
@@ -1143,7 +1144,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
// }
if (! isIsolationStatusSet) { //只设置一次Isolation等级 PG重复设置事务等级会报错
isIsolationStatusSet = true;
- connection.setTransactionIsolation(transactionIsolation);
+ connection.setTransactionIsolation(transactionIsolation); // 这句导致 TDengine 驱动报错
}
connection.setAutoCommit(false); //java.sql.SQLException: Can''t call commit when autocommit=true
}
@@ -1212,6 +1213,12 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
@Override
public ResultSet executeQuery(@NotNull SQLConfig config, String sql) throws Exception {
+ if (config.isTDengine()) {
+ Connection conn = getConnection(config);
+ Statement stt = conn.createStatement();
+ return executeQuery(stt, StringUtil.isEmpty(sql) ? config.getSQL(false) : sql);
+ }
+
PreparedStatement stt = getStatement(config, sql);
ResultSet rs = stt.executeQuery(); //PreparedStatement 不用传 SQL
// if (config.isExplain() && (config.isSQLServer() || config.isOracle())) {
@@ -1221,10 +1228,20 @@ public abstract class AbstractSQLExecutor implements SQLExecutor {
return rs;
}
+
@Override
public int executeUpdate(@NotNull SQLConfig config, String sql) throws Exception {
- PreparedStatement stt = getStatement(config);
- int count = stt.executeUpdate(); // PreparedStatement 不用传 SQL
+ Statement stt;
+ int count;
+ if (config.isTDengine()) {
+ Connection conn = getConnection(config);
+ stt = conn.createStatement();
+ count = stt.executeUpdate(StringUtil.isEmpty(sql) ? config.getSQL(false) : sql);
+ }
+ else {
+ stt = getStatement(config);
+ count = ((PreparedStatement) stt).executeUpdate(); // PreparedStatement 不用传 SQL
+ }
if (count <= 0 && config.isHive()) {
count = 1;
diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java
index e6c8712b..2e5c322a 100755
--- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java
+++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java
@@ -24,6 +24,7 @@ public interface SQLConfig {
String DATABASE_DB2 = "DB2";
String DATABASE_CLICKHOUSE = "CLICKHOUSE";
String DATABASE_HIVE = "HIVE";
+ String DATABASE_TDENGINE = "TDENGINE";
String SCHEMA_INFORMATION = "information_schema"; //MySQL, PostgreSQL, SQL Server 都有的系统模式
String SCHEMA_SYS = "sys"; //SQL Server 系统模式
@@ -41,7 +42,15 @@ public interface SQLConfig {
boolean isDb2();
boolean isClickHouse();
boolean isHive();
- //暂时只兼容以上 5 种
+ default boolean isTDengine() {
+ return isTDengine(getDatabase());
+ }
+
+ static boolean isTDengine(String db) {
+ return DATABASE_TDENGINE.equals(db);
+ }
+
+ //暂时只兼容以上几种
// boolean isSQL();
// boolean isTSQL();
// boolean isPLSQL();
@@ -177,7 +186,7 @@ public interface SQLConfig {
SQLConfig setTable(String table);
/**数据库里的真实Table名
- * 通过 {@link #TABLE_KEY_MAP} 映射
+ * 通过 {@link AbstractSQLConfig.TABLE_KEY_MAP} 映射
* @return
*/
String getSQLTable();
@@ -285,4 +294,5 @@ public interface SQLConfig {
SQLConfig setProcedure(String procedure);
+
}
--
Gitee
From 89c561f4a43789f52528a361542cec3cf879ac6e Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Sat, 16 Jul 2022 06:44:17 +0800
Subject: [PATCH 034/355] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81?=
=?UTF-8?q?=E7=89=A9=E8=81=94=E7=BD=91=E6=97=B6=E5=BA=8F=E6=95=B0=E6=8D=AE?=
=?UTF-8?q?=E5=BA=93=20TDengine?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoSQLConfig.java#L186-L190
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index d30d3a66..2f07d0ba 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,7 @@ This source code is licensed under the Apache License Version 2.0
+
--
Gitee
From 7e0792a923411ce6baab2ee3629e615442b4b639 Mon Sep 17 00:00:00 2001
From: finkyky
Date: Mon, 18 Jul 2022 20:21:19 +0800
Subject: [PATCH 035/355] =?UTF-8?q?doc:=20=E4=BF=AE=E6=94=B9=E6=96=87?=
=?UTF-8?q?=E6=A1=A3=E9=94=99=E5=88=AB=E5=AD=97'=E5=80=9F=E5=8F=A3'?=
=?UTF-8?q?=E4=B8=BA'=E6=8E=A5=E5=8F=A3'=20issue=20#420?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...\345\217\212\345\256\236\347\216\260.docx" | Bin 21457 -> 21525 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
diff --git "a/APIJSON\345\210\235\346\234\237\346\236\204\346\200\235\345\217\212\345\256\236\347\216\260.docx" "b/APIJSON\345\210\235\346\234\237\346\236\204\346\200\235\345\217\212\345\256\236\347\216\260.docx"
index 7522282a320ead9c67f9908c6abb98c548b5f8ad..14667653e03d8694e74f65f65c7266262991e2dc 100644
GIT binary patch
delta 16657
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