From abbfcf4d0bad85353a532deb81c62e82cb53ef46 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 7 Jul 2021 11:33:10 +0800 Subject: [PATCH 001/754] =?UTF-8?q?=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=20APIJSONDemo=5FClickHouse=EF=BC=8C?= =?UTF-8?q?=E6=89=93=E5=93=8D=20OLAP=20=E7=AC=AC=E4=B8=80=E6=9E=AA?= =?UTF-8?q?=EF=BC=8C=E6=84=9F=E8=B0=A2=E4=BD=9C=E8=80=85=E7=9A=84=E8=B4=A1?= =?UTF-8?q?=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/chenyanlann/APIJSONDemo_ClickHouse --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b782365bb..ee66aec17 100644 --- a/README.md +++ b/README.md @@ -418,6 +418,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [ApiJsonByJFinal](https://gitee.com/zhiyuexin/ApiJsonByJFinal) 整合 APIJSON 和 JFinal 的 Demo +[APIJSONDemo_ClickHouse](https://github.com/chenyanlann/APIJSONDemo_ClickHouse) APIJSON + SpringBoot 连接 ClickHouse 使用的 Demo + [apijson-builder](https://github.com/pengxianggui/apijson-builder) 一个方便为 APIJSON 构建 RESTful 请求的 JavaScript 库 [AbsGrade](https://github.com/APIJSON/AbsGrade) 列表级联算法,支持微信朋友圈单层评论、QQ空间双层评论、百度网盘多层(无限层)文件夹等 From ef3971ac3f646c4dd7a10bfb165d709f57e83fcb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 19 Jul 2021 16:08:47 +0800 Subject: [PATCH 002/754] =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E6=95=99=E7=A8=8B=E7=9A=84=E9=93=BE=E6=8E=A5=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E9=85=B7=E5=AE=98=E6=96=B9=E5=8F=B7=E6=94=B9=E4=B8=BA=20Bilibi?= =?UTF-8?q?li=20=E5=AE=98=E6=96=B9=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://space.bilibili.com/437134249?spm_id_from=333.788.b_765f7570696e666f.2 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ee66aec17..686d3239b 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ This source code is licensed under the Apache License Version 2.0

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

From d63fb9dd659ea3ef57ca13db3375b3d4207db5fe Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 19 Jul 2021 16:12:20 +0800 Subject: [PATCH 003/754] =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E6=95=99=E7=A8=8B=E9=93=BE=E6=8E=A5=EF=BC=8CBilibili=20?= =?UTF-8?q?=E5=AE=98=E6=96=B9=E5=8F=B7=E6=94=B9=E4=B8=BA=E5=85=A8=E7=AB=99?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=20APIJSON?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://search.bilibili.com/all?keyword=APIJSON&from_source=webtop_search&spm_id_from=333.999 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 686d3239b..4e04cf505 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ This source code is licensed under the Apache License Version 2.0

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

From c113c2300f763bc750abeee2730e35be1d0d8ed5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 22 Jul 2021 00:21:24 +0800 Subject: [PATCH 004/754] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 4e04cf505..1ae1832d5 100644 --- a/README.md +++ b/README.md @@ -220,6 +220,10 @@ https://github.com/Tencent/APIJSON/issues/36 如果您在使用 APIJSON,请让我们知道,您的使用对我们非常重要(按登记顺序排列):
https://github.com/Tencent/APIJSON/issues/187
+ + + +
From 72a6bc6161dbc0e26173a731e144d94d45677067 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 22 Jul 2021 00:33:05 +0800 Subject: [PATCH 005/754] Update README.md --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1ae1832d5..671f6c945 100644 --- a/README.md +++ b/README.md @@ -220,10 +220,11 @@ https://github.com/Tencent/APIJSON/issues/36 如果您在使用 APIJSON,请让我们知道,您的使用对我们非常重要(按登记顺序排列):
https://github.com/Tencent/APIJSON/issues/187
- - - + + +
+ From a252a7630194056931e360867bf0fff83213e745 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 22 Jul 2021 00:39:39 +0800 Subject: [PATCH 006/754] =?UTF-8?q?=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20Go=20=E7=89=88=20APIJSON=20=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=8D=95=E8=A1=A8=E3=80=81=E6=95=B0=E7=BB=84?= =?UTF-8?q?=E3=80=81=E5=A4=9A=E8=A1=A8=E4=B8=80=E5=AF=B9=E4=B8=80=E5=92=8C?= =?UTF-8?q?=E5=A4=9A=E8=A1=A8=E4=B8=80=E5=AF=B9=E5=A4=9A=20=E5=85=B3?= =?UTF-8?q?=E8=81=94=E6=9F=A5=E8=AF=A2=20=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://gitee.com/tiangao/apijson-go https://github.com/keepfoo/apijson-go --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 671f6c945..771434257 100644 --- a/README.md +++ b/README.md @@ -397,6 +397,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [APIJSON.NET](https://github.com/liaozb/APIJSON.NET) C# 版 APIJSON ,支持 MySQL, PostgreSQL, SQL Server, Oracle, SQLite +[apijson-go](https://gitee.com/tiangao/apijson-go) Go 版 APIJSON ,支持单表查询、数组查询、多表一对一关联查询、多表一对多关联查询 等 + [APIJSON-php](https://github.com/xianglong111/APIJSON-php) PHP 版 APIJSON,基于 ThinkPHP,支持 MySQL, PostgreSQL, SQL Server, Oracle 等 [apijson-php](https://github.com/qq547057827/apijson-php) PHP 版 APIJSON,基于 ThinkPHP,支持 MySQL, PostgreSQL, SQL Server, Oracle 等 From 63d117f4ff1f5109ccf00c20e81e453a26566365 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 22 Jul 2021 01:12:52 +0800 Subject: [PATCH 007/754] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E4=BB=AC?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=AF=B9=205=20=E4=B8=AA=E8=85=BE=E8=AE=AF?= =?UTF-8?q?=E5=B7=A5=E7=A8=8B=E5=B8=88=E3=80=811=20=E4=B8=AA=E7=9F=A5?= =?UTF-8?q?=E4=B9=8E=E5=9F=BA=E7=A1=80=E7=A0=94=E5=8F=91=E6=9E=B6=E6=9E=84?= =?UTF-8?q?=E5=B8=88=E3=80=811=20=E4=B8=AA=E5=AD=97=E8=8A=82=E8=B7=B3?= =?UTF-8?q?=E5=8A=A8=E5=B7=A5=E7=A8=8B=E5=B8=88=20=E7=9A=84=E8=AF=B4?= =?UTF-8?q?=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON#%E8%B4%A1%E7%8C%AE%E8%80%85%E4%BB%AC --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 771434257..7c7f2a34a 100644 --- a/README.md +++ b/README.md @@ -241,7 +241,8 @@ https://github.com/Tencent/APIJSON/issues/187 ### 贡献者们 -主项目 APIJSON 的贡献者们和 生态周边项目 的作者们: +主项目 APIJSON 的贡献者们(5 个腾讯工程师、1 个知乎基础研发架构师 等):
+https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
@@ -268,8 +269,13 @@ https://github.com/Tencent/APIJSON/issues/187 - -
+
+
+ +生态周边项目的作者们(1 个腾讯工程师、1 个字节跳动工程师 等):
+https://github.com/search?o=desc&q=apijson&s=stars&type=Repositories
+https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count
+
Date: Fri, 30 Jul 2021 17:33:55 +0800 Subject: [PATCH 008/754] =?UTF-8?q?=E9=A6=96=E9=A1=B5=E6=96=B0=E5=A2=9E=20?= =?UTF-8?q?=E8=85=BE=E8=AE=AF=E7=8A=80=E7=89=9B=E9=B8=9F=E5=BC=80=E6=BA=90?= =?UTF-8?q?=E4=BA=BA=E6=89=8D=E5=9F=B9=E5=85=BB=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON#%E8%85%BE%E8%AE%AF%E7%8A%80%E7%89%9B%E9%B8%9F%E5%BC%80%E6%BA%90%E4%BA%BA%E6%89%8D%E5%9F%B9%E5%85%BB%E8%AE%A1%E5%88%92 --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7c7f2a34a..a5dec9aaf 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON -

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

+

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

@@ -444,7 +444,12 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md 感谢热心的作者们的贡献,点 ⭐Star 支持下他们吧。 +### 腾讯犀牛鸟开源人才培养计划 + +#### zhouzuobiao 1.完善入门介绍视频 +https://lexiangla.com/teams/k100046/classes/a4eba9f4b6d711eba2ec268dd73d15f1?type=0&company_from=79350bd4d06911ea91f05254002f1020 + ### 持续更新 https://github.com/Tencent/APIJSON/commits/master From 914b50af113a22dc0b4e1e3aa266792525dfea60 Mon Sep 17 00:00:00 2001 From: "bin.li" <626732147@qq.com> Date: Fri, 30 Jul 2021 17:38:37 +0800 Subject: [PATCH 009/754] =?UTF-8?q?fix:bug=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1:修复List集合使用remove方法引发ConcurrentModificationException 2:修复remove RAW_MAP字段后,引发自定义column列@raw报错 --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 1b63d9ba8..fc20567a2 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1046,7 +1046,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 if ("".equals(RAW_MAP.get(c)) || RAW_MAP.containsValue(c)) { // newSQLConfig 提前处理好的 //排除@raw中的值,以避免使用date_format(date,'%Y-%m-%d %H:%i:%s') 时,冒号的解析出错 - column.remove(c); + //column.remove(c); continue; } } From d5047ebaef8f4586412876074e6b6325970182ec Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 30 Jul 2021 18:20:55 +0800 Subject: [PATCH 010/754] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a5dec9aaf..0a9a1357a 100644 --- a/README.md +++ b/README.md @@ -447,6 +447,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md ### 腾讯犀牛鸟开源人才培养计划 #### zhouzuobiao 1.完善入门介绍视频 +APIJSON- 后端零代码接口和文档ORM库 https://lexiangla.com/teams/k100046/classes/a4eba9f4b6d711eba2ec268dd73d15f1?type=0&company_from=79350bd4d06911ea91f05254002f1020 From 730ba4d73780a77441e1b250faa37e642657e315 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 30 Jul 2021 18:21:13 +0800 Subject: [PATCH 011/754] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0a9a1357a..0fe768917 100644 --- a/README.md +++ b/README.md @@ -447,7 +447,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md ### 腾讯犀牛鸟开源人才培养计划 #### zhouzuobiao 1.完善入门介绍视频 -APIJSON- 后端零代码接口和文档ORM库 +APIJSON- 后端零代码接口和文档ORM库
https://lexiangla.com/teams/k100046/classes/a4eba9f4b6d711eba2ec268dd73d15f1?type=0&company_from=79350bd4d06911ea91f05254002f1020 From eb5230e8921f6cce6a425c7cc1831ee9045127fb Mon Sep 17 00:00:00 2001 From: kenlig <28685375+kenlig@users.noreply.github.com> Date: Fri, 30 Jul 2021 19:05:04 +0800 Subject: [PATCH 012/754] add:Add introduction to APIJSON-Java-Server Demo --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0fe768917..cb539fe39 100644 --- a/README.md +++ b/README.md @@ -450,7 +450,18 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md APIJSON- 后端零代码接口和文档ORM库
https://lexiangla.com/teams/k100046/classes/a4eba9f4b6d711eba2ec268dd73d15f1?type=0&company_from=79350bd4d06911ea91f05254002f1020 - +#### zhaoqiming 1.完善入门介绍视频 +APIJSON 后端教程(1):简介 +https://www.bilibili.com/video/BV1vL411W7yd +APIJSON 后端教程(2):数据库 +https://www.bilibili.com/video/BV1eB4y1N77s +APIJSON 后端教程(3):Demo +https://www.bilibili.com/video/BV1FX4y1c7ug +APIJSON 后端教程(4):Boot +https://www.bilibili.com/video/BV18h411z7FK +APIJSON 后端教程(5):Final +https://www.bilibili.com/video/BV1GM4y1N7XJ + ### 持续更新 https://github.com/Tencent/APIJSON/commits/master From f718852646b58c20201095698425aed758e6462c Mon Sep 17 00:00:00 2001 From: kenlig <28685375+kenlig@users.noreply.github.com> Date: Fri, 30 Jul 2021 19:06:00 +0800 Subject: [PATCH 013/754] Add introduction to APIJSON-Java-Server Demo --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index cb539fe39..59999b0b3 100644 --- a/README.md +++ b/README.md @@ -453,12 +453,16 @@ https://lexiangla.com/teams/k100046/classes/a4eba9f4b6d711eba2ec268dd73d15f1?typ #### zhaoqiming 1.完善入门介绍视频 APIJSON 后端教程(1):简介 https://www.bilibili.com/video/BV1vL411W7yd + APIJSON 后端教程(2):数据库 https://www.bilibili.com/video/BV1eB4y1N77s + APIJSON 后端教程(3):Demo https://www.bilibili.com/video/BV1FX4y1c7ug + APIJSON 后端教程(4):Boot https://www.bilibili.com/video/BV18h411z7FK + APIJSON 后端教程(5):Final https://www.bilibili.com/video/BV1GM4y1N7XJ From 5bb9df6bd3ac73966f180e06f495fca7e93fdd2b Mon Sep 17 00:00:00 2001 From: andream7 <60541766+andream7@users.noreply.github.com> Date: Fri, 30 Jul 2021 23:39:09 +0800 Subject: [PATCH 014/754] Update README.md Add apijson-db2 demo --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 0fe768917..50801efec 100644 --- a/README.md +++ b/README.md @@ -450,6 +450,10 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md APIJSON- 后端零代码接口和文档ORM库
https://lexiangla.com/teams/k100046/classes/a4eba9f4b6d711eba2ec268dd73d15f1?type=0&company_from=79350bd4d06911ea91f05254002f1020 +#### zhangshukun 2.接入 presto/hive/clickhouse/db2 任意一个 +APIJSON-Demo接入db2
+https://github.com/andream7/apijson-db2 + ### 持续更新 https://github.com/Tencent/APIJSON/commits/master From 7a1d1a07e996e71e9a04b58b05cf900df8236fcf Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 31 Jul 2021 01:49:53 +0800 Subject: [PATCH 015/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=9C=86=E9=80=9A?= =?UTF-8?q?=E5=B7=A5=E7=A8=8B=E5=B8=88=E7=AD=89=203=20=E4=B8=AA=E8=B4=A1?= =?UTF-8?q?=E7=8C=AE=E8=80=85=EF=BC=9B=E6=96=B0=E5=A2=9E=20apijson-go=20?= =?UTF-8?q?=E4=BD=9C=E8=80=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 4bb2105f3..1fe7d4344 100644 --- a/README.md +++ b/README.md @@ -269,6 +269,10 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
+ + + +


@@ -279,6 +283,8 @@ https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count + From 5ba9132aaeaced2b9e18d824f01c5f4f5e510865 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 31 Jul 2021 01:52:13 +0800 Subject: [PATCH 016/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=9C=86=E9=80=9A?= =?UTF-8?q?=E5=B7=A5=E7=A8=8B=E5=B8=88=E7=AD=89=203=20=E4=B8=AA=E8=B4=A1?= =?UTF-8?q?=E7=8C=AE=E8=80=85=EF=BC=9B=E6=96=B0=E5=A2=9E=20apijson-go=20?= =?UTF-8?q?=E4=BD=9C=E8=80=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://gitee.com/tiangao/apijson-go --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index 1fe7d4344..a3e9d6e30 100644 --- a/README.md +++ b/README.md @@ -269,7 +269,6 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
- @@ -452,10 +451,6 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md ### 腾讯犀牛鸟开源人才培养计划 -#### zhouzuobiao 1.完善入门介绍视频 -APIJSON- 后端零代码接口和文档ORM库
-https://lexiangla.com/teams/k100046/classes/a4eba9f4b6d711eba2ec268dd73d15f1?type=0&company_from=79350bd4d06911ea91f05254002f1020 - #### zhangshukun 2.接入 presto/hive/clickhouse/db2 任意一个 APIJSON-Demo接入db2
https://github.com/andream7/apijson-db2 From ae7bfa6b64cbef8f0c9c0e81fb6f437643011801 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 31 Jul 2021 02:00:23 +0800 Subject: [PATCH 017/754] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a3e9d6e30..d4d654b2c 100644 --- a/README.md +++ b/README.md @@ -241,7 +241,7 @@ https://github.com/Tencent/APIJSON/issues/187 ### 贡献者们 -主项目 APIJSON 的贡献者们(5 个腾讯工程师、1 个知乎基础研发架构师 等):
+主项目 APIJSON 的贡献者们(5 个腾讯工程师、1 个知乎基础研发架构师、1 个圆通工程师 等):
https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
Date: Sat, 31 Jul 2021 02:02:23 +0800 Subject: [PATCH 018/754] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index d4d654b2c..c18a27667 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -[【腾讯犀牛鸟开源人才培养计划】开始啦!加入我们开源共建吧~](https://github.com/Tencent/APIJSON/issues/229) - Tencent is pleased to support the open source community by making APIJSON available.
Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
This source code is licensed under the Apache License Version 2.0
@@ -450,6 +448,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md 感谢热心的作者们的贡献,点 ⭐Star 支持下他们吧。 ### 腾讯犀牛鸟开源人才培养计划 +https://github.com/Tencent/APIJSON/issues/229 #### zhangshukun 2.接入 presto/hive/clickhouse/db2 任意一个 APIJSON-Demo接入db2
From 3451f346019d77e56395715e3bfe4b09338bb38e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 31 Jul 2021 02:47:07 +0800 Subject: [PATCH 019/754] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c18a27667..3bf01dd29 100644 --- a/README.md +++ b/README.md @@ -383,7 +383,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [APIJSON复杂业务深入实践(类似12306订票系统)](https://blog.csdn.net/aa330233789/article/details/105309571) -[全国行政区划数据抓取与处理](https://my.oschina.net/hwxia/blog/4999897) +[全国行政区划数据抓取与处理](https://www.xlongwei.com/detail/21032616) ### 生态项目 [APIJSON-Demo](https://github.com/APIJSON/APIJSON-Demo) APIJSON 各种语言、各种框架 的 使用示例项目、上手文档、测试数据 SQL 文件 等 From 2fc8526863fe18157010b1b613cb79c4fd76774e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 31 Jul 2021 02:55:00 +0800 Subject: [PATCH 020/754] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E4=B8=BA=204.7.1=EF=BC=9B=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 2 +- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 4c1041bc9..ca67738f9 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.6.7 + 4.7.1 jar APIJSONORM diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index fc20567a2..cde3deb20 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1525,7 +1525,7 @@ public static String getLimitString(int page, int count, boolean isTSQL, boolean int offset = getOffset(page, count); if (isTSQL) { // OFFSET FECTH 中所有关键词都不可省略, 另外 Oracle 数据库使用子查询加 where 分页 - return isOracle? " WHERE ROWNUM BETWEEN "+ offset +" AND "+ (offset + count): " OFFSET " + offset + " ROWS FETCH FIRST " + count + " ROWS ONLY"; + return isOracle ? " WHERE ROWNUM BETWEEN "+ offset +" AND "+ (offset + count) : " OFFSET " + offset + " ROWS FETCH FIRST " + count + " ROWS ONLY"; } return " LIMIT " + count + (offset <= 0 ? "" : " OFFSET " + offset); // DELETE, UPDATE 不支持 OFFSET From 042716d3e28125f2f8d4833c32513e72518d9467 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 31 Jul 2021 03:22:23 +0800 Subject: [PATCH 021/754] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E7=9A=84=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/build.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 APIJSONORM/build.sh diff --git a/APIJSONORM/build.sh b/APIJSONORM/build.sh deleted file mode 100644 index e69de29bb..000000000 From b0e10107a6d7ad04933eed2401c034de713f8470 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 31 Jul 2021 03:32:46 +0800 Subject: [PATCH 022/754] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E5=90=8D?= =?UTF-8?q?=E5=8D=95=E6=96=B0=E5=A2=9E=E5=9C=86=E9=80=9A=E5=B7=A5=E7=A8=8B?= =?UTF-8?q?=E5=B8=88=E7=AD=89=203=20=E4=BA=BA=EF=BC=8C=E6=84=9F=E8=B0=A2?= =?UTF-8?q?=E8=B4=A1=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/278 --- CONTRIBUTING.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0fafca62b..b737956dd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,6 +31,9 @@ - [Wscats](https://github.com/Wscats)(腾讯工程师) - [jun0315](https://github.com/jun0315)(腾讯工程师) - [JieJo](https://github.com/JieJo) +- [yeyuezhishou](https://github.com/yeyuezhishou)(圆通工程师) +- [kenlig](https://github.com/kenlig) +- [andream7](https://github.com/andream7) #### 其中特别致谢:
From 3ccd9674ce572e36907dd961ae9386e31abc2483 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 1 Aug 2021 01:02:11 +0800 Subject: [PATCH 023/754] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E8=BF=9C=E7=A8=8B?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E6=8B=BF=E4=B8=8D=E5=88=B0=E6=9C=89=E6=95=88?= =?UTF-8?q?=E7=9A=84=E5=BD=93=E5=89=8D=E5=AF=B9=E8=B1=A1=EF=BC=8C=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E6=A0=A1=E9=AA=8C=E5=8F=82=E6=95=B0=E5=AE=B9=E6=98=93?= =?UTF-8?q?=E6=94=BE=E8=A1=8C=E7=AD=89=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractObjectParser.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 5244abc9a..673348935 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -774,12 +774,10 @@ public void onFunctionResponse(String type) throws Exception { //解析函数function Set> functionSet = map == null ? null : map.entrySet(); if (functionSet != null && functionSet.isEmpty() == false) { - // JSONObject json = "-".equals(type) ? request : response; // key-():function 是实时执行,而不是在这里批量执行 + JSONObject json = "-".equals(type) ? request : response; // key-():function 是实时执行,而不是在这里批量执行 for (Entry entry : functionSet) { - - // parseFunction(json, entry.getKey(), entry.getValue()); - parseFunction(entry.getKey(), entry.getValue(), parentPath, name, response); + parseFunction(entry.getKey(), entry.getValue(), parentPath, name, json); } } } From 646bed418655a4ff80d3c4c883e310dd64447b9d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 1 Aug 2021 01:19:29 +0800 Subject: [PATCH 024/754] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E7=AC=A6=20UNIQUE=20=E6=A0=A1=E9=AA=8C=E4=B8=8D=E5=85=81?= =?UTF-8?q?=E8=AE=B8=E9=87=8D=E5=A4=8D=E5=A4=B1=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractVerifier.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 30aeb5f15..448b54155 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -447,14 +447,14 @@ public void verifyRepeat(String table, String key, Object value, long exceptId) if (exceptId > 0) {//允许修改自己的属性为该属性原来的值 request.put(JSONRequest.KEY_ID + "!", exceptId); // FIXME 这里 id 写死了,不支持自定义 } - JSONObject repeat = createParser().setMethod(GET).setNeedVerify(true).parseResponse( + JSONObject repeat = createParser().setMethod(HEAD).setNeedVerify(true).parseResponse( new JSONRequest(table, request) ); repeat = repeat == null ? null : repeat.getJSONObject(table); if (repeat == null) { throw new Exception("服务器内部错误 verifyRepeat repeat == null"); } - if (repeat.getIntValue(JSONResponse.KEY_CODE) > 0) { + if (repeat.getIntValue(JSONResponse.KEY_COUNT) > 0) { throw new ConflictException(key + ": " + value + " 已经存在,不能重复!"); } } @@ -1424,7 +1424,7 @@ public static void verifyRepeat(String table, String key, Object value, long exc String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey; - SQLConfig config = creator.createSQLConfig().setMethod(RequestMethod.GET).setCount(1).setPage(0); + SQLConfig config = creator.createSQLConfig().setMethod(RequestMethod.HEAD).setCount(1).setPage(0); config.setTable(table); if (exceptId > 0) { //允许修改自己的属性为该属性原来的值 config.putWhere(finalIdKey + "!", exceptId, false); @@ -1437,7 +1437,7 @@ public static void verifyRepeat(String table, String key, Object value, long exc if (result == null) { throw new Exception("服务器内部错误 verifyRepeat result == null"); } - if (result.getIntValue(JSONResponse.KEY_CODE) > 0) { + if (result.getIntValue(JSONResponse.KEY_COUNT) > 0) { throw new ConflictException(key + ": " + value + " 已经存在,不能重复!"); } } finally { From af9a1fc0c1ffc6ccaa2a12dc049649e7541fc54c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 1 Aug 2021 01:47:10 +0800 Subject: [PATCH 025/754] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7=E4=B8=BA=204.7.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index ca67738f9..00b542ab0 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.7.1 + 4.7.2 jar APIJSONORM From 3d00f906976a48be1e88ad3d3c1719c58c86639c Mon Sep 17 00:00:00 2001 From: qiujunlin <1757591067@qq.com> Date: Sun, 1 Aug 2021 14:55:47 +0800 Subject: [PATCH 026/754] readme --- README.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0fe768917..d74bfbdae 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This source code is licensed under the Apache License Version 2.0

APIJSON

- +

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

@@ -224,7 +224,7 @@ https://github.com/Tencent/APIJSON/issues/187
- + @@ -271,7 +271,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md


- + 生态周边项目的作者们(1 个腾讯工程师、1 个字节跳动工程师 等):
https://github.com/search?o=desc&q=apijson&s=stars&type=Repositories
https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count
@@ -445,13 +445,20 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md 感谢热心的作者们的贡献,点 ⭐Star 支持下他们吧。 ### 腾讯犀牛鸟开源人才培养计划 - -#### zhouzuobiao 1.完善入门介绍视频 + +#### qiujunlin **2.接入 presto/hive/clickhouse/db2 任意一个** + +APIJSON 接入 clickhouse 使用demo + +https://github.com/qiujunlin/APIJSONDemo + +#### zhouzuobiao 1.完善入门介绍视频 + APIJSON- 后端零代码接口和文档ORM库
https://lexiangla.com/teams/k100046/classes/a4eba9f4b6d711eba2ec268dd73d15f1?type=0&company_from=79350bd4d06911ea91f05254002f1020 - ### 持续更新 + https://github.com/Tencent/APIJSON/commits/master ### 工蜂主页 From b36739c709e91ccf6b66c7c72fe830be359545df Mon Sep 17 00:00:00 2001 From: Hanxu2018 Date: Sun, 1 Aug 2021 16:12:33 +0800 Subject: [PATCH 027/754] Update README.md --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 190115912..ba38c1b97 100644 --- a/README.md +++ b/README.md @@ -449,6 +449,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md ### 腾讯犀牛鸟开源人才培养计划 https://github.com/Tencent/APIJSON/issues/229 + #### qiujunlin **2.接入 presto/hive/clickhouse/db2 任意一个** @@ -458,6 +459,14 @@ https://github.com/qiujunlin/APIJSONDemo #### zhangshukun 2.接入 presto/hive/clickhouse/db2 任意一个 APIJSON-Demo接入db2
https://github.com/andream7/apijson-db2 + +#### hanxu 1.完善入门介绍视频 +重构 APIJSON 文档 +https://hanxu2018.github.io/APIJSON-DOC/ +文档源码 +https://github.com/HANXU2018/APIJSON-DOC +配套评论区 apijson-doc-Comment +https://github.com/HANXU2018/apijson-doc-Comment #### zhaoqiming 1.完善入门介绍视频 APIJSON 后端教程(1):简介 From b8dc48fdecf21f4b8a36f874d90203f48fc072a2 Mon Sep 17 00:00:00 2001 From: clown <32100214+hclown9804@users.noreply.github.com> Date: Sun, 1 Aug 2021 16:20:57 +0800 Subject: [PATCH 028/754] Update README.md add github link --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 190115912..a128afd48 100644 --- a/README.md +++ b/README.md @@ -474,6 +474,10 @@ https://www.bilibili.com/video/BV18h411z7FK APIJSON 后端教程(5):Final https://www.bilibili.com/video/BV1GM4y1N7XJ + +#### huwen 2.接入 presto/hive/clickhouse/db2 任意一个 +APIJSON-Demo 接入presto +https://github.com/hclown9804/APIJSONDemo_presto ### 持续更新 From 9c37249a9b724745983a1e9e7c74bc1440597154 Mon Sep 17 00:00:00 2001 From: haolingzhang1 <55579125+haolingzhang1@users.noreply.github.com> Date: Sun, 1 Aug 2021 16:31:33 +0800 Subject: [PATCH 029/754] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 190115912..448e518b2 100644 --- a/README.md +++ b/README.md @@ -475,6 +475,10 @@ https://www.bilibili.com/video/BV18h411z7FK APIJSON 后端教程(5):Final https://www.bilibili.com/video/BV1GM4y1N7XJ +#### zhanghaoling 1.完善入门介绍视频 +APIJSON结合已有项目,简化开发流程 +https://github.com/haolingzhang1/APIJson--demo + ### 持续更新 https://github.com/Tencent/APIJSON/commits/master From 5c3d066bc99764fb8ffbc9d3cf455788d10bfc9f Mon Sep 17 00:00:00 2001 From: chenyanlann <62465397+chenyanlann@users.noreply.github.com> Date: Sun, 1 Aug 2021 18:55:35 +0800 Subject: [PATCH 030/754] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 190115912..a2d1a2f9e 100644 --- a/README.md +++ b/README.md @@ -459,6 +459,10 @@ https://github.com/qiujunlin/APIJSONDemo APIJSON-Demo接入db2
https://github.com/andream7/apijson-db2 +#### chenyanlan 2.接入 presto/hive/clickhouse/db2 任意一个 +APIJSON + SpringBoot连接ClickHouse使用的Demo
+https://github.com/chenyanlann/APIJSONDemo_ClickHouse + #### zhaoqiming 1.完善入门介绍视频 APIJSON 后端教程(1):简介 https://www.bilibili.com/video/BV1vL411W7yd From cd9827f18d22cf92749368516e5875889bf24389 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 1 Aug 2021 19:21:42 +0800 Subject: [PATCH 031/754] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1b1e4f0bb..5910b16ea 100644 --- a/README.md +++ b/README.md @@ -461,11 +461,11 @@ APIJSON-Demo接入db2
https://github.com/andream7/apijson-db2 #### hanxu 1.完善入门介绍视频 -重构 APIJSON 文档 -https://hanxu2018.github.io/APIJSON-DOC/ -文档源码 -https://github.com/HANXU2018/APIJSON-DOC -配套评论区 apijson-doc-Comment +重构 APIJSON 文档
+https://hanxu2018.github.io/APIJSON-DOC/
+文档源码
+https://github.com/HANXU2018/APIJSON-DOC
+配套评论区 apijson-doc-Comment
https://github.com/HANXU2018/apijson-doc-Comment #### chenyanlan 2.接入 presto/hive/clickhouse/db2 任意一个 From 9b5f3f25975ef4a44d1c68e027903262a858738b Mon Sep 17 00:00:00 2001 From: chenyanlann <32511cyl@gmail.com> Date: Sun, 1 Aug 2021 20:11:14 +0800 Subject: [PATCH 032/754] Add:ORM's support for ClickHouse --- .../java/apijson/orm/AbstractSQLConfig.java | 24 +++++++++++++++---- .../src/main/java/apijson/orm/SQLConfig.java | 2 ++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index cde3deb20..6d3610ed6 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -124,6 +124,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { DATABASE_LIST.add(DATABASE_SQLSERVER); DATABASE_LIST.add(DATABASE_ORACLE); DATABASE_LIST.add(DATABASE_DB2); + DATABASE_LIST.add(DATABASE_CLICKHOUSE); RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 @@ -508,10 +509,17 @@ public boolean isDb2() { public static boolean isDb2(String db) { return DATABASE_DB2.equals(db); } + @Override + public boolean isClickHouse() { + return isClickHouse(getSQLDatabase()); + } + public static boolean isClickHouse(String db) { + return DATABASE_CLICKHOUSE.equals(db); + } @Override public String getQuote() { - return isMySQL() ? "`" : "\""; + return isMySQL() ? "`" : ( isClickHouse()? "" : "\""); } @Override @@ -2158,6 +2166,9 @@ public String getRegExpString(String key, String value, boolean ignoreCase) { if (isOracle()) { return "regexp_like(" + getKey(key) + ", " + getValue(value) + (ignoreCase ? ", 'i'" : ", 'c'") + ")"; } + if (isClickHouse()) { + return "match(" + (ignoreCase ? "lower(" : "") + getKey(key) + (ignoreCase ? ")" : "") + ", " + (ignoreCase ? "lower(" : "") + getValue(value) + (ignoreCase ? ")" : "") + ")"; + } return getKey(key) + " REGEXP " + (ignoreCase ? "" : "BINARY ") + getValue(value); } //~ regexp >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -2448,7 +2459,12 @@ else if (isOracle()) { else { boolean isNum = c instanceof Number; String v = (isNum ? "" : "\"") + childs[i] + (isNum ? "" : "\""); - condition += ("json_contains(" + getKey(key) + ", " + getValue(v) + ")"); + if (isClickHouse()) { + condition += condition + "has(JSONExtractArrayRaw(assumeNotNull(" + getKey(key) + "))" + ", " + getValue(v) + ")"; + } + else { + condition += ("json_contains(" + getKey(key) + ", " + getValue(v) + ")"); + } } } } @@ -2649,9 +2665,9 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { case POST: return "INSERT INTO " + tablePath + config.getColumnString() + " VALUES" + config.getValuesString(); case PUT: - return "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); + return "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) + (config.isMySQL()||config.isClickHouse() ? config.getLimitString() : ""); case DELETE: - return "DELETE FROM " + tablePath + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); // PostgreSQL 不允许 LIMIT + return "DELETE FROM " + tablePath + config.getWhereString(true) + (config.isMySQL()||config.isClickHouse() ? config.getLimitString() : ""); // PostgreSQL 不允许 LIMIT default: String explain = (config.isExplain() ? (config.isSQLServer() || config.isOracle() ? "SET STATISTICS PROFILE ON " : "EXPLAIN ") : ""); if (config.isTest() && RequestMethod.isGetMethod(config.getMethod(), true)) { diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index 9abce83b9..8710b6e97 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -22,6 +22,7 @@ public interface SQLConfig { String DATABASE_SQLSERVER = "SQLSERVER"; String DATABASE_ORACLE = "ORACLE"; String DATABASE_DB2 = "DB2"; + String DATABASE_CLICKHOUSE = "CLICKHOUSE"; String SCHEMA_INFORMATION = "information_schema"; //MySQL, PostgreSQL, SQL Server 都有的系统模式 String SCHEMA_SYS = "sys"; //SQL Server 系统模式 @@ -37,6 +38,7 @@ public interface SQLConfig { boolean isSQLServer(); boolean isOracle(); boolean isDb2(); + boolean isClickHouse(); //暂时只兼容以上 5 种 // boolean isSQL(); // boolean isTSQL(); From 691d167f22e04c6dbbd2d958e87877901f8aa7b0 Mon Sep 17 00:00:00 2001 From: Neko Null Date: Sun, 1 Aug 2021 22:49:58 +0800 Subject: [PATCH 033/754] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E7=A4=BA=E4=BE=8B=E9=A1=B9=E7=9B=AE=E5=92=8C=E4=B8=80=E7=AF=87?= =?UTF-8?q?=E6=96=87=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 参见 https://github.com/jerrylususu/apijson_todo_demo --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5910b16ea..3a3e275ce 100644 --- a/README.md +++ b/README.md @@ -385,6 +385,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [全国行政区划数据抓取与处理](https://www.xlongwei.com/detail/21032616) +[新手搭建 APIJSON 项目指北](https://github.com/jerrylususu/apijson_todo_demo/blob/master/FULLTEXT.md) + ### 生态项目 [APIJSON-Demo](https://github.com/APIJSON/APIJSON-Demo) APIJSON 各种语言、各种框架 的 使用示例项目、上手文档、测试数据 SQL 文件 等 @@ -444,7 +446,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [Android-ZBLibrary](https://github.com/TommyLemon/Android-ZBLibrary) Android MVP快速开发框架,Demo全面,注释详细,使用简单,代码严谨 - +[APIJSON-ToDo-Demo](https://github.com/jerrylususu/apijson_todo_demo) 一个简单的 todo 示例项目,精简数据,简化上手流程,带自定义鉴权逻辑 + 感谢热心的作者们的贡献,点 ⭐Star 支持下他们吧。 ### 腾讯犀牛鸟开源人才培养计划 From 59d689507cafb601b32f8f84346f23d4388ed3be Mon Sep 17 00:00:00 2001 From: haolingzhang1 <55579125+haolingzhang1@users.noreply.github.com> Date: Sun, 1 Aug 2021 23:43:31 +0800 Subject: [PATCH 034/754] Update README.md --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 448e518b2..9d1fae723 100644 --- a/README.md +++ b/README.md @@ -476,9 +476,19 @@ APIJSON 后端教程(5):Final https://www.bilibili.com/video/BV1GM4y1N7XJ #### zhanghaoling 1.完善入门介绍视频 + APIJSON结合已有项目,简化开发流程 https://github.com/haolingzhang1/APIJson--demo +说明文档 +https://github.com/haolingzhang1/APIJson--demo/tree/main/APIJson集成项目说明 + +(1)官方demo +https://github.com/haolingzhang1/APIJson--demo/blob/main/APIJson集成项目说明/APIJson集成现有项目(1)-%20官方demo.pdf + +(2)单表配置 +https://github.com/haolingzhang1/APIJson--demo/blob/main/APIJson集成项目说明/APIJson集成现有项目(2)-%20单表配置.pdf + ### 持续更新 https://github.com/Tencent/APIJSON/commits/master From ae09a87b8f21e8f519a754377e2168e6f9876d60 Mon Sep 17 00:00:00 2001 From: haolingzhang1 <55579125+haolingzhang1@users.noreply.github.com> Date: Sun, 1 Aug 2021 23:48:37 +0800 Subject: [PATCH 035/754] =?UTF-8?q?update=20README.md=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 9d1fae723..7061af207 100644 --- a/README.md +++ b/README.md @@ -476,7 +476,6 @@ APIJSON 后端教程(5):Final https://www.bilibili.com/video/BV1GM4y1N7XJ #### zhanghaoling 1.完善入门介绍视频 - APIJSON结合已有项目,简化开发流程 https://github.com/haolingzhang1/APIJson--demo From 526da6df86d1acc98366f27631e8bf271b650867 Mon Sep 17 00:00:00 2001 From: funkiz <1244503766@qq.com> Date: Mon, 2 Aug 2021 01:56:20 +0800 Subject: [PATCH 036/754] =?UTF-8?q?=E8=85=BE=E8=AE=AF=E7=8A=80=E7=89=9B?= =?UTF-8?q?=E9=B8=9F=E5=BC=80=E6=BA=90=E4=BA=BA=E6=89=8D=E5=9F=B9=E5=85=BB?= =?UTF-8?q?=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5910b16ea..8e02b8dc6 100644 --- a/README.md +++ b/README.md @@ -449,13 +449,13 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md ### 腾讯犀牛鸟开源人才培养计划 https://github.com/Tencent/APIJSON/issues/229 - + #### qiujunlin **2.接入 presto/hive/clickhouse/db2 任意一个** APIJSON 接入 clickhouse 使用demo
https://github.com/qiujunlin/APIJSONDemo - + #### zhangshukun 2.接入 presto/hive/clickhouse/db2 任意一个 APIJSON-Demo接入db2
https://github.com/andream7/apijson-db2 @@ -467,11 +467,11 @@ https://hanxu2018.github.io/APIJSON-DOC/
https://github.com/HANXU2018/APIJSON-DOC
配套评论区 apijson-doc-Comment
https://github.com/HANXU2018/apijson-doc-Comment - + #### chenyanlan 2.接入 presto/hive/clickhouse/db2 任意一个 APIJSON + SpringBoot连接ClickHouse使用的Demo
https://github.com/chenyanlann/APIJSONDemo_ClickHouse - + #### zhaoqiming 1.完善入门介绍视频 APIJSON 后端教程(1):简介 https://www.bilibili.com/video/BV1vL411W7yd @@ -487,7 +487,7 @@ https://www.bilibili.com/video/BV18h411z7FK APIJSON 后端教程(5):Final https://www.bilibili.com/video/BV1GM4y1N7XJ - + #### huwen 2.接入 presto/hive/clickhouse/db2 任意一个 APIJSON-Demo 接入presto https://github.com/hclown9804/APIJSONDemo_presto @@ -495,7 +495,15 @@ https://github.com/hclown9804/APIJSONDemo_presto #### zhanghaoling 1.完善入门介绍视频 APIJSON结合已有项目,简化开发流程 https://github.com/haolingzhang1/APIJson--demo - + +#### zhoukaile 1.完善入门介绍视频 + +视频链接:https://www.bilibili.com/video/BV1Uh411z7kZ/ + +文档链接:https://gitee.com/funkiz/apijson_camp + + + ### 持续更新 https://github.com/Tencent/APIJSON/commits/master From f6c3ed0b267db0d104f63ecdc12ed998e62cf699 Mon Sep 17 00:00:00 2001 From: "bin.li" <626732147@qq.com> Date: Mon, 2 Aug 2021 10:17:16 +0800 Subject: [PATCH 037/754] =?UTF-8?q?perft:=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1:解决column加了函数(例如date_format)后,导致表达式超过50字符,改成100字符 --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 1b63d9ba8..8a15a05c9 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1171,9 +1171,9 @@ public String getColumnString(boolean inSQLJoin) throws Exception { // } } - if (expression.length() > 50) { + if (expression.length() > 100) { throw new UnsupportedOperationException("@column:value 的 value 中字符串 " + expression + " 不合法!" - + "不允许传超过 50 个字符的函数或表达式!请用 @raw 简化传参!"); + + "不允许传超过 100 个字符的函数或表达式!请用 @raw 简化传参!"); } From 311c39257eb18b7753b9427e2bb0e7fd6a8e39f2 Mon Sep 17 00:00:00 2001 From: aaronlinv <1546848781@qq.com> Date: Mon, 2 Aug 2021 12:03:21 +0800 Subject: [PATCH 038/754] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 854708e3f..9fbd41db8 100644 --- a/README.md +++ b/README.md @@ -514,6 +514,10 @@ https://github.com/haolingzhang1/APIJson--demo/blob/main/APIJson集成项目说 文档链接:https://gitee.com/funkiz/apijson_camp +#### lintao 1.完善入门介绍视频 + +APIJSON 上手教程:https://www.bilibili.com/video/BV1Pq4y1n7rJ + ### 持续更新 https://github.com/Tencent/APIJSON/commits/master From 774c7074b05eab4cb8ab05ab12bcefa6a7e19149 Mon Sep 17 00:00:00 2001 From: Neko Null Date: Tue, 3 Aug 2021 15:45:50 +0300 Subject: [PATCH 039/754] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9fbd41db8..6a7276633 100644 --- a/README.md +++ b/README.md @@ -310,6 +310,7 @@ https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count +

@@ -422,6 +423,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [FfApiJson](https://gitee.com/own_3_0/ff-api-json) 用 JSON 格式直接生成 SQL,借鉴 APIJSON 支持多数据源 +[APIJSON-ToDo-Demo](https://github.com/jerrylususu/apijson_todo_demo) 一个简单的 todo 示例项目,精简数据,简化上手流程,带自定义鉴权逻辑 + [apijson-learn](https://github.com/rainboy-learn/apijson-learn) APIJSON 学习笔记和源码解析 [apijson-sample](https://gitee.com/greyzeng/apijson-sample) APIJSON 简单使用 Demo 及教程 @@ -445,8 +448,6 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [APIJSON-Android-RxJava](https://github.com/TommyLemon/APIJSON-Android-RxJava) 仿微信朋友圈动态实战项目,ZBLibrary(UI) + APIJSON(HTTP) + RxJava(Data) [Android-ZBLibrary](https://github.com/TommyLemon/Android-ZBLibrary) Android MVP快速开发框架,Demo全面,注释详细,使用简单,代码严谨 - -[APIJSON-ToDo-Demo](https://github.com/jerrylususu/apijson_todo_demo) 一个简单的 todo 示例项目,精简数据,简化上手流程,带自定义鉴权逻辑 感谢热心的作者们的贡献,点 ⭐Star 支持下他们吧。 From 6981010a5e2493dd708ecee4bc0e0751fe1b0d7c Mon Sep 17 00:00:00 2001 From: qiujunlin <1757591067@qq.com> Date: Thu, 5 Aug 2021 22:12:13 +0800 Subject: [PATCH 040/754] update clickhouse gettable --- .../src/main/java/apijson/orm/AbstractSQLConfig.java | 2 +- .../src/main/java/apijson/orm/AbstractSQLExecutor.java | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 26391c0d4..e3f71ccbb 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -519,7 +519,7 @@ public static boolean isClickHouse(String db) { @Override public String getQuote() { - return isMySQL() ? "`" : ( isClickHouse()? "" : "\""); + return isMySQL()||isClickHouse() ? "`" : "\""; } @Override diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 100fe7926..1a7d0f3f6 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -287,11 +287,14 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws // bugfix-修复非常规数据库字段,获取表名失败导致输出异常 if (isExplain == false && hasJoin && viceColumnStart > length) { List column = config.getColumn(); - + String sqlTable = rsmd.getTableName(i); + if (config.isClickHouse()&&(sqlTable.startsWith("`")||sqlTable.startsWith("\""))){ + sqlTable = sqlTable.substring(1,sqlTable.length()-1); + } if (column != null && column.isEmpty() == false) { viceColumnStart = column.size() + 1; } - else if (config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { + else if (config.getSQLTable().equalsIgnoreCase(sqlTable) == false) { viceColumnStart = i; } } From c9490e261d671b62925e60b5b41e525e14decda0 Mon Sep 17 00:00:00 2001 From: kenlig <28685375+kenlig@users.noreply.github.com> Date: Mon, 9 Aug 2021 19:28:32 +0800 Subject: [PATCH 041/754] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 6a7276633..0f592316c 100644 --- a/README.md +++ b/README.md @@ -491,6 +491,9 @@ https://www.bilibili.com/video/BV18h411z7FK APIJSON 后端教程(5):Final https://www.bilibili.com/video/BV1GM4y1N7XJ + +APIJSON配套文档: +https://github.com/kenlig/apijsondocs #### huwen 2.接入 presto/hive/clickhouse/db2 任意一个 APIJSON-Demo 接入presto From 86e5f78e8058183b1b6ca4391ad4e164e80ff45d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 15 Aug 2021 22:30:11 +0800 Subject: [PATCH 042/754] =?UTF-8?q?=E9=80=82=E7=94=A8=E8=8C=83=E5=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E4=BD=8E=E4=BB=A3=E7=A0=81/=E9=9B=B6?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=B9=B3=E5=8F=B0=E3=80=81=E5=B0=8F=E7=A8=8B?= =?UTF-8?q?=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f592316c..58161b681 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
为 简单的增删改查、复杂的查询、简单的事务操作 提供了完全自动化的万能 API。
能大幅降低开发和沟通成本,简化开发流程,缩短开发周期。
-适合中小型前后端分离的项目,尤其是 BaaS、Serverless、互联网创业项目和企业自用项目。
+适合中小型前后端分离的项目,尤其是 互联网创业项目、企业自用项目、低代码/零代码平台、小程序、BaaS、Serverless 等。
通过万能的 API,前端可以定制任何数据、任何结构。
大部分 HTTP 请求后端再也不用写接口了,更不用写文档了。
From 6e4894d5f6191b15d31ea1eaf5f9d9c2b103081e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 15 Aug 2021 22:31:01 +0800 Subject: [PATCH 043/754] =?UTF-8?q?=E9=80=82=E7=94=A8=E8=8C=83=E5=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E4=BD=8E=E4=BB=A3=E7=A0=81/=E9=9B=B6?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=B9=B3=E5=8F=B0=E3=80=81=E5=B0=8F=E7=A8=8B?= =?UTF-8?q?=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 58161b681..21107f029 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
为 简单的增删改查、复杂的查询、简单的事务操作 提供了完全自动化的万能 API。
能大幅降低开发和沟通成本,简化开发流程,缩短开发周期。
-适合中小型前后端分离的项目,尤其是 互联网创业项目、企业自用项目、低代码/零代码平台、小程序、BaaS、Serverless 等。
+适合中小型前后端分离的项目,尤其是 创业项目、内部项目、低代码/零代码、小程序、BaaS、Serverless 等。
通过万能的 API,前端可以定制任何数据、任何结构。
大部分 HTTP 请求后端再也不用写接口了,更不用写文档了。
From bb387c2b1922e7ef9a315a3559d355ac3044781a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 15 Aug 2021 22:32:23 +0800 Subject: [PATCH 044/754] =?UTF-8?q?=E9=80=82=E7=94=A8=E8=8C=83=E5=9B=B4?= =?UTF-8?q?=EF=BC=9A=E5=88=9D=E5=88=9B=E9=A1=B9=E7=9B=AE=E3=80=81=E5=86=85?= =?UTF-8?q?=E9=83=A8=E9=A1=B9=E7=9B=AE=E3=80=81=E4=BD=8E=E4=BB=A3=E7=A0=81?= =?UTF-8?q?/=E9=9B=B6=E4=BB=A3=E7=A0=81=E3=80=81=E5=B0=8F=E7=A8=8B?= =?UTF-8?q?=E5=BA=8F=E3=80=81BaaS=E3=80=81Serverless=20=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 21107f029..448ded30e 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
为 简单的增删改查、复杂的查询、简单的事务操作 提供了完全自动化的万能 API。
能大幅降低开发和沟通成本,简化开发流程,缩短开发周期。
-适合中小型前后端分离的项目,尤其是 创业项目、内部项目、低代码/零代码、小程序、BaaS、Serverless 等。
+适合中小型前后端分离的项目,尤其是 初创项目、内部项目、低代码/零代码、小程序、BaaS、Serverless 等。
通过万能的 API,前端可以定制任何数据、任何结构。
大部分 HTTP 请求后端再也不用写接口了,更不用写文档了。
From 3487e6b0a210ca5c8ea418639d1887dd8a24d62e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 18 Aug 2021 11:03:37 +0800 Subject: [PATCH 045/754] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20DB2=20=E5=92=8C=20ClickHouse=20=E7=9A=84=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=EF=BC=8C=E6=84=9F=E8=B0=A2=E4=B8=A4=E4=BD=8D=E4=BD=9C?= =?UTF-8?q?=E8=80=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/andream7/apijson-db2 https://github.com/chenyanlann/APIJSONDemo_ClickHouse --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 448ded30e..722cf554b 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,9 @@ This source code is licensed under the Apache License Version 2.0
    + +

From a2135b7bf34c16ef25a8fa983f4e1c7f8bea0a88 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 19 Aug 2021 23:39:47 +0800 Subject: [PATCH 046/754] =?UTF-8?q?=E6=9D=A5=E8=87=AA=E8=85=BE=E8=AE=AF?= =?UTF-8?q?=E7=9A=84=E4=B8=BB=E9=A1=B9=E7=9B=AE=E8=B4=A1=E7=8C=AE=E8=80=85?= =?UTF-8?q?=E5=92=8C=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE=E4=BD=9C=E8=80=85?= =?UTF-8?q?=E5=88=86=E5=88=AB=20+=201=EF=BC=8C=E6=84=9F=E8=B0=A2=E8=B4=A1?= =?UTF-8?q?=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/292 https://github.com/haolingzhang1/APIJson--demo --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 722cf554b..f781ddc7b 100644 --- a/README.md +++ b/README.md @@ -241,7 +241,7 @@ https://github.com/Tencent/APIJSON/issues/187 ### 贡献者们 -主项目 APIJSON 的贡献者们(5 个腾讯工程师、1 个知乎基础研发架构师、1 个圆通工程师 等):
+主项目 APIJSON 的贡献者们(6 个腾讯工程师、1 个知乎基础研发架构师、1 个圆通工程师 等):
https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md


-生态周边项目的作者们(1 个腾讯工程师、1 个字节跳动工程师 等):
+生态周边项目的作者们(2 个腾讯工程师、1 个字节跳动工程师 等):
https://github.com/search?o=desc&q=apijson&s=stars&type=Repositories
https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count
From bc8086c268538cf9843760a53089995695b03fdb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 24 Aug 2021 16:45:44 +0800 Subject: [PATCH 047/754] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E5=90=8D?= =?UTF-8?q?=E5=8D=95=E6=96=B0=E5=A2=9E=E4=B8=80=E4=B8=AA=E8=85=BE=E8=AE=AF?= =?UTF-8?q?=E5=B7=A5=E7=A8=8B=E5=B8=88=EF=BC=8CPull=20Request=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=B0=8F=E6=94=B9=E6=96=87=E6=A1=A3=E6=88=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=20=E7=9A=84=E7=AE=80=E8=A6=81=E6=AD=A5=E9=AA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md --- CONTRIBUTING.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b737956dd..cbeeda0f5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,6 +34,7 @@ - [yeyuezhishou](https://github.com/yeyuezhishou)(圆通工程师) - [kenlig](https://github.com/kenlig) - [andream7](https://github.com/andream7) +- [haolingzhang1](https://github.com/haolingzhang1)(腾讯工程师,还开源了 APIJson--demo) #### 其中特别致谢:
@@ -74,6 +75,19 @@ APIJSON 作为腾讯开源的知名热门项目,贡献代码除了可以给简 我们除了希望听到您的反馈和建议外,我们也希望您接受代码形式的直接帮助,对我们的 GitHub 发出 Pull Request 请求。 +### 如果是小改文档或代码 + +直接点文件右上角的编辑图标按钮
+![image](https://user-images.githubusercontent.com/5738175/130585672-8bd49ae5-2978-4ad6-a7a6-de0a0c2d0b68.png) + +
+ +然后底部简要输入修改说明,点击 Commit Change 按钮
+![image](https://user-images.githubusercontent.com/5738175/130586073-4a6aea74-3c88-4cd9-9c93-ffaba1270ab8.png) + + +### 如果有比较大的改动 + 以下是具体步骤:(如果使用本步骤,GitHub 可能不会把贡献者添加到 Contributors 中,推荐用以下 [详细的图文步骤](https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md#%E8%AF%A6%E7%BB%86%E7%9A%84%E5%9B%BE%E6%96%87%E6%AD%A5%E9%AA%A4%E5%8F%AF%E5%8F%82%E8%80%83%E4%BB%A5%E4%B8%8B%E4%BB%BB%E6%84%8F%E4%B8%80%E7%AF%87)) #### Fork 仓库 From 31e04e255843b47d271ff4809eaa8101d0e5c8f7 Mon Sep 17 00:00:00 2001 From: kenlig <28685375+kenlig@users.noreply.github.com> Date: Wed, 25 Aug 2021 23:21:04 +0800 Subject: [PATCH 048/754] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index f781ddc7b..ec4942a67 100644 --- a/README.md +++ b/README.md @@ -493,6 +493,9 @@ https://www.bilibili.com/video/BV18h411z7FK APIJSON 后端教程(5):Final https://www.bilibili.com/video/BV1GM4y1N7XJ + +APIJSON 后端教程(6):uliweb_apijson +https://www.bilibili.com/video/BV1yb4y1S79v/ APIJSON配套文档: https://github.com/kenlig/apijsondocs From 31530941337b00cb85288d97aa63978939daca0f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 2 Sep 2021 11:15:30 +0800 Subject: [PATCH 049/754] =?UTF-8?q?=E5=AE=8C=E5=96=84=20=E4=BF=9D=E6=8C=81?= =?UTF-8?q?=E4=B8=8E=20APIJSON=20=E4=BB=93=E5=BA=93=E7=9A=84=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=20=E7=9A=84=E5=8F=AF=E8=A7=86=E5=8C=96=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E6=AD=A5=E9=AA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cbeeda0f5..d782601c9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -114,8 +114,10 @@ $ git remote add APIJSON git@github.com:Tencent/APIJSON.git #### 保持与 APIJSON 仓库的同步 -更新上游仓库: +直接在 fork Repo 的首页点 Contribute > Open pull request +![image](https://user-images.githubusercontent.com/5738175/131775861-15a2bf9e-4e85-4588-bd2d-f9d78b76541c.png) +或者 ```bash $ git pull --rebase # 等同于以下两条命令 From f7a05907de2c2ff3c80b09b5d156308e43091510 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 2 Sep 2021 11:15:56 +0800 Subject: [PATCH 050/754] Update CONTRIBUTING.md --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d782601c9..3d9510094 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -115,6 +115,7 @@ $ git remote add APIJSON git@github.com:Tencent/APIJSON.git #### 保持与 APIJSON 仓库的同步 直接在 fork Repo 的首页点 Contribute > Open pull request + ![image](https://user-images.githubusercontent.com/5738175/131775861-15a2bf9e-4e85-4588-bd2d-f9d78b76541c.png) 或者 From 77177d1ac53a7d93eb3b14aa20403fe09b75e146 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 2 Sep 2021 11:16:23 +0800 Subject: [PATCH 051/754] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3d9510094..0f4b61858 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -116,7 +116,7 @@ $ git remote add APIJSON git@github.com:Tencent/APIJSON.git 直接在 fork Repo 的首页点 Contribute > Open pull request -![image](https://user-images.githubusercontent.com/5738175/131775861-15a2bf9e-4e85-4588-bd2d-f9d78b76541c.png) +![image](https://user-images.githubusercontent.com/5738175/131776033-74caf279-ebbf-45f1-a9c1-beff937a87fb.png) 或者 ```bash From 454ec0399f96f9b2122c8d094f5e3e9852cdb7bf Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 2 Sep 2021 16:12:14 +0800 Subject: [PATCH 052/754] =?UTF-8?q?=E7=AE=80=E4=BB=8B=E6=96=B0=E5=A2=9E=20?= =?UTF-8?q?"=E9=9B=B6=E4=BB=A3=E7=A0=81=E5=AE=9E=E6=97=B6=E6=BB=A1?= =?UTF-8?q?=E8=B6=B3=E5=8D=83=E5=8F=98=E4=B8=87=E5=8C=96=E7=9A=84=E5=90=84?= =?UTF-8?q?=E7=A7=8D=E6=96=B0=E5=A2=9E=E5=92=8C=E5=8F=98=E6=9B=B4=E9=9C=80?= =?UTF-8?q?=E6=B1=82"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ec4942a67..272440207 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
-为 简单的增删改查、复杂的查询、简单的事务操作 提供了完全自动化的万能 API。
+为各种增删改查提供了完全自动化的万能 API,零代码实时满足千变万化的各种新增和变更需求。
能大幅降低开发和沟通成本,简化开发流程,缩短开发周期。
适合中小型前后端分离的项目,尤其是 初创项目、内部项目、低代码/零代码、小程序、BaaS、Serverless 等。
From 685c835c15b38398a92d8c9fc3005dfa0f064d3c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 2 Sep 2021 21:37:31 +0800 Subject: [PATCH 053/754] update users(companies) and contributors --- README-English.md | 72 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 16 deletions(-) diff --git a/README-English.md b/README-English.md index 40ab91fa7..e5bffa45c 100644 --- a/README-English.md +++ b/README-English.md @@ -308,24 +308,48 @@ If you have any questions or suggestions, you can [create an issue](https://gith ### Users of this project:
- - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + +

[More APIJSON Users](https://github.com/Tencent/APIJSON/issues/73) ### Contributers of APIJSON: -Here are the contributers of this project and authors of other projects for ecosystem of APIJSON: +Contributers for the APIJSON core project(6 Tencent engineers、1 Zhihu architect、1 Yuantong engineer, etc.):
+https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
@@ -350,11 +374,23 @@ Here are the contributers of this project and authors of other projects for ecos - -
+ + + + + +
+
+ +Authors of other projects for ecosystem of APIJSON(2 Tencent engineers、1 Bytedance(TikTok) engineer, etc.):
+https://github.com/search?o=desc&q=apijson&s=stars&type=Repositories
+https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count
+
+ @@ -383,8 +419,12 @@ Here are the contributers of this project and authors of other projects for ecos +

-Thanks to all contributers of APIJSON! + +
+Thanks to all contributers of APIJSON! +
From e89f8baf1e19d6fa7707aaf71d5c0491aa7bfd9c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 2 Sep 2021 21:43:05 +0800 Subject: [PATCH 054/754] Update users(Tencent) and contributors(from Tencent, Zhihu, YTO Express) --- README-English.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README-English.md b/README-English.md index e5bffa45c..f49d8385a 100644 --- a/README-English.md +++ b/README-English.md @@ -348,7 +348,7 @@ If you have any questions or suggestions, you can [create an issue](https://gith [More APIJSON Users](https://github.com/Tencent/APIJSON/issues/73) ### Contributers of APIJSON: -Contributers for the APIJSON core project(6 Tencent engineers、1 Zhihu architect、1 Yuantong engineer, etc.):
+Contributers for the APIJSON core project(6 Tencent engineers、1 Zhihu architect、1 YTO Express engineer, etc.):
https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
Date: Thu, 2 Sep 2021 21:44:19 +0800 Subject: [PATCH 055/754] updated users(Tencent) --- README-English.md | 53 ++++++++++++++++------------------------------- 1 file changed, 18 insertions(+), 35 deletions(-) diff --git a/README-English.md b/README-English.md index f49d8385a..cef9f1c02 100644 --- a/README-English.md +++ b/README-English.md @@ -307,42 +307,25 @@ If you have any questions or suggestions, you can [create an issue](https://gith ### Users of this project: +https://github.com/Tencent/APIJSON/issues/187
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ + + +
+ + + + + + + + + + + + +
[More APIJSON Users](https://github.com/Tencent/APIJSON/issues/73) From 4fa1d53c1c8aa547dd905bd31c2a6edf62838fa4 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 2 Sep 2021 21:58:19 +0800 Subject: [PATCH 056/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8C=85=E6=8B=AC=20?= =?UTF-8?q?1=20=E4=B8=AA=E8=85=BE=E8=AE=AF=E5=B7=A5=E7=A8=8B=E5=B8=88?= =?UTF-8?q?=E5=9C=A8=E5=86=85=E7=9A=84=208=20=E4=B8=AA=E8=B4=A1=E7=8C=AE?= =?UTF-8?q?=E8=80=85=EF=BC=8C=E6=84=9F=E8=B0=A2=E5=A4=A7=E5=AE=B6=E7=9A=84?= =?UTF-8?q?=E8=B4=A1=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 272440207..746389473 100644 --- a/README.md +++ b/README.md @@ -272,6 +272,14 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
+ + + + + + + +

From 0a68549610c69466f89f0b7219d7982b1d3f88ba Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 2 Sep 2021 22:12:53 +0800 Subject: [PATCH 057/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8C=85=E6=8B=AC=20?= =?UTF-8?q?1=20=E4=B8=AA=E8=85=BE=E8=AE=AF=E5=B7=A5=E7=A8=8B=E5=B8=88?= =?UTF-8?q?=E5=9C=A8=E5=86=85=E7=9A=84=208=20=E4=B8=AA=E8=B4=A1=E7=8C=AE?= =?UTF-8?q?=E8=80=85=EF=BC=8C=E6=84=9F=E8=B0=A2=E5=A4=A7=E5=AE=B6=E7=9A=84?= =?UTF-8?q?=E8=B4=A1=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0f4b61858..7329001d3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,9 +32,15 @@ - [jun0315](https://github.com/jun0315)(腾讯工程师) - [JieJo](https://github.com/JieJo) - [yeyuezhishou](https://github.com/yeyuezhishou)(圆通工程师) -- [kenlig](https://github.com/kenlig) -- [andream7](https://github.com/andream7) +- [kenlig](https://github.com/kenlig)(还开源了 apijsondocs) +- [andream7](https://github.com/andream7)(还开源了 apijson-db2) +- [qiujunlin](https://github.com/qiujunlin)(还开源了 APIJSONDemo) +- [HANXU2018](https://github.com/HANXU2018)(还开源了 APIJSON-DOC) +- [hclown9804](https://github.com/hclown9804) +- [chenyanlann](https://github.com/chenyanlann)(还开源了 APIJSONDemo_ClickHouse) - [haolingzhang1](https://github.com/haolingzhang1)(腾讯工程师,还开源了 APIJson--demo) +- [jerrylususu](https://github.com/jerrylususu)(还开源了 apijson_todo_demo 和 apijson_role_extend) +- [Dalezee](https://github.com/Dalezee)(还开源了 apijson_camp) #### 其中特别致谢:
From ee5adc0b818f6645d6506f2623cc72780ea9970a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 2 Sep 2021 22:28:00 +0800 Subject: [PATCH 058/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8C=85=E6=8B=AC=20?= =?UTF-8?q?1=20=E4=B8=AA=E8=85=BE=E8=AE=AF=E5=B7=A5=E7=A8=8B=E5=B8=88?= =?UTF-8?q?=E5=9C=A8=E5=86=85=E7=9A=84=208=20=E4=B8=AA=E8=B4=A1=E7=8C=AE?= =?UTF-8?q?=E8=80=85=EF=BC=8C=E6=84=9F=E8=B0=A2=E5=A4=A7=E5=AE=B6=E7=9A=84?= =?UTF-8?q?=E8=B4=A1=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7329001d3..de776b2b9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,7 +41,7 @@ - [haolingzhang1](https://github.com/haolingzhang1)(腾讯工程师,还开源了 APIJson--demo) - [jerrylususu](https://github.com/jerrylususu)(还开源了 apijson_todo_demo 和 apijson_role_extend) - [Dalezee](https://github.com/Dalezee)(还开源了 apijson_camp) - +- [aaronlinv](https://github.com/aaronlinv) #### 其中特别致谢:
justinfengchen 提交的 6 个 Commits, 对 APIJSON 做出了 3,130 增加和 0 处删减(截止 2020/11/04 日);
From 01b310a3e33e54e3d0a73864132492115ac82add Mon Sep 17 00:00:00 2001 From: qiujunlin <1757591067@qq.com> Date: Fri, 3 Sep 2021 02:18:02 +0800 Subject: [PATCH 059/754] add orm support --- .../src/main/java/apijson/StringUtil.java | 16 +- .../java/apijson/orm/AbstractSQLConfig.java | 522 ++++++++---------- .../java/apijson/orm/FunctionsAndRaws.java | 519 +++++++++++++++++ 3 files changed, 756 insertions(+), 301 deletions(-) create mode 100644 APIJSONORM/src/main/java/apijson/orm/FunctionsAndRaws.java diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index 9fac4e34e..bd3b87f7b 100755 --- a/APIJSONORM/src/main/java/apijson/StringUtil.java +++ b/APIJSONORM/src/main/java/apijson/StringUtil.java @@ -725,7 +725,21 @@ public static String getPrice(double price, int formatType) { } } - + /** 数组以指定分隔s拼接 + * @param arr + * @param s + * @return + */ + public static String join(String[] arr, String s) { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < arr.length; i++) { + stringBuilder.append(arr[i]); + if(i TABLE_KEY_MAP; public static final List CONFIG_TABLE_LIST; public static final List DATABASE_LIST; - // 自定义原始 SQL 片段 Map:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL - public static final Map RAW_MAP; - // 允许调用的 SQL 函数:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL - public static final Map SQL_FUNCTION_MAP; + static { // 凡是 SQL 边界符、分隔符、注释符 都不允许,例如 ' " ` ( ) ; # -- ,以免拼接 SQL 时被注入意外可执行指令 PATTERN_RANGE = Pattern.compile("^[0-9%,!=\\<\\>/\\.\\+\\-\\*\\^]+$"); // ^[a-zA-Z0-9_*%!=<>(),"]+$ 导致 exists(select*from(Comment)) 通过! - PATTERN_FUNCTION = Pattern.compile("^[A-Za-z0-9%,:_@&~!=\\<\\>\\|\\[\\]\\{\\} /\\.\\+\\-\\*\\^\\?\\$]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值 - + PATTERN_FUNCTION = Pattern.compile("^[A-Za-z0-9%,:_@&~!=\\<\\>\\|\\[\\]\\{\\} /\\.\\+\\-\\*\\^\\?\\(\\)\\$]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值 + PATTERN_STRING = Pattern.compile("^[,#;\"`]+$"); TABLE_KEY_MAP = new HashMap(); TABLE_KEY_MAP.put(Table.class.getSimpleName(), Table.TABLE_NAME); @@ -126,186 +124,6 @@ public abstract class AbstractSQLConfig implements SQLConfig { DATABASE_LIST.add(DATABASE_DB2); DATABASE_LIST.add(DATABASE_CLICKHOUSE); - - RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 - - - SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 - - // MySQL 字符串函数 - SQL_FUNCTION_MAP.put("ascii", ""); // ASCII(s) 返回字符串 s 的第一个字符的 ASCII 码。 - SQL_FUNCTION_MAP.put("char_length", ""); // CHAR_LENGTH(s) 返回字符串 s 的字符数 - SQL_FUNCTION_MAP.put("character_length", ""); // CHARACTER_LENGTH(s) 返回字符串 s 的字符数 - SQL_FUNCTION_MAP.put("concat", ""); // CONCAT(s1, s2...sn) 字符串 s1,s2 等多个字符串合并为一个字符串 - SQL_FUNCTION_MAP.put("concat_ws", ""); // CONCAT_WS(x, s1, s2...sn) 同 CONCAT(s1, s2 ...) 函数,但是每个字符串之间要加上 x,x 可以是分隔符 - SQL_FUNCTION_MAP.put("field", ""); // FIELD(s, s1, s2...) 返回第一个字符串 s 在字符串列表 (s1, s2...)中的位置 - SQL_FUNCTION_MAP.put("find_in_set", ""); // FIND_IN_SET(s1, s2) 返回在字符串s2中与s1匹配的字符串的位置 - SQL_FUNCTION_MAP.put("format", ""); // FORMAT(x, n) 函数可以将数字 x 进行格式化 "#,###.##", 将 x 保留到小数点后 n 位,最后一位四舍五入。 - SQL_FUNCTION_MAP.put("insert", ""); // INSERT(s1, x, len, s2) 字符串 s2 替换 s1 的 x 位置开始长度为 len 的字符串 - SQL_FUNCTION_MAP.put("locate", ""); // LOCATE(s1, s) 从字符串 s 中获取 s1 的开始位置 - SQL_FUNCTION_MAP.put("lcase", ""); // LCASE(s) 将字符串 s 的所有字母变成小写字母 - SQL_FUNCTION_MAP.put("left", ""); // LEFT(s, n) 返回字符串 s 的前 n 个字符 - SQL_FUNCTION_MAP.put("length", ""); // LENGTH(s) 返回字符串 s 的字符数 - SQL_FUNCTION_MAP.put("lower", ""); // LOWER(s) 将字符串 s 的所有字母变成小写字母 - SQL_FUNCTION_MAP.put("lpad", ""); // LPAD(s1, len, s2) 在字符串 s1 的开始处填充字符串 s2,使字符串长度达到 len - SQL_FUNCTION_MAP.put("ltrim", ""); // LTRIM(s) 去掉字符串 s 开始处的空格 - SQL_FUNCTION_MAP.put("mid", ""); // MID(s, n, len) 从字符串 s 的 n 位置截取长度为 len 的子字符串,同 SUBSTRING(s, n, len) - SQL_FUNCTION_MAP.put("position", ""); // POSITION(s, s1); 从字符串 s 中获取 s1 的开始位置 - SQL_FUNCTION_MAP.put("repeat", ""); // REPEAT(s, n) 将字符串 s 重复 n 次 - SQL_FUNCTION_MAP.put("replace", ""); // REPLACE(s, s1, s2) 将字符串 s2 替代字符串 s 中的字符串 s1 - SQL_FUNCTION_MAP.put("reverse", ""); // REVERSE(s); // ) 将字符串s的顺序反过来 - SQL_FUNCTION_MAP.put("right", ""); // RIGHT(s, n) 返回字符串 s 的后 n 个字符 - SQL_FUNCTION_MAP.put("rpad", ""); // RPAD(s1, len, s2) 在字符串 s1 的结尾处添加字符串 s2,使字符串的长度达到 len - SQL_FUNCTION_MAP.put("rtrim", ""); // RTRIM", ""); // ) 去掉字符串 s 结尾处的空格 - SQL_FUNCTION_MAP.put("space", ""); // SPACE(n) 返回 n 个空格 - SQL_FUNCTION_MAP.put("strcmp", ""); // STRCMP(s1, s2) 比较字符串 s1 和 s2,如果 s1 与 s2 相等返回 0 ,如果 s1>s2 返回 1,如果 s1d2 之间相隔的天数 - SQL_FUNCTION_MAP.put("date_add", ""); // DATE_ADD(d,INTERVAL expr type) 计算起始日期 d 加上一个时间段后的日期 - SQL_FUNCTION_MAP.put("date_format", ""); // DATE_FORMAT(d,f) 按表达式 f的要求显示日期 d - SQL_FUNCTION_MAP.put("date_sub", ""); // DATE_SUB(date,INTERVAL expr type) 函数从日期减去指定的时间间隔。 - SQL_FUNCTION_MAP.put("day", ""); // DAY(d) 返回日期值 d 的日期部分 - SQL_FUNCTION_MAP.put("dayname", ""); // DAYNAME(d) 返回日期 d 是星期几,如 Monday,Tuesday - SQL_FUNCTION_MAP.put("dayofmonth", ""); // DAYOFMONTH(d) 计算日期 d 是本月的第几天 - SQL_FUNCTION_MAP.put("dayofweek", ""); // DAYOFWEEK(d) 日期 d 今天是星期几,1 星期日,2 星期一,以此类推 - SQL_FUNCTION_MAP.put("dayofyear", ""); // DAYOFYEAR(d) 计算日期 d 是本年的第几天 - SQL_FUNCTION_MAP.put("extract", ""); // EXTRACT(type FROM d) 从日期 d 中获取指定的值,type 指定返回的值。 - SQL_FUNCTION_MAP.put("from_days", ""); // FROM_DAYS(n) 计算从 0000 年 1 月 1 日开始 n 天后的日期 - SQL_FUNCTION_MAP.put("hour", ""); // 'HOUR(t) 返回 t 中的小时值 - SQL_FUNCTION_MAP.put("last_day", ""); // LAST_DAY(d) 返回给给定日期的那一月份的最后一天 - SQL_FUNCTION_MAP.put("localtime", ""); // LOCALTIME() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("localtimestamp", ""); // LOCALTIMESTAMP() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("makedate", ""); // MAKEDATE(year, day-of-year) 基于给定参数年份 year 和所在年中的天数序号 day-of-year 返回一个日期 - SQL_FUNCTION_MAP.put("maketime", ""); // MAKETIME(hour, minute, second) 组合时间,参数分别为小时、分钟、秒 - SQL_FUNCTION_MAP.put("microsecond", ""); // MICROSECOND(date) 返回日期参数所对应的微秒数 - SQL_FUNCTION_MAP.put("minute", ""); // MINUTE(t) 返回 t 中的分钟值 - SQL_FUNCTION_MAP.put("monthname", ""); // MONTHNAME(d) 返回日期当中的月份名称,如 November - SQL_FUNCTION_MAP.put("month", ""); // MONTH(d) 返回日期d中的月份值,1 到 12 - SQL_FUNCTION_MAP.put("now", ""); // NOW() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("period_add", ""); // PERIOD_ADD(period, number) 为 年-月 组合日期添加一个时段 - SQL_FUNCTION_MAP.put("period_diff", ""); // PERIOD_DIFF(period1, period2) 返回两个时段之间的月份差值 - SQL_FUNCTION_MAP.put("quarter", ""); // QUARTER(d) 返回日期d是第几季节,返回 1 到 4 - SQL_FUNCTION_MAP.put("second", ""); // SECOND(t) 返回 t 中的秒钟值 - SQL_FUNCTION_MAP.put("sec_to_time", ""); // SEC_TO_TIME", ""); // ) 将以秒为单位的时间 s 转换为时分秒的格式 - SQL_FUNCTION_MAP.put("str_to_date", ""); // STR_TO_DATE", ""); // tring, format_mask) 将字符串转变为日期 - SQL_FUNCTION_MAP.put("subdate", ""); // SUBDATE(d,n) 日期 d 减去 n 天后的日期 - SQL_FUNCTION_MAP.put("subtime", ""); // SUBTIME(t,n) 时间 t 减去 n 秒的时间 - SQL_FUNCTION_MAP.put("sysdate", ""); // SYSDATE() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("time", ""); // TIME(expression) 提取传入表达式的时间部分 - SQL_FUNCTION_MAP.put("time_format", ""); // TIME_FORMAT(t,f) 按表达式 f 的要求显示时间 t - SQL_FUNCTION_MAP.put("time_to_sec", ""); // TIME_TO_SEC(t) 将时间 t 转换为秒 - SQL_FUNCTION_MAP.put("timediff", ""); // TIMEDIFF(time1, time2) 计算时间差值 - SQL_FUNCTION_MAP.put("timestamp", ""); // TIMESTAMP(expression, interval) 单个参数时,函数返回日期或日期时间表达式;有2个参数时,将参数加和 - SQL_FUNCTION_MAP.put("to_days", ""); // TO_DAYS(d) 计算日期 d 距离 0000 年 1 月 1 日的天数 - SQL_FUNCTION_MAP.put("week", ""); // WEEK(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 - SQL_FUNCTION_MAP.put("weekday", ""); // WEEKDAY(d) 日期 d 是星期几,0 表示星期一,1 表示星期二 - SQL_FUNCTION_MAP.put("weekofyear", ""); // WEEKOFYEAR(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 - SQL_FUNCTION_MAP.put("year", ""); // YEAR(d) 返回年份 - SQL_FUNCTION_MAP.put("yearweek", ""); // YEARWEEK(date, mode) 返回年份及第几周(0到53),mode 中 0 表示周天,1表示周一,以此类推 - SQL_FUNCTION_MAP.put("unix_timestamp", ""); // UNIX_TIMESTAMP(date) 获取UNIX时间戳函数,返回一个以 UNIX 时间戳为基础的无符号整数 - SQL_FUNCTION_MAP.put("from_unixtime", ""); // FROM_UNIXTIME(date) 将 UNIX 时间戳转换为时间格式,与UNIX_TIMESTAMP互为反函数 - - // MYSQL JSON 函数 - SQL_FUNCTION_MAP.put("json_append", ""); // JSON_APPEND(json_doc, path, val[, path, val] ...)) 插入JSON数组 - SQL_FUNCTION_MAP.put("json_array", ""); // JSON_ARRAY(val1, val2...) 创建JSON数组 - SQL_FUNCTION_MAP.put("json_array_append", ""); // JSON_ARRAY_APPEND(json_doc, val) 将数据附加到JSON文档 - SQL_FUNCTION_MAP.put("json_array_insert", ""); // JSON_ARRAY_INSERT(json_doc, val) 插入JSON数组 - SQL_FUNCTION_MAP.put("json_contains", ""); // JSON_CONTAINS(json_doc, val) JSON文档是否在路径中包含特定对象 - SQL_FUNCTION_MAP.put("json_contains_path", ""); // JSON_CONTAINS_PATH(json_doc, path) JSON文档是否在路径中包含任何数据 - SQL_FUNCTION_MAP.put("json_depth", ""); // JSON_DEPTH(json_doc) JSON文档的最大深度 - SQL_FUNCTION_MAP.put("json_extract", ""); // JSON_EXTRACT(json_doc, path) 从JSON文档返回数据 - SQL_FUNCTION_MAP.put("json_insert", ""); // JSON_INSERT(json_doc, val) 将数据插入JSON文档 - SQL_FUNCTION_MAP.put("json_keys", ""); // JSON_KEYS(json_doc[, path]) JSON文档中的键数组 - SQL_FUNCTION_MAP.put("json_length", ""); // JSON_LENGTH(json_doc) JSON文档中的元素数 - SQL_FUNCTION_MAP.put("json_merge", ""); // JSON_MERGE(json_doc1, json_doc2) (已弃用) 合并JSON文档,保留重复的键。JSON_MERGE_PRESERVE()的已弃用同义词 - SQL_FUNCTION_MAP.put("json_merge_patch", ""); // JSON_MERGE_PATCH(json_doc1, json_doc2) 合并JSON文档,替换重复键的值 - SQL_FUNCTION_MAP.put("json_merge_preserve", ""); // JSON_MERGE_PRESERVE(json_doc1, json_doc2) 合并JSON文档,保留重复的键 - SQL_FUNCTION_MAP.put("json_object", ""); // JSON_OBJECT(key1, val1, key2, val2...) 创建JSON对象 - SQL_FUNCTION_MAP.put("json_overlaps", ""); // JSON_OVERLAPS(json_doc1, json_doc2) (引入8.0.17) 比较两个JSON文档,如果它们具有相同的键值对或数组元素,则返回TRUE(1),否则返回FALSE(0) - SQL_FUNCTION_MAP.put("json_pretty", ""); // JSON_PRETTY(json_doc) 以易于阅读的格式打印JSON文档 - SQL_FUNCTION_MAP.put("json_quote", ""); // JSON_QUOTE(json_doc1) 引用JSON文档 - SQL_FUNCTION_MAP.put("json_remove", ""); // JSON_REMOVE(json_doc1, path) 从JSON文档中删除数据 - SQL_FUNCTION_MAP.put("json_replace", ""); // JSON_REPLACE(json_doc1, val1, val2) 替换JSON文档中的值 - SQL_FUNCTION_MAP.put("json_schema_valid", ""); // JSON_SCHEMA_VALID(json_doc) (引入8.0.17) 根据JSON模式验证JSON文档;如果文档针对架构进行验证,则返回TRUE / 1;否则,则返回FALSE / 0 - SQL_FUNCTION_MAP.put("json_schema_validation_report", ""); // JSON_SCHEMA_VALIDATION_REPORT(json_doc, mode) (引入8.0.17) 根据JSON模式验证JSON文档;以JSON格式返回有关验证结果的报告,包括成功或失败以及失败原因 - SQL_FUNCTION_MAP.put("json_search", ""); // JSON_SEARCH(json_doc, val) JSON文档中值的路径 - SQL_FUNCTION_MAP.put("json_set", ""); // JSON_SET(json_doc, val) 将数据插入JSON文档 - // SQL_FUNCTION_MAP.put("json_storage_free", ""); // JSON_STORAGE_FREE() 部分更新后,JSON列值的二进制表示形式中的可用空间 - // SQL_FUNCTION_MAP.put("json_storage_size", ""); // JSON_STORAGE_SIZE() 用于存储JSON文档的二进制表示的空间 - SQL_FUNCTION_MAP.put("json_table", ""); // JSON_TABLE() 从JSON表达式返回数据作为关系表 - SQL_FUNCTION_MAP.put("json_type", ""); // JSON_TYPE(json_doc) JSON值类型 - SQL_FUNCTION_MAP.put("json_unquote", ""); // JSON_UNQUOTE(json_doc) 取消引用JSON值 - SQL_FUNCTION_MAP.put("json_valid", ""); // JSON_VALID(json_doc) JSON值是否有效 - SQL_FUNCTION_MAP.put("json_arrayagg", ""); // JSON_ARRAYAGG(key) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 数组 - SQL_FUNCTION_MAP.put("json_objectagg", ""); // JSON_OBJECTAGG(key, val)) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 对象 - - // MySQL 高级函数 - // SQL_FUNCTION_MAP.put("bin", ""); // BIN(x) 返回 x 的二进制编码 - // SQL_FUNCTION_MAP.put("binary", ""); // BINARY(s) 将字符串 s 转换为二进制字符串 - SQL_FUNCTION_MAP.put("case", ""); // CASE 表示函数开始,END 表示函数结束。如果 condition1 成立,则返回 result1, 如果 condition2 成立,则返回 result2,当全部不成立则返回 result,而当有一个成立之后,后面的就不执行了。 - SQL_FUNCTION_MAP.put("cast", ""); // CAST(x AS type) 转换数据类型 - SQL_FUNCTION_MAP.put("coalesce", ""); // COALESCE(expr1, expr2, ...., expr_n) 返回参数中的第一个非空表达式(从左向右) - // SQL_FUNCTION_MAP.put("conv", ""); // CONV(x,f1,f2) 返回 f1 进制数变成 f2 进制数 - // SQL_FUNCTION_MAP.put("convert", ""); // CONVERT(s, cs) 函数将字符串 s 的字符集变成 cs - SQL_FUNCTION_MAP.put("if", ""); // IF(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。 - SQL_FUNCTION_MAP.put("ifnull", ""); // IFNULL(v1,v2) 如果 v1 的值不为 NULL,则返回 v1,否则返回 v2。 - SQL_FUNCTION_MAP.put("isnull", ""); // ISNULL(expression) 判断表达式是否为 NULL - SQL_FUNCTION_MAP.put("nullif", ""); // NULLIF(expr1, expr2) 比较两个字符串,如果字符串 expr1 与 expr2 相等 返回 NULL,否则返回 expr1 - SQL_FUNCTION_MAP.put("group_concat", ""); // GROUP_CONCAT([DISTINCT], s1, s2...) - } @@ -784,14 +602,14 @@ public String getHavingString(boolean hasPrefix) { method = expression.substring(0, start); if (method.isEmpty() == false) { - if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { + if (FunctionsAndRaws.SQL_FUNCTION_MAP == null || FunctionsAndRaws.SQL_FUNCTION_MAP.isEmpty()) { if (StringUtil.isName(method) == false) { throw new IllegalArgumentException("字符 " + method + " 不合法!" + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); } } - else if (SQL_FUNCTION_MAP.containsKey(method) == false) { + else if (FunctionsAndRaws.SQL_FUNCTION_MAP.containsKey(method) == false) { throw new IllegalArgumentException("字符 " + method + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); @@ -983,7 +801,7 @@ public String getRawSQL(String key, Object value) throws Exception { + "对应的 " + key + ":value 中 value 类型只能为 String!"); } - String rawSQL = containRaw ? RAW_MAP.get(value) : null; + String rawSQL = containRaw ? FunctionsAndRaws.RAW_MAP.get(value) : null; if (containRaw) { if (rawSQL == null) { throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" @@ -1052,7 +870,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { for (String c : column) { if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 - if ("".equals(RAW_MAP.get(c)) || RAW_MAP.containsValue(c)) { // newSQLConfig 提前处理好的 + if ("".equals(FunctionsAndRaws.RAW_MAP.get(c)) || FunctionsAndRaws.RAW_MAP.containsValue(c)) { // newSQLConfig 提前处理好的 //排除@raw中的值,以避免使用date_format(date,'%Y-%m-%d %H:%i:%s') 时,冒号的解析出错 //column.remove(c); continue; @@ -1164,7 +982,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { expression = keys[i]; if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 - if ("".equals(RAW_MAP.get(expression)) || RAW_MAP.containsValue(expression)) { // newSQLConfig 提前处理好的 + if ("".equals(FunctionsAndRaws.RAW_MAP.get(expression)) || FunctionsAndRaws.RAW_MAP.containsValue(expression)) { // newSQLConfig 提前处理好的 continue; } @@ -1183,153 +1001,257 @@ public String getColumnString(boolean inSQLJoin) throws Exception { throw new UnsupportedOperationException("@column:value 的 value 中字符串 " + expression + " 不合法!" + "不允许传超过 100 个字符的函数或表达式!请用 @raw 简化传参!"); } + keys[i] = getColumnPrase(expression); + } + String c = StringUtil.getString(keys); + c = c + (StringUtil.isEmpty(joinColumn, true) ? "" : ", " + joinColumn);//不能在这里改,后续还要用到: + return c; + default: + throw new UnsupportedOperationException( + "服务器内部错误:getColumnString 不支持 " + RequestMethod.getName(getMethod()) + + " 等 [GET,GETS,HEAD,HEADS,POST] 外的ReuqestMethod!" + ); + } + } - int start = expression.indexOf("("); - int end = 0; - if (start >= 0) { - end = expression.lastIndexOf(")"); - if (start >= end) { - throw new IllegalArgumentException("字符 " + expression + " 不合法!" - + "@column:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); + /** + * 解析@column 中以“;”分隔的表达式("@column":"expression1;expression2;expression2;....")中的expression + * + * @param expression + * @return + */ + public String getColumnPrase(String expression) { + String quote = getQuote(); + int start = expression.indexOf('('); + if (start < 0) { + //没有函数 ,可能是字段,也可能是 DISTINCT xx + String cks[] = parseArgsSplitWithComma(expression, true); + expression = StringUtil.getString(cks); + } else { + //有函数,但不是窗口函数 + if (expression.indexOf("OVER") < 0) { + int end = expression.lastIndexOf(")"); + if (start >= end) { + throw new IllegalArgumentException("字符 " + expression + " 不合法!" + + "@column:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); + } + String fun = expression.substring(0, start); + if (fun.isEmpty() == false) { + if (FunctionsAndRaws.SQL_FUNCTION_MAP == null || FunctionsAndRaws.SQL_FUNCTION_MAP.isEmpty()) { + if (StringUtil.isName(fun) == false) { + throw new IllegalArgumentException("字符 " + fun + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); + } + } else if (FunctionsAndRaws.SQL_FUNCTION_MAP.containsKey(fun) == false) { + throw new IllegalArgumentException("字符 " + fun + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); } + } - method = expression.substring(0, start); - boolean distinct = i <= 0 && method.startsWith(PREFFIX_DISTINCT); - String fun = distinct ? method.substring(PREFFIX_DISTINCT.length()) : method; + String s = expression.substring(start + 1, end); + // 解析函数内的参数 + String ckeys[] = parseArgsSplitWithComma(s, false); + + String suffix = expression.substring(end + 1, expression.length()); //:contactCount + int index = suffix.lastIndexOf(":"); + String alias = index < 0 ? "" : suffix.substring(index + 1); //contactCount + suffix = index < 0 ? suffix : suffix.substring(0, index); + if (alias.isEmpty() == false && StringUtil.isName(alias) == false) { + throw new IllegalArgumentException("字符串 " + alias + " 不合法!" + + "预编译模式下 @column:value 中 value里面用 ; 分割的每一项" + + " function(arg0,arg1,...):alias 中 alias 必须是1个单词!并且不要有多余的空格!"); + } - if (fun.isEmpty() == false) { - if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { - if (StringUtil.isName(fun) == false) { - throw new IllegalArgumentException("字符 " + method + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); - } - } - else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { - throw new IllegalArgumentException("字符 " + method + " 不合法!" + if (suffix.isEmpty() == false && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { + throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" + + "预编译模式下 @column:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); + } + + String origin = fun + "(" + StringUtil.getString(ckeys) + ")" + suffix; + expression = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); + + } else { + //是窗口函数 fun(arg0,agr1) OVER (agr0 agr1 ...) + int overindex = expression.indexOf("OVER"); // OVER 的位置 + String s1 = expression.substring(0, overindex); // OVER 前半部分 + String s2 = expression.substring(overindex); // OVER 后半部分 + + int index1 = s1.indexOf("("); // 函数 "(" 的起始位置 + String fun = s1.substring(0, index1); // 函数名称 + int end = s2.lastIndexOf(")"); // 后半部分 “)” 的位置 + + if (index1 >= end) { + throw new IllegalArgumentException("字符 " + expression + " 不合法!" + + "@column:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); + } + if (fun.isEmpty() == false) { + if (FunctionsAndRaws.SQL_FUNCTION_MAP == null || FunctionsAndRaws.SQL_FUNCTION_MAP.isEmpty()) { + if (StringUtil.isName(fun) == false) { + throw new IllegalArgumentException("字符 " + fun + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); } + } else if (FunctionsAndRaws.SQL_FUNCTION_MAP.containsKey(fun) == false) { + throw new IllegalArgumentException("字符 " + fun + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); } - } - boolean isColumn = start < 0; + // 获取前半部分函数的参数解析 fun(arg0,agr1) + String agrsString1[] = parseArgsSplitWithComma(s1.substring(index1 + 1, s1.lastIndexOf(")")), false); - String[] ckeys = StringUtil.split(isColumn ? expression : expression.substring(start + 1, end)); - String quote = getQuote(); + int index2 = s2.indexOf("("); // 后半部分 “(”的起始位置 + String argString2 = s2.substring(index2 + 1, end); // 后半部分的参数 + // 别名 + String alias = s2.lastIndexOf(":") < 0 ? null : s2.substring(s2.lastIndexOf(":") + 1); + // 获取后半部分的参数解析 (agr0 agr1 ...) + String argsString2[] = parseArgsSplitWithComma(argString2,false); + expression = fun + "(" + StringUtil.getString(agrsString1) + ")" + " OVER " + "(" + StringUtil.getString(argsString2) + ")" + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); } + } + return expression; - // if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! - if (ckeys != null && ckeys.length > 0) { + } - boolean distinct; - String origin; - String alias; - int index; - for (int j = 0; j < ckeys.length; j++) { - index = isColumn ? ckeys[j].lastIndexOf(":") : -1; //StringUtil.split返回数组中,子项不会有null - origin = index < 0 ? ckeys[j] : ckeys[j].substring(0, index); - alias = index < 0 ? null : ckeys[j].substring(index + 1); + /** + * 解析函数参数或者字段,此函数对于解析字段 和 函数内参数通用 + * + * @param param + * @param isColumn true:不是函数参数。false:是函数参数 + * @return + */ + private String[] parseArgsSplitWithComma(String param, boolean isColumn) { + // 以"," 分割参数 + String quote = getQuote(); + String tableAlias = getAliasWithQuote(); + String ckeys[] = StringUtil.split(param); // 以","分割参数 + if (ckeys != null && ckeys.length > 0) { + String origin; + String alias; + int index; + for (int i = 0; i < ckeys.length; i++) { + // 如果参数包含 "'" ,解析字符串 + if (ckeys[i].contains("'")) { + int count = 0; + for (int j = 0; j < ckeys[i].length(); j++) { + if (ckeys[i].charAt(j) == '\'') count++; + } + // 排除字符串中参数中包含 ' 的情况和不以' 开头和结尾的情况,同时排除 cast('s' as ...) 以空格分隔的参数中包含字符串的情况 + if (count != 2 || !(ckeys[i].startsWith("'") && ckeys[i].endsWith("'"))) { + throw new IllegalArgumentException("字符串 " + ckeys[i] + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中字符串参数不合法,必须以 ' 开头, ' 结尾,字符串中不能包含 ' "); + } + //sql 注入判断 判断 + origin = (ckeys[i].substring(1, ckeys[i].length() - 1)); + if (origin.contains("--") || PATTERN_STRING.matcher(origin).matches() == true) { + throw new IllegalArgumentException("字符 " + ckeys[i] + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中所有字符串 arg 都必须不符合正则表达式 " + PATTERN_STRING + " 且不包含连续减号 -- !"); + } - distinct = j <= 0 && origin.startsWith(PREFFIX_DISTINCT); - if (distinct) { - origin = origin.substring(PREFFIX_DISTINCT.length()); - } + // 1.字符串不是字段也没有别名,所以不解析别名 2. 是字符串,进行预编译,使用getValue() ,对字符串进行截取 + ckeys[i] = getValue(ckeys[i].substring(1, ckeys[i].length() - 1)).toString(); - if (isPrepared()) { - if (isColumn) { - if (StringUtil.isName(origin) == false || (alias != null && StringUtil.isName(alias) == false)) { - throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" - + "预编译模式下 @column:value 中 value里面用 , 分割的每一项" - + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!" - + "DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); - } + } else { + // 参数不包含",",即不是字符串 + // 解析参数:1. 字段 ,2. 是以空格分隔的参数 eg: cast(now() as date) + index = ckeys[i].lastIndexOf(":"); //StringUtil.split返回数组中,子项不会有null + origin = index < 0 ? ckeys[i] : ckeys[i].substring(0, index); //获取 : 之前的 + alias = index < 0 ? null : ckeys[i].substring(index + 1); + if (isPrepared()) { + if (isColumn) { + if (StringUtil.isName(origin) == false || (alias != null && StringUtil.isName(alias) == false)) { + throw new IllegalArgumentException("字符 " + ckeys[i] + " 不合法!" + + "预编译模式下 @column:value 中 value里面用 , 分割的每一项" + + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!" + + "关键字必须全大写,且以空格分隔的参数,空格必须只有 1 个!其它情况不允许空格!"); } - else { - // if ((StringUtil.isName(origin) == false || origin.startsWith("_"))) { - if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { - throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); - } + } else { + if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { + throw new IllegalArgumentException("字符 " + ckeys[i] + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); } } + } + // 以空格分割参数 + String mkes[] = StringUtil.split(ckeys[i], " ", true); - //JOIN 副表不再在外层加副表名前缀 userId AS `Commet.userId`, 而是直接 userId AS `userId` - boolean isName = false; - if (StringUtil.isNumer(origin)) { + boolean isName = false; + //如果参数中含有空格(少数情况) 比如 fun(arg1 arg2 arg3 ,arg4) 中的 arg1 arg2 arg3,比如 DISTINCT id + if (mkes != null && mkes.length >= 2) { + ckeys[i] = praseArgsSplitWithSpace(mkes); + } else { + // 如果参数没有空格 + if ("".equals(FunctionsAndRaws.RAW_MAP.get(origin))) { + // do nothing , 比如 toDate(now()) , + } else if (StringUtil.isNumer(origin)) { //do nothing - } - else if (StringUtil.isName(origin)) { + } else if (StringUtil.isName(origin)) { origin = quote + origin + quote; isName = true; - } - else { + } else { origin = getValue(origin).toString(); } - if (isName && isKeyPrefix()) { - ckeys[j] = tableAlias + "." + origin; - // if (isColumn) { - // ckeys[j] += " AS " + quote + (isMain() ? "" : tableAlias + ".") + (StringUtil.isEmpty(alias, true) ? origin : alias) + quote; - // } + ckeys[i] = tableAlias + "." + origin; if (isColumn && StringUtil.isEmpty(alias, true) == false) { - ckeys[j] += " AS " + quote + alias + quote; + ckeys[i] += " AS " + quote + alias + quote; } } else { - ckeys[j] = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); - } - - if (distinct) { - ckeys[j] = PREFFIX_DISTINCT + ckeys[j]; + ckeys[i] = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); } } - // } - - } - if (isColumn) { - keys[i] = StringUtil.getString(ckeys); } - else { - String suffix = expression.substring(end + 1, expression.length()); //:contactCount - int index = suffix.lastIndexOf(":"); - String alias = index < 0 ? "" : suffix.substring(index + 1); //contactCount - suffix = index < 0 ? suffix : suffix.substring(0, index); + } + } + return ckeys; + } - if (alias.isEmpty() == false && StringUtil.isName(alias) == false) { - throw new IllegalArgumentException("字符串 " + alias + " 不合法!" - + "预编译模式下 @column:value 中 value里面用 ; 分割的每一项" - + " function(arg0,arg1,...):alias 中 alias 必须是1个单词!并且不要有多余的空格!"); - } - if (suffix.isEmpty() == false && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { - throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" - + "预编译模式下 @column:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); + /** + * 只解析以空格分隔的参数 + * + * @param mkes + * @return + */ + private String praseArgsSplitWithSpace(String mkes[]) { + String quote = getQuote(); + String tableAlias = getAliasWithQuote(); + boolean isName = false; + // 包含空格的参数 肯定不包含别名 不用处理别名 + if (mkes != null && mkes.length > 0) { + for (int j = 0; j < mkes.length; j++) { + // now()/AS/ DISTINCT/VALUE 等等放在RAW_MAP中 + if ("".equals(FunctionsAndRaws.RAW_MAP.get(mkes[j]))) { + continue; + } else if (StringUtil.isNumer(mkes[j])) { + // do nothing + } else { + //这里为什么还要做一次判断 是因为解析窗口函数调用的时候会判断一次 + if (isPrepared()) { + if (mkes[j].startsWith("_") || mkes[j].contains("--") || PATTERN_FUNCTION.matcher(mkes[j]).matches() == false) { + throw new IllegalArgumentException("字符 " + mkes[j] + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); + } } - - String origin = method + "(" + StringUtil.getString(ckeys) + ")" + suffix; - // if (isKeyPrefix()) { - // keys[i] = origin + " AS " + quote + (isMain() ? "" : tableAlias + ".") + (StringUtil.isEmpty(alias, true) ? method : alias) + quote; - // } - // else { - keys[i] = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); - // } + mkes[j] = quote + mkes[j] + quote; + isName = true; + } + if (isName && isKeyPrefix()) { + mkes[j] = tableAlias + "." + mkes[j]; } - } - - String c = StringUtil.getString(keys); - c = c + (StringUtil.isEmpty(joinColumn, true) ? "" : ", " + joinColumn);//不能在这里改,后续还要用到: - return isMain() && isDistinct() ? PREFFIX_DISTINCT + c : c; - default: - throw new UnsupportedOperationException( - "服务器内部错误:getColumnString 不支持 " + RequestMethod.getName(getMethod()) - + " 等 [GET,GETS,HEAD,HEADS,POST] 外的ReuqestMethod!" - ); } + // 返回重新以" "拼接后的参数 + return StringUtil.join(mkes, " "); } diff --git a/APIJSONORM/src/main/java/apijson/orm/FunctionsAndRaws.java b/APIJSONORM/src/main/java/apijson/orm/FunctionsAndRaws.java new file mode 100644 index 000000000..e0b945340 --- /dev/null +++ b/APIJSONORM/src/main/java/apijson/orm/FunctionsAndRaws.java @@ -0,0 +1,519 @@ +package apijson.orm; + +import java.util.LinkedHashMap; +import java.util.Map; + + +public class FunctionsAndRaws { + // 自定义原始 SQL 片段 Map:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL + public static final Map RAW_MAP; + // 允许调用的 SQL 函数:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL + public static final Map SQL_FUNCTION_MAP; + static { + RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 + + // mysql关键字 + RAW_MAP.put("AS",""); + RAW_MAP.put("VALUE",""); + RAW_MAP.put("DISTINCT",""); + + //时间 + RAW_MAP.put("DATE",""); + RAW_MAP.put("now()",""); + RAW_MAP.put("DATETIME",""); + RAW_MAP.put("DateTime",""); + RAW_MAP.put("SECOND",""); + RAW_MAP.put("MINUTE",""); + RAW_MAP.put("HOUR",""); + RAW_MAP.put("DAY",""); + RAW_MAP.put("WEEK",""); + RAW_MAP.put("MONTH",""); + RAW_MAP.put("QUARTER",""); + RAW_MAP.put("YEAR",""); + RAW_MAP.put("json",""); + RAW_MAP.put("unit",""); + + //MYSQL 数据类型 BINARY,CHAR,DATETIME,TIME,DECIMAL,SIGNED,UNSIGNED + RAW_MAP.put("BINARY",""); + RAW_MAP.put("SIGNED",""); + RAW_MAP.put("DECIMAL",""); + RAW_MAP.put("BINARY",""); + RAW_MAP.put("UNSIGNED",""); + RAW_MAP.put("CHAR",""); + RAW_MAP.put("TIME",""); + + //窗口函数关键字 + RAW_MAP.put("OVER",""); + RAW_MAP.put("INTERVAL",""); + RAW_MAP.put("ORDER",""); + RAW_MAP.put("BY",""); + RAW_MAP.put("PARTITION",""); //往前 + RAW_MAP.put("DESC",""); + RAW_MAP.put("ASC",""); + RAW_MAP.put("FOLLOWING","");//往后 + RAW_MAP.put("BETWEEN",""); + RAW_MAP.put("AND",""); + RAW_MAP.put("ROWS",""); + + + SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 + + + //窗口函数 + SQL_FUNCTION_MAP.put("rank", "");//得到数据项在分组中的排名,排名相等的时候会留下空位 + SQL_FUNCTION_MAP.put("dense_rank", ""); //得到数据项在分组中的排名,排名相等的时候不会留下空位 + SQL_FUNCTION_MAP.put("row_number", "");//按照分组中的顺序生成序列,不存在重复的序列 + SQL_FUNCTION_MAP.put("ntile", "");//用于将分组数据按照顺序切分成N片,返回当前切片值,不支持ROWS_BETWEE + SQL_FUNCTION_MAP.put("first_value", "");//取分组排序后,截止到当前行,分组内第一个值 + SQL_FUNCTION_MAP.put("last_value", "");//取分组排序后,截止到当前行,分组内的最后一个值 + SQL_FUNCTION_MAP.put("lag", "");//统计窗口内往上第n行值。第一个参数为列名,第二个参数为往上第n行(可选,默认为1),第三个参数为默认值(当往上第n行为NULL时候,取默认值,如不指定,则为NULL) + SQL_FUNCTION_MAP.put("lead", "");//统计窗口内往下第n行值。第一个参数为列名,第二个参数为往下第n行(可选,默认为1),第三个参数为默认值(当往下第n行为NULL时候,取默认值,如不指定,则为NULL) + SQL_FUNCTION_MAP.put("cume_dist", "");//)返回(小于等于当前行值的行数)/(当前分组内的总行数) + SQL_FUNCTION_MAP.put("percent_rank", "");//返回(组内当前行的rank值-1)/(分组内做总行数-1) + + // MySQL 字符串函数 + SQL_FUNCTION_MAP.put("ascii", ""); // ASCII(s) 返回字符串 s 的第一个字符的 ASCII 码。 + SQL_FUNCTION_MAP.put("char_length", ""); // CHAR_LENGTH(s) 返回字符串 s 的字符数 + SQL_FUNCTION_MAP.put("character_length", ""); // CHARACTER_LENGTH(s) 返回字符串 s 的字符数 + SQL_FUNCTION_MAP.put("concat", ""); // CONCAT(s1, s2...sn) 字符串 s1,s2 等多个字符串合并为一个字符串 + SQL_FUNCTION_MAP.put("concat_ws", ""); // CONCAT_WS(x, s1, s2...sn) 同 CONCAT(s1, s2 ...) 函数,但是每个字符串之间要加上 x,x 可以是分隔符 + SQL_FUNCTION_MAP.put("field", ""); // FIELD(s, s1, s2...) 返回第一个字符串 s 在字符串列表 (s1, s2...)中的位置 + SQL_FUNCTION_MAP.put("find_in_set", ""); // FIND_IN_SET(s1, s2) 返回在字符串s2中与s1匹配的字符串的位置 + SQL_FUNCTION_MAP.put("format", ""); // FORMAT(x, n) 函数可以将数字 x 进行格式化 "#,###.##", 将 x 保留到小数点后 n 位,最后一位四舍五入。 + SQL_FUNCTION_MAP.put("insert", ""); // INSERT(s1, x, len, s2) 字符串 s2 替换 s1 的 x 位置开始长度为 len 的字符串 + SQL_FUNCTION_MAP.put("locate", ""); // LOCATE(s1, s) 从字符串 s 中获取 s1 的开始位置 + SQL_FUNCTION_MAP.put("lcase", ""); // LCASE(s) 将字符串 s 的所有字母变成小写字母 + SQL_FUNCTION_MAP.put("left", ""); // LEFT(s, n) 返回字符串 s 的前 n 个字符 + SQL_FUNCTION_MAP.put("length", ""); // LENGTH(s) 返回字符串 s 的字符数 + SQL_FUNCTION_MAP.put("lower", ""); // LOWER(s) 将字符串 s 的所有字母变成小写字母 + SQL_FUNCTION_MAP.put("lpad", ""); // LPAD(s1, len, s2) 在字符串 s1 的开始处填充字符串 s2,使字符串长度达到 len + SQL_FUNCTION_MAP.put("ltrim", ""); // LTRIM(s) 去掉字符串 s 开始处的空格 + SQL_FUNCTION_MAP.put("mid", ""); // MID(s, n, len) 从字符串 s 的 n 位置截取长度为 len 的子字符串,同 SUBSTRING(s, n, len) + SQL_FUNCTION_MAP.put("position", ""); // POSITION(s, s1); 从字符串 s 中获取 s1 的开始位置 + SQL_FUNCTION_MAP.put("repeat", ""); // REPEAT(s, n) 将字符串 s 重复 n 次 + SQL_FUNCTION_MAP.put("replace", ""); // REPLACE(s, s1, s2) 将字符串 s2 替代字符串 s 中的字符串 s1 + SQL_FUNCTION_MAP.put("reverse", ""); // REVERSE(s); // ) 将字符串s的顺序反过来 + SQL_FUNCTION_MAP.put("right", ""); // RIGHT(s, n) 返回字符串 s 的后 n 个字符 + SQL_FUNCTION_MAP.put("rpad", ""); // RPAD(s1, len, s2) 在字符串 s1 的结尾处添加字符串 s2,使字符串的长度达到 len + SQL_FUNCTION_MAP.put("rtrim", ""); // RTRIM", ""); // ) 去掉字符串 s 结尾处的空格 + SQL_FUNCTION_MAP.put("space", ""); // SPACE(n) 返回 n 个空格 + SQL_FUNCTION_MAP.put("strcmp", ""); // STRCMP(s1, s2) 比较字符串 s1 和 s2,如果 s1 与 s2 相等返回 0 ,如果 s1>s2 返回 1,如果 s1d2 之间相隔的天数 + SQL_FUNCTION_MAP.put("date_add", ""); // DATE_ADD(d,INTERVAL expr type) 计算起始日期 d 加上一个时间段后的日期 + SQL_FUNCTION_MAP.put("date_format", ""); // DATE_FORMAT(d,f) 按表达式 f的要求显示日期 d + SQL_FUNCTION_MAP.put("date_sub", ""); // DATE_SUB(date,INTERVAL expr type) 函数从日期减去指定的时间间隔。 + SQL_FUNCTION_MAP.put("day", ""); // DAY(d) 返回日期值 d 的日期部分 + SQL_FUNCTION_MAP.put("dayname", ""); // DAYNAME(d) 返回日期 d 是星期几,如 Monday,Tuesday + SQL_FUNCTION_MAP.put("dayofmonth", ""); // DAYOFMONTH(d) 计算日期 d 是本月的第几天 + SQL_FUNCTION_MAP.put("dayofweek", ""); // DAYOFWEEK(d) 日期 d 今天是星期几,1 星期日,2 星期一,以此类推 + SQL_FUNCTION_MAP.put("dayofyear", ""); // DAYOFYEAR(d) 计算日期 d 是本年的第几天 + SQL_FUNCTION_MAP.put("extract", ""); // EXTRACT(type FROM d) 从日期 d 中获取指定的值,type 指定返回的值。 + SQL_FUNCTION_MAP.put("from_days", ""); // FROM_DAYS(n) 计算从 0000 年 1 月 1 日开始 n 天后的日期 + SQL_FUNCTION_MAP.put("hour", ""); // 'HOUR(t) 返回 t 中的小时值 + SQL_FUNCTION_MAP.put("last_day", ""); // LAST_DAY(d) 返回给给定日期的那一月份的最后一天 + SQL_FUNCTION_MAP.put("localtime", ""); // LOCALTIME() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("localtimestamp", ""); // LOCALTIMESTAMP() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("makedate", ""); // MAKEDATE(year, day-of-year) 基于给定参数年份 year 和所在年中的天数序号 day-of-year 返回一个日期 + SQL_FUNCTION_MAP.put("maketime", ""); // MAKETIME(hour, minute, second) 组合时间,参数分别为小时、分钟、秒 + SQL_FUNCTION_MAP.put("microsecond", ""); // MICROSECOND(date) 返回日期参数所对应的微秒数 + SQL_FUNCTION_MAP.put("minute", ""); // MINUTE(t) 返回 t 中的分钟值 + SQL_FUNCTION_MAP.put("monthname", ""); // MONTHNAME(d) 返回日期当中的月份名称,如 November + SQL_FUNCTION_MAP.put("month", ""); // MONTH(d) 返回日期d中的月份值,1 到 12 + SQL_FUNCTION_MAP.put("now", ""); // NOW() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("period_add", ""); // PERIOD_ADD(period, number) 为 年-月 组合日期添加一个时段 + SQL_FUNCTION_MAP.put("period_diff", ""); // PERIOD_DIFF(period1, period2) 返回两个时段之间的月份差值 + SQL_FUNCTION_MAP.put("quarter", ""); // QUARTER(d) 返回日期d是第几季节,返回 1 到 4 + SQL_FUNCTION_MAP.put("second", ""); // SECOND(t) 返回 t 中的秒钟值 + SQL_FUNCTION_MAP.put("sec_to_time", ""); // SEC_TO_TIME", ""); // ) 将以秒为单位的时间 s 转换为时分秒的格式 + SQL_FUNCTION_MAP.put("str_to_date", ""); // STR_TO_DATE", ""); // tring, format_mask) 将字符串转变为日期 + SQL_FUNCTION_MAP.put("subdate", ""); // SUBDATE(d,n) 日期 d 减去 n 天后的日期 + SQL_FUNCTION_MAP.put("subtime", ""); // SUBTIME(t,n) 时间 t 减去 n 秒的时间 + SQL_FUNCTION_MAP.put("sysdate", ""); // SYSDATE() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("time", ""); // TIME(expression) 提取传入表达式的时间部分 + SQL_FUNCTION_MAP.put("time_format", ""); // TIME_FORMAT(t,f) 按表达式 f 的要求显示时间 t + SQL_FUNCTION_MAP.put("time_to_sec", ""); // TIME_TO_SEC(t) 将时间 t 转换为秒 + SQL_FUNCTION_MAP.put("timediff", ""); // TIMEDIFF(time1, time2) 计算时间差值 + SQL_FUNCTION_MAP.put("timestamp", ""); // TIMESTAMP(expression, interval) 单个参数时,函数返回日期或日期时间表达式;有2个参数时,将参数加和 + SQL_FUNCTION_MAP.put("to_days", ""); // TO_DAYS(d) 计算日期 d 距离 0000 年 1 月 1 日的天数 + SQL_FUNCTION_MAP.put("week", ""); // WEEK(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 + SQL_FUNCTION_MAP.put("weekday", ""); // WEEKDAY(d) 日期 d 是星期几,0 表示星期一,1 表示星期二 + SQL_FUNCTION_MAP.put("weekofyear", ""); // WEEKOFYEAR(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 + SQL_FUNCTION_MAP.put("year", ""); // YEAR(d) 返回年份 + SQL_FUNCTION_MAP.put("yearweek", ""); // YEARWEEK(date, mode) 返回年份及第几周(0到53),mode 中 0 表示周天,1表示周一,以此类推 + SQL_FUNCTION_MAP.put("unix_timestamp", ""); // UNIX_TIMESTAMP(date) 获取UNIX时间戳函数,返回一个以 UNIX 时间戳为基础的无符号整数 + SQL_FUNCTION_MAP.put("from_unixtime", ""); // FROM_UNIXTIME(date) 将 UNIX 时间戳转换为时间格式,与UNIX_TIMESTAMP互为反函数 + + // MYSQL JSON 函数 + SQL_FUNCTION_MAP.put("json_append", ""); // JSON_APPEND(json_doc, path, val[, path, val] ...)) 插入JSON数组 + SQL_FUNCTION_MAP.put("json_array", ""); // JSON_ARRAY(val1, val2...) 创建JSON数组 + SQL_FUNCTION_MAP.put("json_array_append", ""); // JSON_ARRAY_APPEND(json_doc, val) 将数据附加到JSON文档 + SQL_FUNCTION_MAP.put("json_array_insert", ""); // JSON_ARRAY_INSERT(json_doc, val) 插入JSON数组 + SQL_FUNCTION_MAP.put("json_contains", ""); // JSON_CONTAINS(json_doc, val) JSON文档是否在路径中包含特定对象 + SQL_FUNCTION_MAP.put("json_contains_path", ""); // JSON_CONTAINS_PATH(json_doc, path) JSON文档是否在路径中包含任何数据 + SQL_FUNCTION_MAP.put("json_depth", ""); // JSON_DEPTH(json_doc) JSON文档的最大深度 + SQL_FUNCTION_MAP.put("json_extract", ""); // JSON_EXTRACT(json_doc, path) 从JSON文档返回数据 + SQL_FUNCTION_MAP.put("json_insert", ""); // JSON_INSERT(json_doc, val) 将数据插入JSON文档 + SQL_FUNCTION_MAP.put("json_keys", ""); // JSON_KEYS(json_doc[, path]) JSON文档中的键数组 + SQL_FUNCTION_MAP.put("json_length", ""); // JSON_LENGTH(json_doc) JSON文档中的元素数 + SQL_FUNCTION_MAP.put("json_merge", ""); // JSON_MERGE(json_doc1, json_doc2) (已弃用) 合并JSON文档,保留重复的键。JSON_MERGE_PRESERVE()的已弃用同义词 + SQL_FUNCTION_MAP.put("json_merge_patch", ""); // JSON_MERGE_PATCH(json_doc1, json_doc2) 合并JSON文档,替换重复键的值 + SQL_FUNCTION_MAP.put("json_merge_preserve", ""); // JSON_MERGE_PRESERVE(json_doc1, json_doc2) 合并JSON文档,保留重复的键 + SQL_FUNCTION_MAP.put("json_object", ""); // JSON_OBJECT(key1, val1, key2, val2...) 创建JSON对象 + SQL_FUNCTION_MAP.put("json_overlaps", ""); // JSON_OVERLAPS(json_doc1, json_doc2) (引入8.0.17) 比较两个JSON文档,如果它们具有相同的键值对或数组元素,则返回TRUE(1),否则返回FALSE(0) + SQL_FUNCTION_MAP.put("json_pretty", ""); // JSON_PRETTY(json_doc) 以易于阅读的格式打印JSON文档 + SQL_FUNCTION_MAP.put("json_quote", ""); // JSON_QUOTE(json_doc1) 引用JSON文档 + SQL_FUNCTION_MAP.put("json_remove", ""); // JSON_REMOVE(json_doc1, path) 从JSON文档中删除数据 + SQL_FUNCTION_MAP.put("json_replace", ""); // JSON_REPLACE(json_doc1, val1, val2) 替换JSON文档中的值 + SQL_FUNCTION_MAP.put("json_schema_valid", ""); // JSON_SCHEMA_VALID(json_doc) (引入8.0.17) 根据JSON模式验证JSON文档;如果文档针对架构进行验证,则返回TRUE / 1;否则,则返回FALSE / 0 + SQL_FUNCTION_MAP.put("json_schema_validation_report", ""); // JSON_SCHEMA_VALIDATION_REPORT(json_doc, mode) (引入8.0.17) 根据JSON模式验证JSON文档;以JSON格式返回有关验证结果的报告,包括成功或失败以及失败原因 + SQL_FUNCTION_MAP.put("json_search", ""); // JSON_SEARCH(json_doc, val) JSON文档中值的路径 + SQL_FUNCTION_MAP.put("json_set", ""); // JSON_SET(json_doc, val) 将数据插入JSON文档 + // SQL_FUNCTION_MAP.put("json_storage_free", ""); // JSON_STORAGE_FREE() 部分更新后,JSON列值的二进制表示形式中的可用空间 + // SQL_FUNCTION_MAP.put("json_storage_size", ""); // JSON_STORAGE_SIZE() 用于存储JSON文档的二进制表示的空间 + SQL_FUNCTION_MAP.put("json_table", ""); // JSON_TABLE() 从JSON表达式返回数据作为关系表 + SQL_FUNCTION_MAP.put("json_type", ""); // JSON_TYPE(json_doc) JSON值类型 + SQL_FUNCTION_MAP.put("json_unquote", ""); // JSON_UNQUOTE(json_doc) 取消引用JSON值 + SQL_FUNCTION_MAP.put("json_valid", ""); // JSON_VALID(json_doc) JSON值是否有效 + SQL_FUNCTION_MAP.put("json_arrayagg", ""); // JSON_ARRAYAGG(key) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 数组 + SQL_FUNCTION_MAP.put("json_objectagg", ""); // JSON_OBJECTAGG(key, val)) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 对象 + + // MySQL 高级函数 + // SQL_FUNCTION_MAP.put("bin", ""); // BIN(x) 返回 x 的二进制编码 + // SQL_FUNCTION_MAP.put("binary", ""); // BINARY(s) 将字符串 s 转换为二进制字符串 + SQL_FUNCTION_MAP.put("case", ""); // CASE 表示函数开始,END 表示函数结束。如果 condition1 成立,则返回 result1, 如果 condition2 成立,则返回 result2,当全部不成立则返回 result,而当有一个成立之后,后面的就不执行了。 + SQL_FUNCTION_MAP.put("cast", ""); // CAST(x AS type) 转换数据类型 + SQL_FUNCTION_MAP.put("coalesce", ""); // COALESCE(expr1, expr2, ...., expr_n) 返回参数中的第一个非空表达式(从左向右) + // SQL_FUNCTION_MAP.put("conv", ""); // CONV(x,f1,f2) 返回 f1 进制数变成 f2 进制数 + // SQL_FUNCTION_MAP.put("convert", ""); // CONVERT(s, cs) 函数将字符串 s 的字符集变成 cs + SQL_FUNCTION_MAP.put("if", ""); // IF(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。 + SQL_FUNCTION_MAP.put("ifnull", ""); // IFNULL(v1,v2) 如果 v1 的值不为 NULL,则返回 v1,否则返回 v2。 + SQL_FUNCTION_MAP.put("isnull", ""); // ISNULL(expression) 判断表达式是否为 NULL + SQL_FUNCTION_MAP.put("nullif", ""); // NULLIF(expr1, expr2) 比较两个字符串,如果字符串 expr1 与 expr2 相等 返回 NULL,否则返回 expr1 + SQL_FUNCTION_MAP.put("group_concat", ""); // GROUP_CONCAT([DISTINCT], s1, s2...) + + //clickhouse 字符串函数 注释的函数表示返回的格式暂时不支持,如:返回数组 ,同时包含因版本不同 clickhosue不支持的函数,版本 + SQL_FUNCTION_MAP.put("empty", ""); // empty(s) 对于空字符串s返回1,对于非空字符串返回0 + SQL_FUNCTION_MAP.put("notEmpty", ""); //notEmpty(s) 对于空字符串返回0,对于非空字符串返回1。 + SQL_FUNCTION_MAP.put("lengthUTF8", ""); //假定字符串以UTF-8编码组成的文本,返回此字符串的Unicode字符长度。如果传入的字符串不是UTF-8编码,则函数可能返回一个预期外的值 + SQL_FUNCTION_MAP.put("lcase", ""); //将字符串中的ASCII转换为小写 + SQL_FUNCTION_MAP.put("ucase", ""); //将字符串中的ASCII转换为大写。 + SQL_FUNCTION_MAP.put("lowerUTF8", ""); //将字符串转换为小写,函数假设字符串是以UTF-8编码文本的字符集。 + SQL_FUNCTION_MAP.put("upperUTF8", ""); //将字符串转换为大写,函数假设字符串是以UTF-8编码文本的字符集。 + SQL_FUNCTION_MAP.put("isValidUTF8", ""); // 检查字符串是否为有效的UTF-8编码,是则返回1,否则返回0。 + SQL_FUNCTION_MAP.put("toValidUTF8", "");//用�(U+FFFD)字符替换无效的UTF-8字符。所有连续的无效字符都会被替换为一个替换字符。 + SQL_FUNCTION_MAP.put("reverseUTF8", "");//以Unicode字符为单位反转UTF-8编码的字符串。 + SQL_FUNCTION_MAP.put("concatAssumeInjective", ""); // concatAssumeInjective(s1, s2, …) 与concat相同,区别在于,你需要保证concat(s1, s2, s3) -> s4是单射的,它将用于GROUP BY的优化。 + SQL_FUNCTION_MAP.put("substringUTF8", ""); // substringUTF8(s,offset,length)¶ 与’substring’相同,但其操作单位为Unicode字符,函数假设字符串是以UTF-8进行编码的文本。如果不是则可能返回一个预期外的结果(不会抛出异常)。 + SQL_FUNCTION_MAP.put("appendTrailingCharIfAbsent", ""); // appendTrailingCharIfAbsent(s,c) 如果’s’字符串非空并且末尾不包含’c’字符,则将’c’字符附加到末尾 + SQL_FUNCTION_MAP.put("convertCharset", ""); // convertCharset(s,from,to) 返回从’from’中的编码转换为’to’中的编码的字符串’s’。 + SQL_FUNCTION_MAP.put("base64Encode", ""); // base64Encode(s) 将字符串’s’编码成base64 + SQL_FUNCTION_MAP.put("base64Decode", ""); //base64Decode(s) 使用base64将字符串解码成原始字符串。如果失败则抛出异常。 + SQL_FUNCTION_MAP.put("tryBase64Decode", ""); //tryBase64Decode(s) 使用base64将字符串解码成原始字符串。但如果出现错误,将返回空字符串。 + SQL_FUNCTION_MAP.put("endsWith", ""); //endsWith(s,后缀) 返回是否以指定的后缀结尾。如果字符串以指定的后缀结束,则返回1,否则返回0。 + SQL_FUNCTION_MAP.put("startsWith", ""); //startsWith(s,前缀) 返回是否以指定的前缀开头。如果字符串以指定的前缀开头,则返回1,否则返回0。 + SQL_FUNCTION_MAP.put("trimLeft", ""); //trimLeft(s)返回一个字符串,用于删除左侧的空白字符。 + SQL_FUNCTION_MAP.put("trimRight", ""); //trimRight(s) 返回一个字符串,用于删除右侧的空白字符。 + SQL_FUNCTION_MAP.put("trimBoth", ""); //trimBoth(s),用于删除任一侧的空白字符 + SQL_FUNCTION_MAP.put("extractAllGroups", ""); //extractAllGroups(text, regexp) 从正则表达式匹配的非重叠子字符串中提取所有组 + // SQL_FUNCTION_MAP.put("leftPad", ""); //leftPad('string', 'length'[, 'pad_string']) 用空格或指定的字符串从左边填充当前字符串(如果需要,可以多次),直到得到的字符串达到给定的长度 + // SQL_FUNCTION_MAP.put("leftPadUTF8", ""); //leftPadUTF8('string','length'[, 'pad_string']) 用空格或指定的字符串从左边填充当前字符串(如果需要,可以多次),直到得到的字符串达到给定的长度 + // SQL_FUNCTION_MAP.put("rightPad", ""); // rightPad('string', 'length'[, 'pad_string']) 用空格或指定的字符串(如果需要,可以多次)从右边填充当前字符串,直到得到的字符串达到给定的长度 + // SQL_FUNCTION_MAP.put("rightPadUTF8", "");// rightPadUTF8('string','length'[, 'pad_string']) 用空格或指定的字符串(如果需要,可以多次)从右边填充当前字符串,直到得到的字符串达到给定的长度。 + SQL_FUNCTION_MAP.put("normalizeQuery", ""); //normalizeQuery(x) 用占位符替换文字、文字序列和复杂的别名。 + SQL_FUNCTION_MAP.put("normalizedQueryHash", ""); //normalizedQueryHash(x) 为类似查询返回相同的64位散列值,但不包含文字值。有助于对查询日志进行分析 + SQL_FUNCTION_MAP.put("positionUTF8", ""); // positionUTF8(s, needle[, start_pos]) 返回在字符串中找到的子字符串的位置(以Unicode点表示),从1开始。 + SQL_FUNCTION_MAP.put("multiSearchFirstIndex", ""); //multiSearchFirstIndex(s, [needle1, needle2, …, needlen]) 返回字符串s中最左边的needlei的索引i(从1开始),否则返回0 + SQL_FUNCTION_MAP.put("multiSearchAny", ""); // multiSearchAny(s, [needle1, needle2, …, needlen])如果至少有一个字符串needlei匹配字符串s,则返回1,否则返回0。 + SQL_FUNCTION_MAP.put("match", ""); //match(s, pattern) 检查字符串是否与模式正则表达式匹配。re2正则表达式。re2正则表达式的语法比Perl正则表达式的语法更有局限性。 + SQL_FUNCTION_MAP.put("multiMatchAny", ""); //multiMatchAny(s, [pattern1, pattern2, …, patternn]) 与match相同,但是如果没有匹配的正则表达式返回0,如果有匹配的模式返回1 + SQL_FUNCTION_MAP.put("multiMatchAnyIndex", ""); //multiMatchAnyIndex(s, [pattern1, pattern2, …, patternn]) 与multiMatchAny相同,但返回与干堆匹配的任何索引 + SQL_FUNCTION_MAP.put("extract", ""); // extract(s, pattern) 使用正则表达式提取字符串的片段 + SQL_FUNCTION_MAP.put("extractAll", ""); //extractAll(s, pattern) 使用正则表达式提取字符串的所有片段 + SQL_FUNCTION_MAP.put("like", ""); //like(s, pattern) 检查字符串是否与简单正则表达式匹配 + SQL_FUNCTION_MAP.put("notLike", "");// 和‘like’是一样的,但是是否定的 + SQL_FUNCTION_MAP.put("countSubstrings", ""); //countSubstrings(s, needle[, start_pos])返回子字符串出现的次数 + SQL_FUNCTION_MAP.put("countMatches", ""); //返回干s中的正则表达式匹配数。countMatches(s, pattern) + SQL_FUNCTION_MAP.put("replaceOne", ""); //replaceOne(s, pattern, replacement)将' s '中的' pattern '子串的第一个出现替换为' replacement '子串。 + + SQL_FUNCTION_MAP.put("replaceAll", ""); //replaceAll(s, pattern, replacement)/用' replacement '子串替换' s '中所有出现的' pattern '子串 + SQL_FUNCTION_MAP.put("replaceRegexpOne", ""); //replaceRegexpOne(s, pattern, replacement)使用' pattern '正则表达式进行替换 + SQL_FUNCTION_MAP.put("replaceRegexpAll", ""); //replaceRegexpAll(s, pattern, replacement) + SQL_FUNCTION_MAP.put("regexpQuoteMeta", ""); //regexpQuoteMeta(s)该函数在字符串中某些预定义字符之前添加一个反斜杠 + + //clickhouse日期函数 + SQL_FUNCTION_MAP.put("toYear", ""); //将Date或DateTime转换为包含年份编号(AD)的UInt16类型的数字。 + SQL_FUNCTION_MAP.put("toQuarter", ""); //将Date或DateTime转换为包含季度编号的UInt8类型的数字。 + SQL_FUNCTION_MAP.put("toMonth", ""); //Date或DateTime转换为包含月份编号(1-12)的UInt8类型的数字。 + SQL_FUNCTION_MAP.put("toDayOfYear", ""); //将Date或DateTime转换为包含一年中的某一天的编号的UInt16(1-366)类型的数字。 + SQL_FUNCTION_MAP.put("toDayOfMonth", "");//将Date或DateTime转换为包含一月中的某一天的编号的UInt8(1-31)类型的数字。 + SQL_FUNCTION_MAP.put("toDayOfWeek", ""); //将Date或DateTime转换为包含一周中的某一天的编号的UInt8(周一是1, 周日是7)类型的数字。 + SQL_FUNCTION_MAP.put("toHour", ""); //将DateTime转换为包含24小时制(0-23)小时数的UInt8数字。 + SQL_FUNCTION_MAP.put("toMinute", ""); //将DateTime转换为包含一小时中分钟数(0-59)的UInt8数字。 + SQL_FUNCTION_MAP.put("toSecond", ""); //将DateTime转换为包含一分钟中秒数(0-59)的UInt8数字。 + SQL_FUNCTION_MAP.put("toUnixTimestamp", ""); // 对于DateTime参数:将值转换为UInt32类型的数字-Unix时间戳 + SQL_FUNCTION_MAP.put("toStartOfYear", ""); //将Date或DateTime向前取整到本年的第一天。 + SQL_FUNCTION_MAP.put("toStartOfISOYear", ""); // 将Date或DateTime向前取整到ISO本年的第一天。 + SQL_FUNCTION_MAP.put("toStartOfQuarter", "");//将Date或DateTime向前取整到本季度的第一天。 + SQL_FUNCTION_MAP.put("toStartOfMonth", ""); //将Date或DateTime向前取整到本月的第一天。 + SQL_FUNCTION_MAP.put("toMonday", ""); //将Date或DateTime向前取整到本周的星期 + SQL_FUNCTION_MAP.put("toStartOfWeek", ""); //按mode将Date或DateTime向前取整到最近的星期日或星期一。 + SQL_FUNCTION_MAP.put("toStartOfDay", ""); //将DateTime向前取整到今天的开始。 + SQL_FUNCTION_MAP.put("toStartOfHour", ""); //将DateTime向前取整到当前小时的开始。 + SQL_FUNCTION_MAP.put("toStartOfMinute", ""); //将DateTime向前取整到当前分钟的开始。 + SQL_FUNCTION_MAP.put("toStartOfSecond", ""); //将DateTime向前取整到当前秒数的开始。 + SQL_FUNCTION_MAP.put("toStartOfFiveMinute", "");//将DateTime以五分钟为单位向前取整到最接近的时间点。 + SQL_FUNCTION_MAP.put("toStartOfTenMinutes", ""); //将DateTime以十分钟为单位向前取整到最接近的时间点。 + SQL_FUNCTION_MAP.put("toStartOfFifteenMinutes", ""); //将DateTime以十五分钟为单位向前取整到最接近的时间点。 + SQL_FUNCTION_MAP.put("toStartOfInterval", ""); // + SQL_FUNCTION_MAP.put("toTime", ""); //将DateTime中的日期转换为一个固定的日期,同时保留时间部分。 + SQL_FUNCTION_MAP.put("toISOYear", ""); //将Date或DateTime转换为包含ISO年份的UInt16类型的编号。 + SQL_FUNCTION_MAP.put("toISOWeek", ""); // + SQL_FUNCTION_MAP.put("toWeek", "");// 返回Date或DateTime的周数。 + SQL_FUNCTION_MAP.put("toYearWeek", ""); //返回年和周的日期 + SQL_FUNCTION_MAP.put("date_trunc", ""); //截断日期和时间数据到日期的指定部分 + SQL_FUNCTION_MAP.put("date_diff", ""); //回两个日期或带有时间值的日期之间的差值。 + + SQL_FUNCTION_MAP.put("yesterday", ""); //不接受任何参数并在请求执行时的某一刻返回昨天的日期(Date)。 + SQL_FUNCTION_MAP.put("today", ""); //不接受任何参数并在请求执行时的某一刻返回当前日期(Date)。 + SQL_FUNCTION_MAP.put("timeSlot", ""); //将时间向前取整半小时。 + SQL_FUNCTION_MAP.put("toYYYYMM", ""); // + SQL_FUNCTION_MAP.put("toYYYYMMDD", "");// + SQL_FUNCTION_MAP.put("toYYYYMMDDhhmmss", ""); // + SQL_FUNCTION_MAP.put("addYears", ""); // Function adds a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime + SQL_FUNCTION_MAP.put("addMonths", ""); //同上 + SQL_FUNCTION_MAP.put("addWeeks", ""); //同上 + SQL_FUNCTION_MAP.put("addDays", ""); //同上 + SQL_FUNCTION_MAP.put("addHours", ""); //同上 + SQL_FUNCTION_MAP.put("addMinutes", "");//同上 + SQL_FUNCTION_MAP.put("addSeconds", ""); //同上 + SQL_FUNCTION_MAP.put("addQuarters", ""); //同上 + SQL_FUNCTION_MAP.put("subtractYears", ""); //Function subtract a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime + SQL_FUNCTION_MAP.put("subtractMonths", ""); //同上 + SQL_FUNCTION_MAP.put("subtractWeeks", ""); //同上 + SQL_FUNCTION_MAP.put("subtractDays", ""); //同上 + SQL_FUNCTION_MAP.put("subtractours", "");//同上 + SQL_FUNCTION_MAP.put("subtractMinutes", ""); //同上 + SQL_FUNCTION_MAP.put("subtractSeconds", ""); //同上 + SQL_FUNCTION_MAP.put("subtractQuarters", ""); //同上 + SQL_FUNCTION_MAP.put("formatDateTime", ""); //函数根据给定的格式字符串来格式化时间 + SQL_FUNCTION_MAP.put("timestamp_add", ""); //使用提供的日期或日期时间值添加指定的时间值。 + SQL_FUNCTION_MAP.put("timestamp_sub", ""); //从提供的日期或带时间的日期中减去时间间隔。 + + //clickhouse json函数 + SQL_FUNCTION_MAP.put("visitParamHas", ""); //visitParamHas(params, name)检查是否存在«name»名称的字段 + SQL_FUNCTION_MAP.put("visitParamExtractUInt", ""); //visitParamExtractUInt(params, name)将名为«name»的字段的值解析成UInt64。 + SQL_FUNCTION_MAP.put("visitParamExtractInt", ""); //与visitParamExtractUInt相同,但返回Int64。 + SQL_FUNCTION_MAP.put("visitParamExtractFloat", ""); //与visitParamExtractUInt相同,但返回Float64。 + SQL_FUNCTION_MAP.put("visitParamExtractBool", "");//解析true/false值。其结果是UInt8类型的。 + SQL_FUNCTION_MAP.put("visitParamExtractRaw", ""); //返回字段的值,包含空格符。 + SQL_FUNCTION_MAP.put("visitParamExtractString", ""); //使用双引号解析字符串。这个值没有进行转义。如果转义失败,它将返回一个空白字符串。 + SQL_FUNCTION_MAP.put("JSONHas", ""); //如果JSON中存在该值,则返回1。 + SQL_FUNCTION_MAP.put("JSONLength", ""); //返回JSON数组或JSON对象的长度。 + SQL_FUNCTION_MAP.put("JSONType", ""); //返回JSON值的类型。 + SQL_FUNCTION_MAP.put("JSONExtractUInt", ""); //解析JSON并提取值。这些函数类似于visitParam*函数。 + SQL_FUNCTION_MAP.put("JSONExtractInt", ""); // + SQL_FUNCTION_MAP.put("JSONExtractFloat", ""); // + SQL_FUNCTION_MAP.put("JSONExtractBool", ""); // + SQL_FUNCTION_MAP.put("JSONExtractString", ""); //解析JSON并提取字符串。此函数类似于visitParamExtractString函数。 + SQL_FUNCTION_MAP.put("JSONExtract", "");//解析JSON并提取给定ClickHouse数据类型的值。 + SQL_FUNCTION_MAP.put("JSONExtractKeysAndValues", ""); //从JSON中解析键值对,其中值是给定的ClickHouse数据类型 + SQL_FUNCTION_MAP.put("JSONExtractRaw", ""); //返回JSON的部分。 + SQL_FUNCTION_MAP.put("toJSONString", ""); // + + //clickhouse 类型转换函数 + SQL_FUNCTION_MAP.put("toInt8", ""); //toInt8(expr) 转换一个输入值为Int类型 + SQL_FUNCTION_MAP.put("toInt16", ""); + SQL_FUNCTION_MAP.put("toInt32", ""); + SQL_FUNCTION_MAP.put("toInt64", ""); + SQL_FUNCTION_MAP.put("toInt8OrZero", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。 + SQL_FUNCTION_MAP.put("toInt16OrZero", ""); + SQL_FUNCTION_MAP.put("toInt32OrZero", ""); + SQL_FUNCTION_MAP.put("toInt64OrZero", ""); + SQL_FUNCTION_MAP.put("toInt8OrNull", "");//toInt(8|16|32|64)OrNull 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回NULL + SQL_FUNCTION_MAP.put("toInt16OrNull", ""); + SQL_FUNCTION_MAP.put("toInt32OrNull", ""); + SQL_FUNCTION_MAP.put("toInt64OrNull", ""); + SQL_FUNCTION_MAP.put("toUInt8", ""); //toInt8(expr) 转换一个输入值为Int类型 + SQL_FUNCTION_MAP.put("toUInt16", ""); + SQL_FUNCTION_MAP.put("toUInt32", ""); + SQL_FUNCTION_MAP.put("toUInt64", ""); + SQL_FUNCTION_MAP.put("toUInt8OrZero", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。 + SQL_FUNCTION_MAP.put("toUInt16OrZero", ""); + SQL_FUNCTION_MAP.put("toUInt32OrZero", ""); + SQL_FUNCTION_MAP.put("toUInt64OrZero", ""); + SQL_FUNCTION_MAP.put("toUInt8OrNull", "");//toInt(8|16|32|64)OrNull 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回NULL + SQL_FUNCTION_MAP.put("toUInt16OrNull", ""); + SQL_FUNCTION_MAP.put("toUInt32OrNull", ""); + SQL_FUNCTION_MAP.put("toUInt64OrNull", ""); + + SQL_FUNCTION_MAP.put("toFloat32", ""); + SQL_FUNCTION_MAP.put("toFloat64", ""); + SQL_FUNCTION_MAP.put("toFloat32OrZero", ""); + SQL_FUNCTION_MAP.put("toFloat64OrZero", ""); + SQL_FUNCTION_MAP.put("toFloat32OrNull", ""); + SQL_FUNCTION_MAP.put("toFloat64OrNull", ""); + + SQL_FUNCTION_MAP.put("toDate", ""); // + SQL_FUNCTION_MAP.put("toDateOrZero", ""); //toInt16(expr) + SQL_FUNCTION_MAP.put("toDateOrNull", ""); //toInt32(expr) + SQL_FUNCTION_MAP.put("toDateTimeOrZero", ""); //toInt64(expr) + SQL_FUNCTION_MAP.put("toDateTimeOrNull", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。 + + SQL_FUNCTION_MAP.put("toDecimal32", ""); + SQL_FUNCTION_MAP.put("toFixedString", ""); // 将String类型的参数转换为FixedString(N)类型的值 + SQL_FUNCTION_MAP.put("toStringCutToZero", ""); // 接受String或FixedString参数,返回String,其内容在找到的第一个零字节处被截断。 + SQL_FUNCTION_MAP.put("toDecimal256", ""); + SQL_FUNCTION_MAP.put("toDecimal32OrNull", ""); + SQL_FUNCTION_MAP.put("toDecimal64OrNull", ""); + SQL_FUNCTION_MAP.put("toDecimal128OrNull", ""); + SQL_FUNCTION_MAP.put("toDecimal256OrNull", ""); + SQL_FUNCTION_MAP.put("toDecimal32OrZero", ""); + SQL_FUNCTION_MAP.put("toDecimal64OrZero", ""); + SQL_FUNCTION_MAP.put("toDecimal128OrZero", ""); + SQL_FUNCTION_MAP.put("toDecimal256OrZero", ""); + + + SQL_FUNCTION_MAP.put("toIntervalSecond", ""); //把一个数值类型的值转换为Interval类型的数据。 + SQL_FUNCTION_MAP.put("toIntervalMinute", ""); + SQL_FUNCTION_MAP.put("toIntervalHour", ""); + SQL_FUNCTION_MAP.put("toIntervalDay", ""); + SQL_FUNCTION_MAP.put("toIntervalWeek", ""); + SQL_FUNCTION_MAP.put("toIntervalMonth", ""); + SQL_FUNCTION_MAP.put("toIntervalQuarter", ""); + SQL_FUNCTION_MAP.put("toIntervalYear", ""); + SQL_FUNCTION_MAP.put("parseDateTimeBestEffort", ""); //把String类型的时间日期转换为DateTime数据类型。 + SQL_FUNCTION_MAP.put("parseDateTimeBestEffortOrNull", ""); + SQL_FUNCTION_MAP.put("parseDateTimeBestEffortOrZero", ""); + SQL_FUNCTION_MAP.put("toLowCardinality", ""); + + + + ////clickhouse hash函数 + SQL_FUNCTION_MAP.put("halfMD5", ""); //计算字符串的MD5。然后获取结果的前8个字节并将它们作为UInt64(大端)返回 + SQL_FUNCTION_MAP.put("MD5", ""); //计算字符串的MD5并将结果放入FixedString(16)中返回 + + //clickhouse ip地址函数 + SQL_FUNCTION_MAP.put("IPv4NumToString", ""); //接受一个UInt32(大端)表示的IPv4的地址,返回相应IPv4的字符串表现形式,格式为A.B.C.D(以点分割的十进制数字)。 + SQL_FUNCTION_MAP.put("IPv4StringToNum", ""); //与IPv4NumToString函数相反。如果IPv4地址格式无效,则返回0。 + SQL_FUNCTION_MAP.put("IPv6NumToString", ""); //接受FixedString(16)类型的二进制格式的IPv6地址。以文本格式返回此地址的字符串。 + SQL_FUNCTION_MAP.put("IPv6StringToNum", ""); //与IPv6NumToString的相反。如果IPv6地址格式无效,则返回空字节字符串。 + SQL_FUNCTION_MAP.put("IPv4ToIPv6", ""); // 接受一个UInt32类型的IPv4地址,返回FixedString(16)类型的IPv6地址 + SQL_FUNCTION_MAP.put("cutIPv6", ""); //接受一个FixedString(16)类型的IPv6地址,返回一个String,这个String中包含了删除指定位之后的地址的文本格 + SQL_FUNCTION_MAP.put("toIPv4", ""); //IPv4StringToNum()的别名, + SQL_FUNCTION_MAP.put("toIPv6", ""); //IPv6StringToNum()的别名 + SQL_FUNCTION_MAP.put("isIPAddressInRange", ""); //确定一个IP地址是否包含在以CIDR符号表示的网络中 + + //clickhouse Nullable处理函数 + SQL_FUNCTION_MAP.put("isNull", ""); //检查参数是否为NULL。 + SQL_FUNCTION_MAP.put("isNotNull", ""); //检查参数是否不为 NULL. + SQL_FUNCTION_MAP.put("ifNull", ""); //如果第一个参数为«NULL»,则返回第二个参数的值。 + SQL_FUNCTION_MAP.put("assumeNotNull", ""); //将可为空类型的值转换为非Nullable类型的值。 + SQL_FUNCTION_MAP.put("toNullable", ""); //将参数的类型转换为Nullable。 + + //clickhouse UUID函数 + SQL_FUNCTION_MAP.put("generateUUIDv4", ""); // 生成一个UUID + SQL_FUNCTION_MAP.put("toUUID", ""); //toUUID(x) 将String类型的值转换为UUID类型的值。 + + //clickhouse 系统函数 + SQL_FUNCTION_MAP.put("hostName", ""); //hostName()回一个字符串,其中包含执行此函数的主机的名称。 + SQL_FUNCTION_MAP.put("getMacro", ""); //从服务器配置的宏部分获取指定值。 + SQL_FUNCTION_MAP.put("FQDN", "");//返回完全限定的域名。 + SQL_FUNCTION_MAP.put("basename", ""); //提取字符串最后一个斜杠或反斜杠之后的尾随部分 + SQL_FUNCTION_MAP.put("currentUser", ""); //返回当前用户的登录。在分布式查询的情况下,将返回用户的登录,即发起的查询 + SQL_FUNCTION_MAP.put("version", ""); //以字符串形式返回服务器版本。 + SQL_FUNCTION_MAP.put("uptime", "");//以秒为单位返回服务器的正常运行时间。 + + //clickhouse 数学函数 + SQL_FUNCTION_MAP.put("least", ""); //返回a和b中最小的值。 + SQL_FUNCTION_MAP.put("greatest", ""); //返回a和b的最大值。 + SQL_FUNCTION_MAP.put("plus", ""); //plus(a, b), a + b operator¶计算数值的总和。 + SQL_FUNCTION_MAP.put("minus", ""); //minus(a, b), a - b operator 计算数值之间的差,结果总是有符号的。 + SQL_FUNCTION_MAP.put("multiply", "");//multiply(a, b), a * b operator 计算数值的乘积 + SQL_FUNCTION_MAP.put("divide", ""); //divide(a, b), a / b operator 计算数值的商。结果类型始终是浮点类型 + SQL_FUNCTION_MAP.put("intDiv", ""); //intDiv(a,b)计算数值的商,向下舍入取整(按绝对值)。 + SQL_FUNCTION_MAP.put("intDivOrZero", ""); // intDivOrZero(a,b)与’intDiv’的不同之处在于它在除以零或将最小负数除以-1时返回零。 + SQL_FUNCTION_MAP.put("modulo", ""); //modulo(a, b), a % b operator 计算除法后的余数。 + SQL_FUNCTION_MAP.put("moduloOrZero", ""); //和modulo不同之处在于,除以0时结果返回0 + SQL_FUNCTION_MAP.put("negate", ""); //通过改变数值的符号位对数值取反,结果总是有符号 + SQL_FUNCTION_MAP.put("gcd", ""); //gcd(a,b) 返回数值的最大公约数。 + SQL_FUNCTION_MAP.put("lcm", ""); //lcm(a,b) 返回数值的最小公倍数 + SQL_FUNCTION_MAP.put("e", ""); //e() 返回一个接近数学常量e的Float64数字。 + SQL_FUNCTION_MAP.put("pi", ""); //pi() 返回一个接近数学常量π的Float64数字。 + SQL_FUNCTION_MAP.put("exp2", ""); //exp2(x)¶接受一个数值类型的参数并返回它的2的x次幂。 + SQL_FUNCTION_MAP.put("exp10", ""); //exp10(x)¶接受一个数值类型的参数并返回它的10的x次幂。 + SQL_FUNCTION_MAP.put("cbrt", ""); //cbrt(x) 接受一个数值类型的参数并返回它的立方根。 + SQL_FUNCTION_MAP.put("lgamma", ""); //lgamma(x) 返回x的绝对值的自然对数的伽玛函数。 + SQL_FUNCTION_MAP.put("tgamma", ""); //tgamma(x)¶返回x的伽玛函数。 + SQL_FUNCTION_MAP.put("intExp2", ""); //intExp2 接受一个数值类型的参数并返回它的2的x次幂(UInt64) + SQL_FUNCTION_MAP.put("intExp10", ""); //intExp10 接受一个数值类型的参数并返回它的10的x次幂(UInt64)。 + SQL_FUNCTION_MAP.put("cosh", ""); // cosh(x) + SQL_FUNCTION_MAP.put("cosh", ""); //cosh(x) + SQL_FUNCTION_MAP.put("sinh", ""); //sinh(x) + SQL_FUNCTION_MAP.put("asinh", ""); //asinh(x) + SQL_FUNCTION_MAP.put("atanh", ""); //atanh(x) + SQL_FUNCTION_MAP.put("atan2", ""); //atan2(y, x) + SQL_FUNCTION_MAP.put("hypot", ""); //hypot(x, y) + SQL_FUNCTION_MAP.put("log1p", ""); //log1p(x) + SQL_FUNCTION_MAP.put("trunc", ""); //和truncate一样 + SQL_FUNCTION_MAP.put("roundToExp2", ""); //接受一个数字。如果数字小于1,它返回0。 + SQL_FUNCTION_MAP.put("roundDuration", ""); //接受一个数字。如果数字小于1,它返回0。 + SQL_FUNCTION_MAP.put("roundAge", ""); // 接受一个数字。如果数字小于18,它返回0。 + SQL_FUNCTION_MAP.put("roundDown", ""); //接受一个数字并将其舍入到指定数组中的一个元素 + SQL_FUNCTION_MAP.put("bitAnd", ""); //bitAnd(a,b) + SQL_FUNCTION_MAP.put("bitOr", ""); //bitOr(a,b) + } +} From 509f21305b3d6f726efdeb5634c9cda22f749214 Mon Sep 17 00:00:00 2001 From: fineday009 Date: Mon, 6 Sep 2021 12:04:19 +0800 Subject: [PATCH 060/754] commit test --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index d1fd5dbce..e1f10aab2 100644 --- a/Document.md +++ b/Document.md @@ -1,5 +1,5 @@ # APIJSON通用文档 -后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(如果和本文档有出入,以本文档为准,例如正则匹配 key? 已废弃,全面用 key~ 替代) +后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(如果和本文档有出入,以本文档为准,例如正则匹配 key? 已废弃,全面用 key~ 替代。) * ### [1.示例](#1) * ### [2.对比传统方式](#2) From 175a65ccf2ae1658579a79e1bd489fc52bd5b665 Mon Sep 17 00:00:00 2001 From: kenlig <28685375+kenlig@users.noreply.github.com> Date: Mon, 6 Sep 2021 17:09:08 +0800 Subject: [PATCH 061/754] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 746389473..b478ba435 100644 --- a/README.md +++ b/README.md @@ -504,6 +504,9 @@ https://www.bilibili.com/video/BV1GM4y1N7XJ APIJSON 后端教程(6):uliweb_apijson https://www.bilibili.com/video/BV1yb4y1S79v/ + +APIJSON 后端教程(7):问题答疑 +https://www.bilibili.com/video/BV1dQ4y1h7Df APIJSON配套文档: https://github.com/kenlig/apijsondocs From d982e58040c7a6254db4df1418bef5d9dae6931f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 7 Sep 2021 15:35:20 +0800 Subject: [PATCH 062/754] =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=99=BB=E8=AE=B0?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E8=85=BE=E8=AE=AF=E7=A7=91=E6=8A=80?= =?UTF-8?q?=E6=9C=89=E9=99=90=E5=85=AC=E5=8F=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b478ba435..6250fc7c4 100644 --- a/README.md +++ b/README.md @@ -283,6 +283,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md

+* [腾讯科技有限公司](https://www.tencent.com) + 生态周边项目的作者们(2 个腾讯工程师、1 个字节跳动工程师 等):
https://github.com/search?o=desc&q=apijson&s=stars&type=Repositories
https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count
From 2aedc9dac16944ca919ae2275626d67127ac1def Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 7 Sep 2021 15:36:13 +0800 Subject: [PATCH 063/754] =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=99=BB=E8=AE=B0?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E8=85=BE=E8=AE=AF=E7=A7=91=E6=8A=80?= =?UTF-8?q?=E6=9C=89=E9=99=90=E5=85=AC=E5=8F=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6250fc7c4..d595e8339 100644 --- a/README.md +++ b/README.md @@ -283,7 +283,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md

-* [腾讯科技有限公司](https://www.tencent.com) + * [腾讯科技有限公司](https://www.tencent.com) 生态周边项目的作者们(2 个腾讯工程师、1 个字节跳动工程师 等):
https://github.com/search?o=desc&q=apijson&s=stars&type=Repositories
From baf8176aa2caf1fe1fb61dff73932f98158a8d88 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 7 Sep 2021 15:38:31 +0800 Subject: [PATCH 064/754] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=99=BB=E8=AE=B0=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E8=85=BE=E8=AE=AF=E7=A7=91=E6=8A=80?= =?UTF-8?q?=E6=9C=89=E9=99=90=E5=85=AC=E5=8F=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d595e8339..a521615f8 100644 --- a/README.md +++ b/README.md @@ -238,6 +238,8 @@ https://github.com/Tencent/APIJSON/issues/187
+ + * [腾讯科技有限公司](https://www.tencent.com) ### 贡献者们 @@ -283,8 +285,6 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md

- * [腾讯科技有限公司](https://www.tencent.com) - 生态周边项目的作者们(2 个腾讯工程师、1 个字节跳动工程师 等):
https://github.com/search?o=desc&q=apijson&s=stars&type=Repositories
https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count
From 07bc89925d0e14447d417c08ff7149c8c9892d58 Mon Sep 17 00:00:00 2001 From: chenyanlann <32511cyl@gmail.com> Date: Tue, 7 Sep 2021 21:32:29 +0800 Subject: [PATCH 065/754] =?UTF-8?q?Modified:=E4=BF=AE=E5=A4=8Dput=E8=AF=B7?= =?UTF-8?q?=E6=B1=82key=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractObjectParser.java | 4 ++-- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 673348935..a8b4535b2 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -261,8 +261,8 @@ else if ((method == POST || method == PUT) && value instanceof JSONArray && JSONRequest.isTableArray(key)) { // JSONArray,批量新增或修改,往下一级提取 onTableArrayParse(key, (JSONArray) value); } - else if (method == PUT && value instanceof JSONArray - && (whereList == null || whereList.contains(key) == false)) { // PUT JSONArray + else if (method == PUT && value instanceof JSONArray && (whereList == null || whereList.contains(key) == false) && (StringUtil.isName(key) || ((key.endsWith("+") || key.endsWith("-")) && StringUtil.isName(key.substring(0, key.length()-1))))) + { // PUT JSONArray onPUTArrayParse(key, (JSONArray) value); } else { // JSONArray或其它Object,直接填充 diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index e3f71ccbb..fbcc5507f 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -3127,7 +3127,7 @@ else if (w.startsWith("!")) { } //解决AccessVerifier新增userId没有作为条件,而是作为内容,导致PUT,DELETE出错 - if (isWhere) { + if (isWhere || (StringUtil.isName(key) == false)) { tableWhere.put(key, value); if (whereList == null || whereList.contains(key) == false) { andList.add(key); From 4beced4624b67a633096cd44d6fc1bffbf4f1dc8 Mon Sep 17 00:00:00 2001 From: chenyanlann <62465397+chenyanlann@users.noreply.github.com> Date: Wed, 8 Sep 2021 19:23:48 +0800 Subject: [PATCH 066/754] Update AbstractObjectParser.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 简化字符串判断 --- APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index a8b4535b2..979bce409 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -261,7 +261,7 @@ else if ((method == POST || method == PUT) && value instanceof JSONArray && JSONRequest.isTableArray(key)) { // JSONArray,批量新增或修改,往下一级提取 onTableArrayParse(key, (JSONArray) value); } - else if (method == PUT && value instanceof JSONArray && (whereList == null || whereList.contains(key) == false) && (StringUtil.isName(key) || ((key.endsWith("+") || key.endsWith("-")) && StringUtil.isName(key.substring(0, key.length()-1))))) + else if (method == PUT && value instanceof JSONArray && (whereList == null || whereList.contains(key) == false) && StringUtil.isName(key.replaceFirst("[+-]$", ""))) { // PUT JSONArray onPUTArrayParse(key, (JSONArray) value); } From 59525679b8b9b6754a3d0d9e8f2aab2f32991371 Mon Sep 17 00:00:00 2001 From: qiujunlin <1757591067@qq.com> Date: Fri, 10 Sep 2021 11:05:56 +0800 Subject: [PATCH 067/754] add delete and update --- .../src/main/java/apijson/orm/AbstractSQLConfig.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index fbcc5507f..47d07b456 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2587,7 +2587,7 @@ public String getSetString(RequestMethod method, Map content, bo if (setString.isEmpty()) { throw new IllegalArgumentException("PUT 请求必须在Table内设置要修改的 key:value !"); } - return " SET " + setString; + return (isClickHouse()?" ":" SET ") + setString; } /**SET key = concat(key, 'value') @@ -2665,9 +2665,15 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { case POST: return "INSERT INTO " + tablePath + config.getColumnString() + " VALUES" + config.getValuesString(); case PUT: - return "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) + (config.isMySQL()||config.isClickHouse() ? config.getLimitString() : ""); + if(config.isClickHouse()){ + return "ALTER TABLE " + tablePath + " UPDATE"+ config.getSetString()+ config.getWhereString(true); + } + return "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); case DELETE: - return "DELETE FROM " + tablePath + config.getWhereString(true) + (config.isMySQL()||config.isClickHouse() ? config.getLimitString() : ""); // PostgreSQL 不允许 LIMIT + if(config.isClickHouse()){ + return "ALTER TABLE " + tablePath + " DELETE" + config.getWhereString(true); + } + return "DELETE FROM " + tablePath + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); // PostgreSQL 不允许 LIMIT default: String explain = (config.isExplain() ? (config.isSQLServer() || config.isOracle() ? "SET STATISTICS PROFILE ON " : "EXPLAIN ") : ""); if (config.isTest() && RequestMethod.isGetMethod(config.getMethod(), true)) { From e2cfc82db8ae919b50f7de2e3c10db0546470571 Mon Sep 17 00:00:00 2001 From: qiujunlin <1757591067@qq.com> Date: Mon, 13 Sep 2021 14:03:10 +0800 Subject: [PATCH 068/754] update a problem about oracle --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 47d07b456..82bdbf5b4 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2685,7 +2685,7 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { String column = config.getColumnString(); if (config.isOracle()) { //When config's database is oracle,Using subquery since Oracle12 below does not support OFFSET FETCH paging syntax. - return explain + "SELECT * FROM (SELECT"+ (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); + return explain + "SELECT * FROM (SELECT "+ (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); } return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString(); From 60a305c9aad2dc4f1620c8b084c4ac398fb1c951 Mon Sep 17 00:00:00 2001 From: lixin Date: Thu, 16 Sep 2021 12:58:45 +0800 Subject: [PATCH 069/754] =?UTF-8?q?1.=E6=A0=B9=E6=8D=AE=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E4=B8=8D=E5=90=8C=E6=8B=BC=E6=8E=A5=E8=81=9A=E5=90=88=E8=AF=AD?= =?UTF-8?q?=E5=8F=A5=202.=E4=BF=AE=E6=94=B9Oracle=E5=88=86=E7=BB=84?= =?UTF-8?q?=E7=BB=9F=E8=AE=A1=E8=AF=AD=E5=8F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 82bdbf5b4..856c9e8d7 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -21,12 +21,7 @@ import static apijson.JSONObject.KEY_ROLE; import static apijson.JSONObject.KEY_SCHEMA; import static apijson.JSONObject.KEY_USER_ID; -import static apijson.RequestMethod.DELETE; -import static apijson.RequestMethod.GET; -import static apijson.RequestMethod.GETS; -import static apijson.RequestMethod.HEADS; -import static apijson.RequestMethod.POST; -import static apijson.RequestMethod.PUT; +import static apijson.RequestMethod.*; import static apijson.SQL.AND; import static apijson.SQL.NOT; import static apijson.SQL.OR; @@ -890,7 +885,7 @@ public String getOrderString(boolean hasPrefix) { // return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(order, joinOrder, ", "); // } - if (getCount() > 0 && (isOracle() || isSQLServer() || isDb2())) { // Oracle, SQL Server, DB2 的 OFFSET 必须加 ORDER BY + if (getCount() > 0 && (isSQLServer() || isDb2())) { // Oracle, SQL Server, DB2 的 OFFSET 必须加 ORDER BY.去掉Oracle,Oracle里面没有offset关键字 // String[] ss = StringUtil.split(order); if (StringUtil.isEmpty(order, true)) { //SQL Server 子查询内必须指定 OFFSET 才能用 ORDER BY @@ -2685,6 +2680,11 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { String column = config.getColumnString(); if (config.isOracle()) { //When config's database is oracle,Using subquery since Oracle12 below does not support OFFSET FETCH paging syntax. + //针对oracle分组后条数的统计 + if ((config.getMethod() == HEAD || config.getMethod() == HEADS) + && StringUtil.isNotEmpty(config.getGroup(),true)){ + return explain + "SELECT count(*) FROM (SELECT "+ (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); + } return explain + "SELECT * FROM (SELECT "+ (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); } @@ -2693,10 +2693,9 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { } /**获取条件SQL字符串 - * @param page * @param column * @param table - * @param where + * @param config * @return * @throws Exception */ @@ -2708,11 +2707,21 @@ private static String getConditionString(String column, String table, AbstractSQ table = config.getSubqueryString(from) + " AS " + config.getAliasWithQuote() + " "; } - String condition = table + config.getJoinString() + where + ( - RequestMethod.isGetMethod(config.getMethod(), true) == false ? - "" : config.getGroupString(true) + config.getHavingString(true) + config.getOrderString(true) - ) - ; //+ config.getLimitString(); + //根据方法不同,聚合语句不同。GROUP BY 和 HAVING 可以加在 HEAD 上, HAVING 可以加在 PUT, DELETE 上,GET 全加,POST 全都不加 + String aggregation = ""; + if (RequestMethod.isGetMethod(config.getMethod(), true)){ + aggregation = config.getGroupString(true) + config.getHavingString(true) + + config.getOrderString(true); + } + if (RequestMethod.isHeadMethod(config.getMethod(), true)){ + aggregation = config.getGroupString(true) + config.getHavingString(true) ; + } + if (config.getMethod() == PUT || config.getMethod() == DELETE){ + aggregation = config.getHavingString(true) ; + } + + String condition = table + config.getJoinString() + where + aggregation; + ; //+ config.getLimitString(); //no need to optimize // if (config.getPage() <= 0 || ID.equals(column.trim())) { @@ -2749,7 +2758,6 @@ private static String getConditionString(String column, String table, AbstractSQ // return table + " AS t0 INNER JOIN (SELECT id FROM " + condition + ") AS t1 ON t0.id = t1.id"; } - private boolean keyPrefix; @Override public boolean isKeyPrefix() { From 7a7eed635520556eca3172eaca6127f79aed31c3 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 17 Sep 2021 17:39:49 +0800 Subject: [PATCH 070/754] =?UTF-8?q?=E7=94=9F=E6=80=81=E5=91=A8=E8=BE=B9?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E6=96=B0=E5=A2=9E=20apijson-practice?= =?UTF-8?q?=EF=BC=8C=E6=84=9F=E8=B0=A2=E8=B4=A1=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/vcoolwind/apijson-practice --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a521615f8..f63560f83 100644 --- a/README.md +++ b/README.md @@ -435,6 +435,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [FfApiJson](https://gitee.com/own_3_0/ff-api-json) 用 JSON 格式直接生成 SQL,借鉴 APIJSON 支持多数据源 +[apijson-practice](https://github.com/vcoolwind/apijson-practice) 实践一下apijson,对做管理平台还是能有不少提效的 + [APIJSON-ToDo-Demo](https://github.com/jerrylususu/apijson_todo_demo) 一个简单的 todo 示例项目,精简数据,简化上手流程,带自定义鉴权逻辑 [apijson-learn](https://github.com/rainboy-learn/apijson-learn) APIJSON 学习笔记和源码解析 From ac9dc853f3886678c7bffc69f108176a6ccde01e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 19 Sep 2021 22:42:58 +0800 Subject: [PATCH 071/754] =?UTF-8?q?=E6=8A=BD=E5=8F=96=E6=A0=B9=E6=8D=AE=20?= =?UTF-8?q?tag=20=E8=87=AA=E5=8A=A8=E5=8C=85=E8=A3=85=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E7=9A=84=E6=96=B9=E6=B3=95=E4=B8=BA=20wrapRe?= =?UTF-8?q?quest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractParser.java | 55 +++++++++++++------ 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 7051b3e06..f5ba02636 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -30,6 +30,7 @@ import com.alibaba.fastjson.JSONObject; import apijson.JSON; +import apijson.JSONRequest; import apijson.JSONResponse; import apijson.Log; import apijson.NotNull; @@ -513,31 +514,53 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers + "非开放请求必须是后端 Request 表中校验规则允许的操作!\n " + error + "\n如果需要则在 Request 表中新增配置!"); } - JSONObject target = object; - if (object.containsKey(tag) == false) { //tag 是 Table 名或 Table[] + //获取指定的JSON结构 >>>>>>>>>>>>>> + JSONObject target = wrapRequest(object, tag, false); + + //JSONObject clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {} + return getVerifier().verifyRequest(method, name, target, request, maxUpdateCount, getGlobleDatabase(), getGlobleSchema(), creator); + } - boolean isArrayKey = tag.endsWith(":[]"); // JSONRequest.isArrayKey(tag); - String key = isArrayKey ? tag.substring(0, tag.length() - 3) : tag; - if (apijson.JSONObject.isTableKey(key)) { - if (isArrayKey) { //自动为 tag = Comment:[] 的 { ... } 新增键值对 "Comment[]":[] 为 { "Comment[]":[], ... } - target.put(key + "[]", new JSONArray()); - } - else { //自动为 tag = Comment 的 { ... } 包一层为 { "Comment": { ... } } - target = new JSONObject(true); - target.put(tag, object); + /**自动根据 tag 是否为 TableKey 及是否被包含在 object 内来决定是否包装一层,改为 { tag: object, "tag": tag } + * @param object + * @param tag + * @return + */ + public static JSONObject wrapRequest(JSONObject object, String tag, boolean putTag) { + if (object == null || object.containsKey(tag)) { //tag 是 Table 名或 Table[] + if (putTag) { + if (object == null) { + object = new JSONObject(true); } + object.put(JSONRequest.KEY_TAG, tag); } + return object; } - //获取指定的JSON结构 >>>>>>>>>>>>>> - + boolean isDiffArrayKey = tag.endsWith(":[]"); + boolean isArrayKey = isDiffArrayKey || JSONRequest.isArrayKey(tag); + String key = isArrayKey ? tag.substring(0, tag.length() - (isDiffArrayKey ? 3 : 2)) : tag; - //JSONObject clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {} - return getVerifier().verifyRequest(method, name, target, request, maxUpdateCount, getGlobleDatabase(), getGlobleSchema(), creator); + JSONObject target = object; + if (apijson.JSONObject.isTableKey(key)) { + if (isDiffArrayKey) { //自动为 tag = Comment:[] 的 { ... } 新增键值对 "Comment[]":[] 为 { "Comment[]":[], ... } + target.put(key + "[]", new JSONArray()); + } + else { //自动为 tag = Comment 的 { ... } 包一层为 { "Comment": { ... } } + target = new JSONObject(true); + target.put(tag, object); + } + } + + if (putTag) { + target.put(JSONRequest.KEY_TAG, tag); + } + + return target; } - + /**新建带状态内容的JSONObject * @param code * @param msg From 77c375b20de5dd4f5486fde26f1b8e773e562f1c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 23 Sep 2021 18:18:47 +0800 Subject: [PATCH 072/754] =?UTF-8?q?=E5=AE=8C=E5=96=84=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E5=9C=A8=E5=90=84=E7=A7=8D=20method=20?= =?UTF-8?q?=E5=8F=8A=20tag=20=E4=B8=8B=E7=9A=84=E8=87=AA=E5=8A=A8=E8=A1=A5?= =?UTF-8?q?=E5=85=A8=EF=BC=9B=E5=85=81=E8=AE=B8=20GETS=20=E9=80=9A?= =?UTF-8?q?=E8=BF=87=20key[]:{}=20=E6=9D=A5=E6=9F=A5=E5=A4=9A=E6=9D=A1?= =?UTF-8?q?=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractParser.java | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index f5ba02636..6965f2802 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -515,7 +515,7 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers } //获取指定的JSON结构 >>>>>>>>>>>>>> - JSONObject target = wrapRequest(object, tag, false); + JSONObject target = wrapRequest(method, tag, object, true); //JSONObject clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {} return getVerifier().verifyRequest(method, name, target, request, maxUpdateCount, getGlobleDatabase(), getGlobleSchema(), creator); @@ -527,7 +527,9 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers * @param tag * @return */ - public static JSONObject wrapRequest(JSONObject object, String tag, boolean putTag) { + public static JSONObject wrapRequest(RequestMethod method, String tag, JSONObject object, boolean isStructure) { + boolean putTag = ! isStructure; + if (object == null || object.containsKey(tag)) { //tag 是 Table 名或 Table[] if (putTag) { if (object == null) { @@ -544,12 +546,39 @@ public static JSONObject wrapRequest(JSONObject object, String tag, boolean putT JSONObject target = object; if (apijson.JSONObject.isTableKey(key)) { - if (isDiffArrayKey) { //自动为 tag = Comment:[] 的 { ... } 新增键值对 "Comment[]":[] 为 { "Comment[]":[], ... } - target.put(key + "[]", new JSONArray()); + if (isDiffArrayKey) { //自动为 tag = Comment:[] 的 { ... } 新增键值对为 { "Comment[]":[], "TYPE": { "Comment[]": "OBJECT[]" } ... } + if (isStructure && (method == RequestMethod.POST || method == RequestMethod.PUT)) { + String arrKey = key + "[]"; + + if (target.containsKey(arrKey) == false) { + target.put(arrKey, new JSONArray()); + } + + try { + JSONObject type = target.getJSONObject(Operation.TYPE.name()); + if (type == null || (type.containsKey(arrKey) == false)) { + if (type == null) { + type = new JSONObject(true); + } + + type.put(arrKey, "OBJECT[]"); + target.put(Operation.TYPE.name(), type); + } + } + catch (Throwable e) { + Log.w(TAG, "wrapRequest try { JSONObject type = target.getJSONObject(Operation.TYPE.name()); } catch (Exception e) = " + e.getMessage()); + } + } } else { //自动为 tag = Comment 的 { ... } 包一层为 { "Comment": { ... } } - target = new JSONObject(true); - target.put(tag, object); + if (isArrayKey == false || RequestMethod.isGetMethod(method, true)) { + target = new JSONObject(true); + target.put(tag, object); + } + else if (target.containsKey(key) == false) { + target = new JSONObject(true); + target.put(key, object); + } } } @@ -958,10 +987,10 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name } //不能允许GETS,否则会被通过"[]":{"@role":"ADMIN"},"Table":{},"tag":"Table"绕过权限并能批量查询 - if (isSubquery == false && RequestMethod.isGetMethod(requestMethod, false) == false) { - throw new UnsupportedOperationException("key[]:{}只支持GET方法!不允许传 " + name + ":{} !"); + if (isSubquery == false && RequestMethod.isGetMethod(requestMethod, true) == false) { + throw new UnsupportedOperationException("key[]:{} 只支持 GET, GETS 方法!其它方法不允许传 " + name + ":{} 等这种 key[]:{} 格式!"); } - if (request == null || request.isEmpty()) {//jsonKey-jsonValue条件 + if (request == null || request.isEmpty()) { // jsonKey-jsonValue 条件 return null; } String path = getAbsPath(parentPath, name); From bf1c7966bf97e5dab4150db0c05790f012790621 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 23 Sep 2021 21:51:11 +0800 Subject: [PATCH 073/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=80=9A=E7=94=A8?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E7=9A=84=E6=9C=AC=E8=BA=AB=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Document.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Document.md b/Document.md index e1f10aab2..e01c28658 100644 --- a/Document.md +++ b/Document.md @@ -1,5 +1,10 @@ -# APIJSON通用文档 -后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(如果和本文档有出入,以本文档为准,例如正则匹配 key? 已废弃,全面用 key~ 替代。) +# APIJSON 通用文档 +本文是通用文档,只和 APIJSON 协议有关,和 C#, Go, Java, JavaScript, Python, PHP 等开发语言无关。
+具体开发语言相关的 配置、运行、部署 等文档见各个相关项目的文档,可以在首页点击对应语言的入口来查看。
+https://github.com/Tencent/APIJSON +![image](https://user-images.githubusercontent.com/5738175/134518921-0747a035-6f79-4583-9f5e-fba9481c0a87.png) + +后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(非官方,和本文档有出入的地方以本文档为准,例如正则匹配 key? 已废弃,全面用 key~ 替代。) * ### [1.示例](#1) * ### [2.对比传统方式](#2) From 0c08c22232e7d59e735eee77f48fc810ba81ba6c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 23 Sep 2021 21:53:57 +0800 Subject: [PATCH 074/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20apijson-go=20?= =?UTF-8?q?=E7=9A=84=E9=93=BE=E6=8E=A5=EF=BC=8C=E6=84=9F=E8=B0=A2=E4=BD=9C?= =?UTF-8?q?=E8=80=85=E7=9A=84=E8=B4=A1=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://gitee.com/tiangao/apijson-go --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f63560f83..0bfb971a6 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ This source code is licensed under the Apache License Version 2.0

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

③ "format":Boolean,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn:8080/gets/{"tag":"Privacy","Privacy":{"id":82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn:8080/gets/{"version":1,"tag":"Privacy","Privacy":{"id":82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,    "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})
From 5ccb064d98a9dfcdbb3c7fc75b44cae1fb625b96 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 16:16:58 +0800 Subject: [PATCH 087/754] Update Document.md --- Document.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Document.md b/Document.md index bb5acd50c..a9320f146 100644 --- a/Document.md +++ b/Document.md @@ -5,7 +5,7 @@ https://github.com/Tencent/APIJSON ![image](https://user-images.githubusercontent.com/5738175/134520081-a63d3817-321c-4e7b-9e03-73c6827a19c1.png) -后端开发者可以先看 [图文入门教程1](https://vincentcheng.github.io/apijson-doc/zh) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (都非官方,和本文档有出入的点以本文档为准,例如正则匹配 key? 已废弃,用 key~ 替代;例如 "@column":"store_id,sum(amt):totAmt" 中逗号 , 有误,应该用分号 ; 隔开 SQL 函数) +后端开发者可以先看 [图文入门教程1](https://vincentcheng.github.io/apijson-doc/zh) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (都非官方,和本文档有出入的点以本文档为准,例如正则匹配 key? 已废弃,用 key~ 替代;例如 "@column":"store_id,sum(amt):totAmt" 中逗号 , 有误,应该用分号 ; 隔开 SQL 函数,改为 "@column":"store_id;sum(amt):totAmt") * ### [1.示例](#1) * ### [2.对比传统方式](#2) @@ -346,7 +346,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 1.TableName指要查询的数据库表Table的名称字符串。第一个字符为大写字母,剩下的字符要符合英语字母、数字、下划线中的任何一种。对应的值的类型为JSONObject,结构是 {...},里面放的是Table的字段(列名)。下同。
-2."tag":tag 后面的tag是非GET、HEAD请求中匹配请求的JSON结构的标识,一般是要查询的table的名称,由后端Request表中指定。下同。
+2."tag":tag 后面的tag是非GET、HEAD请求中匹配请求的JSON结构的标识,一般是要查询的Table的名称,由后端Request表中指定。下同。
3.GET、HEAD请求是开放请求,可任意组合任意嵌套。其它请求为受限制的安全/私密请求,对应的 方法(method), 标识(tag), 版本(version), 结构(structure) 都必须和 后端Request表中所指定的 一一对应,否则请求将不被通过。version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本。下同。
4.GETS与GET、HEADS与HEAD分别为同一类型的操作方法,请求稍有不同但返回结果相同。下同。
5.在HTTP通信中,自动化接口(get,gets,head,heads,post,put,delete) 全用HTTP POST请求。下同。
From 581cace5f37e176e8639b242a05d97ecceb5e90f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 16:17:25 +0800 Subject: [PATCH 088/754] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index a9320f146..00b3d2fc3 100644 --- a/Document.md +++ b/Document.md @@ -5,7 +5,7 @@ https://github.com/Tencent/APIJSON ![image](https://user-images.githubusercontent.com/5738175/134520081-a63d3817-321c-4e7b-9e03-73c6827a19c1.png) -后端开发者可以先看 [图文入门教程1](https://vincentcheng.github.io/apijson-doc/zh) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (都非官方,和本文档有出入的点以本文档为准,例如正则匹配 key? 已废弃,用 key~ 替代;例如 "@column":"store_id,sum(amt):totAmt" 中逗号 , 有误,应该用分号 ; 隔开 SQL 函数,改为 "@column":"store_id;sum(amt):totAmt") +后端开发者可以先看 [图文入门教程1](https://vincentcheng.github.io/apijson-doc/zh) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (都非官方,和本文档有出入的点以本文档为准。例如正则匹配 key? 已废弃,用 key~ 替代;例如 "@column":"store_id,sum(amt):totAmt" 中逗号 , 有误,应该用分号 ; 隔开 SQL 函数,改为 "@column":"store_id;sum(amt):totAmt") * ### [1.示例](#1) * ### [2.对比传统方式](#2) From 97fc8125458278aa6891a70f0d381fe72fb76daf Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 16:27:39 +0800 Subject: [PATCH 089/754] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 00b3d2fc3..394174341 100644 --- a/Document.md +++ b/Document.md @@ -378,5 +378,5 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

③ "format":Boolean,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn:8080/gets/{"tag":"Privacy","Privacy":{"id":82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn:8080/gets/{"version":1,"tag":"Privacy","Privacy":{"id":82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,    "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}}) + 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

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

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

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

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

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})
From d0cf03908ed64f7f4c5f41e8961ac4e05182a551 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 16:53:17 +0800 Subject: [PATCH 090/754] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 394174341..8ba0dbc9f 100644 --- a/Document.md +++ b/Document.md @@ -378,5 +378,5 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}}) + 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

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

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

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

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

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})
From e5638a3b70e21960faf5a4caff8f0d1e6e715439 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 17:35:20 +0800 Subject: [PATCH 091/754] Update Document.md --- Document.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Document.md b/Document.md index 8ba0dbc9f..27eff492d 100644 --- a/Document.md +++ b/Document.md @@ -336,13 +336,13 @@ https://github.com/Tencent/APIJSON  方法及说明 | URL | Request | Response ------------ | ------------ | ------------ | ------------ -GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 235 的 Moment:
{
   "Moment":{
     "id":235
   }
}
后端校验通过后自动解析为 SQL 并执行:
`SELECT * FROM Moment WHERE id=235 LIMIT 1` | {
   TableName:{
     ...
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "id":235,
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "code":200,
   "msg":"success"
} -HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
{
   "Moment":{
     "userId":38710
   }
}
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} +GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 235 的 Moment:
[{
   "Moment":{
     "id":235
   }
}](url=http%3A%2F%2Flocalhost%3A8080%2Fget&type=JSON&json={"Moment":{"id":235}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT * FROM Moment WHERE id=235 LIMIT 1` | {
   TableName:{
     ...
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "id":235,
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "code":200,
   "msg":"success"
} +HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
[{
   "Moment":{
     "userId":38710
   }
}](url=http%3A%2F%2Flocalhost%3A8080%2Fhead&type=JSON&json={"Moment":{"userId":38710}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,其它同GET | 同GET HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,其它同HEAD | 同HEAD -POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Comment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} -PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。
"tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置;
"tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST -DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} +POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
[{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Comment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Comment"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
[{
   "Comment[]":[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment[]":[{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} +PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
[{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"Moment":{"id":235,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Moment"})
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。
"tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置;
"tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST +DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
[{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fdelete&type=JSON&json={"Comment":{"id{}":[100,110,120]},"tag":"Comment[]"})
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} 1.TableName指要查询的数据库表Table的名称字符串。第一个字符为大写字母,剩下的字符要符合英语字母、数字、下划线中的任何一种。对应的值的类型为JSONObject,结构是 {...},里面放的是Table的字段(列名)。下同。
From d2a3b8ff7d43b0a42a553408801c16fc13a6da28 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 17:40:46 +0800 Subject: [PATCH 092/754] Update Document.md --- Document.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Document.md b/Document.md index 27eff492d..30e7aa914 100644 --- a/Document.md +++ b/Document.md @@ -338,8 +338,8 @@ https://github.com/Tencent/APIJSON ------------ | ------------ | ------------ | ------------ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 235 的 Moment:
[{
   "Moment":{
     "id":235
   }
}](url=http%3A%2F%2Flocalhost%3A8080%2Fget&type=JSON&json={"Moment":{"id":235}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT * FROM Moment WHERE id=235 LIMIT 1` | {
   TableName:{
     ...
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "id":235,
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "code":200,
   "msg":"success"
} HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
[{
   "Moment":{
     "userId":38710
   }
}](url=http%3A%2F%2Flocalhost%3A8080%2Fhead&type=JSON&json={"Moment":{"userId":38710}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} -GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,其它同GET | 同GET -HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,其它同HEAD | 同HEAD +GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,例如 ["tag":"Privacy"](url=http%3A%2F%2Flocalhost%3A8080%2Fgets&type=JSON&json={"Privacy":{"id":82001}}),其它同GET | 同GET +HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,例如 ["tag":"Verify"](url=http%3A%2F%2Flocalhost%3A8080%2Fheads&type=JSON&json={"Verify":{"phone":13000082001}}),其它同HEAD | 同HEAD POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
[{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Comment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Comment"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
[{
   "Comment[]":[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment[]":[{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
[{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"Moment":{"id":235,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Moment"})
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。
"tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置;
"tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
[{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fdelete&type=JSON&json={"Comment":{"id{}":[100,110,120]},"tag":"Comment[]"})
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} From 1e7b901c0cf7f9e3ad257086a65da51fc457ba9f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 17:49:34 +0800 Subject: [PATCH 093/754] Update Document.md --- Document.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Document.md b/Document.md index 30e7aa914..4fca78c97 100644 --- a/Document.md +++ b/Document.md @@ -336,10 +336,10 @@ https://github.com/Tencent/APIJSON  方法及说明 | URL | Request | Response ------------ | ------------ | ------------ | ------------ -GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 235 的 Moment:
[{
   "Moment":{
     "id":235
   }
}](url=http%3A%2F%2Flocalhost%3A8080%2Fget&type=JSON&json={"Moment":{"id":235}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT * FROM Moment WHERE id=235 LIMIT 1` | {
   TableName:{
     ...
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "id":235,
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "code":200,
   "msg":"success"
} -HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
[{
   "Moment":{
     "userId":38710
   }
}](url=http%3A%2F%2Flocalhost%3A8080%2Fhead&type=JSON&json={"Moment":{"userId":38710}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} -GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,例如 ["tag":"Privacy"](url=http%3A%2F%2Flocalhost%3A8080%2Fgets&type=JSON&json={"Privacy":{"id":82001}}),其它同GET | 同GET -HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,例如 ["tag":"Verify"](url=http%3A%2F%2Flocalhost%3A8080%2Fheads&type=JSON&json={"Verify":{"phone":13000082001}}),其它同HEAD | 同HEAD +GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 235 的 Moment:
[{
   "Moment":{
     "id":235
   }
}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fget&type=JSON&json={"Moment"%3A{"id"%3A235}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT * FROM Moment WHERE id=235 LIMIT 1` | {
   TableName:{
     ...
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "id":235,
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "code":200,
   "msg":"success"
} +HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
[{
   "Moment":{
     "userId":38710
   }
}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fhead&type=JSON&json={"Moment"%3A{"userId"%3A38710}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} +GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,例如 ["tag":"Privacy"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}} ),其它同GET | 同GET +HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,例如 ["tag":"Verify"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fheads&type=JSON&json={"tag"%3A"Verify","Verify"%3A{"phone"%3A13000082001}}),其它同HEAD | 同HEAD POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
[{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Comment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Comment"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
[{
   "Comment[]":[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment[]":[{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
[{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"Moment":{"id":235,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Moment"})
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。
"tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置;
"tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
[{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fdelete&type=JSON&json={"Comment":{"id{}":[100,110,120]},"tag":"Comment[]"})
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} From 778b1bcd0e8ff567ddda69a2caf8e4b9bdc2970a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 19:02:36 +0800 Subject: [PATCH 094/754] Update Document.md --- Document.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Document.md b/Document.md index 4fca78c97..729b6c4d1 100644 --- a/Document.md +++ b/Document.md @@ -338,12 +338,12 @@ https://github.com/Tencent/APIJSON ------------ | ------------ | ------------ | ------------ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 235 的 Moment:
[{
   "Moment":{
     "id":235
   }
}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fget&type=JSON&json={"Moment"%3A{"id"%3A235}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT * FROM Moment WHERE id=235 LIMIT 1` | {
   TableName:{
     ...
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "id":235,
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "code":200,
   "msg":"success"
} HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
[{
   "Moment":{
     "userId":38710
   }
}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fhead&type=JSON&json={"Moment"%3A{"userId"%3A38710}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} -GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,例如 ["tag":"Privacy"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}} ),其它同GET | 同GET +GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,例如 ["tag":"Privacy"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}}),其它同GET | 同GET HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,例如 ["tag":"Verify"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fheads&type=JSON&json={"tag"%3A"Verify","Verify"%3A{"phone"%3A13000082001}}),其它同HEAD | 同HEAD POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
[{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Comment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Comment"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
[{
   "Comment[]":[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment[]":[{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
[{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"Moment":{"id":235,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Moment"})
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。
"tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置;
"tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
[{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fdelete&type=JSON&json={"Comment":{"id{}":[100,110,120]},"tag":"Comment[]"})
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} - +以上接口的简单形式:
base_url/{method}/{tag} | GET: 普通获取数据
base_url/get/{tag}

HEAD: 普通获取数量
base_url/head/{tag}

GETS: 安全/私密获取数据
base_url/gets/{tag}

HEADS: 安全/私密获取数量
base_url/heads/{tag}

POST: 新增数据
base_url/post/{tag}

PUT: 修改数据 base_url/put/{tag}

DELETE: 删除数据
base_url/delete/{tag} | 例如安全/私密获取一个 id = 82001 的 Privacy:
[base_url/gets/Privacy/
{"id":82001}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets%2FPrivacy&type=JSON&json={"id"%3A82001})
相当于
[base_url/gets/
{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}})

例如批量修改 id = 114, 124 的 Comment 的 content:
[base_url/put/Comemnt[]/
{
   "id{}":[114,124],
   "content":"test multi put"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput%2FComment[]&type=JSON&json={"id{}"%3A[114,124],"content"%3A"test%20multi%20put"})
相当于
[base_url/put/
{
   "tag":"Comment[]",
   "Comment":{
     "id{}":[114,124],
     "content":"test multi put"
   }
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"tag"%3A"Comment[]","Comment"%3A{"id{}"%3A[114,124],"content"%3A"test%20multi%20put"}}) | 同以上对应的方法 1.TableName指要查询的数据库表Table的名称字符串。第一个字符为大写字母,剩下的字符要符合英语字母、数字、下划线中的任何一种。对应的值的类型为JSONObject,结构是 {...},里面放的是Table的字段(列名)。下同。
2."tag":tag 后面的tag是非GET、HEAD请求中匹配请求的JSON结构的标识,一般是要查询的Table的名称,由后端Request表中指定。下同。
From f3494d79aa31653a6ea45add58dcca89784b4d03 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 19:03:39 +0800 Subject: [PATCH 095/754] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 729b6c4d1..0426f6b8f 100644 --- a/Document.md +++ b/Document.md @@ -343,7 +343,7 @@ HEADS:
安全/私密获取数量,
用于获取银行卡数量等
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
[{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Comment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Comment"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
[{
   "Comment[]":[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment[]":[{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
[{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"Moment":{"id":235,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Moment"})
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。
"tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置;
"tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
[{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fdelete&type=JSON&json={"Comment":{"id{}":[100,110,120]},"tag":"Comment[]"})
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} -以上接口的简单形式:
base_url/{method}/{tag} | GET: 普通获取数据
base_url/get/{tag}

HEAD: 普通获取数量
base_url/head/{tag}

GETS: 安全/私密获取数据
base_url/gets/{tag}

HEADS: 安全/私密获取数量
base_url/heads/{tag}

POST: 新增数据
base_url/post/{tag}

PUT: 修改数据 base_url/put/{tag}

DELETE: 删除数据
base_url/delete/{tag} | 例如安全/私密获取一个 id = 82001 的 Privacy:
[base_url/gets/Privacy/
{"id":82001}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets%2FPrivacy&type=JSON&json={"id"%3A82001})
相当于
[base_url/gets/
{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}})

例如批量修改 id = 114, 124 的 Comment 的 content:
[base_url/put/Comemnt[]/
{
   "id{}":[114,124],
   "content":"test multi put"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput%2FComment[]&type=JSON&json={"id{}"%3A[114,124],"content"%3A"test%20multi%20put"})
相当于
[base_url/put/
{
   "tag":"Comment[]",
   "Comment":{
     "id{}":[114,124],
     "content":"test multi put"
   }
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"tag"%3A"Comment[]","Comment"%3A{"id{}"%3A[114,124],"content"%3A"test%20multi%20put"}}) | 同以上对应的方法 +以上接口的简单形式:
base_url/{method}/{tag} | GET: 普通获取数据
base_url/get/{tag}

HEAD: 普通获取数量
base_url/head/{tag}

GETS: 安全/私密获取数据
base_url/gets/{tag}

HEADS: 安全/私密获取数量
base_url/heads/{tag}

POST: 新增数据
base_url/post/{tag}

PUT: 修改数据 base_url/put/{tag}

DELETE: 删除数据
base_url/delete/{tag} | 例如安全/私密获取一个 id = 82001 的 Privacy:
[base_url/gets/Privacy/
{"id":82001}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets%2FPrivacy&type=JSON&json={"id"%3A82001})
相当于
[base_url/gets/
{"tag":"Privacy", "Privacy":{"id":82001}}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}})

例如批量修改 id = 114, 124 的 Comment 的 content:
[base_url/put/Comemnt[]/
{
   "id{}":[114,124],
   "content":"test multi put"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput%2FComment[]&type=JSON&json={"id{}"%3A[114,124],"content"%3A"test%20multi%20put"})
相当于
[base_url/put/
{
   "tag":"Comment[]",
   "Comment":{
     "id{}":[114,124],
     "content":"test multi put"
   }
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"tag"%3A"Comment[]","Comment"%3A{"id{}"%3A[114,124],"content"%3A"test%20multi%20put"}}) | 同以上对应的方法 1.TableName指要查询的数据库表Table的名称字符串。第一个字符为大写字母,剩下的字符要符合英语字母、数字、下划线中的任何一种。对应的值的类型为JSONObject,结构是 {...},里面放的是Table的字段(列名)。下同。
2."tag":tag 后面的tag是非GET、HEAD请求中匹配请求的JSON结构的标识,一般是要查询的Table的名称,由后端Request表中指定。下同。
From b39c1f06980df44fc88099883335c62fe4a8b0d6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 19:12:30 +0800 Subject: [PATCH 096/754] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 0426f6b8f..907af5311 100644 --- a/Document.md +++ b/Document.md @@ -340,7 +340,7 @@ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {< HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
[{
   "Moment":{
     "userId":38710
   }
}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fhead&type=JSON&json={"Moment"%3A{"userId"%3A38710}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,例如 ["tag":"Privacy"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}}),其它同GET | 同GET HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,例如 ["tag":"Verify"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fheads&type=JSON&json={"tag"%3A"Verify","Verify"%3A{"phone"%3A13000082001}}),其它同HEAD | 同HEAD -POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
[{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Comment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Comment"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
[{
   "Comment[]":[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment[]":[{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} +POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
[{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Comment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Comment"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
[{
   "Comment[]":[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment[]":[{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !');`

`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.');` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
[{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"Moment":{"id":235,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Moment"})
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。
"tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置;
"tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
[{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fdelete&type=JSON&json={"Comment":{"id{}":[100,110,120]},"tag":"Comment[]"})
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} 以上接口的简单形式:
base_url/{method}/{tag} | GET: 普通获取数据
base_url/get/{tag}

HEAD: 普通获取数量
base_url/head/{tag}

GETS: 安全/私密获取数据
base_url/gets/{tag}

HEADS: 安全/私密获取数量
base_url/heads/{tag}

POST: 新增数据
base_url/post/{tag}

PUT: 修改数据 base_url/put/{tag}

DELETE: 删除数据
base_url/delete/{tag} | 例如安全/私密获取一个 id = 82001 的 Privacy:
[base_url/gets/Privacy/
{"id":82001}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets%2FPrivacy&type=JSON&json={"id"%3A82001})
相当于
[base_url/gets/
{"tag":"Privacy", "Privacy":{"id":82001}}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}})

例如批量修改 id = 114, 124 的 Comment 的 content:
[base_url/put/Comemnt[]/
{
   "id{}":[114,124],
   "content":"test multi put"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput%2FComment[]&type=JSON&json={"id{}"%3A[114,124],"content"%3A"test%20multi%20put"})
相当于
[base_url/put/
{
   "tag":"Comment[]",
   "Comment":{
     "id{}":[114,124],
     "content":"test multi put"
   }
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"tag"%3A"Comment[]","Comment"%3A{"id{}"%3A[114,124],"content"%3A"test%20multi%20put"}}) | 同以上对应的方法 From 773c2cb2e5e502a157a50c25d907da547cd06ea1 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 19:15:50 +0800 Subject: [PATCH 097/754] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 907af5311..292153848 100644 --- a/Document.md +++ b/Document.md @@ -257,7 +257,7 @@ https://github.com/Tencent/APIJSON
-[在线测试](http://apijson.org/auto) +[在线测试](http://apijson.cn/api)

From 7cbc85b1603204519c301a54209ae544eed10104 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 22:36:16 +0800 Subject: [PATCH 098/754] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 292153848..1b6129c2d 100644 --- a/Document.md +++ b/Document.md @@ -377,6 +377,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})
From 9bea66e37acb0bb641da61943520dde19f2d72d4 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 22:50:10 +0800 Subject: [PATCH 099/754] =?UTF-8?q?=E9=80=9A=E7=94=A8=E6=96=87=E6=A1=A3=20?= =?UTF-8?q?=203.2=20=E5=8A=9F=E8=83=BD=E7=AC=A6=20=E5=AF=B9=E8=B1=A1?= =?UTF-8?q?=E5=85=B3=E9=94=AE=E8=AF=8D=20=E6=96=B0=E5=A2=9E=20"@datasource?= =?UTF-8?q?":"DRUID"=20=E8=B7=A8=E6=95=B0=E6=8D=AE=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 1b6129c2d..282fa1ff4 100644 --- a/Document.md +++ b/Document.md @@ -377,6 +377,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})
From bbf0fc33a9380bbfad0b465d29cb781d907188c5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 28 Sep 2021 02:14:26 +0800 Subject: [PATCH 101/754] =?UTF-8?q?=E5=89=8D=E7=AB=AF=E4=BC=A0=20SQL=20?= =?UTF-8?q?=E5=85=B3=E9=94=AE=E8=AF=8D=20OVER=20=E5=92=8C=20AGAINST=20?= =?UTF-8?q?=E5=8E=BB=E6=8E=89=E5=A4=9A=E4=BD=99=E7=9A=84=E7=A9=BA=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractSQLConfig.java | 13 +++++++------ .../src/main/java/apijson/orm/AbstractVerifier.java | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index fc3bb3ba5..8204a84b2 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1562,10 +1562,10 @@ public String getColumnPrase(String expression, boolean containRaw) { // 全文索引 math(name,tag) AGAINST ('a b +c -d' IN NATURALE LANGUAGE MODE) // IN BOOLEAN MODE //有函数,但不是窗口函数 - int overIndex = expression.indexOf(") OVER ("); - int againstIndex = expression.indexOf(") AGAINST ("); - boolean containOver = overIndex > 0 && overIndex < expression.length() - ") OVER (".length(); - boolean containAgainst = againstIndex > 0 && againstIndex < expression.length() - ") AGAINST (".length(); + int overIndex = expression.indexOf(")OVER("); // 传参不传空格,拼接带空格 ") OVER ("); + int againstIndex = expression.indexOf(")AGAINST("); // 传参不传空格,拼接带空格 ") AGAINST ("); + boolean containOver = overIndex > 0 && overIndex < expression.length() - ")OVER(".length(); + boolean containAgainst = againstIndex > 0 && againstIndex < expression.length() - ")AGAINST(".length(); if (containOver && containAgainst) { throw new IllegalArgumentException("字符 " + expression + " 不合法!" @@ -1658,8 +1658,9 @@ public String getColumnPrase(String expression, boolean containRaw) { // 别名 String alias = s2.lastIndexOf(":") < s2.lastIndexOf(")") ? null : s2.substring(s2.lastIndexOf(":") + 1); // 获取后半部分的参数解析 (agr0 agr1 ...) - String argsString2[] = parseArgsSplitWithComma(argString2, false, containRaw); - expression = fun + "(" + StringUtil.getString(agrsString1) + (containOver ? ") OVER (" : ") AGAINST (") + StringUtil.getString(argsString2) + ")" + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); } + String argsString2[] = parseArgsSplitWithComma(argString2, false, containRaw); + expression = fun + "(" + StringUtil.getString(agrsString1) + (containOver ? ") OVER (" : ") AGAINST (") // 传参不传空格,拼接带空格 + + StringUtil.getString(argsString2) + ")" + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); } } return expression; diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 5527f17c4..4cab85683 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -961,7 +961,7 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, //不在target内的 key:{} if (rk.startsWith("@") == false && objKeySet.contains(rk) == false) { if (rv instanceof JSONObject) { - throw new UnsupportedOperationException(method + " 请求," +name + " 里面不允许传 " + rk + ":{} !"); + throw new UnsupportedOperationException(method + " 请求," + name + " 里面不允许传 " + rk + ":{} !"); } if ((method == RequestMethod.POST || method == RequestMethod.PUT) && rv instanceof JSONArray && JSONRequest.isArrayKey(rk)) { throw new UnsupportedOperationException(method + " 请求," + name + " 里面不允许 " + rk + ":[] 等未定义的 Table[]:[{}] 批量操作键值对!"); From 406ab54e67a682b28e8edeebc63ca6dbfae70939 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 28 Sep 2021 02:41:52 +0800 Subject: [PATCH 102/754] =?UTF-8?q?=E4=B8=BB=E9=A1=B9=E7=9B=AE=E8=B4=A1?= =?UTF-8?q?=E7=8C=AE=E8=80=85=E6=96=B0=E5=A2=9E=201=20=E4=BA=BA=EF=BC=8C?= =?UTF-8?q?=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE=E8=B4=A1=E7=8C=AE=E8=80=85?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=207=20=E4=BA=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0bfb971a6..79d4209ef 100644 --- a/README.md +++ b/README.md @@ -283,10 +283,11 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
+
-生态周边项目的作者们(2 个腾讯工程师、1 个字节跳动工程师 等):
+生态周边项目的作者们(2 个腾讯工程师、1 个 BAT 技术专家、1 个字节跳动工程师 等):
https://github.com/search?o=desc&q=apijson&s=stars&type=Repositories
https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count
@@ -297,7 +298,7 @@ https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count - + + + + + + + + + -

@@ -436,12 +444,12 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [FfApiJson](https://gitee.com/own_3_0/ff-api-json) 用 JSON 格式直接生成 SQL,借鉴 APIJSON 支持多数据源 -[apijson-practice](https://github.com/vcoolwind/apijson-practice) 实践一下apijson,对做管理平台还是能有不少提效的 - [APIJSON-ToDo-Demo](https://github.com/jerrylususu/apijson_todo_demo) 一个简单的 todo 示例项目,精简数据,简化上手流程,带自定义鉴权逻辑 [apijson-learn](https://github.com/rainboy-learn/apijson-learn) APIJSON 学习笔记和源码解析 +[apijson-practice](https://github.com/vcoolwind/apijson-practice) 实践一下 apijson,对做管理平台还是能有不少提效的 + [apijson-sample](https://gitee.com/greyzeng/apijson-sample) APIJSON 简单使用 Demo 及教程 [apijson-examples](https://gitee.com/drone/apijson-examples) APIJSON 的前端、业务后端、管理后端 Demo @@ -462,7 +470,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [APIJSON-Android-RxJava](https://github.com/TommyLemon/APIJSON-Android-RxJava) 仿微信朋友圈动态实战项目,ZBLibrary(UI) + APIJSON(HTTP) + RxJava(Data) -[Android-ZBLibrary](https://github.com/TommyLemon/Android-ZBLibrary) Android MVP快速开发框架,Demo全面,注释详细,使用简单,代码严谨 +[Android-ZBLibrary](https://github.com/TommyLemon/Android-ZBLibrary) Android MVP 快速开发框架,Demo 全面,注释详细,使用简单,代码严谨 感谢热心的作者们的贡献,点 ⭐Star 支持下他们吧。 From 47a83330e6e149c68bbb2b4bbcda87ebc8ec7201 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 28 Sep 2021 02:44:50 +0800 Subject: [PATCH 103/754] =?UTF-8?q?=E8=A1=A5=E5=85=85=20-=20=E4=B8=BB?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E8=B4=A1=E7=8C=AE=E8=80=85=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=201=20=E4=BA=BA=EF=BC=8C=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E6=96=B0=E5=A2=9E=207=20=E4=BA=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 79d4209ef..9a8c4aa3f 100644 --- a/README.md +++ b/README.md @@ -310,7 +310,8 @@ https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count - + + From d6cb2e14fb878cd83d0bb4f6972935fd8c3aa7e9 Mon Sep 17 00:00:00 2001 From: tianzhenyu Date: Tue, 28 Sep 2021 16:30:39 +0800 Subject: [PATCH 104/754] =?UTF-8?q?fix=20PG=E7=9A=84=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E4=BA=8B=E5=8A=A1=E7=AD=89=E7=BA=A7=E7=9A=84?= =?UTF-8?q?bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractSQLExecutor.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 760781c3a..89a672aac 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -764,13 +764,18 @@ public void setTransactionIsolation(int transactionIsolation) { } @Override + private boolean setIsolationStatus = false; //设置事务等级 public void begin(int transactionIsolation) throws SQLException { Log.d("\n\n" + TAG, "<<<<<<<<<<<<<< TRANSACTION begin transactionIsolation = " + transactionIsolation + " >>>>>>>>>>>>>>>>>>>>>>> \n\n"); //不做判断,如果掩盖了问题,调用层都不知道为啥事务没有提交成功 // if (connection == null || connection.isClosed()) { // return; // } - connection.setTransactionIsolation(transactionIsolation); + connection.setAutoCommit(false); + if(! this.setIsolationStatus){ //只设置一次Isolation等级 PG重复设置事务等级会报错 + connection.setTransactionIsolation(transactionIsolation); + } + this.setIsolationStatus=true; connection.setAutoCommit(false); //java.sql.SQLException: Can''t call commit when autocommit=true } @Override From 0ca192ab5d3e5960842752d3ef61b9596f93fd6d Mon Sep 17 00:00:00 2001 From: tianzhenyu Date: Tue, 28 Sep 2021 16:35:25 +0800 Subject: [PATCH 105/754] =?UTF-8?q?fix=20PG=E7=9A=84=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E4=BA=8B=E5=8A=A1=E7=AD=89=E7=BA=A7=E7=9A=84?= =?UTF-8?q?bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java | 1 - 1 file changed, 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 89a672aac..c9f2d0dc4 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -771,7 +771,6 @@ public void begin(int transactionIsolation) throws SQLException { // if (connection == null || connection.isClosed()) { // return; // } - connection.setAutoCommit(false); if(! this.setIsolationStatus){ //只设置一次Isolation等级 PG重复设置事务等级会报错 connection.setTransactionIsolation(transactionIsolation); } From fe3d7f6c82b3257028e722803056c2383d968a5d Mon Sep 17 00:00:00 2001 From: yeyuezhishou <626732147@qq.com> Date: Thu, 30 Sep 2021 13:30:47 +0800 Subject: [PATCH 106/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E7=99=BB=E5=BD=95=EF=BC=9A=E5=9C=86=E9=80=9A=E7=A7=91=E6=8A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 公司:圆通科技 链接:https://www.yto.net.cn/ 场景:大数据应用APP内部接口 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7c7f2a34a..086ef296e 100644 --- a/README.md +++ b/README.md @@ -237,6 +237,7 @@ https://github.com/Tencent/APIJSON/issues/187 +
From 08780c77d03ec1989871ea68614889da7918b3ca Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 30 Sep 2021 14:06:33 +0800 Subject: [PATCH 107/754] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractSQLExecutor.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index c9f2d0dc4..b7cf2d0f9 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -763,18 +763,18 @@ public void setTransactionIsolation(int transactionIsolation) { this.transactionIsolation = transactionIsolation; } + private boolean isIsolationStatusSet = false; //已设置事务等级 @Override - private boolean setIsolationStatus = false; //设置事务等级 public void begin(int transactionIsolation) throws SQLException { Log.d("\n\n" + TAG, "<<<<<<<<<<<<<< TRANSACTION begin transactionIsolation = " + transactionIsolation + " >>>>>>>>>>>>>>>>>>>>>>> \n\n"); //不做判断,如果掩盖了问题,调用层都不知道为啥事务没有提交成功 // if (connection == null || connection.isClosed()) { // return; // } - if(! this.setIsolationStatus){ //只设置一次Isolation等级 PG重复设置事务等级会报错 + if (! isIsolationStatusSet) { //只设置一次Isolation等级 PG重复设置事务等级会报错 + isIsolationStatusSet = true; connection.setTransactionIsolation(transactionIsolation); } - this.setIsolationStatus=true; connection.setAutoCommit(false); //java.sql.SQLException: Can''t call commit when autocommit=true } @Override From 310f611477f8d94e129748dc83cdbd1340820c76 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 30 Sep 2021 15:57:33 +0800 Subject: [PATCH 108/754] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index 8a4d7f7c6..fd436f605 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -684,7 +684,7 @@ MOMENT表示动态,类似微信朋友圈、QQ空间的动态,每一条动态 { "[]": { "Sale":{ - "@column":"store_id,sum(amt):totAmt", + "@column":"store_id;sum(amt):totAmt", "@group":"store_id" } } From 57b45f16efdd5e0435570411a33ba3d450766d44 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 30 Sep 2021 16:02:32 +0800 Subject: [PATCH 109/754] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...64\346\230\216\346\226\207\346\241\243.md" | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index fd436f605..96b4bfc20 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -43,7 +43,7 @@ ### B1.下载项目 ```bash -git clone https://github.com/TommyLemon/APIJSON.git +git clone https://github.com/Tencent/APIJSON.git ``` 或者,直接下载ZIP打包好的项目文件。 @@ -101,25 +101,38 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs 那么需要在`DemoSQLConfig`,40-61行,改为自己数据库对应的链接 ```java + static { + DEFAULT_DATABASE = DATABASE_MYSQL; // TODO 默认数据库类型,改成你自己的 + DEFAULT_SCHEMA = "sys"; // TODO 默认数据库名/模式,改成你自己的,默认情况是 MySQL: sys, PostgreSQL: public, SQL Server: dbo, Oracle: + } + + @Override + public String getDBVersion() { + return "5.7.22"; // "8.0.11"; // TODO 改成你自己的 MySQL 或 PostgreSQL 数据库版本号 // MYSQL 8 和 7 使用的 JDBC 配置不一样 + } + + @JSONField(serialize = false) // 不在日志打印 账号/密码 等敏感信息 @Override public String getDBUri() { - //TODO 改成你自己的 - return DATABASE_POSTGRESQL.equalsIgnoreCase(getDatabase()) ? "jdbc:postgresql://localhost:5432/postgres" : "jdbc:mysql://192.168.71.146:3306/"; + // 这个是 MySQL 8.0 及以上,要加 userSSL=false return "jdbc:mysql://localhost:3306?userSSL=false&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8"; + // 以下是 MySQL 5.7 及以下 + return "jdbc:mysql://localhost:3306?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8"; //TODO 改成你自己的,TiDB 可以当成 MySQL 使用,默认端口为 4000 } + + @JSONField(serialize = false) // 不在日志打印 账号/密码 等敏感信息 @Override public String getDBAccount() { - return DATABASE_POSTGRESQL.equalsIgnoreCase(getDatabase()) ? "postgres" : "root"; //TODO 改成你自己的 + return "root"; // TODO 改成你自己的 } + + @JSONField(serialize = false) // 不在日志打印 账号/密码 等敏感信息 @Override public String getDBPassword() { - return DATABASE_POSTGRESQL.equalsIgnoreCase(getDatabase()) ? null : "root"; //TODO 改成你自己的 - } - @Override - public String getSchema() { - String s = super.getSchema(); - return StringUtil.isEmpty(s, true) ? "thea" : s; //TODO 改成你自己的。例如:将"thea"替换成你自己的“数据库名字” + return "apijson"; // TODO 改成你自己的,TiDB 可以当成 MySQL 使用, 默认密码为空字符串 "" } ``` +具体见源码
+https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONDemo/src/main/java/apijson/demo/DemoSQLConfig.java #### C-1-2.导入表 @@ -142,7 +155,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs 为了方便测试,我这里使用的Chrome浏览器的Restlet Client插件,大家可以根据自己的喜好使用不同的工具测试。 -使用` http://localhost:8080/get`测试结果。(请注意 APIJSONApplication.java 中使用 Tomcat 默认的 8080 端口,如果不小心开着PC端的“微信”,8080端口会被占用,建议改成 8081, 9090 等其它应用程序未占用的端口。) +使用` http://localhost:8080/get`测试结果。(请注意 DemoApplication.java 中使用 Tomcat 默认的 8080 端口,如果不小心开着PC端的“微信”,8080端口会被占用,建议改成 8081, 9090 等其它应用程序未占用的端口。) 随便找一个表,比如`Moment`表,我们取其中ID为12的一条出来看看 From 5d99841ccd9003f4bb1c89c58360e0759a52196d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 30 Sep 2021 16:10:07 +0800 Subject: [PATCH 110/754] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...257\264\346\230\216\346\226\207\346\241\243.md" | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index 96b4bfc20..57bfd5379 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -1,16 +1,16 @@ # APIJSON -[TOC] - +可以先看更清晰直观的视频教程
+https://search.bilibili.com/all?keyword=APIJSON&from_source=webtop_search&spm_id_from=333.851 +![image](https://user-images.githubusercontent.com/5738175/135413311-0207ec13-f7ea-4767-9e34-1a6d08438295.png) ## A.介绍 ### A1.关于接口开发 -首先是看名字`APIJSON`,API是说这个项目是属于接口开发的项目,JSON是指传输数据格式是JSON格式。介于各位看官的水平高低不齐,这里就先为没有项目经验朋友啰嗦两句接口开发的内容。有经验的朋友可以跳到`A2`继续查看。 - -(此处内容以后会有的) +首先是看名字`APIJSON`,API是说这个项目是属于接口开发的项目,JSON是指传输数据格式是JSON格式。介于各位看官的水平高低不齐,这里就先为没有项目经验朋友啰嗦两句接口开发的内容。有经验的朋友可以跳到`A2`继续查看。完整的详细介绍见项目首页
+https://github.com/Tencent/APIJSON#--apijson ### A2.功能说明 @@ -43,12 +43,12 @@ ### B1.下载项目 ```bash -git clone https://github.com/Tencent/APIJSON.git +git clone https://github.com/APIJSON/APIJSON-Demo.git ``` 或者,直接下载ZIP打包好的项目文件。 -![1542255627809](assets/1542255627809.png) +![image](https://user-images.githubusercontent.com/5738175/135412855-574cae7b-402c-4fe0-9959-711e580799af.png) From 9bc895b53759cb294878588f13c86558adc8a717 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 30 Sep 2021 16:12:40 +0800 Subject: [PATCH 111/754] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index 57bfd5379..d6f8b6979 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -66,7 +66,7 @@ Eclipse导入: 为了方便修改源代码,你可以像我一样不添加`libs/apijson-orm-3.5.1.jar`文件到`Build Path`中。而是`libs/apijson-orm-3.5.1.jar`的源码,复制到当前项目里。 -源代码在`APIJSON-Master/APIJSON-Java-Server/APIJSONORM`项目中。 +源代码在`https://github.com/Tencent/APIJSON/tree/master/APIJSONORM`项目中。 ### B3. pom.xml的错误修改。 有可能这时候pom.xml中报错,例如: From 10c3916887209b8896bc4dedb7833af805e2a8fa Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 30 Sep 2021 16:13:34 +0800 Subject: [PATCH 112/754] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...2\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index d6f8b6979..971653b4e 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -64,9 +64,10 @@ Eclipse导入: ![1542345887787](assets/1542345887787.png) -为了方便修改源代码,你可以像我一样不添加`libs/apijson-orm-3.5.1.jar`文件到`Build Path`中。而是`libs/apijson-orm-3.5.1.jar`的源码,复制到当前项目里。 +为了方便修改源代码,你可以像我一样不添加`libs/apijson-orm.jar`文件到`Build Path`中。而是`libs/apijson-orm.jar`的源码,复制到当前项目里。 -源代码在`https://github.com/Tencent/APIJSON/tree/master/APIJSONORM`项目中。 +源代码在
+https://github.com/Tencent/APIJSON/tree/master/APIJSONORM ### B3. pom.xml的错误修改。 有可能这时候pom.xml中报错,例如: From de915017c437671fe21f160501e7f0537f962c8d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 30 Sep 2021 16:16:34 +0800 Subject: [PATCH 113/754] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...32\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index 971653b4e..a2ab977be 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -58,7 +58,7 @@ Eclipse导入: 顶部菜单File > Import > Maven > Existing Maven Projects > Next > Browse -`APIJSON-Master/APIJSON-Java-Server/APIJSONBoot` +[APIJSON-Demo-Master/APIJSON-Java-Server/APIJSONDemo](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server/APIJSONDemo) 报依赖错误的时候,同目录下的`lib`里面的`jar`添加到`Build Path`中。 @@ -137,7 +137,7 @@ https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSOND #### C-1-2.导入表 -在`APIJSON-Master/MySQL`目录下有一批SQL脚本,他们看起来是这样的 +在 [APIJSON-Demo-Master/MySQL](https://github.com/APIJSON/APIJSON-Demo/tree/master/MySQL) 目录下有一批SQL脚本,他们看起来是这样的 ![1542345654422](assets/1542345654422.png) From 724cf362308526b606a7af5e509678da410293c0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 30 Sep 2021 16:18:39 +0800 Subject: [PATCH 114/754] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...32\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index a2ab977be..b38c2e6ca 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -4,6 +4,8 @@ https://search.bilibili.com/all?keyword=APIJSON&from_source=webtop_search&spm_id_from=333.851 ![image](https://user-images.githubusercontent.com/5738175/135413311-0207ec13-f7ea-4767-9e34-1a6d08438295.png) +其它各种官方和第三方文档见首页相关推荐
+https://github.com/Tencent/APIJSON#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90 ## A.介绍 @@ -924,7 +926,7 @@ static { //注册权限 "INSERT": { "@role": "OWNER" } //如果没传@role就自动添加 "UPDATE": { "id@": "User/id" } //强制放入键值对 ``` -全部操作符见 [Operation.java](https://github.com/APIJSON/APIJSON/blob/master/APIJSON-Java-Server/APIJSONORM/src/main/java/apijson/orm/Operation.java) 的注释 +全部操作符见 [Operation.java](https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/Operation.java) 的注释

From 77a07af56c5c7a4951b6cc221be694f93c4d5f6c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 30 Sep 2021 16:19:07 +0800 Subject: [PATCH 115/754] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index b38c2e6ca..216311ea4 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -1,4 +1,4 @@ -# APIJSON +# APIJSON 入门教程 可以先看更清晰直观的视频教程
https://search.bilibili.com/all?keyword=APIJSON&from_source=webtop_search&spm_id_from=333.851 From 6d6de899c8d30ed08c84af34dd6b8e670782b57d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 30 Sep 2021 16:29:48 +0800 Subject: [PATCH 116/754] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index 216311ea4..7ebab3f40 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -934,6 +934,6 @@ static { //注册权限 :first_quarter_moon_with_face:此处的介绍都只是简要介绍,只是为了引导刚刚接触APIJSON的道友快速了解APIJSON,并不代表APIJSON只有这些功能,具体功能详情参考下列图表 #### 4. 完整功能图表 -https://github.com/TommyLemon/APIJSON/blob/master/Document.md#3 +https://github.com/Tencent/APIJSON/blob/master/Document.md#3 From 29129cf5bf4d5e9f8928b68f40e6cab58a804a3e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 30 Sep 2021 16:41:54 +0800 Subject: [PATCH 117/754] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=99=BB=E8=AE=B0?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=9C=86=E9=80=9A=E5=85=AC=E5=8F=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c1ccf4582..cae9e4f50 100644 --- a/README.md +++ b/README.md @@ -238,7 +238,7 @@ https://github.com/Tencent/APIJSON/issues/187 - +
* [腾讯科技有限公司](https://www.tencent.com) From 52a2b64e28c61de95fa3898de7e73dded1bdea71 Mon Sep 17 00:00:00 2001 From: WaizLee <91610687+WaizLee@users.noreply.github.com> Date: Mon, 18 Oct 2021 14:38:12 +0800 Subject: [PATCH 118/754] =?UTF-8?q?list=E7=B1=BB=E5=9E=8B=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E5=8F=82=E6=95=B0=E9=80=9A=E8=BF=87put=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E5=88=B0=E8=BF=9C=E7=A8=8B=E5=87=BD=E6=95=B0=E5=90=8E?= =?UTF-8?q?=E4=B8=A2=E5=A4=B1=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java | 1 + 1 file changed, 1 insertion(+) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index fa4da155d..537eea16f 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -549,6 +549,7 @@ public JSON onChildParse(int index, String key, JSONObject value) throws Excepti @Override public void onPUTArrayParse(@NotNull String key, @NotNull JSONArray array) throws Exception { if (isTable == false || array.isEmpty()) { + sqlRequest.put(key, array); Log.e(TAG, "onPUTArrayParse isTable == false || array == null || array.isEmpty() >> return;"); return; } From bf1457276e52e2bf7f0bb5022f4883900e1bfb9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B2=88=E5=94=81?= <52o@qq52o.cn> Date: Thu, 21 Oct 2021 14:05:42 +0800 Subject: [PATCH 119/754] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cae9e4f50..1a7274f76 100644 --- a/README.md +++ b/README.md @@ -226,12 +226,12 @@ https://github.com/Tencent/APIJSON/issues/187
- - + + - + From 9b25ee37ab7e3f1355dea452edb96d4b3c4bc71f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B2=88=E5=94=81?= <52o@qq52o.cn> Date: Thu, 21 Oct 2021 14:06:48 +0800 Subject: [PATCH 120/754] Update README-English.md --- README-English.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README-English.md b/README-English.md index cef9f1c02..c945ce8c2 100644 --- a/README-English.md +++ b/README-English.md @@ -314,12 +314,12 @@ https://github.com/Tencent/APIJSON/issues/187
- - + + - + From 3868e8e308e527f50f2b599897e86651cd98a4e8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 25 Oct 2021 14:11:44 +0800 Subject: [PATCH 121/754] =?UTF-8?q?=20=E8=A7=A3=E5=86=B3=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=20CIRCLE=20=E8=A7=92=E8=89=B2=E6=97=B6=E6=B2=A1=E6=9C=89?= =?UTF-8?q?=E7=AE=97=E5=BD=93=E5=89=8D=E7=94=A8=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 2 +- APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 00b542ab0..f1fdcd63a 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.7.2 + 4.8.0 jar APIJSONORM diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 4cab85683..cf8dd8954 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -282,7 +282,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception { //不能在Visitor内null -> [] ! 否则会导致某些查询加上不需要的条件! List list = visitor.getContactIdList() == null ? new ArrayList() : new ArrayList(visitor.getContactIdList()); - if (role == CIRCLE) { + if (CIRCLE.equals(role)) { list.add(visitorId); } From 4327d6a3a34622db5a5ba28ca63e53be6bf4eb16 Mon Sep 17 00:00:00 2001 From: sy-records <52o@qq52o.cn> Date: Mon, 25 Oct 2021 14:30:35 +0800 Subject: [PATCH 122/754] Update README --- CONTRIBUTING.md | 1 + README-English.md | 26 ++++++++++++++++++-------- README.md | 17 +++++++++-------- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index de776b2b9..703b9224b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,6 +42,7 @@ - [jerrylususu](https://github.com/jerrylususu)(还开源了 apijson_todo_demo 和 apijson_role_extend) - [Dalezee](https://github.com/Dalezee)(还开源了 apijson_camp) - [aaronlinv](https://github.com/aaronlinv) +- [sy-records](https://github.com/sy-records) #### 其中特别致谢:
justinfengchen 提交的 6 个 Commits, 对 APIJSON 做出了 3,130 增加和 0 处删减(截止 2020/11/04 日);
diff --git a/README-English.md b/README-English.md index c945ce8c2..331ed25b2 100644 --- a/README-English.md +++ b/README-English.md @@ -316,16 +316,16 @@ https://github.com/Tencent/APIJSON/issues/187 - - + + - - - - - - + + + + + +
[More APIJSON Users](https://github.com/Tencent/APIJSON/issues/73) @@ -362,6 +362,16 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
+ + + + + + + + + +
diff --git a/README.md b/README.md index 1a7274f76..ffd4d057f 100644 --- a/README.md +++ b/README.md @@ -228,16 +228,16 @@ https://github.com/Tencent/APIJSON/issues/187 - - + + - - - - - - + + + + + +
@@ -285,6 +285,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
+
From d662ca3f3540d731d4b5d38d1aa5a1715305e6cf Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 25 Oct 2021 16:02:24 +0800 Subject: [PATCH 123/754] =?UTF-8?q?=E5=B0=86=E9=9A=90=E8=97=8F=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E5=8A=9F=E8=83=BD=E5=8D=95=E7=8B=AC=E6=8A=BD=E5=8F=96?= =?UTF-8?q?=E6=96=B9=E6=B3=95=20isHideColumn=EF=BC=8C=E6=96=B9=E4=BE=BF?= =?UTF-8?q?=E9=87=8D=E5=86=99=E6=9D=A5=E8=87=AA=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLExecutor.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index b7cf2d0f9..c0fce49f7 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -514,9 +514,8 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd , final int tablePosition, @NotNull JSONObject table, final int columnIndex, Map childMap) throws Exception { - if (rsmd.getColumnName(columnIndex).startsWith("_")) { - Log.i(TAG, "select while (rs.next()){ ..." - + " >> rsmd.getColumnName(i).startsWith(_) >> continue;"); + if (isHideColumn(config, rs, rsmd, tablePosition, table, columnIndex, childMap)) { + Log.i(TAG, "onPutColumn isHideColumn(config, rs, rsmd, tablePosition, table, columnIndex, childMap) >> return table;"); return table; } @@ -572,6 +571,22 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r return table; } + + /**如果不需要这个功能,在子类重写并直接 return false; 来提高性能 + * @param config + * @param rs + * @param rsmd + * @param tablePosition + * @param table + * @param columnIndex + * @param childMap + * @return + * @throws SQLException + */ + protected boolean isHideColumn(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd + , final int tablePosition, @NotNull JSONObject table, final int columnIndex, Map childMap) throws SQLException { + return rsmd.getColumnName(columnIndex).startsWith("_"); + } /**resultList.put(position, table); * @param config From 5a2ab0f2bcf81c20a20c86fa5431206e03aaf520 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 25 Oct 2021 16:05:11 +0800 Subject: [PATCH 124/754] =?UTF-8?q?AbstractSQLConfig.getValue=20=E4=BF=AE?= =?UTF-8?q?=E9=A5=B0=E7=AC=A6=20private=20=E6=94=B9=E4=B8=BA=20protected?= =?UTF-8?q?=20=E6=96=B9=E4=BE=BF=E5=AD=90=E7=B1=BB=E9=87=8D=E5=86=99?= =?UTF-8?q?=E6=9D=A5=E5=AE=9E=E7=8E=B0=E5=85=BC=E5=AE=B9=20Oracle=20DATETI?= =?UTF-8?q?ME=20=E7=AD=89=E7=B1=BB=E5=9E=8B=EF=BC=8C=E5=AF=B9=E5=BA=94=20P?= =?UTF-8?q?OST/PUT=20=20to=5Fdate(=3F,'yyyy-mm-dd=20hh24:mi:ss')?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 8204a84b2..11651c9fe 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2503,7 +2503,7 @@ public String getSQLKey(String key) { * 使用prepareStatement预编译,值为 ? ,后续动态set进去 */ private List preparedValueList = new ArrayList<>(); - private Object getValue(@NotNull Object value) { + protected Object getValue(@NotNull Object value) { if (isPrepared()) { preparedValueList.add(value); return "?"; From bac5eab40d8ce8d7b378c1cf47962de51beadb6e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 25 Oct 2021 16:41:14 +0800 Subject: [PATCH 125/754] =?UTF-8?q?AbstractSQLConfig.preparedValueList=20?= =?UTF-8?q?=E4=BF=AE=E9=A5=B0=E7=AC=A6=E6=94=B9=E4=B8=BA=20protected=20?= =?UTF-8?q?=E6=96=B9=E4=BE=BF=E5=AD=90=E7=B1=BB=E9=87=8D=E5=86=99=E6=9D=A5?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=85=BC=E5=AE=B9=20Oracle=20DATETIME,TIMEST?= =?UTF-8?q?AMP=20=E7=AD=89=E6=97=A5=E6=9C=9F=E6=97=B6=E9=97=B4=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=EF=BC=8C=E5=AF=B9=E5=BA=94=20POST/PUT=20to=5Fdate(=3F?= =?UTF-8?q?,'yyyy-mm-dd=20hh24:mi:ss')?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 11651c9fe..442e9eb4a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2502,7 +2502,7 @@ public String getSQLKey(String key) { /** * 使用prepareStatement预编译,值为 ? ,后续动态set进去 */ - private List preparedValueList = new ArrayList<>(); + protected List preparedValueList = new ArrayList<>(); protected Object getValue(@NotNull Object value) { if (isPrepared()) { preparedValueList.add(value); From ad412fc4fe80c75b4a809834e29e76b08c25d10d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 25 Oct 2021 17:25:27 +0800 Subject: [PATCH 126/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81=20?= =?UTF-8?q?@column:"`key`"=20=E5=8F=8D=E5=BC=95=E5=8F=B7=E6=8C=87=E5=AE=9A?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 51 ++++++++++++++----- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 442e9eb4a..af5199b04 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -145,9 +145,11 @@ public abstract class AbstractSQLConfig implements SQLConfig { RAW_MAP.put("DISTINCT", ""); //时间 - RAW_MAP.put("DATE", ""); RAW_MAP.put("now()", ""); + RAW_MAP.put("DATE", ""); + RAW_MAP.put("TIME", ""); RAW_MAP.put("DATETIME", ""); + RAW_MAP.put("TIMESTAMP", ""); RAW_MAP.put("DateTime", ""); RAW_MAP.put("SECOND", ""); RAW_MAP.put("MINUTE", ""); @@ -157,17 +159,33 @@ public abstract class AbstractSQLConfig implements SQLConfig { RAW_MAP.put("MONTH", ""); RAW_MAP.put("QUARTER", ""); RAW_MAP.put("YEAR", ""); - RAW_MAP.put("json", ""); - RAW_MAP.put("unit", ""); +// RAW_MAP.put("json", ""); +// RAW_MAP.put("unit", ""); //MYSQL 数据类型 BINARY,CHAR,DATETIME,TIME,DECIMAL,SIGNED,UNSIGNED RAW_MAP.put("BINARY", ""); RAW_MAP.put("SIGNED", ""); RAW_MAP.put("DECIMAL", ""); + RAW_MAP.put("DOUBLE", ""); + RAW_MAP.put("FLOAT", ""); + RAW_MAP.put("BOOLEAN", ""); + RAW_MAP.put("ENUM", ""); + RAW_MAP.put("SET", ""); + RAW_MAP.put("POINT", ""); + RAW_MAP.put("BLOB", ""); + RAW_MAP.put("LONGBLOB", ""); RAW_MAP.put("BINARY", ""); RAW_MAP.put("UNSIGNED", ""); + RAW_MAP.put("BIT", ""); + RAW_MAP.put("TINYINT", ""); + RAW_MAP.put("SMALLINT", ""); + RAW_MAP.put("INT", ""); + RAW_MAP.put("BIGINT", ""); RAW_MAP.put("CHAR", ""); - RAW_MAP.put("TIME", ""); + RAW_MAP.put("VARCHAR", ""); + RAW_MAP.put("TEXT", ""); + RAW_MAP.put("LONGTEXT", ""); + RAW_MAP.put("JSON", ""); //窗口函数关键字 RAW_MAP.put("OVER", ""); @@ -1686,28 +1704,33 @@ private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean String ck = ckeys[i]; // 如果参数包含 "'" ,解析字符串 - if (ck.contains("'")) { - int count = 0; - for (int j = 0; j < ck.length(); j++) { - if (ck.charAt(j) == '\'') count++; + if (ck.startsWith("`") && ck.endsWith("`")) { + origin = ck.substring(1, ck.length() - 1); + //sql 注入判断 判断 + if (StringUtil.isName(origin) == false) { + throw new IllegalArgumentException("字符 " + ck + " 不合法!" + + "预编译模式下 @column:\"`column0`,`column1`:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中所有字符串 column 都必须必须为1个单词 !"); } - // FIXME 把 `column` 和 '2 values with [ / : ] ..' 按引号位置分割才能满足全文索引、窗口函数的需要 - // 排除字符串中参数中包含 ' 的情况和不以' 开头和结尾的情况,同时排除 cast('s' as ...) 以空格分隔的参数中包含字符串的情况 - if (count != 2 || !(ck.startsWith("'") && ck.endsWith("'"))) { + + ckeys[i] = getKey(origin).toString(); + } + else if (ck.startsWith("'") && ck.endsWith("'")) { + origin = ck.substring(1, ck.length() - 1); + if (origin.contains("'")) { throw new IllegalArgumentException("字符串 " + ck + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中字符串参数不合法,必须以 ' 开头, ' 结尾,字符串中不能包含 ' "); } //sql 注入判断 判断 - origin = (ck.substring(1, ck.length() - 1)); if (origin.contains("--") || PATTERN_STRING.matcher(origin).matches() == true) { throw new IllegalArgumentException("字符 " + ck + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中所有字符串 arg 都必须不符合正则表达式 " + PATTERN_STRING + " 且不包含连续减号 -- !"); } - + // 1.字符串不是字段也没有别名,所以不解析别名 2. 是字符串,进行预编译,使用getValue() ,对字符串进行截取 - ckeys[i] = getValue(ck.substring(1, ck.length() - 1)).toString(); + ckeys[i] = getValue(origin).toString(); } else { // 参数不包含",",即不是字符串 From b1522c6e7b4c79ba6101f5592f222a405b0a33ed Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 25 Oct 2021 18:07:30 +0800 Subject: [PATCH 127/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81=20?= =?UTF-8?q?@column:"cast(`date`=20AS=20TIME)"=20=E8=BF=99=E7=A7=8D?= =?UTF-8?q?=E5=9C=A8=E5=87=BD=E6=95=B0=E5=86=85=20`key`=20=E4=B8=8E?= =?UTF-8?q?=E5=85=B3=E9=94=AE=E8=AF=8D=E7=AD=89=E7=BB=84=E5=90=88=E7=9A=84?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index af5199b04..97ee35a25 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -65,7 +65,6 @@ import apijson.orm.model.PgAttribute; import apijson.orm.model.PgClass; import apijson.orm.model.Request; -import apijson.orm.model.Response; import apijson.orm.model.SysColumn; import apijson.orm.model.SysTable; import apijson.orm.model.Table; @@ -115,7 +114,6 @@ public abstract class AbstractSQLConfig implements SQLConfig { CONFIG_TABLE_LIST = new ArrayList<>(); // Table, Column 等是系统表 AbstractVerifier.SYSTEM_ACCESS_MAP.keySet()); CONFIG_TABLE_LIST.add(Function.class.getSimpleName()); CONFIG_TABLE_LIST.add(Request.class.getSimpleName()); - CONFIG_TABLE_LIST.add(Response.class.getSimpleName()); CONFIG_TABLE_LIST.add(Access.class.getSimpleName()); CONFIG_TABLE_LIST.add(Document.class.getSimpleName()); CONFIG_TABLE_LIST.add(TestRecord.class.getSimpleName()); From 8b00c69caa05b2157d1734d86d1f794ff9209779 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 25 Oct 2021 18:50:50 +0800 Subject: [PATCH 128/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81=20?= =?UTF-8?q?CASE=20WHEN=EF=BC=8C=E4=BE=8B=E5=A6=82=20(CASE=20WHEN=20sex=20*?= =?UTF-8?q?=201=20=3D=200=20THEN=20'=E7=94=B7'=20WHEN=20sex=20>=3D=201=20T?= =?UTF-8?q?HEN=20'=E5=A5=B3'=20ELSE=20'=E5=85=B6=E5=AE=83'=20END)=EF=BC=9B?= =?UTF-8?q?=E8=A7=A3=E5=86=B3=E9=80=9A=E8=BF=87=20`=5Fkey`=20=E7=BB=95?= =?UTF-8?q?=E8=BF=87=E9=9A=90=E8=97=8F=E5=AD=97=E6=AE=B5=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 60 +++++++++++++++---- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 97ee35a25..5e5a59917 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -129,6 +129,20 @@ public abstract class AbstractSQLConfig implements SQLConfig { RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 + + RAW_MAP.put("+", ""); + RAW_MAP.put("-", ""); + RAW_MAP.put("*", ""); + RAW_MAP.put("/", ""); + RAW_MAP.put("=", ""); + RAW_MAP.put("!=", ""); + RAW_MAP.put(">", ""); + RAW_MAP.put(">=", ""); + RAW_MAP.put("<", ""); + RAW_MAP.put("<=", ""); + RAW_MAP.put("%", ""); + RAW_MAP.put("(", ""); + RAW_MAP.put(")", ""); // MySQL 关键字 RAW_MAP.put("AS", ""); @@ -141,6 +155,11 @@ public abstract class AbstractSQLConfig implements SQLConfig { RAW_MAP.put("NOT", ""); RAW_MAP.put("VALUE", ""); RAW_MAP.put("DISTINCT", ""); + RAW_MAP.put("CASE", ""); + RAW_MAP.put("WHEN", ""); + RAW_MAP.put("THEN", ""); + RAW_MAP.put("ELSE", ""); + RAW_MAP.put("END", ""); //时间 RAW_MAP.put("now()", ""); @@ -1705,7 +1724,7 @@ private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean if (ck.startsWith("`") && ck.endsWith("`")) { origin = ck.substring(1, ck.length() - 1); //sql 注入判断 判断 - if (StringUtil.isName(origin) == false) { + if (origin.startsWith("_") || StringUtil.isName(origin) == false) { throw new IllegalArgumentException("字符 " + ck + " 不合法!" + "预编译模式下 @column:\"`column0`,`column1`:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中所有字符串 column 都必须必须为1个单词 !"); @@ -1720,12 +1739,6 @@ else if (ck.startsWith("'") && ck.endsWith("'")) { + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中字符串参数不合法,必须以 ' 开头, ' 结尾,字符串中不能包含 ' "); } - //sql 注入判断 判断 - if (origin.contains("--") || PATTERN_STRING.matcher(origin).matches() == true) { - throw new IllegalArgumentException("字符 " + ck + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中所有字符串 arg 都必须不符合正则表达式 " + PATTERN_STRING + " 且不包含连续减号 -- !"); - } // 1.字符串不是字段也没有别名,所以不解析别名 2. 是字符串,进行预编译,使用getValue() ,对字符串进行截取 ckeys[i] = getValue(origin).toString(); @@ -1745,7 +1758,7 @@ else if (ck.startsWith("'") && ck.endsWith("'")) { + "关键字必须全大写,且以空格分隔的参数,空格必须只有 1 个!其它情况不允许空格!"); } } else { - if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { + if (origin.startsWith("_") || origin.contains("--")) { // || PATTERN_FUNCTION.matcher(origin).matches() == false) { throw new IllegalArgumentException("字符 " + ck + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); @@ -1818,12 +1831,35 @@ private String praseArgsSplitWithSpace(String mkes[]) { } //这里为什么还要做一次判断 是因为解析窗口函数调用的时候会判断一次 - if (isPrepared()) { - if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { - throw new IllegalArgumentException("字符 " + origin + " 不合法!" + String ck = origin; + // 如果参数包含 "`" 或 "'" ,解析字符串 + if (ck.startsWith("`") && ck.endsWith("`")) { + origin = ck.substring(1, ck.length() - 1); + if (origin.startsWith("_") || StringUtil.isName(origin) == false) { + throw new IllegalArgumentException("字符 " + ck + " 不合法!" + + "预编译模式下 @column:\"`column0`,`column1`:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中所有字符串 column 都必须必须为1个单词 !"); + } + + mkes[j] = getKey(origin).toString(); + continue; + } + else if (ck.startsWith("'") && ck.endsWith("'")) { + origin = ck.substring(1, ck.length() - 1); + if (origin.contains("'")) { + throw new IllegalArgumentException("字符串 " + ck + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); + + " 中字符串参数不合法,必须以 ' 开头, ' 结尾,字符串中不能包含 ' "); } + + // 1.字符串不是字段也没有别名,所以不解析别名 2. 是字符串,进行预编译,使用getValue() ,对字符串进行截取 + mkes[j] = getValue(origin).toString(); + continue; + } + else if (ck.contains("`") || ck.contains("'") || origin.startsWith("_") || origin.contains("--")) { // || PATTERN_FUNCTION.matcher(origin).matches() == false) { + throw new IllegalArgumentException("字符 " + origin + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); } boolean isName = false; From 0ca17e631fb851090adb6a8e8376973839871dd1 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 25 Oct 2021 23:06:51 +0800 Subject: [PATCH 129/754] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E4=B8=94=E6=9C=AA=E5=AE=9E=E9=99=85=E7=94=A8=E4=B8=8A=E7=9A=84?= =?UTF-8?q?=E7=9A=84=20Response.java?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractVerifier.java | 2 -- .../src/main/java/apijson/orm/model/Response.java | 15 --------------- 2 files changed, 17 deletions(-) delete mode 100755 APIJSONORM/src/main/java/apijson/orm/model/Response.java diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index cf8dd8954..d983da6ae 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -63,7 +63,6 @@ import apijson.orm.model.PgAttribute; import apijson.orm.model.PgClass; import apijson.orm.model.Request; -import apijson.orm.model.Response; import apijson.orm.model.SysColumn; import apijson.orm.model.SysTable; import apijson.orm.model.Table; @@ -149,7 +148,6 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { SYSTEM_ACCESS_MAP.put(Access.class.getSimpleName(), getAccessMap(Access.class.getAnnotation(MethodAccess.class))); SYSTEM_ACCESS_MAP.put(Function.class.getSimpleName(), getAccessMap(Function.class.getAnnotation(MethodAccess.class))); SYSTEM_ACCESS_MAP.put(Request.class.getSimpleName(), getAccessMap(Request.class.getAnnotation(MethodAccess.class))); - SYSTEM_ACCESS_MAP.put(Response.class.getSimpleName(), getAccessMap(Response.class.getAnnotation(MethodAccess.class))); if (Log.DEBUG) { SYSTEM_ACCESS_MAP.put(Table.class.getSimpleName(), getAccessMap(Table.class.getAnnotation(MethodAccess.class))); diff --git a/APIJSONORM/src/main/java/apijson/orm/model/Response.java b/APIJSONORM/src/main/java/apijson/orm/model/Response.java deleted file mode 100755 index f5dbc3926..000000000 --- a/APIJSONORM/src/main/java/apijson/orm/model/Response.java +++ /dev/null @@ -1,15 +0,0 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. - -This source code is licensed under the Apache License Version 2.0.*/ - - -package apijson.orm.model; - -import apijson.MethodAccess; - -/**结果处理 - * @author Lemon - */ -@MethodAccess(POST = {}, PUT = {}, DELETE = {}) -public class Response { -} From 3c92e777b54ea2a0ffb126dd40cb47685ae1a9eb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 26 Oct 2021 00:02:40 +0800 Subject: [PATCH 130/754] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ffd4d057f..6588a07cb 100644 --- a/README.md +++ b/README.md @@ -451,7 +451,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [apijson-learn](https://github.com/rainboy-learn/apijson-learn) APIJSON 学习笔记和源码解析 -[apijson-practice](https://github.com/vcoolwind/apijson-practice) 实践一下 apijson,对做管理平台还是能有不少提效的 +[apijson-practice](https://github.com/vcoolwind/apijson-practice) BAT 技术专家开源的 APIJSON 参数校验注解 Library 及相关 Demo [apijson-sample](https://gitee.com/greyzeng/apijson-sample) APIJSON 简单使用 Demo 及教程 From aaf38e10e2cc8795eaed46ad91a9eb31eacb50b1 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 26 Oct 2021 00:16:45 +0800 Subject: [PATCH 131/754] =?UTF-8?q?=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20apijson-db2=20=E5=92=8C=20ClickHouse=20Dem?= =?UTF-8?q?o=20APIJSONDemo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit APIJSON 接入 IBM 数据库 DB2 的 Demo: https://github.com/andream7/apijson-db2 APIJSON 接入 ClickHouse 的 Demo:https://github.com/qiujunlin/APIJSONDemo --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 6588a07cb..a0669b22e 100644 --- a/README.md +++ b/README.md @@ -465,6 +465,10 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [ApiJsonByJFinal](https://gitee.com/zhiyuexin/ApiJsonByJFinal) 整合 APIJSON 和 JFinal 的 Demo +[apijson-db2](https://github.com/andream7/apijson-db2) APIJSON 接入 IBM 数据库 DB2 的 Demo + +[APIJSONDemo](https://github.com/qiujunlin/APIJSONDemo) APIJSON 接入 ClickHouse 的 Demo + [APIJSONDemo_ClickHouse](https://github.com/chenyanlann/APIJSONDemo_ClickHouse) APIJSON + SpringBoot 连接 ClickHouse 使用的 Demo [apijson-builder](https://github.com/pengxianggui/apijson-builder) 一个方便为 APIJSON 构建 RESTful 请求的 JavaScript 库 From b4de2c2ee3de4436602b1c90196e83aa0bf54e3e Mon Sep 17 00:00:00 2001 From: abigeater Date: Tue, 2 Nov 2021 17:57:39 +0800 Subject: [PATCH 132/754] =?UTF-8?q?add=20=E5=A2=9E=E5=8A=A0=E7=94=9F?= =?UTF-8?q?=E6=80=81=E9=A1=B9=E7=9B=AEapijson-hyperf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a0669b22e..a3465b33d 100644 --- a/README.md +++ b/README.md @@ -439,6 +439,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [apijson-php](https://github.com/qq547057827/apijson-php) PHP 版 APIJSON,基于 ThinkPHP,支持 MySQL, PostgreSQL, SQL Server, Oracle 等 +[apijson-hyperf](https://github.com/kvnZero/hyperf-APIJSON.git) PHP 版 APIJSON,基于 Hyperf 支持 MySQL + [apijson-node](https://github.com/kevinaskin/apijson-node) Node.ts 版 APIJSON,提供 nestjs 和 typeorm 的 Demo,由字节跳动工程师开发 [uliweb-apijson](https://github.com/zhangchunlin/uliweb-apijson) Python 版 APIJSON,支持 MySQL, PostgreSQL, SQL Server, Oracle, SQLite 等 From 147cb7a3c8c0e677a66cacf3f9000c24f1d7ccbf Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 2 Nov 2021 21:19:42 +0800 Subject: [PATCH 133/754] =?UTF-8?q?=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20PHP=20=E7=89=88=20APIJSON=20=E5=8F=AB=20ap?= =?UTF-8?q?ijson-hyperf=EF=BC=8C=E6=84=9F=E8=B0=A2=E4=BD=9C=E8=80=85?= =?UTF-8?q?=E7=9A=84=E8=B4=A1=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/kvnZero/hyperf-APIJSON --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a3465b33d..dd8d36af8 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This source code is licensed under the Apache License Version 2.0
  -   +    

@@ -435,12 +435,12 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [apijson-go](https://gitee.com/tiangao/apijson-go) Go 版 APIJSON ,支持单表查询、数组查询、多表一对一关联查询、多表一对多关联查询 等 +[apijson-hyperf](https://github.com/kvnZero/hyperf-APIJSON.git) PHP 版 APIJSON,基于 Hyperf 支持 MySQL + [APIJSON-php](https://github.com/xianglong111/APIJSON-php) PHP 版 APIJSON,基于 ThinkPHP,支持 MySQL, PostgreSQL, SQL Server, Oracle 等 [apijson-php](https://github.com/qq547057827/apijson-php) PHP 版 APIJSON,基于 ThinkPHP,支持 MySQL, PostgreSQL, SQL Server, Oracle 等 -[apijson-hyperf](https://github.com/kvnZero/hyperf-APIJSON.git) PHP 版 APIJSON,基于 Hyperf 支持 MySQL - [apijson-node](https://github.com/kevinaskin/apijson-node) Node.ts 版 APIJSON,提供 nestjs 和 typeorm 的 Demo,由字节跳动工程师开发 [uliweb-apijson](https://github.com/zhangchunlin/uliweb-apijson) Python 版 APIJSON,支持 MySQL, PostgreSQL, SQL Server, Oracle, SQLite 等 From 914e22dc5e54e6ba245798437866b8057284016f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 11 Nov 2021 19:57:36 +0800 Subject: [PATCH 134/754] =?UTF-8?q?=E6=8A=A5=E9=94=99=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=90=9C=E7=B4=A2=E9=93=BE=E6=8E=A5=E5=8F=8A?= =?UTF-8?q?=E5=B8=A6=E7=B3=BB=E7=BB=9F=E4=BF=A1=E6=81=AF=E7=9A=84=E6=8F=90?= =?UTF-8?q?=E4=BA=A4=E9=97=AE=E9=A2=98=E6=A8=A1=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/Log.java | 3 + .../apijson/orm/AbstractObjectParser.java | 50 ++++++++++++-- .../main/java/apijson/orm/AbstractParser.java | 66 ++++++++++++++++++- 3 files changed, 111 insertions(+), 8 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/Log.java b/APIJSONORM/src/main/java/apijson/Log.java index e3f64f86d..d67f19a71 100755 --- a/APIJSONORM/src/main/java/apijson/Log.java +++ b/APIJSONORM/src/main/java/apijson/Log.java @@ -13,6 +13,9 @@ public class Log { public static boolean DEBUG = true; + + public static final String VERSION = "4.8.0"; + public static final String KEY_SYSTEM_INFO_DIVIDER = "---|-----APIJSON SYSTEM INFO-----|---"; //默认的时间格式 public static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS"); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 537eea16f..b35d0c5a4 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -116,7 +116,7 @@ public AbstractObjectParser(@NotNull JSONObject request, String parentPath, SQLC public String getParentPath() { return parentPath; } - + @Override public AbstractObjectParser setParentPath(String parentPath) { this.parentPath = parentPath; @@ -284,6 +284,42 @@ else if (method == PUT && value instanceof JSONArray && (whereList == null || wh } } catch (Exception e) { if (tri == false) { + if (Log.DEBUG && sqlConfig != null && e.getMessage().contains(Log.KEY_SYSTEM_INFO_DIVIDER) == false) { + try { + String db = sqlConfig.getDatabase(); + if (db == null) { + if (sqlConfig.isPostgreSQL()) { + db = SQLConfig.DATABASE_POSTGRESQL; + } + else if (sqlConfig.isSQLServer()) { + db = SQLConfig.DATABASE_SQLSERVER; + } + else if (sqlConfig.isOracle()) { + db = SQLConfig.DATABASE_ORACLE; + } + else if (sqlConfig.isDb2()) { + db = SQLConfig.DATABASE_DB2; + } + else if (sqlConfig.isClickHouse()) { + db = SQLConfig.DATABASE_CLICKHOUSE; + } + else { + db = SQLConfig.DATABASE_MYSQL; + } + } + + Class clazz = e.getClass(); + e = clazz.getConstructor(String.class).newInstance( + e.getMessage() + + " " + Log.KEY_SYSTEM_INFO_DIVIDER + " \n**环境信息** " + + "\n系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version") + + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch") + + "\n数据库: " + db + " " + sqlConfig.getDBVersion() + + "\nAPIJSON: " + Log.VERSION + ); + } catch (Throwable e2) {} + } + throw e; // 不忽略错误,抛异常 } invalidate(); // 忽略错误,还原request @@ -860,14 +896,14 @@ public JSONObject onSQLExecute() throws Exception { boolean isSimpleArray = false; List rawList = null; - + if (isArrayMainTable && position == 0 && result != null) { - + isSimpleArray = (functionMap == null || functionMap.isEmpty()) && (customMap == null || customMap.isEmpty()) && (childMap == null || childMap.isEmpty()) && (table.equals(arrayTable)); - + // 提取并缓存数组主表的列表数据 rawList = (List) result.remove(SQLExecutor.KEY_RAW_LIST); if (rawList != null) { @@ -875,7 +911,7 @@ public JSONObject onSQLExecute() throws Exception { if (isSimpleArray == false) { long startTime = System.currentTimeMillis(); - + for (int i = 1; i < rawList.size(); i++) { // 从 1 开始,0 已经处理过 JSONObject obj = rawList.get(i); @@ -883,7 +919,7 @@ public JSONObject onSQLExecute() throws Exception { parser.putQueryResult(arrayPath + "/" + i + "/" + name, obj); // 解决获取关联数据时requestObject里不存在需要的关联数据 } } - + long endTime = System.currentTimeMillis(); // 3ms - 8ms Log.e(TAG, "\n onSQLExecute <<<<<<<<<<<<<<<<<<<<<<<<<<<<\n for (int i = 1; i < list.size(); i++) startTime = " + startTime + "; endTime = " + endTime + "; duration = " + (endTime - startTime) + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n "); @@ -895,7 +931,7 @@ public JSONObject onSQLExecute() throws Exception { if (isSubquery == false && result != null) { parser.putQueryResult(path, result); // 解决获取关联数据时requestObject里不存在需要的关联数据 - + if (isSimpleArray && rawList != null) { result.put(SQLExecutor.KEY_RAW_LIST, rawList); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index b4de3e7c1..5210e58b6 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -9,6 +9,7 @@ import static apijson.RequestMethod.GET; import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; import java.sql.Connection; import java.sql.SQLException; import java.sql.Savepoint; @@ -641,8 +642,35 @@ public static JSONObject newSuccessResult() { * @return */ public static JSONObject extendErrorResult(JSONObject object, Exception e) { + String msg = e.getMessage(); + + if (Log.DEBUG) { + try { + int index = msg.lastIndexOf(Log.KEY_SYSTEM_INFO_DIVIDER); + String info = index >= 0 ? msg.substring(index + Log.KEY_SYSTEM_INFO_DIVIDER.length()).trim() + : "\n**环境信息** " + + "\n系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version") + + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch") + + "\n数据库: " + + "\nAPIJSON: " + Log.VERSION; + + msg = index < 0 ? msg : msg.substring(0, index).trim(); + String encodedMsg = URLEncoder.encode(msg, "UTF-8"); + + msg += " \n\n\n浏览器打开以下链接搜索答案\nGitHub: https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+" + encodedMsg + + " \n\nGoogle:https://www.google.com/search?q=" + encodedMsg + + " \n\nBaidu:https://www.baidu.com/s?ie=UTF-8&wd=" + encodedMsg + + " \n\n都没找到答案?打开这个链接 https://github.com/Tencent/APIJSON/issues/new?assignees=&labels=&template=--bug.md " + + "\n然后提交问题,推荐用以下模板修改,注意要换行保持清晰可读。" + + "\n【标题】:" + msg + + "\n【内容】:" + info + "\n\n**问题描述**\n" + msg; + } catch (Throwable e2) { + e2.printStackTrace(); + } + } + JSONObject error = newErrorResult(e); - return extendResult(object, error.getIntValue(JSONResponse.KEY_CODE), error.getString(JSONResponse.KEY_MSG)); + return extendResult(object, error.getIntValue(JSONResponse.KEY_CODE), msg); } /**新建错误状态内容 * @param e @@ -1682,6 +1710,42 @@ public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Except return result; } catch (Exception e) { + if (Log.DEBUG && e.getMessage().contains(Log.KEY_SYSTEM_INFO_DIVIDER) == false) { + try { + String db = config.getDatabase(); + if (db == null) { + if (config.isPostgreSQL()) { + db = SQLConfig.DATABASE_POSTGRESQL; + } + else if (config.isSQLServer()) { + db = SQLConfig.DATABASE_SQLSERVER; + } + else if (config.isOracle()) { + db = SQLConfig.DATABASE_ORACLE; + } + else if (config.isDb2()) { + db = SQLConfig.DATABASE_DB2; + } + else if (config.isClickHouse()) { + db = SQLConfig.DATABASE_CLICKHOUSE; + } + else { + db = SQLConfig.DATABASE_MYSQL; + } + } + + Class clazz = e.getClass(); + e = clazz.getConstructor(String.class).newInstance( + e.getMessage() + + " " + Log.KEY_SYSTEM_INFO_DIVIDER + " \n**环境信息** " + + "\n系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version") + + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch") + + "\n数据库: " + db + " " + config.getDBVersion() + + "\nAPIJSON: " + Log.VERSION + ); + } catch (Throwable e2) {} + } + if (Log.DEBUG == false && e instanceof SQLException) { throw new SQLException("数据库驱动执行异常SQLException,非 Log.DEBUG 模式下不显示详情,避免泄漏真实模式名、表名等隐私信息", e); } From c397c82584daede3ce1237618174ca2ad744514a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 11 Nov 2021 23:01:35 +0800 Subject: [PATCH 135/754] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=8A=A5=E9=94=99?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=EF=BC=8C=E5=BC=95=E5=AF=BC=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E8=87=AA=E8=A1=8C=E8=A7=A3=E5=86=B3=E5=8F=8A=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=9B=E8=A7=A3=E5=86=B3=20AbstractVerifie?= =?UTF-8?q?r.verifyAccess=20=E5=8F=AA=E5=85=81=E8=AE=B8=20Number=20?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E7=9A=84=20id=EF=BC=8C=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=8F=98=E9=87=8F=E5=90=8D=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apijson/orm/AbstractObjectParser.java | 2 +- .../main/java/apijson/orm/AbstractParser.java | 125 +++++++++++++----- .../java/apijson/orm/AbstractVerifier.java | 22 +-- 3 files changed, 107 insertions(+), 42 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index b35d0c5a4..453db9c5f 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -313,8 +313,8 @@ else if (sqlConfig.isClickHouse()) { e.getMessage() + " " + Log.KEY_SYSTEM_INFO_DIVIDER + " \n**环境信息** " + "\n系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version") - + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch") + "\n数据库: " + db + " " + sqlConfig.getDBVersion() + + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch") + "\nAPIJSON: " + Log.VERSION ); } catch (Throwable e2) {} diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 5210e58b6..65b79cea9 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -9,6 +9,8 @@ import static apijson.RequestMethod.GET; import java.io.UnsupportedEncodingException; +import java.lang.management.ManagementFactory; +import java.net.InetAddress; import java.net.URLEncoder; import java.sql.Connection; import java.sql.SQLException; @@ -26,6 +28,9 @@ import java.util.concurrent.TimeoutException; import javax.activation.UnsupportedDataTypeException; +import javax.management.MBeanServer; +import javax.management.ObjectName; +import javax.management.Query; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; @@ -153,6 +158,15 @@ public AbstractParser setTag(String tag) { return this; } + protected String requestURL; + public String getRequestURL() { + return requestURL; + } + public AbstractParser setRequestURL(String requestURL) { + this.requestURL = requestURL; + return this; + } + protected JSONObject requestObject; @Override public JSONObject getRequest() { @@ -354,7 +368,7 @@ public JSONObject parseResponse(JSONObject request) { onVerifyContent(); } } catch (Exception e) { - return extendErrorResult(requestObject, e); + return extendErrorResult(requestObject, e, requestMethod, getRequestURL()); } } @@ -364,7 +378,7 @@ public JSONObject parseResponse(JSONObject request) { setGlobleRole(requestObject.getString(JSONRequest.KEY_ROLE)); requestObject.remove(JSONRequest.KEY_ROLE); } catch (Exception e) { - return extendErrorResult(requestObject, e); + return extendErrorResult(requestObject, e, requestMethod, getRequestURL()); } } @@ -383,7 +397,7 @@ public JSONObject parseResponse(JSONObject request) { requestObject.remove(JSONRequest.KEY_EXPLAIN); requestObject.remove(JSONRequest.KEY_CACHE); } catch (Exception e) { - return extendErrorResult(requestObject, e); + return extendErrorResult(requestObject, e, requestMethod, getRequestURL()); } final String requestString = JSON.toJSONString(request);//request传进去解析后已经变了 @@ -407,7 +421,7 @@ public JSONObject parseResponse(JSONObject request) { onRollback(); } - requestObject = error == null ? extendSuccessResult(requestObject) : extendErrorResult(requestObject, error); + requestObject = error == null ? extendSuccessResult(requestObject) : extendErrorResult(requestObject, error, requestMethod, getRequestURL()); JSONObject res = (globleFormat != null && globleFormat) && JSONResponse.isSuccess(requestObject) ? new JSONResponse(requestObject) : requestObject; @@ -608,8 +622,9 @@ public static JSONObject extendResult(JSONObject object, int code, String msg) { if (object == null) { object = new JSONObject(true); } + boolean isOk = JSONResponse.isSuccess(code); if (object.containsKey(JSONResponse.KEY_OK) == false) { - object.put(JSONResponse.KEY_OK, JSONResponse.isSuccess(code)); + object.put(JSONResponse.KEY_OK, isOk); } if (object.containsKey(JSONResponse.KEY_CODE) == false) { object.put(JSONResponse.KEY_CODE, code); @@ -619,6 +634,7 @@ public static JSONObject extendResult(JSONObject object, int code, String msg) { if (m.isEmpty() == false) { msg = m + " ;\n " + StringUtil.getString(msg); } + object.put(JSONResponse.KEY_MSG, msg); return object; } @@ -642,36 +658,84 @@ public static JSONObject newSuccessResult() { * @return */ public static JSONObject extendErrorResult(JSONObject object, Exception e) { + return extendErrorResult(object, e, null, null); + } + /**添加请求成功的状态内容 + * @param object + * @return + */ + public static JSONObject extendErrorResult(JSONObject object, Exception e, RequestMethod requestMethod, String url) { String msg = e.getMessage(); - + if (Log.DEBUG) { try { int index = msg.lastIndexOf(Log.KEY_SYSTEM_INFO_DIVIDER); String info = index >= 0 ? msg.substring(index + Log.KEY_SYSTEM_INFO_DIVIDER.length()).trim() : "\n**环境信息** " + "\n系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version") - + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch") + "\n数据库: " + + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch") + "\nAPIJSON: " + Log.VERSION; msg = index < 0 ? msg : msg.substring(0, index).trim(); String encodedMsg = URLEncoder.encode(msg, "UTF-8"); + + if (StringUtil.isEmpty(url, true)) { + String host = "localhost"; + try { + host = InetAddress.getLocalHost().getHostAddress(); + } catch (Throwable e2) {} + + String port = "8080"; + try { + MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer(); + + Set objectNames = beanServer.queryNames( + new ObjectName("*:type=Connector,*"), + Query.match(Query.attr("protocol"), Query.value("HTTP/1.1")) + ); + String p = objectNames.iterator().next().getKeyProperty("port"); + port = StringUtil.isEmpty(p, true) ? port : p; + } catch (Throwable e2) {} + + url = "http://" + host + ":" + port + "/" + (requestMethod == null ? RequestMethod.GET : requestMethod).name().toLowerCase(); + } + + String req = JSON.toJSONString(object); + try { + req = URLEncoder.encode(req, "UTF-8"); + } catch (Throwable e2) {} + + + boolean isSQLException = e instanceof SQLException; // SQL 报错一般都是通用问题,优先搜索引擎 + String apiatuoAndGitHubLink = "\n【APIAuto】: \n http://apijson.cn/api?type=JSON&url=" + URLEncoder.encode(url, "UTF-8") + "&json=" + req + + " \n\n【GitHub】: \n https://www.google.com/search?q=site%3Agithub.com%2FTencent%2FAPIJSON+++" + encodedMsg; - msg += " \n\n\n浏览器打开以下链接搜索答案\nGitHub: https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+" + encodedMsg - + " \n\nGoogle:https://www.google.com/search?q=" + encodedMsg - + " \n\nBaidu:https://www.baidu.com/s?ie=UTF-8&wd=" + encodedMsg - + " \n\n都没找到答案?打开这个链接 https://github.com/Tencent/APIJSON/issues/new?assignees=&labels=&template=--bug.md " + msg += " \n\n\n浏览器打开以下链接查看解答" + + (isSQLException ? "" : apiatuoAndGitHubLink) + // GitHub Issue 搜索貌似是精准包含,不易找到答案 + " \n\nGitHub: \n https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+" + encodedMsg + + " \n\n【Google】:\n https://www.google.com/search?q=" + encodedMsg + + " \n\n【百度】:\n https://www.baidu.com/s?ie=UTF-8&wd=" + encodedMsg + + (isSQLException ? apiatuoAndGitHubLink : "") + + " \n\n都没找到答案?打开这个链接 \n https://github.com/Tencent/APIJSON/issues/new?assignees=&labels=&template=--bug.md " + "\n然后提交问题,推荐用以下模板修改,注意要换行保持清晰可读。" + "\n【标题】:" + msg - + "\n【内容】:" + info + "\n\n**问题描述**\n" + msg; - } catch (Throwable e2) { - e2.printStackTrace(); - } + + "\n【内容】:" + info + "\n\n**问题描述**\n" + msg + + "\n\n" + + "\n\nPOST " + url + + "\n请求 Request JSON:\n ```js" + + "\n 请填写,例如 { \"Users\":{} }" + + "\n```" + + "\n\n返回结果 Response JSON:\n ```js" + + "\n 请填写,例如 { \"Users\": {}, \"code\": 401, \"msg\": \"Users 不允许 UNKNOWN 用户的 GET 请求!\" }" + + "\n```"; + } catch (Throwable e2) {} } - + JSONObject error = newErrorResult(e); return extendResult(object, error.getIntValue(JSONResponse.KEY_CODE), msg); } + /**新建错误状态内容 * @param e * @return @@ -1000,7 +1064,7 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name //不能改变,因为后面可能继续用到,导致1以上都改变 []:{0:{Comment[]:{0:{Comment:{}},1:{...},...}},1:{...},...} final String query = request.getString(JSONRequest.KEY_QUERY); final Integer count = request.getInteger(JSONRequest.KEY_COUNT); //TODO 如果不想用默认数量可以改成 getIntValue(JSONRequest.KEY_COUNT); - final int page = request.getIntValue(JSONRequest.KEY_PAGE); + final Integer page = request.getInteger(JSONRequest.KEY_PAGE); final Object join = request.get(JSONRequest.KEY_JOIN); int query2; @@ -1026,8 +1090,9 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name } } + int page2 = page == null ? 0 : page; int maxPage = getMaxQueryPage(); - if (page < 0 || page > maxPage) { + if (page2 < 0 || page2 > maxPage) { throw new IllegalArgumentException(path + "/" + JSONRequest.KEY_PAGE + ":value 中 value 的值不合法!必须在 0-" + maxPage + " 内 !"); } @@ -1052,8 +1117,8 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name JSONArray response = null; try { - int size = count2 == 0 ? max : count2;//count为每页数量,size为第page页实际数量,max(size) = count - Log.d(TAG, "onArrayParse size = " + size + "; page = " + page); + int size = count2 == 0 ? max : count2; //count为每页数量,size为第page页实际数量,max(size) = count + Log.d(TAG, "onArrayParse size = " + size + "; page = " + page2); //key[]:{Table:{}}中key equals Table时 提取Table @@ -1076,13 +1141,13 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // SQLConfig config = createSQLConfig() .setMethod(requestMethod) .setCount(size) - .setPage(page) + .setPage(page2) .setQuery(query2) .setTable(arrTableKey) .setJoinList(onJoinParse(join, request)); JSONObject parent; - + boolean isExtract = true; //生成size个 @@ -1091,7 +1156,7 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // if (parent == null || parent.isEmpty()) { break; } - + long startTime = System.currentTimeMillis(); /* 这里优化了 Table[]: { Table:{} } 这种情况下的性能 @@ -1101,13 +1166,13 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // JSONObject fo = i != 0 || arrTableKey == null ? null : parent.getJSONObject(arrTableKey); @SuppressWarnings("unchecked") List list = fo == null ? null : (List) fo.remove(SQLExecutor.KEY_RAW_LIST); - + if (list != null && list.isEmpty() == false) { isExtract = false; - + list.set(0, fo); // 不知道为啥第 0 项也加了 @RAW@LIST response.addAll(list); // List cannot match List response = new JSONArray(list); - + long endTime = System.currentTimeMillis(); // 0ms Log.d(TAG, "\n onArrayParse <<<<<<<<<<<<<<<<<<<<<<<<<<<<\n for (int i = 0; i < (isSubquery ? 1 : size); i++) " + " startTime = " + startTime + "; endTime = " + endTime + "; duration = " + (endTime - startTime) + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); @@ -1117,7 +1182,7 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // //key[]:{Table:{}}中key equals Table时 提取Table response.add(getValue(parent, childKeys)); //null有意义 } - + //Table>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -1143,7 +1208,7 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // if (fo instanceof Boolean || fo instanceof Number || fo instanceof String) { //[{}] 和 [[]] 都没意义 putQueryResult(path, response); } - + long endTime = System.currentTimeMillis(); Log.d(TAG, "\n onArrayParse <<<<<<<<<<<<<<<<<<<<<<<<<<<<\n isExtract >> putQueryResult " + " startTime = " + startTime + "; endTime = " + endTime + "; duration = " + (endTime - startTime) + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); @@ -1739,13 +1804,13 @@ else if (config.isClickHouse()) { e.getMessage() + " " + Log.KEY_SYSTEM_INFO_DIVIDER + " \n**环境信息** " + "\n系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version") - + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch") + "\n数据库: " + db + " " + config.getDBVersion() + + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch") + "\nAPIJSON: " + Log.VERSION ); } catch (Throwable e2) {} } - + if (Log.DEBUG == false && e instanceof SQLException) { throw new SQLException("数据库驱动执行异常SQLException,非 Log.DEBUG 模式下不显示详情,避免泄漏真实模式名、表名等隐私信息", e); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index d983da6ae..1f366d0c0 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -268,7 +268,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception { //验证角色,假定真实强制匹配<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - String visitorIdkey = getVisitorIdKey(config); + String visitorIdKey = getVisitorIdKey(config); Object requestId; switch (role) { @@ -285,9 +285,9 @@ public boolean verifyAccess(SQLConfig config) throws Exception { } //key!{}:[] 或 其它没有明确id的条件 等 可以和key{}:list组合。类型错误就报错 - requestId = (Number) config.getWhere(visitorIdkey, true);//JSON里数值不能保证是Long,可能是Integer + requestId = config.getWhere(visitorIdKey, true);//JSON里数值不能保证是Long,可能是Integer @SuppressWarnings("unchecked") - Collection requestIdArray = (Collection) config.getWhere(visitorIdkey + "{}", true);//不能是 &{}, |{} 不要传,直接{} + Collection requestIdArray = (Collection) config.getWhere(visitorIdKey + "{}", true);//不能是 &{}, |{} 不要传,直接{} if (requestId != null) { if (requestIdArray == null) { requestIdArray = new JSONArray(); @@ -296,7 +296,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception { } if (requestIdArray == null) {//可能是@得到 || requestIdArray.isEmpty()) {//请求未声明key:id或key{}:[...]条件,自动补全 - config.putWhere(visitorIdkey+"{}", JSON.parseArray(list), true); //key{}:[]有效,SQLConfig里throw NotExistException + config.putWhere(visitorIdKey+"{}", JSON.parseArray(list), true); //key{}:[]有效,SQLConfig里throw NotExistException } else {//请求已声明key:id或key{}:[]条件,直接验证 for (Object id : requestIdArray) { @@ -307,7 +307,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception { throw new UnsupportedDataTypeException(table + ".id类型错误,id类型必须是Long!"); } if (list.contains(Long.valueOf("" + id)) == false) {//Integer等转为Long才能正确判断。强转崩溃 - throw new IllegalAccessException(visitorIdkey + " = " + id + " 的 " + table + throw new IllegalAccessException(visitorIdKey + " = " + id + " 的 " + table + " 不允许 " + role + " 用户的 " + method.name() + " 请求!"); } } @@ -321,20 +321,20 @@ public boolean verifyAccess(SQLConfig config) throws Exception { throw new IllegalArgumentException("POST 请求必须在Table内设置要保存的 key:value !"); } - int index = c.indexOf(visitorIdkey); + int index = c.indexOf(visitorIdKey); if (index >= 0) { Object oid; for (List ovl : ovs) { oid = ovl == null || index >= ovl.size() ? null : ovl.get(index); if (oid == null || StringUtil.getString(oid).equals("" + visitorId) == false) { - throw new IllegalAccessException(visitorIdkey + " = " + oid + " 的 " + table + throw new IllegalAccessException(visitorIdKey + " = " + oid + " 的 " + table + " 不允许 " + role + " 用户的 " + method.name() + " 请求!"); } } } else { List nc = new ArrayList<>(c); - nc.add(visitorIdkey); + nc.add(visitorIdKey); config.setColumn(nc); List> nvs = new ArrayList<>(); @@ -349,13 +349,13 @@ public boolean verifyAccess(SQLConfig config) throws Exception { } } else { - requestId = config.getWhere(visitorIdkey, true);//JSON里数值不能保证是Long,可能是Integer + requestId = config.getWhere(visitorIdKey, true);//JSON里数值不能保证是Long,可能是Integer if (requestId != null && StringUtil.getString(requestId).equals(StringUtil.getString(visitorId)) == false) { - throw new IllegalAccessException(visitorIdkey + " = " + requestId + " 的 " + table + throw new IllegalAccessException(visitorIdKey + " = " + requestId + " 的 " + table + " 不允许 " + role + " 用户的 " + method.name() + " 请求!"); } - config.putWhere(visitorIdkey, visitorId, true); + config.putWhere(visitorIdKey, visitorId, true); } break; case ADMIN://这里不好做,在特定接口内部判。 可以是 /get/admin + 固定秘钥 Parser#needVerify,之后全局跳过验证 From 0ffe78c9d9c60c0144a5d8bf99a3763382e62616 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 12 Nov 2021 05:58:05 +0800 Subject: [PATCH 136/754] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index 7ebab3f40..93279022b 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -148,7 +148,7 @@ https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSOND ### C-1-2-1.更多测试用例 如果需要更多测试用例,请按照以下步骤获取: -1、在浏览器中输入 apijson.org; +1、在浏览器中输入 apijson.cn; 2、点击右上角的“登录”按钮登录; 3、点击“测试账号”按钮左边第二个按钮。(也就是“-”左边的第一个)获取各种测试用例 4、欢迎大家踊跃共享自己的测试用例; From 0a5b950dbca681aef80f4bff5d0b1a3d3507ef2d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 15 Nov 2021 00:39:08 +0800 Subject: [PATCH 137/754] Update README-English.md --- README-English.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README-English.md b/README-English.md index 331ed25b2..eeceb8783 100644 --- a/README-English.md +++ b/README-English.md @@ -331,7 +331,7 @@ https://github.com/Tencent/APIJSON/issues/187 [More APIJSON Users](https://github.com/Tencent/APIJSON/issues/73) ### Contributers of APIJSON: -Contributers for the APIJSON core project(6 Tencent engineers、1 Zhihu architect、1 YTO Express engineer, etc.):
+Contributers for the APIJSON core project(6 Tencent engineers, 1 Zhihu architect, 1 YTO Express engineer, etc.):
https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md

-Authors of other projects for ecosystem of APIJSON(2 Tencent engineers、1 Bytedance(TikTok) engineer, etc.):
+Authors of other projects for ecosystem of APIJSON(2 Tencent engineers, 1 BAT(Baidu/Alibaba/Tencent) specialist, 1 Bytedance(TikTok) engineer, etc.):
https://github.com/search?o=desc&q=apijson&s=stars&type=Repositories
https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count
From f044d5f903e6a109014ab438b23a36689340b705 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 15 Nov 2021 00:41:42 +0800 Subject: [PATCH 138/754] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 5da8589ae..73f262b15 100644 --- a/Document.md +++ b/Document.md @@ -5,7 +5,7 @@ https://github.com/Tencent/APIJSON ![image](https://user-images.githubusercontent.com/5738175/134520081-a63d3817-321c-4e7b-9e03-73c6827a19c1.png) -后端开发者可以先看 [图文入门教程1](https://vincentcheng.github.io/apijson-doc/zh) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (都非官方,和本文档有出入的点以本文档为准。例如正则匹配 key? 已废弃,用 key~ 替代;例如 "@column":"store_id,sum(amt):totAmt" 中逗号 , 有误,应该用分号 ; 隔开 SQL 函数,改为 "@column":"store_id;sum(amt):totAmt") +后端开发者可以先看 [图文入门教程1](http://apijson.cn/doc/zh/) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (和本文档有出入的点以本文档为准。例如正则匹配 key? 已废弃,用 key~ 替代;例如 "@column":"store_id,sum(amt):totAmt" 中逗号 , 有误,应该用分号 ; 隔开 SQL 函数,改为 "@column":"store_id;sum(amt):totAmt") * ### [1.示例](#1) * ### [2.对比传统方式](#2) From 7e60828122961f7cd50fa29deec64c1814e04a85 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 15 Nov 2021 00:52:57 +0800 Subject: [PATCH 139/754] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dd8d36af8..7b1908260 100644 --- a/README.md +++ b/README.md @@ -353,7 +353,7 @@ https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count -[DB2](https://www.ibm.com/support/knowledgecenter/SSEPGG_11.1.0/com.ibm.db2.luw.sql.ref.doc/doc/r0059224.html), [Elasticsearch](https://www.elastic.co/cn/what-is/elasticsearch-sql), [ClickHouse](https://clickhouse.tech/docs/zh/sql-reference/syntax/), [OceanBase](https://www.oceanbase.com/docs/oceanbase/V2.2.50/ss-sr-select_daur3l), [Presto](https://prestodb.io/docs/current/admin/function-namespace-managers.html), [Spark](http://spark.apache.org/sql/), [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select)(延伸支持 Hadoop, Spark), [Phoenix](http://phoenix.apache.org/language/index.html#select)(延伸支持 HBase), [Presto/Trino](https://prestodb.io/docs/current/sql/select.html)(延伸支持 Redis, Hive, Kafka, Elasticsearch, Thrift, Cassandra, MySQL, PostgreSQL, Oracle, MongoDB...) +[Elasticsearch](https://www.elastic.co/cn/what-is/elasticsearch-sql), [OceanBase](https://www.oceanbase.com/docs/oceanbase/V2.2.50/ss-sr-select_daur3l), [Presto](https://prestodb.io/docs/current/admin/function-namespace-managers.html), [Spark](http://spark.apache.org/sql/), [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select)(延伸支持 Hadoop, Spark), [Phoenix](http://phoenix.apache.org/language/index.html#select)(延伸支持 HBase), [Presto/Trino](https://prestodb.io/docs/current/sql/select.html)(延伸支持 Redis, Hive, Kafka, Elasticsearch, Thrift, Cassandra, MySQL, PostgreSQL, Oracle, MongoDB...) ### 我要赞赏 如果你喜欢 APIJSON,感觉 APIJSON 帮助到了你,可以点右上角 ⭐Star 支持一下,谢谢 ^_^
From 76c8885a9d47198fb07aee314944f4b39c91d0f0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 15 Nov 2021 00:53:45 +0800 Subject: [PATCH 140/754] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b1908260..61e4709bb 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ This source code is licensed under the Apache License Version 2.0
- +

From b85aed18b27e3ff130b6a6c6b707ceb80d666b23 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 15 Nov 2021 00:58:35 +0800 Subject: [PATCH 141/754] Update Roadmap.md --- Roadmap.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Roadmap.md b/Roadmap.md index 74c3b4ca4..572fa41c1 100644 --- a/Roadmap.md +++ b/Roadmap.md @@ -7,7 +7,7 @@ ### 新增功能 部分功能描述可在 [APIAuto](https://github.com/TommyLemon/APIAuto) 上查看
账号 13000002020 密码 123456
-http://apijson.org:8000/auto/
+http://apijson.cn:8000/api
##### 基本原则 1.一定要有相关的应用场景,不能是伪需求,最好举例说明
@@ -35,6 +35,7 @@ POST: 用不上,不处理
@having! 必须性不大,可通过反转内部条件来实现,但如果实现简单、且不影响原来的功能,则可以顺便加上。
#### 新增支持 @column! +【更新:已提供字段插件 [apijson-column](https://github.com/APIJSON/apijson-column),支持 字段名映射 和 !key 反选字段。】 这个只在 [apijson-framework](https://github.com/APIJSON/apijson-framework) 支持,需要配置每个接口版本、每张表所拥有的全部字段,然后排除掉 @column! 的。
可新增一个 VersionedColumn 表记录来代替 HashMap 代码配置。
@@ -191,7 +192,8 @@ https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/ ### 提高性能 -20200205 更新:最近的两次大幅提升性能相关优化及 Release
+20200205 更新:最近的及次大幅提升性能相关优化及 Release
+[4.8.0【性能】大幅提升提升单表数组查询性能](https://github.com/Tencent/APIJSON/releases/tag/4.8.0)
[4.6.0【性能】大幅提升数组内主表查询性能](https://github.com/Tencent/APIJSON/releases/tag/4.6.0)
[4.4.5【性能】大幅提升增删改的性能](https://github.com/Tencent/APIJSON/releases/tag/4.4.5)
From 1d9f0e24998e2c4bea2234bb214c694a7bb46905 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 15 Nov 2021 01:03:20 +0800 Subject: [PATCH 142/754] Update Roadmap.md --- Roadmap.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Roadmap.md b/Roadmap.md index 572fa41c1..4a479cd2e 100644 --- a/Roadmap.md +++ b/Roadmap.md @@ -7,7 +7,7 @@ ### 新增功能 部分功能描述可在 [APIAuto](https://github.com/TommyLemon/APIAuto) 上查看
账号 13000002020 密码 123456
-http://apijson.cn:8000/api
+http://apijson.cn/api
##### 基本原则 1.一定要有相关的应用场景,不能是伪需求,最好举例说明
@@ -35,7 +35,7 @@ POST: 用不上,不处理
@having! 必须性不大,可通过反转内部条件来实现,但如果实现简单、且不影响原来的功能,则可以顺便加上。
#### 新增支持 @column! -【更新:已提供字段插件 [apijson-column](https://github.com/APIJSON/apijson-column),支持 字段名映射 和 !key 反选字段。】 +20210415 更新:已提供字段插件 [apijson-column](https://github.com/APIJSON/apijson-column),支持 字段名映射 和 !key 反选字段。 这个只在 [apijson-framework](https://github.com/APIJSON/apijson-framework) 支持,需要配置每个接口版本、每张表所拥有的全部字段,然后排除掉 @column! 的。
可新增一个 VersionedColumn 表记录来代替 HashMap 代码配置。
@@ -193,7 +193,6 @@ https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/ ### 提高性能 20200205 更新:最近的及次大幅提升性能相关优化及 Release
-[4.8.0【性能】大幅提升提升单表数组查询性能](https://github.com/Tencent/APIJSON/releases/tag/4.8.0)
[4.6.0【性能】大幅提升数组内主表查询性能](https://github.com/Tencent/APIJSON/releases/tag/4.6.0)
[4.4.5【性能】大幅提升增删改的性能](https://github.com/Tencent/APIJSON/releases/tag/4.4.5)
@@ -231,7 +230,7 @@ https://github.com/Tencent/APIJSON/issues/created_by/QiAnXinCodeSafe ##### [APIAuto](https://github.com/TommyLemon/APIAuto) 上统计的 bug 账号 13000002000 密码 123456
-http://apijson.org:8000/auto/
+http://apijson.cn/api
##### 其它发现的 Bug https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aopen+label%3Abug
@@ -239,13 +238,14 @@ https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aopen+label%3Abug
+http://apijson.cn/api
-##### 接入 UnitAuto-机器学习自动化单元测试平台,每次启动都自动测试所有可测方法并输出报告 -https://gitee.com/TommyLemon/UnitAuto
+##### 在 UnitAuto-机器学习自动化单元测试平台 上传更多、更全面、更细致的测试用例、动态参数等 +http://apijson.cn/unit
### 完善文档 +20211112 更新:已在官网部署文档 http://apijson.cn/doc/zh 20200205 更新:最近完善及更新了通用文档、上手文档、图文入门文档等,还对首页引导文档加了导航目录 https://github.com/Tencent/APIJSON/blob/master/Navigation.md From d970eeda31912621bc1c01f7732d7900b825b58a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 15 Nov 2021 01:11:42 +0800 Subject: [PATCH 143/754] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 3 +++ 1 file changed, 3 insertions(+) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index 93279022b..4a25253a3 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -4,6 +4,9 @@ https://search.bilibili.com/all?keyword=APIJSON&from_source=webtop_search&spm_id_from=333.851 ![image](https://user-images.githubusercontent.com/5738175/135413311-0207ec13-f7ea-4767-9e34-1a6d08438295.png) +本文档已部署到官网,浏览和检索体验更好
+http://apijson.cn/doc/zh/ + 其它各种官方和第三方文档见首页相关推荐
https://github.com/Tencent/APIJSON#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90 From fb26ccfc5af79270865f1a81489d09222c06550f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 25 Nov 2021 21:20:59 +0800 Subject: [PATCH 144/754] =?UTF-8?q?=E8=B7=AF=E7=BA=BF=E8=A7=84=E5=88=92?= =?UTF-8?q?=EF=BC=9A=E6=96=B0=E5=A2=9E=204.8.0=20=E5=A4=A7=E5=B9=85?= =?UTF-8?q?=E6=8F=90=E5=8D=87=E5=8D=95=E8=BE=B9=E6=95=B0=E7=BB=84=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E6=80=A7=E8=83=BD=E7=9A=84=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/releases/tag/4.8.0 --- Roadmap.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Roadmap.md b/Roadmap.md index 4a479cd2e..dd8fcea9e 100644 --- a/Roadmap.md +++ b/Roadmap.md @@ -193,6 +193,7 @@ https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/ ### 提高性能 20200205 更新:最近的及次大幅提升性能相关优化及 Release
+[新增支持 ClickHouse、窗口函数 OVER、反引号 `key`、单引号 'value';大幅提升单表数组查询性能](https://github.com/Tencent/APIJSON/releases/tag/4.8.0)
[4.6.0【性能】大幅提升数组内主表查询性能](https://github.com/Tencent/APIJSON/releases/tag/4.6.0)
[4.4.5【性能】大幅提升增删改的性能](https://github.com/Tencent/APIJSON/releases/tag/4.4.5)
@@ -279,7 +280,7 @@ https://github.com/APIJSON/APIJSON#%E7%94%9F%E6%80%81%E9%A1%B9%E7%9B%AE
JavaScript 前端,TypeScript 前端,微信等小程序,
Android 客户端,iOS 客户端,C# 游戏客户端等。
-Java, C#, Node, Python 等后端 Demo 及数据。
+Java, C#, PHP, Node, Python 等后端 Demo 及数据。
https://github.com/APIJSON/APIJSON-Demo
#### 新增扩展 From 12cdd0f5848fe58a56ca6c0799e69cead5553f94 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 27 Nov 2021 02:24:37 +0800 Subject: [PATCH 145/754] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 61e4709bb..282325e22 100644 --- a/README.md +++ b/README.md @@ -147,13 +147,13 @@ https://www.bilibili.com/video/BV1yv411p7Y4
### 为什么选择 APIJSON? -前后端 关于接口的 开发、文档、联调 等 10 个痛点解析
+前后端 关于接口的 开发、文档、联调 等 10 大痛点解析
https://github.com/Tencent/APIJSON/wiki -* **解决十个痛点** (APIJSON 可帮助用户 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等) +* **解决十大痛点** (APIJSON 可帮助用户 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等) * **开发提速很大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) -* **社区影响力大** (GitHub 1W+ Star 在 350W Java 项目中排名前 140,远超 FLAG, BAT 等国内外绝大部分开源项目) +* **社区影响力大** (GitHub 1W+ Star 在 350W Java 项目中排名前 120,远超 FLAG, BAT 等国内外绝大部分开源项目) * **多样用户案例** (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等) * **适用场景广泛** (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) * **周边生态丰富** (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) @@ -164,7 +164,7 @@ https://github.com/Tencent/APIJSON/wiki * **高质可靠代码** (代码严谨规范,商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 Bug 率低至 0.15%) * **兼容各种项目** (协议不限 HTTP,与其它库无冲突,对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo) * **工程轻量小巧** (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1) -* **多年持续迭代** (自 2016 年开源至今已连续维护 4 年,累计 2000+ Commits、70+ Releases,不断更新迭代中...) +* **多年持续迭代** (自 2016 年开源至今已连续维护 5 年,累计 2000+ Commits、80+ Releases,不断更新迭代中...) ### 常见问题 From 128ca8e76a2d56a849ce63e717c2b8f3932311e7 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 27 Nov 2021 02:25:22 +0800 Subject: [PATCH 146/754] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 282325e22..7704d2e5e 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ https://github.com/Tencent/APIJSON/wiki * **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动控制权限、自动校验参数、自动防SQL注入等) * **灵活定制业务** (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) * **高质可靠代码** (代码严谨规范,商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 Bug 率低至 0.15%) -* **兼容各种项目** (协议不限 HTTP,与其它库无冲突,对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo) +* **兼容各种项目** (协议不限 HTTP,与其它库无冲突,对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的示例) * **工程轻量小巧** (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1) * **多年持续迭代** (自 2016 年开源至今已连续维护 5 年,累计 2000+ Commits、80+ Releases,不断更新迭代中...) From 44917295296fa4dd6479ea08f9618ccb84238774 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 1 Dec 2021 01:37:04 +0800 Subject: [PATCH 147/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=207=20=E7=AF=87?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=88=86=E6=9E=90=E7=9B=B8=E5=85=B3=E6=96=87?= =?UTF-8?q?=E7=AB=A0=EF=BC=8C=E5=9F=BA=E6=9C=AC=E9=83=BD=E6=98=AF=2027=20?= =?UTF-8?q?=E7=AF=87=E4=B8=AD=E7=9A=84=E5=BC=80=E7=AF=87=EF=BC=8C=E6=84=9F?= =?UTF-8?q?=E8=B0=A2=203=20=E4=B8=AA=E5=8D=9A=E4=B8=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/edit/master/README.md#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90 --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 7704d2e5e..bb6639c11 100644 --- a/README.md +++ b/README.md @@ -411,6 +411,21 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [全国行政区划数据抓取与处理](https://www.xlongwei.com/detail/21032616) [新手搭建 APIJSON 项目指北](https://github.com/jerrylususu/apijson_todo_demo/blob/master/FULLTEXT.md) + +[APIJSON(一:综述)](https://blog.csdn.net/qq_50861917/article/details/120556168) + +[APIJSON 代码分析(三:demo主体代码)](https://blog.csdn.net/qq_50861917/article/details/120751630) + +[APIJSON 代码分析(二)AbstractParser类(解析器)](https://blog.csdn.net/weixin_45767055/article/details/120815927) + +[APIJSON 代码分析(四:AbstractObjectParser源码阅读)](https://blog.csdn.net/qq_50861917/article/details/120896381) + +[APIJSON 代码分析 AbstractSQLConfig 第二篇](https://blog.csdn.net/csascscascd/article/details/120684889) + +[APIJSON 代码分析(六)APIJSON—Verifier检查类](https://blog.csdn.net/weixin_45767055/article/details/121321731) + +[APIJSON 代码分析(四)AbstractSQLExecutor—SQL执行器](https://blog.csdn.net/weixin_45767055/article/details/121069887) + ### 生态项目 [APIJSON-Demo](https://github.com/APIJSON/APIJSON-Demo) APIJSON 各种语言、各种框架 的 使用示例项目、上手文档、测试数据 SQL 文件 等 @@ -483,6 +498,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md 感谢热心的作者们的贡献,点 ⭐Star 支持下他们吧。 + ### 腾讯犀牛鸟开源人才培养计划 https://github.com/Tencent/APIJSON/issues/229 From e114eff51ed1c9c2c9fc2776a3a5462c4a311ed2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 1 Dec 2021 01:38:36 +0800 Subject: [PATCH 148/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=207=20=E7=AF=87?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=88=86=E6=9E=90=E7=9B=B8=E5=85=B3=E6=96=87?= =?UTF-8?q?=E7=AB=A0=EF=BC=8C=E5=9F=BA=E6=9C=AC=E9=83=BD=E6=98=AF=2027=20?= =?UTF-8?q?=E7=AF=87=E4=B8=AD=E7=9A=84=E5=BC=80=E7=AF=87=EF=BC=8C=E6=84=9F?= =?UTF-8?q?=E8=B0=A2=203=20=E4=B8=AA=E5=8D=9A=E4=B8=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index bb6639c11..376bb2a2f 100644 --- a/README.md +++ b/README.md @@ -412,6 +412,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [新手搭建 APIJSON 项目指北](https://github.com/jerrylususu/apijson_todo_demo/blob/master/FULLTEXT.md) + [APIJSON(一:综述)](https://blog.csdn.net/qq_50861917/article/details/120556168) [APIJSON 代码分析(三:demo主体代码)](https://blog.csdn.net/qq_50861917/article/details/120751630) From 7a0c85d961116c191b73fd919335b7e66a90db22 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 2 Dec 2021 20:20:59 +0800 Subject: [PATCH 149/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=96=87=E7=AB=A0=20?= =?UTF-8?q?=E4=BD=BF=E7=94=A8APIJSON=E5=86=99=E4=BD=8E=E4=BB=A3=E7=A0=81Cr?= =?UTF-8?q?ud=E6=8E=A5=E5=8F=A3=EF=BC=8C=E6=84=9F=E8=B0=A2=E5=8D=9A?= =?UTF-8?q?=E4=B8=BB=E8=B4=A1=E7=8C=AE~?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://blog.csdn.net/weixin_42375862/article/details/121654264 位于相关推荐的多篇代码分析博文上方 https://github.com/Tencent/APIJSON#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 376bb2a2f..7388173da 100644 --- a/README.md +++ b/README.md @@ -412,6 +412,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [新手搭建 APIJSON 项目指北](https://github.com/jerrylususu/apijson_todo_demo/blob/master/FULLTEXT.md) +[使用APIJSON写低代码Crud接口](https://blog.csdn.net/weixin_42375862/article/details/121654264) [APIJSON(一:综述)](https://blog.csdn.net/qq_50861917/article/details/120556168) From 6023bc0fba0ad9a6d4101e361d9fc987d4548139 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 5 Dec 2021 01:24:56 +0800 Subject: [PATCH 150/754] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E6=9F=90=E4=B8=AA?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=80=BC=E4=B8=BA=20null=20=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E4=B8=AD=E6=96=AD=E5=90=8E=E7=BB=AD=E6=AD=A3=E5=B8=B8?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E5=80=BC=EF=BC=9B=E8=A7=A3=E5=86=B3=20LEFT/R?= =?UTF-8?q?IGHT=20JOIN=20=E5=89=AF=E8=A1=A8=E5=85=B3=E8=81=94=E4=B8=BB?= =?UTF-8?q?=E8=A1=A8=E5=A4=96=E9=94=AE=E7=9A=84=E5=AD=97=E6=AE=B5=E5=8F=96?= =?UTF-8?q?=E5=88=AB=E5=90=8D=E5=AF=BC=E8=87=B4=20SQL=20=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractParser.java | 1 + .../java/apijson/orm/AbstractSQLConfig.java | 45 +++++++++++-------- .../java/apijson/orm/AbstractSQLExecutor.java | 15 ++++--- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 65b79cea9..9be40b0d2 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -432,6 +432,7 @@ public JSONObject parseResponse(JSONObject request) { requestObject.put("sql:generate|cache|execute|maxExecute", getSQLExecutor().getGeneratedSQLCount() + "|" + getSQLExecutor().getCachedSQLCount() + "|" + getSQLExecutor().getExecutedSQLCount() + "|" + getMaxSQLCount()); requestObject.put("depth:count|max", queryDepth + "|" + getMaxQueryDepth()); requestObject.put("time:start|duration|end", startTime + "|" + duration + "|" + endTime); +// TODO 放在 msg 中的调试和提示信息应该单独放一个字段,避免 APIAuto 异常分支不显示提示语或太长,以及 DEBUG 和非 DEBUG 模式下提示语不一致 requestObject.put("debug", debugStr); if (error != null) { requestObject.put("throw", error.getClass().getName()); requestObject.put("trace", error.getStackTrace()); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 5e5a59917..dc6a33647 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -83,7 +83,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { // * 和 / 不能同时出现,防止 /* */ 段注释! # 和 -- 不能出现,防止行注释! ; 不能出现,防止隔断SQL语句!空格不能出现,防止 CRUD,DROP,SHOW TABLES等语句! private static final Pattern PATTERN_RANGE; private static final Pattern PATTERN_FUNCTION; - private static final Pattern PATTERN_STRING; + private static final Pattern PATTERN_STRING; /** * 表名映射,隐藏真实表名,对安全要求很高的表可以这么做 @@ -1490,9 +1490,8 @@ public String getColumnString(boolean inSQLJoin) throws Exception { return "(" + s + ")"; case GET: case GETS: - boolean isQuery = RequestMethod.isQueryMethod(method); //TODO 这个有啥用?上面应是 getMethod 的值 GET 和 GETS 了。 String joinColumn = ""; - if (isQuery && joinList != null) { + if (joinList != null) { SQLConfig ecfg; SQLConfig cfg; String c; @@ -1501,23 +1500,31 @@ public String getColumnString(boolean inSQLJoin) throws Exception { if (j.isAppJoin()) { continue; } - - ecfg = j.getOuterConfig(); - if (ecfg != null && ecfg.getColumn() != null) { //优先级更高 - cfg = ecfg; - } - else { - cfg = j.getJoinConfig(); - } - - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } - - c = ((AbstractSQLConfig) cfg).getColumnString(true); - if (StringUtil.isEmpty(c, true) == false) { - joinColumn += (first ? "" : ", ") + c; + + if (j.isLeftOrRightJoin()) { + // 改为 SELECT ViceTable.* 解决 SELECT sum(ViceTable.id) LEFT/RIGHT JOIN (SELECT sum(id) FROM ViceTable...) AS ViceTable + // 不仅导致 SQL 函数重复计算,还有时导致 SQL 报错或对应字段未返回 + String quote = getQuote(); + joinColumn += (first ? "" : ", ") + quote + (StringUtil.isEmpty(j.getAlias(), true) ? j.getTable() : j.getAlias()) + quote + ".*"; first = false; + } else { + ecfg = j.getOuterConfig(); + if (ecfg != null && ecfg.getColumn() != null) { //优先级更高 + cfg = ecfg; + } + else { + cfg = j.getJoinConfig(); + } + + if (StringUtil.isEmpty(cfg.getAlias(), true)) { + cfg.setAlias(cfg.getTable()); + } + + c = ((AbstractSQLConfig) cfg).getColumnString(true); + if (StringUtil.isEmpty(c, true) == false) { + joinColumn += (first ? "" : ", ") + c; + first = false; + } } inSQLJoin = true; diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index c0fce49f7..49703a2a1 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -542,7 +542,8 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r if (joinList != null) { for (Join j : joinList) { childConfig = j.isAppJoin() ? null : j.getCacheConfig(); //这里用config改了getSQL后再还原很麻烦,所以提前给一个config2更好 - + + // FIXME 副表的 SQL 函数,甚至普通字段都可能从 rsmd.getTableName(columnIndex) 拿到 "" if (childConfig != null && childTable.equalsIgnoreCase(childConfig.getSQLTable())) { childConfig.putWhere(j.getKey(), table.get(j.getTargetKey()), true); @@ -561,13 +562,13 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r } Object value = getValue(config, rs, rsmd, tablePosition, table, columnIndex, lable, childMap); - if (value != null) { - if (finalTable == null) { - finalTable = new JSONObject(true); - childMap.put(childSql, finalTable); - } - finalTable.put(lable, value); + // 必须 put 进去,否则某个字段为 null 可能导致中断后续正常返回值 if (value != null) { + if (finalTable == null) { + finalTable = new JSONObject(true); + childMap.put(childSql, finalTable); } + finalTable.put(lable, value); + // } return table; } From 00dae1b6bfa0de09b3e2465f62319c278524f375 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 7 Dec 2021 03:49:49 +0800 Subject: [PATCH 151/754] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20JOIN=20=E5=89=AF?= =?UTF-8?q?=E8=A1=A8=E5=8C=85=E5=90=AB=20SQL=20=E5=87=BD=E6=95=B0=E6=97=B6?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E8=BF=94=E5=9B=9E=20SQL=20=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E7=9A=84=E6=89=A7=E8=A1=8C=E7=BB=93=E6=9E=9C=E4=BB=A5=E5=8F=8A?= =?UTF-8?q?=E6=9C=AA=E7=94=A8=E4=B8=8A=20SQL=20=E7=BC=93=E5=AD=98=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E5=86=97=E4=BD=99=20SQL=20=E6=9F=A5=E8=AF=A2=20#341?= =?UTF-8?q?=EF=BC=9B=E6=8F=90=E5=8D=87=20JOIN=20=E5=B0=81=E8=A3=85?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E7=9A=84=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLExecutor.java | 183 ++++++++++++++---- 1 file changed, 146 insertions(+), 37 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 49703a2a1..72065302f 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Set; import com.alibaba.fastjson.JSON; @@ -267,17 +268,29 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws // childMap = new HashMap<>(); //要存到cacheMap @@ -286,7 +342,14 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws Join[] columnIndexAndJoinMap = isExplain || ! hasJoin ? null : new Join[length]; // int viceColumnStart = length + 1; //第一个副表字段的index + +// FIXME 统计游标查找的时长?可能 ResultSet.next() 及 getTableName, getColumnName, getObject 比较耗时,因为不是一次加载到内存,而是边读边发 + + long lastCursorTime = System.currentTimeMillis(); while (rs.next()) { + sqlResultDuration += System.currentTimeMillis() - lastCursorTime; + lastCursorTime = System.currentTimeMillis(); + index ++; Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n execute while (rs.next()){ index = " + index + "\n\n"); @@ -318,8 +381,10 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws if (StringUtil.isEmpty(sqlTable, true)) { if (toFindJoin) { // 在主表字段数量内的都归属主表 + long startTime3 = System.currentTimeMillis(); sqlTable = rsmd.getTableName(i); // SQL 函数甚至部分字段都不返回表名,当然如果没传 @column 生成的 Table.* 则返回的所有字段都会带表名 - + sqlResultDuration += System.currentTimeMillis() - startTime3; + if (StringUtil.isEmpty(sqlTable, true)) { // hasJoin 已包含这个判断 && joinList != null) { int nextViceColumnStart = lastViceColumnStart; // 主表没有 @column 时会偏小 lastViceColumnStart @@ -588,12 +653,19 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map int index = -1; + long startTime2 = System.currentTimeMillis(); ResultSetMetaData rsmd = rs.getMetaData(); final int length = rsmd.getColumnCount(); - + sqlResultDuration += System.currentTimeMillis() - startTime2; + JSONObject result; String cacheSql; + + long lastCursorTime = System.currentTimeMillis(); while (rs.next()) { //FIXME 同时有 @ APP JOIN 和 < 等 SQL JOIN 时,next = false 总是无法进入循环,导致缓存失效,可能是连接池或线程问题 + sqlResultDuration += System.currentTimeMillis() - lastCursorTime; + lastCursorTime = System.currentTimeMillis(); + index ++; Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n executeAppJoin while (rs.next()){ index = " + index + "\n\n"); @@ -708,13 +780,20 @@ protected List onPutTable(@NotNull SQLConfig config, @NotNull Result protected String getKey(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd , final int tablePosition, @NotNull JSONObject table, final int columnIndex, Map childMap) throws Exception { - String key = rsmd.getColumnLabel(columnIndex);// dotIndex < 0 ? lable : lable.substring(dotIndex + 1); + long startTime = System.currentTimeMillis(); + String key = rsmd.getColumnLabel(columnIndex); // dotIndex < 0 ? lable : lable.substring(dotIndex + 1); + sqlResultDuration += System.currentTimeMillis() - startTime; + if (config.isHive()) { String table_name = config.getTable(); - if(AbstractSQLConfig.TABLE_KEY_MAP.containsKey(table_name)) table_name = AbstractSQLConfig.TABLE_KEY_MAP.get(table_name); + if (AbstractSQLConfig.TABLE_KEY_MAP.containsKey(table_name)) { + table_name = AbstractSQLConfig.TABLE_KEY_MAP.get(table_name); + } String pattern = "^" + table_name + "\\." + "[a-zA-Z]+$"; boolean isMatch = Pattern.matches(pattern, key); - if(isMatch) key = key.split("\\.")[1]; + if (isMatch) { + key = key.split("\\.")[1]; + } } return key; } @@ -722,7 +801,10 @@ protected String getKey(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNu protected Object getValue(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd , final int tablePosition, @NotNull JSONObject table, final int columnIndex, String lable, Map childMap) throws Exception { + long startTime = System.currentTimeMillis(); Object value = rs.getObject(columnIndex); + sqlResultDuration += System.currentTimeMillis() - startTime; + // Log.d(TAG, "name:" + rsmd.getColumnName(i)); // Log.d(TAG, "lable:" + rsmd.getColumnLabel(i)); // Log.d(TAG, "type:" + rsmd.getColumnType(i)); @@ -799,7 +881,10 @@ else if (value instanceof Clob) { //SQL Server TEXT 类型 居然走这个 @Override public boolean isJSONType(@NotNull SQLConfig config, ResultSetMetaData rsmd, int position, String lable) { try { + long startTime = System.currentTimeMillis(); String column = rsmd.getColumnTypeName(position); + sqlResultDuration += System.currentTimeMillis() - startTime; + //TODO CHAR和JSON类型的字段,getColumnType返回值都是1 ,如果不用CHAR,改用VARCHAR,则可以用上面这行来提高性能。 //return rsmd.getColumnType(position) == 1; @@ -965,19 +1050,28 @@ public void close() { @Override public ResultSet executeQuery(@NotNull SQLConfig config) throws Exception { - return getStatement(config).executeQuery(); //PreparedStatement 不用传 SQL + PreparedStatement s = getStatement(config); +// 不准,getStatement 有时比 execute sql 更耗时 executedSQLStartTime = System.currentTimeMillis(); + ResultSet rs = s.executeQuery(); //PreparedStatement 不用传 SQL +// executedSQLEndTime = System.currentTimeMillis(); + return rs; } @Override public int executeUpdate(@NotNull SQLConfig config) throws Exception { PreparedStatement s = getStatement(config); - int count = s.executeUpdate(); //PreparedStatement 不用传 SQL - if (config.isHive() && count==0) count = 1; - - if (config.getMethod() == RequestMethod.POST && config.getId() == null) { //自增id +// 不准,getStatement 有时比 execute sql 更耗时 executedSQLStartTime = System.currentTimeMillis(); + int count = s.executeUpdate(); // PreparedStatement 不用传 SQL +// executedSQLEndTime = System.currentTimeMillis(); + + if (count <= 0 && config.isHive()) { + count = 1; + } + + if (config.getId() == null && config.getMethod() == RequestMethod.POST) { // 自增id ResultSet rs = s.getGeneratedKeys(); if (rs != null && rs.next()) { - config.setId(rs.getLong(1));//返回插入的主键id + config.setId(rs.getLong(1)); //返回插入的主键id FIXME Oracle 拿不到 } } diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java index 9fe5917a7..3cb2bf60a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java @@ -115,5 +115,8 @@ public interface SQLExecutor { int getExecutedSQLCount(); - + long getExecutedSQLDuration(); + + long getSqlResultDuration(); + } From a6ac4b726a649f277384f525e0d9f339b7f59862 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 30 Jan 2022 18:44:50 +0800 Subject: [PATCH 162/754] Update README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 33e7ec713..3a6f0cf6a 100644 --- a/README.md +++ b/README.md @@ -244,7 +244,11 @@ https://github.com/Tencent/APIJSON/issues/187
* [腾讯科技有限公司](https://www.tencent.com) - + * [腾讯音乐娱乐集团](https://www.tencentmusic.com) + * [华能贵成信托有限公司](https://www.hngtrust.com) + * [投投科技](https://www.toutou.com.cn) + * [圆通科技](https://www.tencentmusic.com) + * [乐拼科技](https://www.lepinyongche.com) ### 贡献者们 主项目 APIJSON 的贡献者们(6 个腾讯工程师、1 个知乎基础研发架构师、1 个圆通工程师 等):
From ff8efebd33eb89eeb997bc8555d527ad4ee449f0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 30 Jan 2022 18:46:49 +0800 Subject: [PATCH 163/754] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a6f0cf6a..eaf7445e4 100644 --- a/README.md +++ b/README.md @@ -245,7 +245,7 @@ https://github.com/Tencent/APIJSON/issues/187 * [腾讯科技有限公司](https://www.tencent.com) * [腾讯音乐娱乐集团](https://www.tencentmusic.com) - * [华能贵成信托有限公司](https://www.hngtrust.com) + * [华能贵诚信托有限公司](https://www.hngtrust.com) * [投投科技](https://www.toutou.com.cn) * [圆通科技](https://www.tencentmusic.com) * [乐拼科技](https://www.lepinyongche.com) From bfe4c9d8a1c0b4c1cef510e9ddb3e273ef473378 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 30 Jan 2022 18:50:12 +0800 Subject: [PATCH 164/754] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eaf7445e4..f63435b13 100644 --- a/README.md +++ b/README.md @@ -247,7 +247,7 @@ https://github.com/Tencent/APIJSON/issues/187 * [腾讯音乐娱乐集团](https://www.tencentmusic.com) * [华能贵诚信托有限公司](https://www.hngtrust.com) * [投投科技](https://www.toutou.com.cn) - * [圆通科技](https://www.tencentmusic.com) + * [圆通科技](https://www.yto.net.cn) * [乐拼科技](https://www.lepinyongche.com) ### 贡献者们 From 6ef55cb4ca8f01a15f690fa31295fb87e1fd2c33 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 30 Jan 2022 18:52:21 +0800 Subject: [PATCH 165/754] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f63435b13..d367d6109 100644 --- a/README.md +++ b/README.md @@ -247,7 +247,7 @@ https://github.com/Tencent/APIJSON/issues/187 * [腾讯音乐娱乐集团](https://www.tencentmusic.com) * [华能贵诚信托有限公司](https://www.hngtrust.com) * [投投科技](https://www.toutou.com.cn) - * [圆通科技](https://www.yto.net.cn) + * [圆通速递](https://www.yto.net.cn) * [乐拼科技](https://www.lepinyongche.com) ### 贡献者们 From 2da22e618585f4009145c69965092ab3987004b3 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Feb 2022 21:08:59 +0800 Subject: [PATCH 166/754] =?UTF-8?q?=E6=8F=90=E5=8D=87=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7=E4=B8=BA=204.9.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 2 +- APIJSONORM/src/main/java/apijson/Log.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index f1fdcd63a..377ecc644 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.8.0 + 4.9.0 jar APIJSONORM diff --git a/APIJSONORM/src/main/java/apijson/Log.java b/APIJSONORM/src/main/java/apijson/Log.java index 6dfc96caf..f97fae342 100755 --- a/APIJSONORM/src/main/java/apijson/Log.java +++ b/APIJSONORM/src/main/java/apijson/Log.java @@ -14,7 +14,7 @@ public class Log { public static boolean DEBUG = true; - public static final String VERSION = "4.8.5"; + public static final String VERSION = "4.9.0"; public static final String KEY_SYSTEM_INFO_DIVIDER = "---|-----APIJSON SYSTEM INFO-----|---"; //默认的时间格式 From d6bd9dd4e4725c35acdcddfca5feb7347dd7ef12 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Feb 2022 21:15:24 +0800 Subject: [PATCH 167/754] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d367d6109..c5f4f9069 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,8 @@ https://github.com/Tencent/APIJSON/issues/187 * [腾讯科技有限公司](https://www.tencent.com) * [腾讯音乐娱乐集团](https://www.tencentmusic.com) + * [深圳市传音通讯有限公司](http://www.transsion.com) + * [社宝信息科技(上海)有限公司](http://shebaochina.com) * [华能贵诚信托有限公司](https://www.hngtrust.com) * [投投科技](https://www.toutou.com.cn) * [圆通速递](https://www.yto.net.cn) From 7531e2e6602a52c51472d0c6722ca7a3bf04c99f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Feb 2022 21:18:14 +0800 Subject: [PATCH 168/754] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c5f4f9069..7fea8ebd9 100644 --- a/README.md +++ b/README.md @@ -245,8 +245,8 @@ https://github.com/Tencent/APIJSON/issues/187 * [腾讯科技有限公司](https://www.tencent.com) * [腾讯音乐娱乐集团](https://www.tencentmusic.com) - * [深圳市传音通讯有限公司](http://www.transsion.com) - * [社宝信息科技(上海)有限公司](http://shebaochina.com) + * [深圳市传音通讯有限公司](https://www.transsion.com) + * [社宝信息科技(上海)有限公司](https://shebaochina.com) * [华能贵诚信托有限公司](https://www.hngtrust.com) * [投投科技](https://www.toutou.com.cn) * [圆通速递](https://www.yto.net.cn) From 5b29c96691df01b5a06da51fe58c8d490c9ce439 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 20 Feb 2022 20:45:08 +0800 Subject: [PATCH 169/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E6=BC=94=E7=A4=BA=E8=AF=B4=E6=98=8E=20GIF=20=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 7fea8ebd9..2a1d989a5 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,7 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+

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

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

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

+

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

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

+

+ +   +   +

    -

-

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

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

- -   -   + +   +  

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

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

+

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

+

+  中文版  +  Document  +  Video  +  Test  +

    - + + + + +

- + +   -   +     -

+

+ +   +   +

    -

-

-  中文版  -  Document  -  Video  -  Test  -

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

⑥ `"@schema":"sys"` Can be set as default setting.

⑦ `"@database":"POSTGRESQL"` Get data from a different database.Can be set as default setting.

⑧ `"@role":"OWNER"` Get information of the user, including
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
Can be set as default setting.
You can self-define a new role or rewrite a role. Use`Verifier.verify` etc. to self-define validation methods.

⑨ `"@explain":true` Profiling. Can be set as default setting.

⑩ `"@otherKey":Object` Self-define keyword | ① Search *Users* that *name* or *tag* contains the letter "a":
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})

② Only search column id,sex,name and return with the same order:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})

③ Search Users that have descending order of name and default order of id:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})

④ Search Moment grouped with userId:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})

⑤ Search Moments that id equals or less than 100 and group with userId:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
You can also define the name of the returned function:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})

⑥ Check Users table in sys:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})

⑦ Check Users table in PostgreSQL:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL"}})

⑧ Check the current user's activity:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑨ Turn on profiling:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑩ Get the No.0 picture from pictureList:
["@position":0, //self-defined keyword
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + Keywords in an Array: It can be self-defined. | As for `"key":Object`, *key* is the keyword of *{}* in *"[]":{}*. The type of *Object* is up to *key*.

① `"count":Integer` It's used to count the number. The default largest number is 100.

② `"page":Integer` It’s used for getting data from which page, starting from 0. The default largest number is 100. It’s usually used with COUNT.

③ `"query":Integer` Get the number of items that match conditions
When to get the object, the integer should be 0; when to get the total number, it’s 1; when both above, it’s 2.
You can get the total number with keyword total. It can be referred to other values.
Eg.
`"total@":"/[]/total"`
Put it as the same level of query.
*Query* and *total* are used in GET requests just for convenience. Generally, HEAD request is for getting numbers like the total number.

④ `"join":"&/Table0,Join tables:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
Where @ APP JOIN is in application layer.It’ll get all the keys in tables that refKeys in result tables are referred to, like refKeys:[value0, value1….]. Then, as the results get data according to `key=$refKey` a number of times (COUNT), it uses key `IN($refKeys)` to put these counts together in just one SQL query, in order to improve the performance.
Other JOIN functions are the same as those in SQL.
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
will return
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ `"otherKey":Object` Self-defined keyword other than those that already in the system. It also returns with self-defined keywords.| ① Get User arrays with maximum of 5:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})

② Look into User arrays on page 3. Show 5 of them each page.
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})

③ Get User Arrays and count the total number of Users:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal"})
Questions like total page numbers or if there's next page can be solved by total,count,page functions,
Total page number:
`int totalPage = Math.ceil(total / count)`
If this is the last page:
`boolean hasNextPage = total > count*page`
If this is the first page:
`boolean isFirstPage = page <= 0`
If it's the last page:
`boolean isLastPage = total <= count*page`
...

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join": "&/User,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@": "/Moment/userId"
   },
   "Comment":{
     "momentId@": "/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser,\<%252FComment","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ Add the current user to every level:
["User":{},
"[]":{
   "name@":"User/name", //self-defined keyword
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) + Keywords in Objects: It can be self-defined. | `"@key":Object` @key is the keyword of {} in Table:{}. The type of Object is decided by @key

① `"@combine":"&key0,&key1,\|key2,key3,`
`!key4,!key5,&key6,key7..."`
First, it’ll group data with same operators. Within one group, it operates from left to right. Then it’ll follow the order of & \| ! to do the operation. Different groups are connected with &. So the expression above will be :
(key0 & key1 & key6 & other key) & (key2 \| key3 \| key7) & !(key4 \| key5)
\| is optional.

② `"@column":"column;function(arg)..."` Return with specific columns.

③ `"@order":"column0+,column1-..."` Decide the order of returning results:

④ `"@group":"column0,column1..."` How to group data. If @column has declared Table id, this id need to be included in @group. In other situations, at least one of the following needs to be done:
1.Group id is declared in @column
2.Primary Key of the table is declared in @group.

⑤ `@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2..."` Add conditions on return results with @having. Usually working with@group, it’s declared in @column.

⑥ `"@schema":"sys"` Can be set as default setting.

⑦ `"@database":"POSTGRESQL"` Get data from a different database.Can be set as default setting.

⑧ `"@role":"OWNER"` Get information of the user, including
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
Can be set as default setting.
You can self-define a new role or rewrite a role. Use`Verifier.verify` etc. to self-define validation methods.

⑨ `"@explain":true` Profiling. Can be set as default setting.

⑩ `"@otherKey":Object` Self-define keyword | ① Search *Users* that *name* or *tag* contains the letter "a":
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})

② Only search column id,sex,name and return with the same order:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})

③ Search Users that have descending order of name and default order of id:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})

④ Search Moment grouped with userId:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})

⑤ Search Moments that id equals or less than 100 and group with userId:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
You can also define the name of the returned function:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"(maxId)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"(maxId)>=100"}}})

⑥ Check Users table in sys:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})

⑦ Check Users table in PostgreSQL:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL"}})

⑧ Check the current user's activity:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑨ Turn on profiling:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑩ Get the No.0 picture from pictureList:
["@position":0, //self-defined keyword
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From df74e3a904f20d7717f53702e04835f5010c99a1 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 16 Apr 2022 15:35:13 +0800 Subject: [PATCH 226/754] Update README-English.md --- README-English.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README-English.md b/README-English.md index c0d40e09c..cc599efea 100644 --- a/README-English.md +++ b/README-English.md @@ -12,8 +12,8 @@ This source code is licensed under the Apache License Version 2.0

 中文版   Document  -  Video  -  Test  +  Video  +  Test 

From 286dd3d41f9114378f5c7e943cc1bd0f03725289 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 16 Apr 2022 15:35:27 +0800 Subject: [PATCH 227/754] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3937a0806..ca8cbc6ab 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This source code is licensed under the Apache License Version 2.0

English  通用文档 - 视频教程 + 视频教程 在线体验

From ec8c2601ff736f50aa0fd2d0ba4d365dcd324464 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 18 Apr 2022 01:00:17 +0800 Subject: [PATCH 228/754] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=EF=BC=8C=E5=AF=B9=E5=A4=96=E6=9A=B4=E9=9C=B2?= =?UTF-8?q?=E7=B1=BB=20RESTful=20=E6=8E=A5=E5=8F=A3=EF=BC=8C=E5=86=85?= =?UTF-8?q?=E9=83=A8=E8=BD=AC=E6=88=90=20APIJSON=20=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/APIJSON/apijson-router --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ca8cbc6ab..f2893e86c 100644 --- a/README.md +++ b/README.md @@ -451,8 +451,10 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [apijson-orm](https://github.com/APIJSON/apijson-orm) APIJSON ORM 库,可通过 Maven, Gradle 等远程依赖 -[apijson-framework](https://github.com/APIJSON/apijson-framework) APIJSON 服务端框架,可通过 Maven, Gradle 等远程依赖 +[apijson-framework](https://github.com/APIJSON/apijson-framework) APIJSON 服务端框架,通过数据库表配置角色权限、参数校验等,简化使用 +[apijson-router](https://github.com/APIJSON/apijson-router) APIJSON 的路由插件,对外暴露类 RESTful 接口,内部转成 APIJSON 接口执行 + [apijson-column](https://github.com/APIJSON/apijson-column) APIJSON 的字段插件,支持 字段名映射 和 !key 反选字段 [APIAuto](https://github.com/TommyLemon/APIAuto) 敏捷开发最强大易用的 HTTP 接口工具,机器学习零代码测试、生成代码与静态检查、生成文档与光标悬浮注释 From 30bbea94acef614fb2c1004266d75a5f08a40ead Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 18 Apr 2022 01:10:36 +0800 Subject: [PATCH 229/754] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=A7=92=E8=89=B2?= =?UTF-8?q?=E6=9D=83=E9=99=90=E3=80=81=E5=8F=82=E6=95=B0=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E3=80=81=E8=BF=9C=E7=A8=8B=E5=87=BD=E6=95=B0=E7=9A=84=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=EF=BC=9B=E8=A7=A3=E5=86=B3=20format:=20true?= =?UTF-8?q?=20=E5=9C=A8=20Log.DEBUG=20=E6=97=B6=E4=B9=9F=E4=B8=8D=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=20SQL=E3=80=81=E6=97=B6=E9=97=B4=E7=AD=89=E8=B0=83?= =?UTF-8?q?=E8=AF=95=E4=BF=A1=E6=81=AF=EF=BC=9B=E5=8D=87=E7=BA=A7=E8=87=AA?= =?UTF-8?q?=E8=BA=AB=E7=89=88=E6=9C=AC=E4=B8=BA=205.0.5=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 2 +- APIJSONORM/src/main/java/apijson/StringUtil.java | 16 ++++++++++++++-- .../java/apijson/orm/AbstractFunctionParser.java | 2 +- .../main/java/apijson/orm/AbstractParser.java | 14 +++++++------- .../main/java/apijson/orm/AbstractVerifier.java | 10 +++++----- 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index edd10dfb9..187aa087f 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 5.0.0 + 5.0.5 jar APIJSONORM diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index ecc64a083..795392e49 100755 --- a/APIJSONORM/src/main/java/apijson/StringUtil.java +++ b/APIJSONORM/src/main/java/apijson/StringUtil.java @@ -348,6 +348,7 @@ public static boolean isNotEmpty(String s, boolean trim) { public static final Pattern PATTERN_NAME; public static final Pattern PATTERN_ALPHA_BIG; public static final Pattern PATTERN_ALPHA_SMALL; + public static final Pattern PATTERN_BRANCH_URL; static { PATTERN_NUMBER = Pattern.compile("^[0-9]+$"); PATTERN_ALPHA = Pattern.compile("^[a-zA-Z]+$"); @@ -359,6 +360,7 @@ public static boolean isNotEmpty(String s, boolean trim) { PATTERN_EMAIL = Pattern.compile("^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$"); PATTERN_ID_CARD = Pattern.compile("(^[1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$)|(^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{2}$)"); PATTERN_PASSWORD = Pattern.compile("^[0-9a-zA-Z]+$"); + PATTERN_BRANCH_URL = Pattern.compile("^[0-9a-zA-Z-_/]+$"); } /**判断手机格式是否正确 @@ -392,7 +394,7 @@ public static boolean isNumberPassword(String s) { * @return */ public static boolean isEmail(String email) { - if (isNotEmpty(email, true) == false) { + if (isEmpty(email, true)) { return false; } @@ -512,7 +514,8 @@ public static boolean isIDCard(String number) { public static boolean isUrl(String url) { if (isNotEmpty(url, true) == false) { return false; - } else if (! url.startsWith(URL_PREFIX) && ! url.startsWith(URL_PREFIXs)) { + } + if (! url.startsWith(URL_PREFIX) && ! url.startsWith(URL_PREFIXs)) { return false; } @@ -520,6 +523,15 @@ public static boolean isUrl(String url) { return true; } + public static boolean isBranchUrl(String branchUrl) { + if (isEmpty(branchUrl, false)) { + return false; + } + + return PATTERN_BRANCH_URL.matcher(branchUrl).matches(); + } + + public static final String FILE_PATH_PREFIX = "file://"; /**判断文件路径是否存在 * @param path diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java index 3debe096a..880b917f0 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java @@ -28,7 +28,7 @@ public class AbstractFunctionParser implements FunctionParser { // // > - public static final Map FUNCTION_MAP; + public static Map FUNCTION_MAP; static { FUNCTION_MAP = new HashMap<>(); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index f56af8cc8..f3fa6e18c 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -441,16 +441,16 @@ public JSONObject parseResponse(JSONObject request) { long duration = endTime - startTime; if (Log.DEBUG) { - requestObject.put("sql:generate|cache|execute|maxExecute", getSQLExecutor().getGeneratedSQLCount() + "|" + getSQLExecutor().getCachedSQLCount() + "|" + getSQLExecutor().getExecutedSQLCount() + "|" + getMaxSQLCount()); - requestObject.put("depth:count|max", queryDepth + "|" + getMaxQueryDepth()); + res.put("sql:generate|cache|execute|maxExecute", getSQLExecutor().getGeneratedSQLCount() + "|" + getSQLExecutor().getCachedSQLCount() + "|" + getSQLExecutor().getExecutedSQLCount() + "|" + getMaxSQLCount()); + res.put("depth:count|max", queryDepth + "|" + getMaxQueryDepth()); executedSQLDuration += sqlExecutor.getExecutedSQLDuration() + sqlExecutor.getSqlResultDuration(); long parseDuration = duration - executedSQLDuration; - requestObject.put("time:start|duration|end|parse|sql", startTime + "|" + duration + "|" + endTime + "|" + parseDuration + "|" + executedSQLDuration); + res.put("time:start|duration|end|parse|sql", startTime + "|" + duration + "|" + endTime + "|" + parseDuration + "|" + executedSQLDuration); if (error != null) { - requestObject.put("trace:throw", error.getClass().getName()); - requestObject.put("trace:stack", error.getStackTrace()); + res.put("trace:throw", error.getClass().getName()); + res.put("trace:stack", error.getStackTrace()); } } @@ -947,7 +947,7 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, } if (result == null) { - if (AbstractVerifier.REQUEST_MAP.isEmpty() == false) { + if (Log.DEBUG == false && AbstractVerifier.REQUEST_MAP.isEmpty() == false) { return null; // 已使用 REQUEST_MAP 缓存全部,但没查到 } @@ -961,7 +961,7 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, where.put(JSONRequest.KEY_TAG, tag); if (version > 0) { - where.put(JSONRequest.KEY_VERSION + "{}", ">=" + version); + where.put(JSONRequest.KEY_VERSION + ">=", version); } config.setWhere(where); config.setOrder(JSONRequest.KEY_VERSION + (version > 0 ? "+" : "-")); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index feda82ac1..0431d1773 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -109,14 +109,14 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { // > // > @NotNull - public static final Map> SYSTEM_ACCESS_MAP; + public static Map> SYSTEM_ACCESS_MAP; @NotNull - public static final Map> ACCESS_MAP; + public static Map> ACCESS_MAP; // > // > @NotNull - public static final Map> REQUEST_MAP; + public static Map> REQUEST_MAP; // 正则匹配的别名快捷方式,例如用 "PHONE" 代替 "^((13[0-9])|(15[^4,\\D])|(18[0-2,5-9])|(17[0-9]))\\d{8}$" @NotNull @@ -164,7 +164,7 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { ACCESS_MAP = new HashMap<>(SYSTEM_ACCESS_MAP); - REQUEST_MAP = new HashMap<>(ACCESS_MAP.size()*6); // 单个与批量增删改 + REQUEST_MAP = new HashMap<>(ACCESS_MAP.size()*7); // 单个与批量增删改 COMPILE_MAP = new HashMap(); } @@ -1495,7 +1495,7 @@ public static void verifyRepeat(String table, String key, Object value, long exc } public static String getCacheKeyForRequest(String method, String tag) { - return method + " " + tag; + return method + "/" + tag; } From e5641b721cd8c2523533903a7235e3d9e2fccdef Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 19 Apr 2022 22:43:10 +0800 Subject: [PATCH 230/754] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 703b9224b..0cb35f97d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,7 +52,7 @@ Zerounary 提交的 6 个 Commits, 对 APIJSON 做出了 1,104 增加和 1 处
APIJSON 持续招募贡献者,即使是在 Issue 中回答问题,或者做一些简单的 Bug Fix ,也会给 APIJSON 带来很大的帮助。
APIJSON 已开发近 4 年,在此感谢所有开发者对于 APIJSON 的喜欢和支持,希望你能够成为 APIJSON 的核心贡献者,
-加入 APIJSON ,共同打造一个更棒的自动化 ORM 库!🍾🎉 +加入 APIJSON ,共同打造一个更棒的零代码、全自动、强安全 ORM 库!🍾🎉 ### 为什么一定要贡献代码? APIJSON 作为腾讯开源的知名热门项目,贡献代码除了可以给简历加亮点、为面试加分,还可以避免你碰到以下麻烦:
From b83a891b7e6d662e4e766b9d6a4c093ee73bd2e0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 22 Apr 2022 21:56:27 +0800 Subject: [PATCH 231/754] Update README.md --- README.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index f2893e86c..b02b4dfbe 100644 --- a/README.md +++ b/README.md @@ -65,13 +65,6 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 ### 特点功能 -#### 对于前端 -* 不用再向后端催接口、求文档 -* 数据和结构完全定制,要啥有啥 -* 看请求知结果,所求即所得 -* 可一次获取任何数据、任何结构 -* 能去除多余数据,节省流量提高速度 - #### 对于后端 * 提供通用接口,大部分 API 不用再写 * 自动生成文档,不用再编写和维护 @@ -79,6 +72,13 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 * 开放 API 无需划分版本,始终保持兼容 * 支持增删改查、复杂查询、跨库连表、远程函数等 +#### 对于前端 +* 不用再向后端催接口、求文档 +* 数据和结构完全定制,要啥有啥 +* 看请求知结果,所求即所得 +* 可一次获取任何数据、任何结构 +* 能去除多余数据,节省流量提高速度 +
### APIJSON 接口展示 @@ -385,11 +385,15 @@ https://github.com/Tencent/APIJSON/blob/master/Roadmap.md 如果你解决了某些bug,或者新增了一些功能,欢迎 [贡献代码](https://github.com/Tencent/APIJSON/pulls),感激不尽~
https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md -QQ 技术群: 734652054(新)、607020115(旧) +QQ 技术群: 734652054(新)、607020115(旧)
+ +**原则上优先解决 登记用户 和 贡献者 的问题,不解决 态度无礼 或 问题描述简陋 的问题!** + +如果你 [登记了自己使用 APIJSON 的公司](https://github.com/Tencent/APIJSON/issues/187),可以加专门的 企业用户支持群,作者亲自答疑。 如果你为 APIJSON 做出了以下任何一个贡献:
-[提交了 PR 且被合并](https://github.com/Tencent/APIJSON/pull/92)、[提交了优质 Issue](https://github.com/Tencent/APIJSON/issues/189)、[发表了优质文章](https://blog.csdn.net/qq_41829492/article/details/88670940)、[开发了可用的生态项目](https://github.com/zhangchunlin/uliweb-apijson) 或 [登记了你的公司](https://github.com/Tencent/APIJSON/issues/187),可以加
-贡献者微信群,注意联系 LonelyExplorer,加好友描述中附上贡献链接,谢谢 +[提交了 PR 且被合并](https://github.com/Tencent/APIJSON/pull/92)、[提交了优质 Issue](https://github.com/Tencent/APIJSON/issues/189)、[发表了优质文章](https://blog.csdn.net/qq_41829492/article/details/88670940)、[开发了可用的生态项目](https://github.com/zhangchunlin/uliweb-apijson),
+可以加 贡献者交流群,注意联系 LonelyExplorer,加好友描述中附上贡献链接,谢谢 ### 相关推荐 From e3ecce3a2c6643bedf4fd9e75e69e0263c6afdad Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 22 Apr 2022 22:11:31 +0800 Subject: [PATCH 232/754] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b02b4dfbe..29de492ac 100644 --- a/README.md +++ b/README.md @@ -387,7 +387,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md QQ 技术群: 734652054(新)、607020115(旧)
-**原则上优先解决 登记用户 和 贡献者 的问题,不解决 态度无礼 或 问题描述简陋 的问题!** +**时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,不解决 文档/视频/常见问题 已明确说明、描述简陋 或 态度无礼 的问题!** 如果你 [登记了自己使用 APIJSON 的公司](https://github.com/Tencent/APIJSON/issues/187),可以加专门的 企业用户支持群,作者亲自答疑。 From a591ff1784e7c78bd1619860ea7a09dc89bbcf4d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 22 Apr 2022 22:14:11 +0800 Subject: [PATCH 233/754] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 29de492ac..1d647188e 100644 --- a/README.md +++ b/README.md @@ -387,7 +387,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md QQ 技术群: 734652054(新)、607020115(旧)
-**时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,不解决 文档/视频/常见问题 已明确说明、描述简陋 或 态度无礼 的问题!** +时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,
+不解决 文档/视频/常见问题 已明确说明、描述简陋 或 态度无礼 的问题! 如果你 [登记了自己使用 APIJSON 的公司](https://github.com/Tencent/APIJSON/issues/187),可以加专门的 企业用户支持群,作者亲自答疑。 From ce636ceab65a2a228043c8a8e95a4abeb6e19d29 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 22 Apr 2022 22:16:13 +0800 Subject: [PATCH 234/754] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1d647188e..5eb054cfd 100644 --- a/README.md +++ b/README.md @@ -387,8 +387,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md QQ 技术群: 734652054(新)、607020115(旧)
-时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,
-不解决 文档/视频/常见问题 已明确说明、描述简陋 或 态度无礼 的问题! +**时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,**
+**不解决 文档/视频/常见问题 已明确说明、描述简陋 或 态度无礼 的问题!** 如果你 [登记了自己使用 APIJSON 的公司](https://github.com/Tencent/APIJSON/issues/187),可以加专门的 企业用户支持群,作者亲自答疑。 From 0580b3038c3184f0dca2f3cca6b0a05c6b5cfa0d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 22 Apr 2022 22:17:21 +0800 Subject: [PATCH 235/754] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5eb054cfd..79b085933 100644 --- a/README.md +++ b/README.md @@ -387,7 +387,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md QQ 技术群: 734652054(新)、607020115(旧)
-**时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,**
+**开发者时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,**
**不解决 文档/视频/常见问题 已明确说明、描述简陋 或 态度无礼 的问题!** 如果你 [登记了自己使用 APIJSON 的公司](https://github.com/Tencent/APIJSON/issues/187),可以加专门的 企业用户支持群,作者亲自答疑。 From 3d42b895b581aa9b6d319e4cbd6a91e973458dff Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Apr 2022 00:18:27 +0800 Subject: [PATCH 236/754] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 79b085933..0e326d860 100644 --- a/README.md +++ b/README.md @@ -66,11 +66,11 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 ### 特点功能 #### 对于后端 -* 提供通用接口,大部分 API 不用再写 -* 自动生成文档,不用再编写和维护 +* 提供万能通用接口,大部分 HTTP API 不用再写 +* 零代码增删改查、各种跨库连表、多层嵌套子查询等 +* 自动生成文档,不用再编写和维护,且自动静态检查 * 自动校验权限、自动管理版本、自动防 SQL 注入 -* 开放 API 无需划分版本,始终保持兼容 -* 支持增删改查、复杂查询、跨库连表、远程函数等 +* 开放 HTTP API 无需划分版本,始终保持兼容 #### 对于前端 * 不用再向后端催接口、求文档 From 61c21535509dacc35aa3cc4a4cccae9745473a91 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Apr 2022 01:08:56 +0800 Subject: [PATCH 237/754] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0e326d860..be1828fa7 100644 --- a/README.md +++ b/README.md @@ -54,11 +54,11 @@ This source code is licensed under the Apache License Version 2.0
APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
-为各种增删改查提供了完全自动化的万能 API,零代码实时满足千变万化的各种新增和变更需求。
+为各种增删改查提供了完全自动化的万能通用接口,零代码实时满足千变万化的各种新增和变更需求。
能大幅降低开发和沟通成本,简化开发流程,缩短开发周期。
适合中小型前后端分离的项目,尤其是 初创项目、内部项目、低代码/零代码、小程序、BaaS、Serverless 等。
-通过万能的 API,前端可以定制任何数据、任何结构。
+通过万能通用接口,前端可以定制任何数据、任何结构。
大部分 HTTP 请求后端再也不用写接口了,更不用写文档了。
前端再也不用和后端沟通接口或文档问题了。再也不会被文档各种错误坑了。
后端再也不用为了兼容旧接口写新版接口和文档了。再也不会被前端随时随地没完没了地烦了。 From c3c6a08c12f2ca4b7f4dfbae015f29f656a4cef8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Apr 2022 02:49:20 +0800 Subject: [PATCH 238/754] =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=8E=A8=E8=8D=90?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E3=80=8Aapijson=E5=9C=A8=E5=90=8C=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=E6=8E=A5=E5=8F=A3=E8=B0=83=E7=94=A8=E4=B8=AD=20?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E8=BF=9C=E7=A8=8B=E5=87=BD=E6=95=B0=E5=86=99?= =?UTF-8?q?=E5=85=A5=E6=9B=B4=E6=96=B0=E6=97=B6=E9=97=B4=E5=92=8C=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E6=97=B6=E9=97=B4=E3=80=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 点赞、收藏支持下博主吧~ https://blog.csdn.net/qietingfengsong/article/details/124097229 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index be1828fa7..c8574d809 100644 --- a/README.md +++ b/README.md @@ -435,6 +435,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [新手搭建 APIJSON 项目指北](https://github.com/jerrylususu/apijson_todo_demo/blob/master/FULLTEXT.md) [使用APIJSON写低代码Crud接口](https://blog.csdn.net/weixin_42375862/article/details/121654264) + +[apijson在同一个接口调用中 使用远程函数写入更新时间和创建时间](https://blog.csdn.net/qietingfengsong/article/details/124097229) [APIJSON(一:综述)](https://blog.csdn.net/qq_50861917/article/details/120556168) From 23032713dc90b4391694141134f98ac59e09d413 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Apr 2022 21:53:47 +0800 Subject: [PATCH 239/754] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index 6e545c22d..a9662be96 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -4,6 +4,26 @@ about: Create a report to help us improve --- +**提 bug 请发请求和响应的【完整截屏】,没图的自行解决! +开发者有限的时间和精力主要放在【维护项目源码和文档】上! +【描述不详细】 或 【文档/常见问题 已有答案】 的问题可能会被忽略!! +【态度 不文明/不友善】的可能会被拉黑,问题也可能不予解答!!!** + +请求参数 JSON 中表名、字段名、关键词及对应的值都是大小写敏感、逗号敏感、分号敏感、空格敏感、换行敏感, +大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 设计规范 来调用 API #181 + +1.尝试在 [常见问题](https://github.com/Tencent/APIJSON/issues/36) 和 [历史问题](https://github.com/TommyLemon/APIJSON/issues?q=is%3Aissue+is%3Aclosed) 搜索答案。 +2.尝试阅读 [通用文档](https://github.com/TommyLemon/APIJSON/blob/master/Document.md) 或看 [视频教程](https://search.bilibili.com/all?keyword=APIJSON) 找到答案。 +3.尝试阅读 [Demo 示例代码](https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoSQLConfig.java) 以找到答案。 +4.尝试自己 [检查或试验](http://apijson.cn/api) 以找到答案。 +5.尝试阅读 [源码和注释](https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java) 以找到答案。 + +如果以上都尝试过了请填写以下模板提一个新的issue。 +强烈推荐阅读 [《如何向开源社区提问题》](https://github.com/seajs/seajs/issues/545)、[《如何有效地报告 Bug》](http://www.chiark.greenend.org.uk/~sgtatham/bugs-cn.html)、[《如何向开源项目提交无法解答的问题》](https://zhuanlan.zhihu.com/p/25795393) +和 [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way),更好的问题更容易获得帮助。 + + + **环境信息** - 系统: - JDK: @@ -12,8 +32,9 @@ about: Create a report to help us improve **问题描述** - +可以输入具体的步骤,代码信息,或者截图,能帮助我们更快的解决您的问题 **错误信息** - \ No newline at end of file +运行日志面板错误信息,错误截图,或者【帮助>切换开发者工具】日志,以及【帮助>查看运行日志】log.log 文件 + From 614cc0a1c27971ecda0a06348337391b0e92ecb3 Mon Sep 17 00:00:00 2001 From: SingleDogL <100330117+SingleDogL@users.noreply.github.com> Date: Wed, 27 Apr 2022 10:38:09 +0800 Subject: [PATCH 240/754] =?UTF-8?q?=E4=BF=AE=E5=A4=8Doracle=E5=88=86?= =?UTF-8?q?=E9=A1=B5=E8=8E=B7=E5=8F=96=E6=97=B6=E6=97=A0=E6=B3=95=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E9=99=A4=E7=AC=AC=E4=B8=80=E9=A1=B5=E4=BB=A5=E5=A4=96?= =?UTF-8?q?=E7=9A=84=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原来的getLimitString的方法所生成的语句oracle并无法正确获取,因为oracle的between并不支持获取除1开始以外的数据 也就是 无法获取到除了第一页以外的数据 --- .../main/java/apijson/orm/AbstractSQLConfig.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 6698e4f4b..4c2c81aee 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -3946,13 +3946,20 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { && StringUtil.isNotEmpty(config.getGroup(),true)){ return explain + "SELECT count(*) FROM (SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); } - return explain + "SELECT * FROM (SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); + String sql = "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config); + return explain + config.getOraclePageSql(config, sql); } - return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString(); } - } + } + private String getOraclePageSql(AbstractSQLConfig config, String sql) { + int offset = getOffset(config.getPage(), config.getCount()); + String pageSql; + pageSql = "SELECT * FROM (SELECT t.*,ROWNUM RN FROM (" + sql + ") t WHERE ROWNUM <= " + (offset + count) + ") WHERE RN > " + offset; + return pageSql; + } + /**获取条件SQL字符串 * @param column * @param table From 4f40cbd56192dce95e2f51beb2723e994e313e7b Mon Sep 17 00:00:00 2001 From: SingleDogL <100330117+SingleDogL@users.noreply.github.com> Date: Fri, 29 Apr 2022 09:23:39 +0800 Subject: [PATCH 241/754] Update AbstractSQLConfig.java --- .../src/main/java/apijson/orm/AbstractSQLConfig.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 4c2c81aee..87d44f7d9 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -3952,7 +3952,12 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString(); } } - + + /**Oracle的分页获取 + * @param config + * @param sql + * @return + */ private String getOraclePageSql(AbstractSQLConfig config, String sql) { int offset = getOffset(config.getPage(), config.getCount()); String pageSql; From 5b7d54a492eb93ccfd57918bfa84cb792469b5b4 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 1 May 2022 16:24:44 +0800 Subject: [PATCH 242/754] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/RequestMethod.java | 6 ++-- .../java/apijson/orm/AbstractSQLConfig.java | 34 +++++++++++-------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/RequestMethod.java b/APIJSONORM/src/main/java/apijson/RequestMethod.java index e196e4dc0..035147d3a 100755 --- a/APIJSONORM/src/main/java/apijson/RequestMethod.java +++ b/APIJSONORM/src/main/java/apijson/RequestMethod.java @@ -52,8 +52,7 @@ public enum RequestMethod { * @return */ public static boolean isGetMethod(RequestMethod method, boolean containPrivate) { - boolean is = method == null || method == GET; - return containPrivate == false ? is : is || method == GETS; + return method == null || method == GET || (containPrivate && method == GETS); } /**是否为HEAD请求方法 @@ -62,8 +61,7 @@ public static boolean isGetMethod(RequestMethod method, boolean containPrivate) * @return */ public static boolean isHeadMethod(RequestMethod method, boolean containPrivate) { - boolean is = method == HEAD; - return containPrivate == false ? is : is || method == HEADS; + return method == HEAD || (containPrivate && method == HEADS); } /**是否为查询的请求方法 diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 87d44f7d9..2f6b2fba7 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -27,7 +27,6 @@ import static apijson.RequestMethod.DELETE; import static apijson.RequestMethod.GET; import static apijson.RequestMethod.GETS; -import static apijson.RequestMethod.HEAD; import static apijson.RequestMethod.HEADS; import static apijson.RequestMethod.POST; import static apijson.RequestMethod.PUT; @@ -3942,28 +3941,30 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { if (config.isOracle()) { //When config's database is oracle,Using subquery since Oracle12 below does not support OFFSET FETCH paging syntax. //针对oracle分组后条数的统计 - if ((config.getMethod() == HEAD || config.getMethod() == HEADS) - && StringUtil.isNotEmpty(config.getGroup(),true)){ + if (StringUtil.isNotEmpty(config.getGroup(),true) && RequestMethod.isHeadMethod(config.getMethod(), true)){ return explain + "SELECT count(*) FROM (SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); } + String sql = "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config); - return explain + config.getOraclePageSql(config, sql); + return explain + config.getOraclePageSql(sql); } + return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString(); } - } - + } + /**Oracle的分页获取 * @param config * @param sql * @return */ - private String getOraclePageSql(AbstractSQLConfig config, String sql) { - int offset = getOffset(config.getPage(), config.getCount()); - String pageSql; - pageSql = "SELECT * FROM (SELECT t.*,ROWNUM RN FROM (" + sql + ") t WHERE ROWNUM <= " + (offset + count) + ") WHERE RN > " + offset; - return pageSql; - } + protected String getOraclePageSql(String sql) { + int count = getCount(); + int offset = getOffset(getPage(), count); + String alias = getAliasWithQuote(); + + return "SELECT * FROM (SELECT " + alias + ".*, ROWNUM RN FROM (" + sql + ") " + alias + " WHERE ROWNUM <= " + (offset + count) + ") WHERE RN > " + offset; + } /**获取条件SQL字符串 * @param column @@ -3981,16 +3982,19 @@ private static String getConditionString(String column, String table, AbstractSQ } //根据方法不同,聚合语句不同。GROUP BY 和 HAVING 可以加在 HEAD 上, HAVING 可以加在 PUT, DELETE 上,GET 全加,POST 全都不加 - String aggregation = ""; + String aggregation; if (RequestMethod.isGetMethod(config.getMethod(), true)) { aggregation = config.getGroupString(true) + config.getHavingString(true) + config.getOrderString(true); } - if (RequestMethod.isHeadMethod(config.getMethod(), true)) { // TODO 加参数 isPagenation 判断是 GET 内分页 query:2 查总数,不用加这些条件 + else if (RequestMethod.isHeadMethod(config.getMethod(), true)) { // TODO 加参数 isPagenation 判断是 GET 内分页 query:2 查总数,不用加这些条件 aggregation = config.getGroupString(true) + config.getHavingString(true) ; } - if (config.getMethod() == PUT || config.getMethod() == DELETE) { + else if (config.getMethod() == PUT || config.getMethod() == DELETE) { aggregation = config.getHavingString(true) ; } + else { + aggregation = ""; + } String condition = table + config.getJoinString() + where + aggregation; ; //+ config.getLimitString(); From 0fb42569cfbce38d6ace21611f6ec3cf512747d6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 1 May 2022 16:34:06 +0800 Subject: [PATCH 243/754] =?UTF-8?q?=E4=BC=98=E5=8C=96=20AbstractSQLExecuto?= =?UTF-8?q?r=20=E5=85=B3=E4=BA=8E=E8=80=97=E6=97=B6=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLExecutor.java | 68 +++++-------------- 1 file changed, 17 insertions(+), 51 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 3606c5043..208fc165f 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -45,15 +45,9 @@ public abstract class AbstractSQLExecutor implements SQLExecutor { private static final String TAG = "AbstractSQLExecutor"; - - private int generatedSQLCount; - private int cachedSQLCount; - private int executedSQLCount; - public AbstractSQLExecutor() { - generatedSQLCount = 0; - cachedSQLCount = 0; - executedSQLCount = 0; - } + private int generatedSQLCount = 0; + private int cachedSQLCount = 0; + private int executedSQLCount = 0; @Override public int getGeneratedSQLCount() { @@ -68,26 +62,11 @@ public int getExecutedSQLCount() { return executedSQLCount; } - // 只要不是并发执行且执行完立刻获取,就不会是错的,否则需要一并返回,可以 JSONObject.put("@EXECUTED_SQL_TIME:START|DURATION|END", ) - private long executedSQLStartTime; - private long executedSQLEndTime; - private long executedSQLDuration; - private long sqlResultDuration; - - public long getExecutedSQLStartTime() { - return executedSQLStartTime; - } - public long getExecutedSQLEndTime() { - return executedSQLEndTime; - } + private long executedSQLDuration = 0; + private long sqlResultDuration = 0; @Override public long getExecutedSQLDuration() { - if (executedSQLDuration <= 0) { - long startTime = getExecutedSQLStartTime(); - long endTime = getExecutedSQLEndTime(); - executedSQLDuration = startTime <= 0 || endTime <= 0 ? 0 : endTime - startTime; // FIXME 有时莫名其妙地算出来是负数 - } - return executedSQLDuration < 0 ? 0 : executedSQLDuration; + return executedSQLDuration; } @Override @@ -96,7 +75,7 @@ public long getSqlResultDuration() { } /** - * 缓存map + * 缓存 Map */ protected Map> cacheMap = new HashMap<>(); @@ -104,7 +83,7 @@ public long getSqlResultDuration() { /**保存缓存 * @param sql * @param list - * @param isStatic + * @param type */ @Override public void putCache(String sql, List list, int type) { @@ -116,7 +95,7 @@ public void putCache(String sql, List list, int type) { } /**移除缓存 * @param sql - * @param isStatic + * @param type */ @Override public void removeCache(String sql, int type) { @@ -126,7 +105,10 @@ public void removeCache(String sql, int type) { } cacheMap.remove(sql); } - + /**获取缓存 + * @param sql + * @param type + */ @Override public List getCache(String sql, int type) { return cacheMap.get(sql); @@ -154,24 +136,18 @@ public JSONObject getCacheItem(String sql, int position, int type) { @Override public ResultSet executeQuery(@NotNull Statement statement, String sql) throws Exception { -// executedSQLStartTime = System.currentTimeMillis(); ResultSet rs = statement.executeQuery(sql); -// executedSQLEndTime = System.currentTimeMillis(); return rs; } @Override public int executeUpdate(@NotNull Statement statement, String sql) throws Exception { -// executedSQLStartTime = System.currentTimeMillis(); int c = statement.executeUpdate(sql); -// executedSQLEndTime = System.currentTimeMillis(); return c; } @Override public ResultSet execute(@NotNull Statement statement, String sql) throws Exception { -// executedSQLStartTime = System.currentTimeMillis(); statement.execute(sql); ResultSet rs = statement.getResultSet(); -// executedSQLEndTime = System.currentTimeMillis(); return rs; } @@ -182,10 +158,7 @@ public ResultSet execute(@NotNull Statement statement, String sql) throws Except */ @Override public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws Exception { -// executedSQLDuration = 0; - executedSQLStartTime = System.currentTimeMillis(); - executedSQLEndTime = executedSQLStartTime; -// sqlResultDuration = 0; + long executedSQLStartTime = System.currentTimeMillis(); boolean isPrepared = config.isPrepared(); @@ -231,8 +204,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws rs = execute(statement, sql); int updateCount = statement.getUpdateCount(); if (isExplain == false) { - executedSQLEndTime = System.currentTimeMillis(); - executedSQLDuration += executedSQLEndTime - executedSQLStartTime; + executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime; } result = new JSONObject(true); @@ -251,8 +223,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws } int updateCount = executeUpdate(config); if (isExplain == false) { - executedSQLEndTime = System.currentTimeMillis(); - executedSQLDuration += executedSQLEndTime - executedSQLStartTime; + executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime; } if (updateCount <= 0) { @@ -294,8 +265,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws } rs = executeQuery(config); //FIXME SQL Server 是一次返回两个结果集,包括查询结果和执行计划,需要 moreResults if (isExplain == false) { - executedSQLEndTime = System.currentTimeMillis(); - executedSQLDuration += executedSQLEndTime - executedSQLStartTime; + executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime; } break; @@ -1131,9 +1101,7 @@ public void close() { @Override public ResultSet executeQuery(@NotNull SQLConfig config) throws Exception { PreparedStatement stt = getStatement(config); - // 不准,getStatement 有时比 execute sql 更耗时 executedSQLStartTime = System.currentTimeMillis(); ResultSet rs = stt.executeQuery(); //PreparedStatement 不用传 SQL - // executedSQLEndTime = System.currentTimeMillis(); // if (config.isExplain() && (config.isSQLServer() || config.isOracle())) { // FIXME 返回的是 boolean 值 rs = stt.getMoreResults(Statement.CLOSE_CURRENT_RESULT); // } @@ -1144,9 +1112,7 @@ public ResultSet executeQuery(@NotNull SQLConfig config) throws Exception { @Override public int executeUpdate(@NotNull SQLConfig config) throws Exception { PreparedStatement stt = getStatement(config); -// 不准,getStatement 有时比 execute sql 更耗时 executedSQLStartTime = System.currentTimeMillis(); int count = stt.executeUpdate(); // PreparedStatement 不用传 SQL -// executedSQLEndTime = System.currentTimeMillis(); if (count <= 0 && config.isHive()) { count = 1; From b92fab305df2620b62904756614f5f0712703c08 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 1 May 2022 16:55:47 +0800 Subject: [PATCH 244/754] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index a9662be96..3683b0f3e 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -12,7 +12,7 @@ about: Create a report to help us improve 请求参数 JSON 中表名、字段名、关键词及对应的值都是大小写敏感、逗号敏感、分号敏感、空格敏感、换行敏感, 大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 设计规范 来调用 API #181 -1.尝试在 [常见问题](https://github.com/Tencent/APIJSON/issues/36) 和 [历史问题](https://github.com/TommyLemon/APIJSON/issues?q=is%3Aissue+is%3Aclosed) 搜索答案。 +1.尝试在 [常见问题](https://github.com/Tencent/APIJSON/issues/36) 和 [历史问题](https://github.com/TommyLemon/APIJSON/issues?q=is%3Aissue) 搜索答案。 2.尝试阅读 [通用文档](https://github.com/TommyLemon/APIJSON/blob/master/Document.md) 或看 [视频教程](https://search.bilibili.com/all?keyword=APIJSON) 找到答案。 3.尝试阅读 [Demo 示例代码](https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoSQLConfig.java) 以找到答案。 4.尝试自己 [检查或试验](http://apijson.cn/api) 以找到答案。 From 5d402171b0d6f1d5395a037b51eb6bac7d450f00 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 1 May 2022 17:10:25 +0800 Subject: [PATCH 245/754] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20JOIN=20=E5=89=AF?= =?UTF-8?q?=E8=A1=A8=E8=BF=94=E5=9B=9E=E7=A9=BA=E5=AF=B9=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractSQLExecutor.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 208fc165f..01cdb308d 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -783,9 +783,10 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r String lable = getKey(config, rs, rsmd, tablePosition, table, columnIndex, childMap); Object value = getValue(config, rs, rsmd, tablePosition, table, columnIndex, lable, childMap); - // 必须 put 进去,否则某个字段为 null 可能导致中断后续正常返回值 if (value != null) { - table.put(lable, value); - // } + // 主表必须 put 至少一个 null 进去,否则全部字段为 null 都不 put 会导致中断后续正常返回值 + if (value != null || (join == null && table.isEmpty())) { + table.put(lable, value); + } return table; } From 8df36e26d7ad74f2df4df4e9ce5b29530d814141 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 2 May 2022 00:32:51 +0800 Subject: [PATCH 246/754] =?UTF-8?q?=E4=BC=98=E5=8C=96=20SQL=20=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E7=BC=93=E5=AD=98=EF=BC=9B=E4=BC=98=E5=8C=96=E4=B8=BB?= =?UTF-8?q?=E9=94=AE=E6=B3=9B=E5=9E=8B=EF=BC=9B=E9=83=A8=E5=88=86=E5=B8=B8?= =?UTF-8?q?=E9=87=8F=E6=94=B9=E4=B8=BA=E5=8F=AF=E8=87=AA=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=E9=9D=99=E6=80=81=E5=8F=98=E9=87=8F=EF=BC=9B=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E9=AB=98=E5=B9=B6=E5=8F=91=E4=B8=8B=20id=20=E5=86=B2=E7=AA=81?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E6=96=B0=E5=A2=9E=E8=AE=B0=E5=BD=95=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/RequestMethod.java | 1 + .../apijson/orm/AbstractFunctionParser.java | 4 +- .../apijson/orm/AbstractObjectParser.java | 4 +- .../main/java/apijson/orm/AbstractParser.java | 84 ++++++++++--------- .../java/apijson/orm/AbstractSQLConfig.java | 50 ++++++----- .../java/apijson/orm/AbstractSQLExecutor.java | 66 ++++++++------- .../java/apijson/orm/AbstractVerifier.java | 39 ++++----- .../src/main/java/apijson/orm/Parser.java | 14 +--- .../main/java/apijson/orm/ParserCreator.java | 2 +- .../main/java/apijson/orm/SQLExecutor.java | 29 ++++--- .../src/main/java/apijson/orm/Subquery.java | 2 +- .../java/apijson/orm/VerifierCreator.java | 2 +- 12 files changed, 156 insertions(+), 141 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/RequestMethod.java b/APIJSONORM/src/main/java/apijson/RequestMethod.java index 035147d3a..caca99225 100755 --- a/APIJSONORM/src/main/java/apijson/RequestMethod.java +++ b/APIJSONORM/src/main/java/apijson/RequestMethod.java @@ -45,6 +45,7 @@ public enum RequestMethod { */ DELETE; + public static final RequestMethod[] ALL = new RequestMethod[]{ GET, HEAD, GETS, HEADS, POST, PUT, DELETE}; /**是否为GET请求方法 * @param method diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java index 880b917f0..cde0a6228 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java @@ -178,7 +178,7 @@ public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull Str + "\n请检查函数名和参数数量是否与已定义的函数一致!" + "\n且必须为 function(key0,key1,...) 这种单函数格式!" + "\nfunction必须符合Java函数命名,key是用于在request内取值的键!" - + "\n调用时不要有空格!"); + + "\n调用时不要有空格!" + e.getMessage()); } if (e instanceof InvocationTargetException) { Throwable te = ((InvocationTargetException) e).getTargetException(); @@ -186,7 +186,7 @@ public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull Str throw te instanceof Exception ? (Exception) te : new Exception(te.getMessage()); } throw new IllegalArgumentException("字符 " + function + " 对应的远程函数传参类型错误!" - + "\n请检查 key:value 中value的类型是否满足已定义的函数 " + getFunction(fb.getMethod(), fb.getKeys()) + " 的要求!"); + + "\n请检查 key:value 中value的类型是否满足已定义的函数 " + getFunction(fb.getMethod(), fb.getKeys()) + " 的要求!" + e.getMessage()); } throw e; } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index c64236960..bdbb366e7 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -908,7 +908,7 @@ public JSONObject onSQLExecute() throws Exception { && (table.equals(arrayTable)); // 提取并缓存数组主表的列表数据 - rawList = (List) result.remove(SQLExecutor.KEY_RAW_LIST); + rawList = (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST); if (rawList != null) { String arrayPath = parentPath.substring(0, parentPath.lastIndexOf("[]") + 2); @@ -936,7 +936,7 @@ public JSONObject onSQLExecute() throws Exception { parser.putQueryResult(path, result); // 解决获取关联数据时requestObject里不存在需要的关联数据 if (isSimpleArray && rawList != null) { - result.put(SQLExecutor.KEY_RAW_LIST, rawList); + result.put(AbstractSQLExecutor.KEY_RAW_LIST, rawList); } } } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index f3fa6e18c..5135a7279 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -50,7 +50,7 @@ /**parser for parsing request to JSONObject * @author Lemon */ -public abstract class AbstractParser implements Parser, ParserCreator, VerifierCreator, SQLCreator { +public abstract class AbstractParser implements Parser, ParserCreator, VerifierCreator, SQLCreator { protected static final String TAG = "AbstractParser"; /** @@ -72,6 +72,49 @@ public abstract class AbstractParser implements Parser, ParserCreator, public static boolean IS_PRINT_REQUEST_ENDTIME_LOG = false; + public static int DEFAULT_QUERY_COUNT = 10; + public static int MAX_QUERY_PAGE = 100; + public static int MAX_QUERY_COUNT = 100; + public static int MAX_UPDATE_COUNT = 10; + public static int MAX_SQL_COUNT = 200; + public static int MAX_OBJECT_COUNT = 5; + public static int MAX_ARRAY_COUNT = 5; + public static int MAX_QUERY_DEPTH = 5; + + @Override + public int getDefaultQueryCount() { + return DEFAULT_QUERY_COUNT; + } + @Override + public int getMaxQueryPage() { + return MAX_QUERY_PAGE; + } + @Override + public int getMaxQueryCount() { + return MAX_QUERY_COUNT; + } + @Override + public int getMaxUpdateCount() { + return MAX_UPDATE_COUNT; + } + @Override + public int getMaxSQLCount() { + return MAX_SQL_COUNT; + } + @Override + public int getMaxObjectCount() { + return MAX_OBJECT_COUNT; + } + @Override + public int getMaxArrayCount() { + return MAX_ARRAY_COUNT; + } + @Override + public int getMaxQueryDepth() { + return MAX_QUERY_DEPTH; + } + + /** * method = null */ @@ -1276,7 +1319,7 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // */ JSONObject fo = i != 0 || arrTableKey == null ? null : parent.getJSONObject(arrTableKey); @SuppressWarnings("unchecked") - List list = fo == null ? null : (List) fo.remove(SQLExecutor.KEY_RAW_LIST); + List list = fo == null ? null : (List) fo.remove(AbstractSQLExecutor.KEY_RAW_LIST); if (list != null && list.isEmpty() == false) { isExtract = false; @@ -1629,43 +1672,6 @@ else if (join != null){ return joinList; } - - - - @Override - public int getDefaultQueryCount() { - return DEFAULT_QUERY_COUNT; - } - @Override - public int getMaxQueryPage() { - return MAX_QUERY_PAGE; - } - @Override - public int getMaxQueryCount() { - return MAX_QUERY_COUNT; - } - @Override - public int getMaxUpdateCount() { - return MAX_UPDATE_COUNT; - } - @Override - public int getMaxSQLCount() { - return MAX_SQL_COUNT; - } - @Override - public int getMaxObjectCount() { - return MAX_OBJECT_COUNT; - } - @Override - public int getMaxArrayCount() { - return MAX_ARRAY_COUNT; - } - @Override - public int getMaxQueryDepth() { - return MAX_QUERY_DEPTH; - } - - /**根据路径取值 * @param parent * @param pathKeys diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 2f6b2fba7..c9c9c8623 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -103,22 +103,21 @@ public abstract class AbstractSQLConfig implements SQLConfig { public static String PREFFIX_DISTINCT = "DISTINCT "; // * 和 / 不能同时出现,防止 /* */ 段注释! # 和 -- 不能出现,防止行注释! ; 不能出现,防止隔断SQL语句!空格不能出现,防止 CRUD,DROP,SHOW TABLES等语句! - private static final Pattern PATTERN_RANGE; - private static final Pattern PATTERN_FUNCTION; + private static Pattern PATTERN_RANGE; + private static Pattern PATTERN_FUNCTION; /** * 表名映射,隐藏真实表名,对安全要求很高的表可以这么做 */ - public static final Map TABLE_KEY_MAP; - public static final List CONFIG_TABLE_LIST; - public static final List DATABASE_LIST; + public static Map TABLE_KEY_MAP; + public static List CONFIG_TABLE_LIST; + public static List DATABASE_LIST; // 自定义原始 SQL 片段 Map:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL - public static final Map RAW_MAP; + public static Map RAW_MAP; // 允许调用的 SQL 函数:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL - public static final Map SQL_AGGREGATE_FUNCTION_MAP; - public static final Map SQL_FUNCTION_MAP; - + public static Map SQL_AGGREGATE_FUNCTION_MAP; + public static Map SQL_FUNCTION_MAP; static { // 凡是 SQL 边界符、分隔符、注释符 都不允许,例如 ' " ` ( ) ; # -- /**/ ,以免拼接 SQL 时被注入意外可执行指令 PATTERN_RANGE = Pattern.compile("^[0-9%,!=\\<\\>/\\.\\+\\-\\*\\^]+$"); // ^[a-zA-Z0-9_*%!=<>(),"]+$ 导致 exists(select*from(Comment)) 通过! @@ -4339,7 +4338,7 @@ protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException * @return * @throws Exception */ - public static SQLConfig newSQLConfig(RequestMethod method, String table, String alias, JSONObject request, List joinList, boolean isProcedure, Callback callback) throws Exception { + public static SQLConfig newSQLConfig(RequestMethod method, String table, String alias, JSONObject request, List joinList, boolean isProcedure, Callback callback) throws Exception { if (request == null) { // User:{} 这种空内容在查询时也有效 throw new NullPointerException(TAG + ": newSQLConfig request == null!"); } @@ -4357,7 +4356,7 @@ public static SQLConfig newSQLConfig(RequestMethod method, String table, String String schema = request.getString(KEY_SCHEMA); String datasource = request.getString(KEY_DATASOURCE); - SQLConfig config = callback.getSQLConfig(method, database, schema, table); + SQLConfig config = callback.getSQLConfig(method, database, schema, datasource, table); config.setAlias(alias); config.setDatabase(database); //不删,后面表对象还要用的,必须放在 parseJoin 前 @@ -4404,7 +4403,7 @@ public static SQLConfig newSQLConfig(RequestMethod method, String table, String Object id = request.get(idKey); if (id == null && method == POST) { - id = callback.newId(method, database, schema, table); // null 表示数据库自增 id + id = callback.newId(method, database, schema, datasource, table); // null 表示数据库自增 id } if (id != null) { //null无效 @@ -4992,7 +4991,7 @@ else if (newHaving != null) { * @return * @throws Exception */ - public static SQLConfig parseJoin(RequestMethod method, SQLConfig config, List joinList, Callback callback) throws Exception { + public static SQLConfig parseJoin(RequestMethod method, SQLConfig config, List joinList, Callback callback) throws Exception { boolean isQuery = RequestMethod.isQueryMethod(method); config.setKeyPrefix(isQuery && config.isMain() == false); @@ -5203,7 +5202,7 @@ else if (key.endsWith("-")) {//缩减,PUT查询时处理 } - public static interface IdCallback { + public static interface IdCallback { /**为 post 请求新建 id, 只能是 Long 或 String * @param method * @param database @@ -5211,7 +5210,7 @@ public static interface IdCallback { * @param table * @return */ - Object newId(RequestMethod method, String database, String schema, String table); + T newId(RequestMethod method, String database, String schema, String datasource, String table); /**获取主键名 @@ -5231,7 +5230,7 @@ public static interface IdCallback { String getUserIdKey(String database, String schema, String datasource, String table); } - public static interface Callback extends IdCallback { + public static interface Callback extends IdCallback { /**获取 SQLConfig 的实例 * @param method * @param database @@ -5239,7 +5238,7 @@ public static interface Callback extends IdCallback { * @param table * @return */ - SQLConfig getSQLConfig(RequestMethod method, String database, String schema, String table); + SQLConfig getSQLConfig(RequestMethod method, String database, String schema, String datasource, String table); /**combine 里的 key 在 request 中 value 为 null 或不存在,即 request 中缺少用来作为 combine 条件的 key: value * @param combine @@ -5249,12 +5248,23 @@ public static interface Callback extends IdCallback { public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception; } - public static abstract class SimpleCallback implements Callback { + public static Long LAST_ID; + static { + LAST_ID = System.currentTimeMillis(); + } + public static abstract class SimpleCallback implements Callback { + @SuppressWarnings("unchecked") @Override - public Object newId(RequestMethod method, String database, String schema, String table) { - return System.currentTimeMillis(); + public T newId(RequestMethod method, String database, String schema, String datasource, String table) { + Long id = System.currentTimeMillis(); + if (id <= LAST_ID) { + id = LAST_ID + 1; // 解决高并发下 id 冲突导致新增记录失败 + } + LAST_ID = id; + + return (T) id; } @Override diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 01cdb308d..1095658bc 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -45,6 +45,8 @@ public abstract class AbstractSQLExecutor implements SQLExecutor { private static final String TAG = "AbstractSQLExecutor"; + public static String KEY_RAW_LIST = "@RAW@LIST"; // 避免和字段命名冲突,不用 $RAW@LIST$ 是因为 $ 会在 fastjson 内部转义,浪费性能 + private int generatedSQLCount = 0; private int cachedSQLCount = 0; private int executedSQLCount = 0; @@ -74,64 +76,66 @@ public long getSqlResultDuration() { return sqlResultDuration; } + /** * 缓存 Map */ protected Map> cacheMap = new HashMap<>(); - /**保存缓存 - * @param sql - * @param list - * @param type + * @param sql key + * @param list value + * @param config 一般主表 SQLConfig 不为 null,JOIN 副表的为 null */ @Override - public void putCache(String sql, List list, int type) { - if (sql == null || list == null) { //空map有效,说明查询过sql了 || list.isEmpty()) { + public void putCache(String sql, List list, SQLConfig config) { + if (sql == null || list == null) { // 空 list 有效,说明查询过 sql 了 || list.isEmpty()) { Log.i(TAG, "saveList sql == null || list == null >> return;"); return; } + cacheMap.put(sql, list); } - /**移除缓存 - * @param sql - * @param type - */ - @Override - public void removeCache(String sql, int type) { - if (sql == null) { - Log.i(TAG, "removeList sql == null >> return;"); - return; - } - cacheMap.remove(sql); - } + /**获取缓存 - * @param sql - * @param type + * @param sql key + * @param config 一般主表 SQLConfig 不为 null,JOIN 副表的为 null */ @Override - public List getCache(String sql, int type) { + public List getCache(String sql, SQLConfig config) { return cacheMap.get(sql); } /**获取缓存 - * @param sql + * @param sql key * @param position - * @param type + * @param config 一般主表 SQLConfig 不为 null,JOIN 副表的为 null * @return */ @Override - public JSONObject getCacheItem(String sql, int position, int type) { - List list = getCache(sql, type); - //只要map不为null,则如果 list.get(position) == null,则返回 {} ,避免再次SQL查询 + public JSONObject getCacheItem(String sql, int position, SQLConfig config) { + List list = getCache(sql, config); + // 只要 list 不为 null,则如果 list.get(position) == null,则返回 {} ,避免再次 SQL 查询 if (list == null) { return null; } - + JSONObject result = position >= list.size() ? null : list.get(position); return result != null ? result : new JSONObject(); } + /**移除缓存 + * @param sql key + * @param config + */ + @Override + public void removeCache(String sql, SQLConfig config) { + if (sql == null) { + Log.i(TAG, "removeList sql == null >> return;"); + return; + } + cacheMap.remove(sql); + } @Override @@ -250,7 +254,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws case GETS: case HEAD: case HEADS: - result = isHead || isExplain ? null : getCacheItem(sql, position, config.getCache()); + result = isHead || isExplain ? null : getCacheItem(sql, position, config); Log.i(TAG, ">>> execute result = getCache('" + sql + "', " + position + ") = " + result); if (result != null) { cachedSQLCount ++; @@ -306,7 +310,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws capacity = ((Collection) idIn).size(); } else { // 预估容量 - capacity = config.getCount() <= 0 ? Parser.MAX_QUERY_COUNT : config.getCount(); + capacity = config.getCount() <= 0 ? AbstractParser.MAX_QUERY_COUNT : config.getCount(); if (capacity > 100) { // 有 WHERE 条件,条件越多过滤数据越多,暂时不考虑 @combine:"a | (b & !c)" 里面 | OR 和 ! NOT 条件,太复杂也不是很必要 Map> combine = config.getCombineMap(); @@ -594,10 +598,10 @@ else if (curJoin.isOuterJoin() || curJoin.isAntiJoin()) { for (Entry entry : set) { List l = new ArrayList<>(); l.add(entry.getValue()); - putCache(entry.getKey(), l, JSONRequest.CACHE_ROM); + putCache(entry.getKey(), l, null); } - putCache(sql, resultList, config.getCache()); + putCache(sql, resultList, config); Log.i(TAG, ">>> execute putCache('" + sql + "', resultList); resultList.size() = " + resultList.size()); // 数组主表对象额外一次返回全部,方便 Parser 缓存来提高性能 diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 0431d1773..f62dfa94d 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -73,7 +73,7 @@ * @author Lemon * @param id 与 userId 的类型,一般为 Long */ -public abstract class AbstractVerifier implements Verifier, IdCallback { +public abstract class AbstractVerifier implements Verifier, IdCallback { private static final String TAG = "AbstractVerifier"; /**未登录,不明身份的用户 @@ -102,9 +102,9 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { // 共享 STRUCTURE_MAP 则不能 remove 等做任何变更,否则在并发情况下可能会出错,加锁效率又低,所以这里改为忽略对应的 key - public static final Map> ROLE_MAP; + public static Map> ROLE_MAP; - public static final List OPERATION_KEY_LIST; + public static List OPERATION_KEY_LIST; // > // > @@ -205,9 +205,10 @@ public String getUserIdKey(String database, String schema, String datasource, St return apijson.JSONObject.KEY_USER_ID; } + @SuppressWarnings("unchecked") @Override - public Object newId(RequestMethod method, String database, String schema, String table) { - return System.currentTimeMillis(); + public T newId(RequestMethod method, String database, String schema, String datasource, String table) { + return (T) Long.valueOf(System.currentTimeMillis()); } @@ -437,17 +438,17 @@ public void verifyUseRole(SQLConfig config, String table, RequestMethod method, public void verifyLogin() throws Exception { //未登录没有权限操作 if (visitorId == null) { - throw new NotLoggedInException("未登录,请登录后再操作!"); + throw new NotLoggedInException("未登录或登录超时,请登录后再操作!"); } if (visitorId instanceof Number) { if (((Number) visitorId).longValue() <= 0) { - throw new NotLoggedInException("未登录,请登录后再操作!"); + throw new NotLoggedInException("未登录或登录超时,请登录后再操作!"); } } else if (visitorId instanceof String) { if (StringUtil.isEmpty(visitorId, true)) { - throw new NotLoggedInException("未登录,请登录后再操作!"); + throw new NotLoggedInException("未登录或登录超时,请登录后再操作!"); } } else { @@ -539,7 +540,7 @@ public JSONObject verifyRequest(@NotNull final RequestMethod method, final Strin */ public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name , final JSONObject target, final JSONObject request, final SQLCreator creator) throws Exception { - return verifyRequest(method, name, target, request, Parser.MAX_UPDATE_COUNT, creator); + return verifyRequest(method, name, target, request, AbstractParser.MAX_UPDATE_COUNT, creator); } /**从request提取target指定的内容 * @param method @@ -568,9 +569,9 @@ public static JSONObject verifyRequest(@NotNull final RequestMethod method, fina * @return * @throws Exception */ - public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name + public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name , final JSONObject target, final JSONObject request, final int maxUpdateCount - , final String database, final String schema, final IdCallback idCallback, final SQLCreator creator) throws Exception { + , final String database, final String schema, final IdCallback idCallback, final SQLCreator creator) throws Exception { return verifyRequest(method, name, target, request, maxUpdateCount, database, schema, null, idCallback, creator); } /**从request提取target指定的内容 @@ -585,9 +586,9 @@ public static JSONObject verifyRequest(@NotNull final RequestMethod method, fina * @return * @throws Exception */ - public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name + public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name , final JSONObject target, final JSONObject request, final int maxUpdateCount - , final String database, final String schema, final String datasource, final IdCallback idCallback, final SQLCreator creator) throws Exception { + , final String database, final String schema, final String datasource, final IdCallback idCallback, final SQLCreator creator) throws Exception { Log.i(TAG, "verifyRequest method = " + method + "; name = " + name + "; target = \n" + JSON.toJSONString(target) @@ -782,9 +783,9 @@ public static JSONObject verifyResponse(@NotNull final RequestMethod method, fin * @return * @throws Exception */ - public static JSONObject verifyResponse(@NotNull final RequestMethod method, final String name + public static JSONObject verifyResponse(@NotNull final RequestMethod method, final String name , final JSONObject target, final JSONObject response, final String database, final String schema - , final IdCallback idKeyCallback, SQLCreator creator, OnParseCallback callback) throws Exception { + , final IdCallback idKeyCallback, SQLCreator creator, OnParseCallback callback) throws Exception { Log.i(TAG, "verifyResponse method = " + method + "; name = " + name + "; target = \n" + JSON.toJSONString(target) @@ -832,8 +833,8 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, * @return * @throws Exception */ - public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real - , final String database, final String schema, final IdCallback idCallback, SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { + public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real + , final String database, final String schema, final IdCallback idCallback, SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { return parse(method, name, target, real, database, schema, null, idCallback, creator, callback); } /**对request和response不同的解析用callback返回 @@ -850,8 +851,8 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, * @return * @throws Exception */ - public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real - , final String database, final String schema, final String datasource, final IdCallback idCallback, SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { + public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real + , final String database, final String schema, final String datasource, final IdCallback idCallback, SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { if (target == null) { return null; } diff --git a/APIJSONORM/src/main/java/apijson/orm/Parser.java b/APIJSONORM/src/main/java/apijson/orm/Parser.java index db98ea1f5..50b86b5f5 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Parser.java +++ b/APIJSONORM/src/main/java/apijson/orm/Parser.java @@ -17,18 +17,8 @@ /**解析器 * @author Lemon */ -public interface Parser { - - int DEFAULT_QUERY_COUNT = 10; - int MAX_QUERY_PAGE = 100; - int MAX_QUERY_COUNT = 100; - int MAX_UPDATE_COUNT = 10; - int MAX_SQL_COUNT = 200; - int MAX_OBJECT_COUNT = 5; - int MAX_ARRAY_COUNT = 5; - int MAX_QUERY_DEPTH = 5; - - +public interface Parser { + @NotNull Visitor getVisitor(); Parser setVisitor(@NotNull Visitor visitor); diff --git a/APIJSONORM/src/main/java/apijson/orm/ParserCreator.java b/APIJSONORM/src/main/java/apijson/orm/ParserCreator.java index 2af2767c3..4da410b46 100755 --- a/APIJSONORM/src/main/java/apijson/orm/ParserCreator.java +++ b/APIJSONORM/src/main/java/apijson/orm/ParserCreator.java @@ -10,7 +10,7 @@ /**SQL相关创建器 * @author Lemon */ -public interface ParserCreator { +public interface ParserCreator { @NotNull Parser createParser(); diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java index 3cb2bf60a..7e700c269 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java @@ -22,31 +22,34 @@ */ public interface SQLExecutor { - String KEY_RAW_LIST = "@RAW@LIST"; // 避免和字段命名冲突,不用 $RAW@LIST$ 是因为 $ 会在 fastjson 内部转义,浪费性能 - /**保存缓存 * @param sql - * @param map - * @param type + * @param list + * @param config */ - void putCache(String sql, List list, int type);; - - List getCache(String sql, int type); + void putCache(String sql, List list, SQLConfig config); - /**移除缓存 + /**获取缓存 * @param sql - * @param type + * @param config + * @return */ - void removeCache(String sql, int type); + List getCache(String sql, SQLConfig config); + /**获取缓存 * @param sql * @param position - * @param type + * @param config * @return */ - JSONObject getCacheItem(String sql, int position, int type); - + JSONObject getCacheItem(String sql, int position, SQLConfig config); + /**移除缓存 + * @param sql + * @param config + */ + void removeCache(String sql, SQLConfig config); + /**执行SQL * @param config * @return diff --git a/APIJSONORM/src/main/java/apijson/orm/Subquery.java b/APIJSONORM/src/main/java/apijson/orm/Subquery.java index 66fdf14df..de8603b6d 100644 --- a/APIJSONORM/src/main/java/apijson/orm/Subquery.java +++ b/APIJSONORM/src/main/java/apijson/orm/Subquery.java @@ -18,7 +18,7 @@ public class Subquery { private JSONObject originValue; // { "from": "Comment", "Comment": {...} } private String from; // Comment - private String range; // any, all + private String range; // ANY, ALL private String key; //id{} private SQLConfig config; diff --git a/APIJSONORM/src/main/java/apijson/orm/VerifierCreator.java b/APIJSONORM/src/main/java/apijson/orm/VerifierCreator.java index 735b1104d..0caa6f8b1 100644 --- a/APIJSONORM/src/main/java/apijson/orm/VerifierCreator.java +++ b/APIJSONORM/src/main/java/apijson/orm/VerifierCreator.java @@ -10,7 +10,7 @@ /**验证器相关创建器 * @author Lemon */ -public interface VerifierCreator { +public interface VerifierCreator { @NotNull Verifier createVerifier(); From 0e2b645465a3189e467aa7be4575d18cd1a16d10 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 2 May 2022 00:39:16 +0800 Subject: [PATCH 247/754] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E6=8A=A5=E9=94=99=E5=92=8C=E8=BF=9C=E7=A8=8B=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E6=8A=A5=E9=94=99=E7=9A=84=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractFunctionParser.java | 6 ++++-- APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java index cde0a6228..795062d7a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java @@ -16,6 +16,7 @@ import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; +import apijson.Log; import apijson.NotNull; import apijson.RequestMethod; import apijson.StringUtil; @@ -178,7 +179,7 @@ public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull Str + "\n请检查函数名和参数数量是否与已定义的函数一致!" + "\n且必须为 function(key0,key1,...) 这种单函数格式!" + "\nfunction必须符合Java函数命名,key是用于在request内取值的键!" - + "\n调用时不要有空格!" + e.getMessage()); + + "\n调用时不要有空格!" + (Log.DEBUG ? e.getMessage() : "")); } if (e instanceof InvocationTargetException) { Throwable te = ((InvocationTargetException) e).getTargetException(); @@ -186,7 +187,8 @@ public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull Str throw te instanceof Exception ? (Exception) te : new Exception(te.getMessage()); } throw new IllegalArgumentException("字符 " + function + " 对应的远程函数传参类型错误!" - + "\n请检查 key:value 中value的类型是否满足已定义的函数 " + getFunction(fb.getMethod(), fb.getKeys()) + " 的要求!" + e.getMessage()); + + "\n请检查 key:value 中value的类型是否满足已定义的函数 " + getFunction(fb.getMethod(), fb.getKeys()) + " 的要求!" + + (Log.DEBUG ? e.getMessage() : "")); } throw e; } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index f62dfa94d..c46b055bd 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -438,17 +438,17 @@ public void verifyUseRole(SQLConfig config, String table, RequestMethod method, public void verifyLogin() throws Exception { //未登录没有权限操作 if (visitorId == null) { - throw new NotLoggedInException("未登录或登录超时,请登录后再操作!"); + throw new NotLoggedInException("未登录或登录过期,请登录后再操作!"); } if (visitorId instanceof Number) { if (((Number) visitorId).longValue() <= 0) { - throw new NotLoggedInException("未登录或登录超时,请登录后再操作!"); + throw new NotLoggedInException("未登录或登录过期,请登录后再操作!"); } } else if (visitorId instanceof String) { if (StringUtil.isEmpty(visitorId, true)) { - throw new NotLoggedInException("未登录或登录超时,请登录后再操作!"); + throw new NotLoggedInException("未登录或登录过期,请登录后再操作!"); } } else { From 92c396ced2d13756487444cdeedf4f7b5967d053 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 2 May 2022 05:04:33 +0800 Subject: [PATCH 248/754] =?UTF-8?q?=E8=AF=B7=E6=B1=82=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=EF=BC=9AREFUSE=20=E6=96=B0=E5=A2=9E=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20!key=20=E6=8E=92=E9=99=A4=E7=A6=81=E6=AD=A2?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=EF=BC=8C=E4=BC=98=E5=8C=96=20MUST=20?= =?UTF-8?q?=E5=92=8C=20REFUSE=20=E5=A4=84=E7=90=86=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractVerifier.java | 93 +++++++++++++------ 1 file changed, 65 insertions(+), 28 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index c46b055bd..4b4c08b2c 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -103,7 +103,7 @@ public abstract class AbstractVerifier implements Verifier, // 共享 STRUCTURE_MAP 则不能 remove 等做任何变更,否则在并发情况下可能会出错,加锁效率又低,所以这里改为忽略对应的 key public static Map> ROLE_MAP; - + public static List OPERATION_KEY_LIST; // > @@ -129,7 +129,7 @@ public abstract class AbstractVerifier implements Verifier, ROLE_MAP.put(CIRCLE, new Entry("userId-()", "verifyCircle()")); // "userId{}", "circleIdList")); // 还是 {"userId":"currentUserId", "userId{}": "contactIdList", "@combine": "userId,userId{}" } ? ROLE_MAP.put(OWNER, new Entry("userId", "userId")); ROLE_MAP.put(ADMIN, new Entry("userId-()", "verifyAdmin()")); - + OPERATION_KEY_LIST = new ArrayList<>(); OPERATION_KEY_LIST.add(TYPE.name()); OPERATION_KEY_LIST.add(VERIFY.name()); @@ -204,7 +204,7 @@ public String getIdKey(String database, String schema, String datasource, String public String getUserIdKey(String database, String schema, String datasource, String table) { return apijson.JSONObject.KEY_USER_ID; } - + @SuppressWarnings("unchecked") @Override public T newId(RequestMethod method, String database, String schema, String datasource, String table) { @@ -247,7 +247,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception { if (table == null) { return true; } - + String role = config.getRole(); if (role == null) { role = UNKNOWN; @@ -265,10 +265,10 @@ public boolean verifyAccess(SQLConfig config) throws Exception { RequestMethod method = config.getMethod(); verifyRole(config, table, method, role); - + return true; } - + @Override public void verifyRole(SQLConfig config, String table, RequestMethod method, String role) throws Exception { verifyAllowRole(config, table, method, role); //验证允许的角色 @@ -289,7 +289,7 @@ public void verifyAllowRole(SQLConfig config, String table, RequestMethod method if (table == null) { table = config == null ? null : config.getTable(); } - + if (table != null) { if (method == null) { method = config == null ? GET : config.getMethod(); @@ -297,7 +297,7 @@ public void verifyAllowRole(SQLConfig config, String table, RequestMethod method if (role == null) { role = config == null ? UNKNOWN : config.getRole(); } - + Map map = ACCESS_MAP.get(table); if (map == null || Arrays.asList(map.get(method)).contains(role) == false) { @@ -329,7 +329,7 @@ public void verifyUseRole(SQLConfig config, String table, RequestMethod method, if (role == null) { role = config == null ? UNKNOWN : config.getRole(); } - + Object requestId; switch (role) { case LOGIN://verifyRole通过就行 @@ -882,11 +882,15 @@ public static JSONObject parse(@NotNull final RequestMethod m // 判断必要字段是否都有<<<<<<<<<<<<<<<<<<< String[] musts = StringUtil.split(must); - List mustList = musts == null ? new ArrayList() : Arrays.asList(musts); - for (String s : mustList) { - if (real.get(s) == null) { // 可能传null进来,这里还会通过 real.containsKey(s) == false) { - throw new IllegalArgumentException(method + "请求," + name - + " 里面不能缺少 " + s + " 等[" + must + "]内的任何字段!"); + Set mustSet = new HashSet(); + + if (musts != null && musts.length > 0) { + for (String s : musts) { + if (real.get(s) == null) { // 可能传null进来,这里还会通过 real.containsKey(s) == false) { + throw new IllegalArgumentException(method + "请求," + name + " 里面不能缺少 " + s + " 等[" + must + "]内的任何字段!"); + } + + mustSet.add(s); } } //判断必要字段是否都有>>>>>>>>>>>>>>>>>>> @@ -947,28 +951,61 @@ public static JSONObject parse(@NotNull final RequestMethod m Set rkset = real.keySet(); //解析内容并没有改变rkset //解析不允许的字段<<<<<<<<<<<<<<<<<<< - List refuseList = new ArrayList(); - if ("!".equals(refuse)) {//所有非 must,改成 !must 更好 - for (String key : rkset) {//对@key放行,@role,@column,自定义@position等 - if (key != null && key.startsWith("@") == false - && mustList.contains(key) == false && objKeySet.contains(key) == false) { - refuseList.add(key); + String[] refuses = StringUtil.split(refuse); + Set refuseSet = new HashSet(); + + if (refuses != null && refuses.length > 0) { + Set notRefuseSet = new HashSet(); + + for (String rfs : refuses) { + if (rfs == null) { // StringUtil.isEmpty(rfs, true) { + continue; + } + + if (rfs.startsWith("!")) { + rfs = rfs.substring(1); + + if (notRefuseSet.contains(rfs)) { + throw new ConflictException(REFUSE.name() + ":value 中出现了重复的 !" + rfs + " !不允许重复,也不允许一个 key 和取反 !key 同时使用!"); + } + if (refuseSet.contains(rfs)) { + throw new ConflictException(REFUSE.name() + ":value 中同时出现了 " + rfs + " 和 !" + rfs + " !不允许重复,也不允许一个 key 和取反 !key 同时使用!"); + } + + if (rfs.equals("")) { // 所有非 MUST + for (String key : rkset) { // 对@key放行,@role,@column,自定义@position等, @key:{ "Table":{} } 不会解析内部 + if (key == null || key.startsWith("@") || notRefuseSet.contains(key) || mustSet.contains(key) || objKeySet.contains(key)) { + continue; + } + + refuseSet.add(key); + } + } + else { // 排除 !key 后再禁传其它的 + notRefuseSet.add(rfs); + } + } + else { + if (refuseSet.contains(rfs)) { + throw new ConflictException(REFUSE.name() + ":value 中出现了重复的 " + rfs + " !不允许重复,也不允许一个 key 和取反 !key 同时使用!"); + } + if (notRefuseSet.contains(rfs)) { + throw new ConflictException(REFUSE.name() + ":value 中同时出现了 " + rfs + " 和 !" + rfs + " !不允许重复,也不允许一个 key 和取反 !key 同时使用!"); + } + + refuseSet.add(rfs); } - } - } else { - String[] refuses = StringUtil.split(refuse); - if (refuses != null && refuses.length > 0) { - refuseList.addAll(Arrays.asList(refuses)); } } + //解析不允许的字段>>>>>>>>>>>>>>>>>>> //判断不允许传的key<<<<<<<<<<<<<<<<<<<<<<<<< for (String rk : rkset) { - if (refuseList.contains(rk)) { //不允许的字段 + if (refuseSet.contains(rk)) { //不允许的字段 throw new IllegalArgumentException(method + "请求," + name - + " 里面不允许传 " + rk + " 等" + StringUtil.getString(refuseList) + "内的任何字段!"); + + " 里面不允许传 " + rk + " 等" + StringUtil.getString(refuseSet) + "内的任何字段!"); } if (rk == null) { //无效的key @@ -1391,7 +1428,7 @@ private static void verifyCondition(@NotNull String funChar, @NotNull JSONObject } finally { executor.close(); } - + if (result != null && JSONResponse.isExist(result.getIntValue(JSONResponse.KEY_COUNT)) == false) { throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 '" + tk + "': '" + tv + "' !"); } From f442543f7a1f21eae2444da187dbf72a9909db38 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 4 May 2022 01:08:08 +0800 Subject: [PATCH 249/754] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E9=A2=84=E4=BC=B0?= =?UTF-8?q?=E5=AE=B9=E9=87=8F=E5=88=A4=E6=96=AD=20NOT=20=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E7=94=A8=E9=94=99=E9=80=BB=E8=BE=91=20key?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 1095658bc..cd705a88a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -321,7 +321,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws List orList = combine == null ? null : combine.get("|"); int orCondCount = orList == null ? 0 : orList.size(); - List notList = combine == null ? null : combine.get("|"); + List notList = combine == null ? null : combine.get("!"); int notCondCount = notList == null ? 0 : notList.size(); // 有 GROUP BY 分组,字段越少过滤数据越多 From 95432dde2c7a1f5147f9fe2f77f5500d33c651d0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 4 May 2022 01:52:49 +0800 Subject: [PATCH 250/754] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7=E4=B8=BA=205.1.0=EF=BC=9B=E5=88=A0=E9=99=A4=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E9=9C=80=E8=A6=81=E7=9A=84=E4=BE=9D=E8=B5=96=20javax.?= =?UTF-8?q?activation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 7 +------ APIJSONORM/src/main/java/apijson/Log.java | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 187aa087f..63d7086c7 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 5.0.5 + 5.1.0 jar APIJSONORM @@ -23,11 +23,6 @@ fastjson 1.2.79 - - javax.activation - activation - 1.1.1 - diff --git a/APIJSONORM/src/main/java/apijson/Log.java b/APIJSONORM/src/main/java/apijson/Log.java index 9c2c60a98..f3328c768 100755 --- a/APIJSONORM/src/main/java/apijson/Log.java +++ b/APIJSONORM/src/main/java/apijson/Log.java @@ -14,7 +14,7 @@ public class Log { public static boolean DEBUG = true; - public static final String VERSION = "5.0.0"; + public static final String VERSION = "5.1.0"; public static final String KEY_SYSTEM_INFO_DIVIDER = "---|-----APIJSON SYSTEM INFO-----|---"; //默认的时间格式 From 9f724c171978c89f52bff957d208b23abf9b2f65 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 4 May 2022 06:14:40 +0800 Subject: [PATCH 251/754] =?UTF-8?q?=E8=BF=98=E5=8E=9F=E4=BE=9D=E8=B5=96=20?= =?UTF-8?q?javax.activation=EF=BC=8C=E5=AE=9E=E6=B5=8B=20JDK=2011,=2013=20?= =?UTF-8?q?=E9=83=BD=E9=9C=80=E8=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 否则 APIJSONBoot DemoApplication 第 95 行报错 NoClassDefFoundError static { Map COMPILE_MAP = AbstractVerifier.COMPILE_MAP; ... } Exception in thread "main" java.lang.NoClassDefFoundError: javax/activation/UnsupportedDataTypeException https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONBoot/src/main/java/apijson/boot/DemoApplication.java#L95 --- APIJSONORM/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 63d7086c7..c1bdf23dc 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -23,6 +23,11 @@ fastjson 1.2.79 + + javax.activation + activation + 1.1.1 + From ffdc33229dd044a7a22681e88e37a4942415c3c8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 7 May 2022 22:47:53 +0800 Subject: [PATCH 252/754] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c8574d809..236292a55 100644 --- a/README.md +++ b/README.md @@ -460,7 +460,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [apijson-framework](https://github.com/APIJSON/apijson-framework) APIJSON 服务端框架,通过数据库表配置角色权限、参数校验等,简化使用 -[apijson-router](https://github.com/APIJSON/apijson-router) APIJSON 的路由插件,对外暴露类 RESTful 接口,内部转成 APIJSON 接口执行 +[apijson-router](https://github.com/APIJSON/apijson-router) APIJSON 的路由插件,可控地对公网暴露类 RESTful 简单接口,内部转成 APIJSON 格式请求来执行。 [apijson-column](https://github.com/APIJSON/apijson-column) APIJSON 的字段插件,支持 字段名映射 和 !key 反选字段 From be268dc5a028a9f8fe0148e7b3bd52dcbc8590e6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 8 May 2022 01:22:38 +0800 Subject: [PATCH 253/754] Update README.md --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 236292a55..5b36395a1 100644 --- a/README.md +++ b/README.md @@ -175,6 +175,31 @@ https://github.com/Tencent/APIJSON/wiki * **工程轻量小巧** (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1) * **多年持续迭代** (自 2016 年开源至今已连续维护 5 年多,累计 2000+ Commits、80+ Releases,不断更新迭代中...) +![image](https://user-images.githubusercontent.com/5738175/167264836-9c5d8f8a-99e1-4e1e-9864-e8f906b8e704.png) + +### 用户反馈 +**腾讯 IEG 数据产品开发组负责人 xinlin:** +“腾讯的 APIJSON 开源方案,它可以做到零代码生成接口和文档,并且整个生成过程是自动化。当企业有元数据的时候,马上就可以获得接口” + +**腾讯科技 后台开发高级工程师 雷大锤:** +“可以抽出时间来看apijson了,这个可以为T10做准备,也是业界很火的东西,可以提升个人影响力!” + +**腾讯 bodian520:** +“在调试GET、POST、PUT接口时遇到了一些问题,把个人的摸索经验分享一下,希望作者能梳理下文档,方便我们更好的接入” + +**华为 minshiwu:** +“demo工程,默认使用apijson-framework,可以做到无任何配置即可体验apijson的各种能力。” + +**百度智慧城市研发 lpeng:** +“很兴奋的发现APIJSON很适合我们的一个开发场景,作为我们协议定义的一部分” + +**中兴工程师 duyijiang:** +“感谢腾讯大大提供的框架,很好用” + +https://github.com/Tencent/APIJSON/issues/132#issuecomment-1106669540 + +
+ ### 常见问题 #### 1.如何定制业务逻辑? 在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象、参数名称 等,然后对查到的数据自定义处理
From 19706c6fa5eef31bfd3854c0ea475945638c2954 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 8 May 2022 01:25:55 +0800 Subject: [PATCH 254/754] =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=8E=A8=E8=8D=90?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E8=85=BE=E8=AE=AF=E4=B8=9A=E5=8A=A1?= =?UTF-8?q?=E7=99=BE=E4=B8=87=E6=95=B0=E6=8D=AE=206s=20=E5=93=8D=E5=BA=94?= =?UTF-8?q?=EF=BC=8CAPIJSON=20=E6=80=A7=E8=83=BD=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=83=8C=E5=90=8E=E7=9A=84=E6=95=85=E4=BA=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://my.oschina.net/tommylemon/blog/5375645 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5b36395a1..f37bea636 100644 --- a/README.md +++ b/README.md @@ -435,6 +435,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [APIJSON对接分布式HTAP数据库TiDB](https://asktug.com/t/htap-tidb/395) +[腾讯业务百万数据 6s 响应,APIJSON 性能优化背后的故事](https://my.oschina.net/tommylemon/blog/5375645) + [APIJSON教程(一):上手apijson项目,学习apijson语法,并实现持久层配置](https://zhuanlan.zhihu.com/p/375681893) [apijson简单demo](https://blog.csdn.net/dmw412724/article/details/113558115) From beac8231a65899741f3a973524253ffe75da8509 Mon Sep 17 00:00:00 2001 From: Montos <1367654518@qq.com> Date: Tue, 10 May 2022 00:20:06 +0800 Subject: [PATCH 255/754] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AF=B9LocalDateTim?= =?UTF-8?q?e=E7=B1=BB=E5=9E=8B=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 增加对LocalDateTime类型支持 --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index cd705a88a..a02bd79b0 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -19,6 +19,7 @@ import java.sql.Statement; import java.sql.Time; import java.sql.Timestamp; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -879,6 +880,9 @@ else if (value instanceof Date) { else if (value instanceof Time) { value = ((Time) value).toString(); } + else if (value instanceof LocalDateTime) { + value = ((LocalDateTime) value).toString(); + } else if (value instanceof String && isJSONType(config, rsmd, columnIndex, lable)) { //json String castToJson = true; } From 89accdacdc24fabaa6c9527931fe475cccedb71b Mon Sep 17 00:00:00 2001 From: ysy Date: Sun, 29 May 2022 11:20:48 +0800 Subject: [PATCH 256/754] fastjson up2 2.0.4 --- APIJSONORM/pom.xml | 4 ++-- APIJSONORM/src/main/java/apijson/JSON.java | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index c1bdf23dc..3f776fb6a 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 5.1.0 + 5.1.0-F2 jar APIJSONORM @@ -21,7 +21,7 @@ com.alibaba fastjson - 1.2.79 + 2.0.4 javax.activation diff --git a/APIJSONORM/src/main/java/apijson/JSON.java b/APIJSONORM/src/main/java/apijson/JSON.java index 28c124cab..a5b501ead 100755 --- a/APIJSONORM/src/main/java/apijson/JSON.java +++ b/APIJSONORM/src/main/java/apijson/JSON.java @@ -64,18 +64,20 @@ public static String getCorrectJson(String s, boolean isArray) { * @param json * @return */ + private static final Feature[] DEFAULT_FASTJSON_FEATURES = {Feature.OrderedField, Feature.AllowSingleQuotes, Feature.DisableCircularReferenceDetect, Feature.UseBigDecimal, Feature.UseObjectArray}; public static Object parse(Object obj) { int features = com.alibaba.fastjson.JSON.DEFAULT_PARSER_FEATURE; features |= Feature.OrderedField.getMask(); try { - return com.alibaba.fastjson.JSON.parse(obj instanceof String ? (String) obj : toJSONString(obj), features); + return com.alibaba.fastjson.JSON.parse(obj instanceof String ? (String) obj : toJSONString(obj), DEFAULT_FASTJSON_FEATURES); } catch (Exception e) { Log.i(TAG, "parse catch \n" + e.getMessage()); } return null; } + /**obj转JSONObject - * @param json + * @param obj * @return */ public static JSONObject parseObject(Object obj) { @@ -89,16 +91,17 @@ public static JSONObject parseObject(Object obj) { * @return */ public static JSONObject parseObject(String json) { - int features = com.alibaba.fastjson.JSON.DEFAULT_PARSER_FEATURE; - features |= Feature.OrderedField.getMask(); - return parseObject(json, features); + return parseObject(json, DEFAULT_FASTJSON_FEATURES); } - /**json转JSONObject + + /** + * json转JSONObject + * * @param json * @param features * @return */ - public static JSONObject parseObject(String json, int features) { + public static JSONObject parseObject(String json, Feature... features) { try { return com.alibaba.fastjson.JSON.parseObject(getCorrectJson(json), JSONObject.class, features); } catch (Exception e) { @@ -127,7 +130,7 @@ public static T parseObject(String json, Class clazz) { try { int features = com.alibaba.fastjson.JSON.DEFAULT_PARSER_FEATURE; features |= Feature.OrderedField.getMask(); - return com.alibaba.fastjson.JSON.parseObject(getCorrectJson(json), clazz, features); + return com.alibaba.fastjson.JSON.parseObject(getCorrectJson(json), clazz, DEFAULT_FASTJSON_FEATURES); } catch (Exception e) { Log.i(TAG, "parseObject catch \n" + e.getMessage()); } From 641d9409413b9b8c0cb4bba03bdefce18c25374d Mon Sep 17 00:00:00 2001 From: ysy Date: Sun, 29 May 2022 12:33:57 +0800 Subject: [PATCH 257/754] fix dead loop --- APIJSONORM/src/main/java/apijson/JSON.java | 2 +- .../apijson/orm/AbstractObjectParser.java | 40 +++++++++---------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/JSON.java b/APIJSONORM/src/main/java/apijson/JSON.java index a5b501ead..09dcd058d 100755 --- a/APIJSONORM/src/main/java/apijson/JSON.java +++ b/APIJSONORM/src/main/java/apijson/JSON.java @@ -64,7 +64,7 @@ public static String getCorrectJson(String s, boolean isArray) { * @param json * @return */ - private static final Feature[] DEFAULT_FASTJSON_FEATURES = {Feature.OrderedField, Feature.AllowSingleQuotes, Feature.DisableCircularReferenceDetect, Feature.UseBigDecimal, Feature.UseObjectArray}; + private static final Feature[] DEFAULT_FASTJSON_FEATURES = {Feature.OrderedField, Feature.AllowSingleQuotes, Feature.UseBigDecimal, Feature.UseObjectArray}; public static Object parse(Object obj) { int features = com.alibaba.fastjson.JSON.DEFAULT_PARSER_FEATURE; features |= Feature.OrderedField.getMask(); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index bdbb366e7..a034d0e81 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -5,13 +5,19 @@ package apijson.orm; -import static apijson.JSONObject.KEY_COMBINE; -import static apijson.JSONObject.KEY_DROP; -import static apijson.JSONObject.KEY_TRY; -import static apijson.RequestMethod.POST; -import static apijson.RequestMethod.PUT; -import static apijson.orm.SQLConfig.TYPE_ITEM; +import apijson.JSONResponse; +import apijson.Log; +import apijson.NotNull; +import apijson.RequestMethod; +import apijson.StringUtil; +import apijson.orm.AbstractFunctionParser.FunctionBean; +import apijson.orm.exception.ConflictException; +import apijson.orm.exception.NotExistException; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import javax.activation.UnsupportedDataTypeException; import java.rmi.ServerException; import java.util.ArrayList; import java.util.Arrays; @@ -22,20 +28,12 @@ import java.util.Map.Entry; import java.util.Set; -import javax.activation.UnsupportedDataTypeException; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; - -import apijson.JSONResponse; -import apijson.Log; -import apijson.NotNull; -import apijson.RequestMethod; -import apijson.StringUtil; -import apijson.orm.AbstractFunctionParser.FunctionBean; -import apijson.orm.exception.ConflictException; -import apijson.orm.exception.NotExistException; +import static apijson.JSONObject.KEY_COMBINE; +import static apijson.JSONObject.KEY_DROP; +import static apijson.JSONObject.KEY_TRY; +import static apijson.RequestMethod.POST; +import static apijson.RequestMethod.PUT; +import static apijson.orm.SQLConfig.TYPE_ITEM; /**简化Parser,getObject和getArray(getArrayConfig)都能用 @@ -572,7 +570,7 @@ public JSON onChildParse(int index, String key, JSONObject value) throws Excepti invalidate(); } } - Log.i(TAG, "onChildParse ObjectParser.onParse key = " + key + "; child = " + child); +// Log.i(TAG, "onChildParse ObjectParser.onParse key = " + key + "; child = " + child); return isEmpty ? null : child;//只添加! isChildEmpty的值,可能数据库返回数据不够count } From be00ec44a1e4a56dfa67db68d3deeb06ff895511 Mon Sep 17 00:00:00 2001 From: ysy Date: Sun, 29 May 2022 15:47:55 +0800 Subject: [PATCH 258/754] fix field order --- APIJSONORM/src/main/java/apijson/JSON.java | 39 +++---------------- .../apijson/orm/AbstractObjectParser.java | 2 +- 2 files changed, 6 insertions(+), 35 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/JSON.java b/APIJSONORM/src/main/java/apijson/JSON.java index 09dcd058d..b610cfc5b 100755 --- a/APIJSONORM/src/main/java/apijson/JSON.java +++ b/APIJSONORM/src/main/java/apijson/JSON.java @@ -6,8 +6,8 @@ import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; -import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.serializer.SerializerFeature; +import com.alibaba.fastjson2.JSONReader; import java.util.List; @@ -64,12 +64,10 @@ public static String getCorrectJson(String s, boolean isArray) { * @param json * @return */ - private static final Feature[] DEFAULT_FASTJSON_FEATURES = {Feature.OrderedField, Feature.AllowSingleQuotes, Feature.UseBigDecimal, Feature.UseObjectArray}; + private static final JSONReader.Feature[] DEFAULT_FASTJSON_FEATURES = {JSONReader.Feature.FieldBased, JSONReader.Feature.UseBigDecimalForDoubles}; public static Object parse(Object obj) { - int features = com.alibaba.fastjson.JSON.DEFAULT_PARSER_FEATURE; - features |= Feature.OrderedField.getMask(); try { - return com.alibaba.fastjson.JSON.parse(obj instanceof String ? (String) obj : toJSONString(obj), DEFAULT_FASTJSON_FEATURES); + return com.alibaba.fastjson2.JSON.parse(obj instanceof String ? (String) obj : toJSONString(obj), DEFAULT_FASTJSON_FEATURES); } catch (Exception e) { Log.i(TAG, "parse catch \n" + e.getMessage()); } @@ -91,32 +89,7 @@ public static JSONObject parseObject(Object obj) { * @return */ public static JSONObject parseObject(String json) { - return parseObject(json, DEFAULT_FASTJSON_FEATURES); - } - - /** - * json转JSONObject - * - * @param json - * @param features - * @return - */ - public static JSONObject parseObject(String json, Feature... features) { - try { - return com.alibaba.fastjson.JSON.parseObject(getCorrectJson(json), JSONObject.class, features); - } catch (Exception e) { - Log.i(TAG, "parseObject catch \n" + e.getMessage()); - } - return null; - } - - /**JSONObject转实体类 - * @param object - * @param clazz - * @return - */ - public static T parseObject(JSONObject object, Class clazz) { - return parseObject(toJSONString(object), clazz); + return parseObject(json, JSONObject.class); } /**json转实体类 * @param json @@ -128,9 +101,7 @@ public static T parseObject(String json, Class clazz) { Log.e(TAG, "parseObject clazz == null >> return null;"); } else { try { - int features = com.alibaba.fastjson.JSON.DEFAULT_PARSER_FEATURE; - features |= Feature.OrderedField.getMask(); - return com.alibaba.fastjson.JSON.parseObject(getCorrectJson(json), clazz, DEFAULT_FASTJSON_FEATURES); + return com.alibaba.fastjson2.JSON.parseObject(getCorrectJson(json), clazz, DEFAULT_FASTJSON_FEATURES); } catch (Exception e) { Log.i(TAG, "parseObject catch \n" + e.getMessage()); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index a034d0e81..e571cd8b4 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -425,7 +425,7 @@ else if (value instanceof String) { // //key{}@ getRealKey, 引用赋值路径 String replaceKey = key.substring(0, key.length() - 1); // System.out.println("getObject key.endsWith(@) >> parseRelation = " + parseRelation); - String targetPath = AbstractParser.getValuePath(type == TYPE_ITEM ? path : parentPath, new String((String) value)); + String targetPath = AbstractParser.getValuePath(type == TYPE_ITEM ? path : parentPath, (String) value); //先尝试获取,尽量保留缺省依赖路径,这样就不需要担心路径改变 Object target = onReferenceParse(targetPath); From 3ed76c34a3fb700c45463e47664a7614a200b2f2 Mon Sep 17 00:00:00 2001 From: huangcanjia Date: Thu, 2 Jun 2022 13:07:17 +0800 Subject: [PATCH 259/754] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=A4=9A?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=8F=82=E4=B8=8Ejoin=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E5=91=BD=E4=B8=AD=E7=BC=93=E5=AD=98=E8=80=8C?= =?UTF-8?q?=E5=87=BA=E7=8E=B0=E7=9A=841+N=E6=9F=A5=E8=AF=A2=E6=80=A7?= =?UTF-8?q?=E8=83=BD=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在缓存副表数据到ChildMap时,反向遍历onList集合,避免除了idKey,userKey之外的字段在putWhere时,跟前端传参指定的顺序相反,导致没有命中缓存。 - 将onList反转 issue #402 --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index a02bd79b0..1f1d4bd96 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -519,6 +520,7 @@ else if (config.isClickHouse() && (sqlTable.startsWith("`") || sqlTable.startsWi if (viceConfig != null) { //FIXME 只有和主表关联才能用 item,否则应该从 childMap 查其它副表数据 List onList = curJoin.getOnList(); if (onList != null) { + Collections.reverse(onList); for (On on : onList) { if (on != null) { String ok = on.getOriginKey(); From 6ab88d13d17265bf637c81eda5f1b95239f68dcb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 8 Jun 2022 21:16:21 +0800 Subject: [PATCH 260/754] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f37bea636..5b7be0593 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ Tencent is pleased to support the open source community by making APIJSON availa Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
This source code is licensed under the Apache License Version 2.0
+[APIJSON 已加入 腾源会开源摘星计划(WeOpen Star),该计划提供奖励以鼓励你加入我们的社区](https://github.com/weopenprojects/WeOpen-Star/issues/79)

APIJSON From 51f94299189a58e1971913274ffb7769cca216b7 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 8 Jun 2022 21:24:40 +0800 Subject: [PATCH 261/754] Update CONTRIBUTING.md --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0cb35f97d..e424b7050 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,9 +50,9 @@ ruoranw 提交的 18 个 Commits, 对 APIJSON 做出了 328 增加和 520 处删 Zerounary 提交的 6 个 Commits, 对 APIJSON 做出了 1,104 增加和 1 处删减(截止 2020/11/04 日)。

-APIJSON 持续招募贡献者,即使是在 Issue 中回答问题,或者做一些简单的 Bug Fix ,也会给 APIJSON 带来很大的帮助。
-APIJSON 已开发近 4 年,在此感谢所有开发者对于 APIJSON 的喜欢和支持,希望你能够成为 APIJSON 的核心贡献者,
-加入 APIJSON ,共同打造一个更棒的零代码、全自动、强安全 ORM 库!🍾🎉 +APIJSON 持续招募贡献者,新增功能、修复 Bug、完善文档、修正错误、宣传推广、回答问题等,都能帮助项目及广大用户。
+APIJSON 已开发近 6 年,在此感谢所有开发者对于 APIJSON 的喜欢和支持,希望你能够成为 APIJSON 的核心贡献者,
+加入 APIJSON ,共同打造一个更棒的零代码、全功能、强安全 ORM 库,造福更多前后端开发者!🍾🎉 ### 为什么一定要贡献代码? APIJSON 作为腾讯开源的知名热门项目,贡献代码除了可以给简历加亮点、为面试加分,还可以避免你碰到以下麻烦:
From f8a3c6706b9dec303e8802ccb8caaf740a3ebc5d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 8 Jun 2022 21:25:42 +0800 Subject: [PATCH 262/754] Update CONTRIBUTING.md --- CONTRIBUTING.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e424b7050..939328aa7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,21 +61,6 @@ APIJSON 作为腾讯开源的知名热门项目,贡献代码除了可以给简 3.你需要自己维护你的代码,每次升级 APIJSON 版本时,你都需要下载 APIJSON 新代码再合并你自己的更改
#### 所以为了让你自己的更改始终能跟上项目版本,得到他人给予的可靠且持续的维护,强烈建议 [提交 Pull Request](/CONTRIBUTING.md#pull-request) 来贡献代码。 -​ - -## Issue 提交 - -#### 对于贡献者 - -在提 Issue 前请确保满足一下条件: - -- 必须是一个 Bug 或者功能新增。 -- 必须是 APIJSON 相关问题。 -- 已经在 Issue 中搜索过,并且没有找到相似的 Issue 或者解决方案。 -- 完善下面模板中的信息 - -如果已经满足以上条件,我们提供了 Issue 的标准模版,请按照模板填写。 - ​ ## Pull Request @@ -145,3 +130,19 @@ https://www.jianshu.com/p/00cf29d2d66c

如何在 Github 上给别人的项目贡献代码
https://git-scm.com/book/zh/v2/GitHub-%E5%AF%B9%E9%A1%B9%E7%9B%AE%E5%81%9A%E5%87%BA%E8%B4%A1%E7%8C%AE + + +​ + +## Issue 提交 + +#### 对于贡献者 + +在提 Issue 前请确保满足一下条件: + +- 必须是一个 Bug 或者功能新增。 +- 必须是 APIJSON 相关问题。 +- 已经在 Issue 中搜索过,并且没有找到相似的 Issue 或者解决方案。 +- 完善下面模板中的信息 + +如果已经满足以上条件,我们提供了 Issue 的标准模版,请按照模板填写。 From 9fb6c885f0f5d493bf160e8f60762266823e838c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 17:24:11 +0800 Subject: [PATCH 263/754] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5b7be0593..a9f52add0 100644 --- a/README.md +++ b/README.md @@ -219,8 +219,8 @@ https://github.com/Tencent/APIJSON/issues/36
### 注意事项 -请求参数 JSON 中表名、字段名、关键词及对应的值都是大小写敏感、逗号敏感、分号敏感、空格敏感、换行敏感,
-大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 [设计规范](https://github.com/Tencent/APIJSON/blob/master/Document.md#3) 来调用 API +**请求参数 JSON 中表名、字段名、关键词及对应的值都是大小写敏感、逗号敏感、分号敏感、空格敏感、换行敏感,
+大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 [设计规范](https://github.com/Tencent/APIJSON/blob/master/Document.md#3) 来调用 API !** [#181](https://github.com/Tencent/APIJSON/issues/181)

From ed935c1c4ad32157cf35ab91a0752985c14bfc73 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 17:27:56 +0800 Subject: [PATCH 264/754] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a9f52add0..17777f3a1 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这
#### APIAuto 展示 APIJSON -使用 APIAuto-机器学习接口工具 来管理和测试 HTTP API 可大幅提升接口联调效率
+**使用 APIAuto-机器学习接口工具 来管理和测试 HTTP API 可大幅 减少传参错误、提升联调效率**
(注意网页工具界面是 APIAuto,里面的 URL+JSON 才是 APIJSON 的 HTTP API):

From 0600a8e93436b0ac60ca8f18c9c02176287b6434 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 18:50:31 +0800 Subject: [PATCH 265/754] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index 3683b0f3e..35df8dccd 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -4,6 +4,12 @@ about: Create a report to help us improve --- +如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献谢谢,开源要大家参与贡献才会更美好。
+开发者也是人,也需要工作和休息,养活自己和家人,也会有心情不好和身体病痛,往往没有额外的时间精力顾及一些小问题,请理解和支持~ +https://github.com/Tencent/APIJSON/issues/406 + +_________________________________ + **提 bug 请发请求和响应的【完整截屏】,没图的自行解决! 开发者有限的时间和精力主要放在【维护项目源码和文档】上! 【描述不详细】 或 【文档/常见问题 已有答案】 的问题可能会被忽略!! From e2826242b89291ecd0505efe7aa61eb8f1eef1d5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 18:57:52 +0800 Subject: [PATCH 266/754] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index 35df8dccd..193baa055 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -4,8 +4,9 @@ about: Create a report to help us improve --- -如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献谢谢,开源要大家参与贡献才会更美好。
-开发者也是人,也需要工作和休息,养活自己和家人,也会有心情不好和身体病痛,往往没有额外的时间精力顾及一些小问题,请理解和支持~ +如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。 +开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛, +往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~ https://github.com/Tencent/APIJSON/issues/406 _________________________________ From bd3dc264f234f4834c3296c3f3bfd327d5e2165e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 18:58:19 +0800 Subject: [PATCH 267/754] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index 193baa055..c752ac3d0 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -4,9 +4,9 @@ about: Create a report to help us improve --- -如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。 -开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛, -往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~ +如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。
+开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛,
+往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~
https://github.com/Tencent/APIJSON/issues/406 _________________________________ From d95a1b90c8ead2e6a5d2b0f219b5795b706532e2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 18:58:59 +0800 Subject: [PATCH 268/754] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index c752ac3d0..1bb8e0264 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -4,9 +4,9 @@ about: Create a report to help us improve --- -如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。
-开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛,
-往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~
+如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。 +开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛, +往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~ https://github.com/Tencent/APIJSON/issues/406 _________________________________ From 88d895d0f06911693e1b6289a00a8a1ca51f3341 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 19:00:54 +0800 Subject: [PATCH 269/754] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index 1bb8e0264..545c06b9c 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -4,9 +4,9 @@ about: Create a report to help us improve --- -如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。 -开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛, -往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~ +如果你已经知道问题所在、怎么解决,请直接 提交 Pull Request 为社区做贡献,非常感谢。 +开发者也是人,也需要工作、休息、恋爱、陪伴家人、走亲访友等,也有心情不好和身体病痛, +往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~ https://github.com/Tencent/APIJSON/issues/406 _________________________________ From f4d63d1799f160c32390b462ea4e68ee2c7ac422 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 19:08:23 +0800 Subject: [PATCH 270/754] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index 545c06b9c..ec35b18dc 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -7,6 +7,7 @@ about: Create a report to help us improve 如果你已经知道问题所在、怎么解决,请直接 提交 Pull Request 为社区做贡献,非常感谢。 开发者也是人,也需要工作、休息、恋爱、陪伴家人、走亲访友等,也有心情不好和身体病痛, 往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~ +少数个人的热情终有被耗尽的一天,只有大家共同建设和繁荣社区,才能让开源可持续发展! https://github.com/Tencent/APIJSON/issues/406 _________________________________ From 76c91a9557c34a9b187fee1befedc5a8c36b0c6a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 19:09:12 +0800 Subject: [PATCH 271/754] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index ec35b18dc..072aba60f 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -5,7 +5,7 @@ about: Create a report to help us improve --- 如果你已经知道问题所在、怎么解决,请直接 提交 Pull Request 为社区做贡献,非常感谢。 -开发者也是人,也需要工作、休息、恋爱、陪伴家人、走亲访友等,也有心情不好和身体病痛, +开发者也是人,也需要工作、休息、恋爱、陪伴家人、走亲会友等,也有心情不好和身体病痛, 往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~ 少数个人的热情终有被耗尽的一天,只有大家共同建设和繁荣社区,才能让开源可持续发展! https://github.com/Tencent/APIJSON/issues/406 From 0a764540fdaf5cd80ec79728604080087d29bb78 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 23 Jun 2022 23:45:26 +0800 Subject: [PATCH 272/754] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index fefcd5eae..afc131f5a 100644 --- a/Document.md +++ b/Document.md @@ -409,7 +409,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 存储过程 | "@key()":"SQL函数表达式",函数表达式为
function(key0,key1...)
会调用后端数据库对应的存储过程 SQL函数
function(String key0, String key1...)
除了参数会提前赋值,其它和 远程函数 一致 | ["@limit":10,
"@offset":0,
"@procedure()":"getCommentByUserId(id,@limit,@offset)"](http://apijson.cn:8080/get/{"User":{"@limit":10,"@offset":0,"@procedure()":"getCommentByUserId(id,@limit,@offset)"}})
会转为
`getCommentByUserId(38710,10,0)`
来调用存储过程 SQL 函数
`getCommentByUserId(IN id bigint, IN limit int, IN offset int)`
然后变为
"procedure":{
   "count":-1,
   "update":false,
   "list":[]
}
其中 count 是指写操作影响记录行数,-1 表示不是写操作;update 是指是否为写操作(增删改);list 为返回结果集 引用赋值 | "key@":"key0/key1/.../refKey",引用路径为用/分隔的字符串。以/开头的是缺省引用路径,从声明key所处容器的父容器路径开始;其它是完整引用路径,从最外层开始。
被引用的refKey必须在声明key的上面。如果对refKey的容器指定了返回字段,则被引用的refKey必须写在@column对应的值内,例如 "@column":"refKey,key1,..." | ["Moment":{
   "userId":38710
},
"User":{
   "id@":"/Moment/userId"
}](http://apijson.cn:8080/get/{"Moment":{"userId":38710},"User":{"id@":"%252FMoment%252FuserId"}})
User内的id引用了与User同级的Moment内的userId,
即User.id = Moment.userId,请求完成后
"id@":"/Moment/userId" 会变成 "id":38710 子查询 | "key@":{
   "range":"ALL",
   "from":"Table",
   "Table":{ ... }
}
其中:
range 可为 ALL,ANY;
from 为目标表 Table 的名称;
@ 后面的对象类似数组对象,可使用 count 和 join 等功能。 | ["id@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id=(SELECT min(userId) FROM Comment) - 模糊搜索 | "key$":"SQL搜索表达式" => "key$":["SQL搜索表达式"],任意SQL搜索表达式字符串,如 %key%(包含key), key%(以key开始), %k%e%y%(包含字母k,e,y) 等,%表示任意字符 | ["name$":"%m%"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"name$":"%2525m%2525"}}}),对应SQL是`name LIKE '%m%'`,查询name包含"m"的一个User数组 + 模糊搜索 | `"key$":"SQL搜索表达式"` => `"key$":["SQL搜索表达式"]`,任意SQL搜索表达式字符串,如 %key%(包含key), key%(以key开始), %k%e%y%(包含字母k,e,y) 等,%表示任意字符 | ["name$":"%m%"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"name$":"%2525m%2525"}}}),对应SQL是`name LIKE '%m%'`,查询name包含"m"的一个User数组 正则匹配 | "key~":"正则表达式" => "key~":["正则表达式"],任意正则表达式字符串,如 ^[0-9]+$ ,*~ 忽略大小写,可用于高级搜索 | ["name~":"^[0-9]+$"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"name~":"^[0-9]%252B$"}}}),对应SQL是`name REGEXP '^[0-9]+$'`,查询name中字符全为数字的一个User数组 连续范围 | "key%":"start,end" => "key%":["start,end"],其中 start 和 end 都只能为 Boolean, Number, String 中的一种,如 "2017-01-01,2019-01-01" ,["1,90000", "82001,100000"] ,可用于连续范围内的筛选 | ["date%":"2017-10-01,2018-10-01"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"date%2525":"2017-10-01,2018-10-01"}}}),对应SQL是`date BETWEEN '2017-10-01' AND '2018-10-01'`,查询在2017-10-01和2018-10-01期间注册的用户的一个User数组 新建别名 | "name:alias",name映射为alias,用alias替代name。可用于 column,Table,SQL函数 等。只用于GET类型、HEAD类型的请求 | ["@column":"toId:parentId"](http://apijson.cn:8080/get/{"Comment":{"@column":"id,toId:parentId","id":51}}),对应SQL是`toId AS parentId`,将查询的字段toId变为parentId返回 From 4dfd9d4d4fa1b5a524634580cc8beed60027bdf9 Mon Sep 17 00:00:00 2001 From: huangcanjia Date: Wed, 6 Jul 2022 18:51:20 +0800 Subject: [PATCH 273/754] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=B7=A8?= =?UTF-8?q?=E5=B1=82=E7=BA=A7app=20join?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 优化app join模式下,一对多join表查询时的1+N性能问题 - 支持客户端join字段,path的多层路径指定 - Join类增加count字段,以支持在生成副表sql时,按照指定count数量生成 - 处理App Join的查询结果时,将'一条条缓存'调整为'攒一起再缓存',防止错误替换 --- .../main/java/apijson/orm/AbstractParser.java | 18 +++++++++---- .../java/apijson/orm/AbstractSQLConfig.java | 2 +- .../java/apijson/orm/AbstractSQLExecutor.java | 26 +++++++++++-------- .../src/main/java/apijson/orm/Join.java | 8 ++++++ 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 5135a7279..c472ceb80 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1457,10 +1457,11 @@ else if (join != null){ // } path = path.substring(index + 1); - index = path.indexOf("/"); + index = path.lastIndexOf("/"); String tableKey = index < 0 ? path : path.substring(0, index); // User:owner apijson.orm.Entry entry = Pair.parseEntry(tableKey, true); - String table = entry.getKey(); // User + String[] tablePath = entry.getKey().split("/"); // User + String table = tableKey = tablePath[tablePath.length - 1]; // path最后一级为真实table;如:@/A/b/id@,b为目录最后一级 if (StringUtil.isName(table) == false) { throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 Table 值 " + table + " 不合法!" + "必须为 &/Table0, onList = new ArrayList<>(); for (Entry refEntry : refSet) { @@ -1656,7 +1664,7 @@ else if (join != null){ if (refObj.size() != tableObj.size()) { // 把 key 强制放最前,AbstractSQLExcecutor 中 config.putWhere 也是放尽可能最前 refObj.putAll(tableObj); - request.put(tableKey, refObj); + parentPathObj.put(tableKey, refObj); // tableObj.clear(); // tableObj.putAll(refObj); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index c9c9c8623..cfe4df81d 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -5008,7 +5008,7 @@ public static SQLConfig parseJoin(RequestMethod method, SQLCo alias = j.getAlias(); //JOIN子查询不能设置LIMIT,因为ON关系是在子查询后处理的,会导致结果会错误 SQLConfig joinConfig = newSQLConfig(method, table, alias, j.getRequest(), null, false, callback); - SQLConfig cacheConfig = j.canCacheViceTable() == false ? null : newSQLConfig(method, table, alias, j.getRequest(), null, false, callback).setCount(1); + SQLConfig cacheConfig = j.canCacheViceTable() == false ? null : newSQLConfig(method, table, alias, j.getRequest(), null, false, callback).setCount(j.getCount()); if (j.isAppJoin() == false) { //除了 @ APP JOIN,其它都是 SQL JOIN,则副表要这样配置 if (joinConfig.getDatabase() == null) { diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 1f1d4bd96..fb5713be3 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -260,7 +260,9 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws Log.i(TAG, ">>> execute result = getCache('" + sql + "', " + position + ") = " + result); if (result != null) { cachedSQLCount ++; - + if (getCache(sql,config).size() > 1) { + result.put(KEY_RAW_LIST, getCache(sql,config)); + } Log.d(TAG, "\n\n execute result != null >> return result;" + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n"); return result; } @@ -589,19 +591,17 @@ else if (curJoin.isOuterJoin() || curJoin.isAntiJoin()) { if (isHead == false) { // @ APP JOIN 查询副表并缓存到 childMap <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - executeAppJoin(config, resultList, childMap); + Map> appJoinChildMap = new HashMap<>(); + executeAppJoin(config, resultList, appJoinChildMap); // @ APP JOIN 查询副表并缓存到 childMap >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //子查询 SELECT Moment.*, Comment.id 中的 Comment 内字段 - Set> set = childMap.entrySet(); + Set>> set = appJoinChildMap.entrySet(); // - for (Entry entry : set) { - List l = new ArrayList<>(); - l.add(entry.getValue()); - putCache(entry.getKey(), l, null); + for (Entry> entry : set) { + putCache(entry.getKey(), entry.getValue(), null); } putCache(sql, resultList, config); @@ -633,7 +633,7 @@ else if (curJoin.isOuterJoin() || curJoin.isAntiJoin()) { * @param childMap * @throws Exception */ - protected void executeAppJoin(SQLConfig config, List resultList, Map childMap) throws Exception { + protected void executeAppJoin(SQLConfig config, List resultList, Map> childMap) throws Exception { List joinList = config.getJoinList(); if (joinList != null) { @@ -737,8 +737,12 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map } } cacheSql = cc.getSQL(false); - childMap.put(cacheSql, result); - + List results = childMap.get(cacheSql); + if (results == null) { + results = new ArrayList<>(); + childMap.put(cacheSql,results); + } + results.add(result); Log.d(TAG, ">>> executeAppJoin childMap.put('" + cacheSql + "', result); childMap.size() = " + childMap.size()); } } diff --git a/APIJSONORM/src/main/java/apijson/orm/Join.java b/APIJSONORM/src/main/java/apijson/orm/Join.java index 4fb34b0a9..af3c2979e 100644 --- a/APIJSONORM/src/main/java/apijson/orm/Join.java +++ b/APIJSONORM/src/main/java/apijson/orm/Join.java @@ -22,6 +22,7 @@ public class Join { private String joinType; // "@" - APP, "<" - LEFT, ">" - RIGHT, "*" - CROSS, "&" - INNER, "|" - FULL, "!" - OUTER, "^" - SIDE, "(" - ANTI, ")" - FOREIGN private String table; // User private String alias; // owner + private int count = 1; // 当app join子表,需要返回子表的行数,默认1行; private List onList; // ON User.id = Moment.userId AND ... private JSONObject request; // { "id@":"/Moment/userId" } @@ -39,6 +40,13 @@ public void setPath(String path) { this.path = path; } + public int getCount() { + return count; + } + public void setCount(int count) { + this.count = count; + } + public String getJoinType() { return joinType; } From 5525eab38d5c3886a93d082102835b08ad338979 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 7 Jul 2022 05:52:44 +0800 Subject: [PATCH 274/754] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=AF=B9=20APP=20JOI?= =?UTF-8?q?N=20=E5=90=8C=E5=B1=82=E5=92=8C=E8=B7=A8=E5=B1=82=E7=9A=84?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=EF=BC=9B=E5=AE=8C=E5=96=84=E5=AF=B9=20APP=20?= =?UTF-8?q?JOIN=20=E7=9A=84=20SQL=20=E6=89=A7=E8=A1=8C=E4=B8=8E=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E6=AC=A1=E6=95=B0=E7=BB=9F=E8=AE=A1=EF=BC=9B=E8=A7=A3?= =?UTF-8?q?=E5=86=B3=E5=90=8C=E5=B1=82=20JOIN=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=9A=84=E6=8A=A5=E9=94=99=20bug=EF=BC=9B=E8=A7=A3=E5=86=B3=20?= =?UTF-8?q?APP=20JOIN=20=E5=89=AF=E8=A1=A8=E8=BF=94=E5=9B=9E=E5=86=85?= =?UTF-8?q?=E9=83=A8=E5=AD=97=E6=AE=B5=20@RAW@LIST=EF=BC=9Bfastjson2=20?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=201.2.79?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 2 +- APIJSONORM/src/main/java/apijson/JSON.java | 9 +- .../apijson/orm/AbstractObjectParser.java | 20 +-- .../main/java/apijson/orm/AbstractParser.java | 160 +++++++++++------- .../java/apijson/orm/AbstractSQLExecutor.java | 159 +++++++++-------- 5 files changed, 202 insertions(+), 148 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 3f776fb6a..fcc4ffa63 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -21,7 +21,7 @@ com.alibaba fastjson - 2.0.4 + 1.2.79 javax.activation diff --git a/APIJSONORM/src/main/java/apijson/JSON.java b/APIJSONORM/src/main/java/apijson/JSON.java index b610cfc5b..40ef46f9c 100755 --- a/APIJSONORM/src/main/java/apijson/JSON.java +++ b/APIJSONORM/src/main/java/apijson/JSON.java @@ -6,8 +6,9 @@ import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.serializer.SerializerFeature; -import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson.JSONReader; import java.util.List; @@ -64,10 +65,10 @@ public static String getCorrectJson(String s, boolean isArray) { * @param json * @return */ - private static final JSONReader.Feature[] DEFAULT_FASTJSON_FEATURES = {JSONReader.Feature.FieldBased, JSONReader.Feature.UseBigDecimalForDoubles}; + private static final Feature[] DEFAULT_FASTJSON_FEATURES = {Feature.OrderedField, Feature.UseBigDecimal}; public static Object parse(Object obj) { try { - return com.alibaba.fastjson2.JSON.parse(obj instanceof String ? (String) obj : toJSONString(obj), DEFAULT_FASTJSON_FEATURES); + return com.alibaba.fastjson.JSON.parse(obj instanceof String ? (String) obj : toJSONString(obj), DEFAULT_FASTJSON_FEATURES); } catch (Exception e) { Log.i(TAG, "parse catch \n" + e.getMessage()); } @@ -101,7 +102,7 @@ public static T parseObject(String json, Class clazz) { Log.e(TAG, "parseObject clazz == null >> return null;"); } else { try { - return com.alibaba.fastjson2.JSON.parseObject(getCorrectJson(json), clazz, DEFAULT_FASTJSON_FEATURES); + return com.alibaba.fastjson.JSON.parseObject(getCorrectJson(json), clazz, DEFAULT_FASTJSON_FEATURES); } catch (Exception e) { Log.i(TAG, "parseObject catch \n" + e.getMessage()); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index e571cd8b4..68b31a87e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -71,7 +71,7 @@ public AbstractObjectParser setParser(AbstractParser parser) { * @param parentPath * @param request * @param name - * @throws Exception + * @throws Exception */ public AbstractObjectParser(@NotNull JSONObject request, String parentPath, SQLConfig arrayConfig , boolean isSubquery, boolean isTable, boolean isArrayMainTable) throws Exception { @@ -400,7 +400,7 @@ public boolean onParse(@NotNull String key, @NotNull Object value) throws Except if (arrObj == null) { throw new IllegalArgumentException("子查询 " + path + "/" + key + ":{ from:value } 中 value 对应的主表对象 " + from + ":{} 不存在!"); } - // + // SQLConfig cfg = (SQLConfig) arrObj.get(AbstractParser.KEY_CONFIG); if (cfg == null) { throw new NotExistException(TAG + ".onParse cfg == null"); @@ -453,7 +453,7 @@ else if (value instanceof String) { // //key{}@ getRealKey, 引用赋值路径 Log.d(TAG, "onParse isTable(table) == false >> return true;"); return true;//舍去,对Table无影响 } - } + } //直接替换原来的key@:path为key:target Log.i(TAG, "onParse >> key = replaceKey; value = target;"); @@ -517,7 +517,7 @@ else if (isTable && key.startsWith("@") && JSONRequest.TABLE_KEY_LIST.contains(k /** * @param key * @param value - * @param isFirst + * @param isFirst * @return * @throws Exception */ @@ -553,7 +553,7 @@ public JSON onChildParse(int index, String key, JSONObject value) throws Excepti + "数组 []:{} 中每个 key:{} 都必须是表 TableKey:{} 或 数组 arrayKey[]:{} !"); } - if ( //避免使用 "test":{"Test":{}} 绕过限制,实现查询爆炸 isTableKey && + if ( //避免使用 "test":{"Test":{}} 绕过限制,实现查询爆炸 isTableKey && (arrayConfig == null || arrayConfig.getPosition() == 0)) { objectCount ++; int maxObjectCount = parser.getMaxObjectCount(); @@ -577,7 +577,7 @@ public JSON onChildParse(int index, String key, JSONObject value) throws Excepti - //TODO 改用 MySQL json_add,json_remove,json_contains 等函数! + //TODO 改用 MySQL json_add,json_remove,json_contains 等函数! /**PUT key:[] * @param key * @param array @@ -757,7 +757,7 @@ public AbstractObjectParser executeSQL() throws Exception { //执行SQL操作数据库 if (isTable == false) {//提高性能 sqlReponse = new JSONObject(sqlRequest); - } + } else { try { sqlReponse = onSQLExecute(); @@ -896,7 +896,8 @@ public JSONObject onSQLExecute() throws Exception { result = parser.executeSQL(sqlConfig, isSubquery); boolean isSimpleArray = false; - List rawList = null; + // 提取并缓存数组主表的列表数据 + List rawList = (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST); if (isArrayMainTable && position == 0 && result != null) { @@ -905,8 +906,7 @@ public JSONObject onSQLExecute() throws Exception { && (childMap == null || childMap.isEmpty()) && (table.equals(arrayTable)); - // 提取并缓存数组主表的列表数据 - rawList = (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST); + // APP JOIN 副表时副表返回了这个字段 rawList = (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST); if (rawList != null) { String arrayPath = parentPath.substring(0, parentPath.lastIndexOf("[]") + 2); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index c472ceb80..3f598df1d 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -6,6 +6,7 @@ package apijson.orm; import static apijson.JSONObject.KEY_EXPLAIN; +import static apijson.JSONObject.KEY_JSON; import static apijson.RequestMethod.GET; import java.io.UnsupportedEncodingException; @@ -80,7 +81,7 @@ public abstract class AbstractParser implements Parser, Par public static int MAX_OBJECT_COUNT = 5; public static int MAX_ARRAY_COUNT = 5; public static int MAX_QUERY_DEPTH = 5; - + @Override public int getDefaultQueryCount() { return DEFAULT_QUERY_COUNT; @@ -122,13 +123,13 @@ public AbstractParser() { this(null); } /**needVerify = true - * @param requestMethod null ? requestMethod = GET + * @param method null ? requestMethod = GET */ public AbstractParser(RequestMethod method) { this(method, true); } /** - * @param requestMethod null ? requestMethod = GET + * @param method null ? requestMethod = GET * @param needVerify 仅限于为服务端提供方法免验证特权,普通请求不要设置为 false ! 如果对应Table有权限也建议用默认值 true,保持和客户端权限一致 */ public AbstractParser(RequestMethod method, boolean needVerify) { @@ -136,7 +137,7 @@ public AbstractParser(RequestMethod method, boolean needVerify) { setMethod(method); setNeedVerify(needVerify); } - + protected boolean isRoot = true; public boolean isRoot() { return isRoot; @@ -145,7 +146,7 @@ public AbstractParser setRoot(boolean isRoot) { this.isRoot = isRoot; return this; } - + @NotNull protected Visitor visitor; @@ -464,7 +465,7 @@ public JSONObject parseResponse(JSONObject request) { try { queryDepth = 0; executedSQLDuration = 0; - + requestObject = onObjectParse(request, null, null, null, false); onCommit(); @@ -486,7 +487,7 @@ public JSONObject parseResponse(JSONObject request) { if (Log.DEBUG) { res.put("sql:generate|cache|execute|maxExecute", getSQLExecutor().getGeneratedSQLCount() + "|" + getSQLExecutor().getCachedSQLCount() + "|" + getSQLExecutor().getExecutedSQLCount() + "|" + getMaxSQLCount()); res.put("depth:count|max", queryDepth + "|" + getMaxQueryDepth()); - + executedSQLDuration += sqlExecutor.getExecutedSQLDuration() + sqlExecutor.getSqlResultDuration(); long parseDuration = duration - executedSQLDuration; res.put("time:start|duration|end|parse|sql", startTime + "|" + duration + "|" + endTime + "|" + parseDuration + "|" + executedSQLDuration); @@ -551,7 +552,7 @@ public void onVerifyRole(@NotNull SQLConfig config) throws Exception { /**解析请求JSONObject * @param request => URLDecoder.decode(request, UTF_8); * @return - * @throws Exception + * @throws Exception */ @NotNull public static JSONObject parseRequest(String request) throws Exception { @@ -624,7 +625,7 @@ public static JSONObject wrapRequest(RequestMethod method, String tag, JSONObjec String arrKey = key + "[]"; if (target.containsKey(arrKey) == false) { - target.put(arrKey, new JSONArray()); + target.put(arrKey, new JSONArray()); } try { @@ -680,7 +681,7 @@ public static JSONObject newResult(int code, String msg) { public static JSONObject newResult(int code, String msg, boolean isRoot) { return extendResult(null, code, msg, isRoot); } - + /**添加JSONObject的状态内容,一般用于错误提示结果 * @param object * @param code @@ -702,13 +703,13 @@ public static JSONObject extendResult(JSONObject object, int code, String msg, b + " \n | \n 常见问题:https://github.com/Tencent/APIJSON/issues/36" + " \n 通用文档:https://github.com/Tencent/APIJSON/blob/master/Document.md" + " \n 视频教程:https://search.bilibili.com/all?keyword=APIJSON"); - + msg = index >= 0 ? msg.substring(0, index) : msg; - + if (object == null) { object = new JSONObject(true); } - + if (object.containsKey(JSONResponse.KEY_OK) == false) { object.put(JSONResponse.KEY_OK, JSONResponse.isSuccess(code)); } @@ -720,12 +721,12 @@ public static JSONObject extendResult(JSONObject object, int code, String msg, b if (m.isEmpty() == false) { msg = m + " ;\n " + StringUtil.getString(msg); } - + object.put(JSONResponse.KEY_MSG, msg); if (debug != null) { object.put("debug:info|help", debug); } - + return object; } @@ -758,7 +759,7 @@ public static JSONObject newSuccessResult() { public static JSONObject newSuccessResult(boolean isRoot) { return newResult(JSONResponse.CODE_SUCCESS, JSONResponse.MSG_SUCCEED, isRoot); } - + /**添加请求成功的状态内容 * @param object * @param e @@ -872,7 +873,7 @@ public static JSONObject newErrorResult(Exception e, boolean isRoot) { int code; if (e instanceof UnsupportedEncodingException) { code = JSONResponse.CODE_UNSUPPORTED_ENCODING; - } + } else if (e instanceof IllegalAccessException) { code = JSONResponse.CODE_ILLEGAL_ACCESS; } @@ -890,7 +891,7 @@ else if (e instanceof NotLoggedInException) { } else if (e instanceof TimeoutException) { code = JSONResponse.CODE_TIME_OUT; - } + } else if (e instanceof ConflictException) { code = JSONResponse.CODE_CONFLICT; } @@ -921,10 +922,8 @@ else if (e instanceof NullPointerException) { //TODO 启动时一次性加载Request所有内容,作为初始化。 /**获取正确的请求,非GET请求必须是服务器指定的 - * @param method - * @param request * @return - * @throws Exception + * @throws Exception */ @Override public JSONObject parseCorrectRequest() throws Exception { @@ -1027,13 +1026,14 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, // protected SQLConfig itemConfig; /**获取单个对象,该对象处于parentObject内 - * @param parentPath parentObject的路径 - * @param name parentObject的key - * @param request parentObject的value - * @param config for array item - * @return - * @throws Exception - */ + * @param request parentObject 的 value + * @param parentPath parentObject 的路径 + * @param name parentObject 的 key + * @param arrayConfig config for array item + * @param isSubquery 是否为子查询 + * @return + * @throws Exception + */ @Override public JSONObject onObjectParse(final JSONObject request , String parentPath, String name, final SQLConfig arrayConfig, boolean isSubquery) throws Exception { @@ -1093,7 +1093,7 @@ public JSONObject onObjectParse(final JSONObject request if (type == SQLConfig.TYPE_ITEM_CHILD_0 && query != JSONRequest.QUERY_TABLE && position == 0) { //TODO 应在这里判断 @column 中是否有聚合函数,而不是 AbstractSQLConfig.getColumnString - + JSONObject rp; Boolean compat = arrayConfig.getCompat(); if (compat != null && compat) { @@ -1192,7 +1192,7 @@ public JSONObject onObjectParse(final JSONObject request * @param parentPath parentObject的路径 * @param name parentObject的key * @param request parentObject的value - * @return + * @return * @throws Exception */ @Override @@ -1283,7 +1283,7 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name String[] childKeys = StringUtil.split(childPath, "-", false); if (childKeys == null || childKeys.length <= 0 || request.containsKey(childKeys[0]) == false) { childKeys = null; - } + } else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // 可能无需提取,直接返回 rawList 即可 arrTableKey = childKeys[0]; } @@ -1402,12 +1402,12 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_ORDER); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_RAW); } - + /**JOIN 多表同时筛选 * @param join "&/User,0"} * @param request - * @return - * @throws Exception + * @return + * @throws Exception */ private List onJoinParse(Object join, JSONObject request) throws Exception { JSONObject joinMap = null; @@ -1440,7 +1440,7 @@ else if (join != null){ // 分割 /Table/key String path = e == null ? null : e.getKey(); Object outer = path == null ? null : e.getValue(); - + if (outer instanceof JSONObject == false) { throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中value不合法!" + "必须为 &/Table0/key0, entry = Pair.parseEntry(tableKey, true); - String[] tablePath = entry.getKey().split("/"); // User - String table = tableKey = tablePath[tablePath.length - 1]; // path最后一级为真实table;如:@/A/b/id@,b为目录最后一级 + int index2 = tableKey.lastIndexOf("/"); + String arrKey = index2 < 0 ? null : tableKey.substring(0, index2); + if (arrKey != null && JSONRequest.isArrayKey(arrKey) == false) { + throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 对应的 " + arrKey + " 不是合法的数组 key[] !" + + "@ APP JOIN 最多允许跨 1 层,只能是子数组,且数组对象中不能有 join: value 键值对!"); + } + + tableKey = index2 < 0 ? tableKey : tableKey.substring(index2+1); + + apijson.orm.Entry entry = Pair.parseEntry(tableKey, true); + String table = entry.getKey(); // User if (StringUtil.isName(table) == false) { throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 Table 值 " + table + " 不合法!" + "必须为 &/Table0,> tableSet = tableObj.entrySet(); // 取出所有 join 条件 JSONObject requestObj = new JSONObject(true); // (JSONObject) obj.clone(); @@ -1538,7 +1565,19 @@ else if (join != null){ apijson.orm.Entry te = tk == null || p.substring(ind2 + 1).indexOf("/") >= 0 ? null : Pair.parseEntry(tk, true); if (te != null && JSONRequest.isTableKey(te.getKey()) && request.get(tk) instanceof JSONObject) { - refObj.put(k, v); + if (isAppJoin) { + if (refObj.size() >= 1) { + throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":" + e.getKey() + " 中 " + k + " 不合法!" + + "@ APP JOIN 必须有且只有一个引用赋值键值对!"); + } + + if (StringUtil.isName(k.substring(0, k.length() - 1)) == false) { + throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 中 " + k + " 不合法 !" + + "@ APP JOIN 只允许 key@:/Table/refKey 这种 = 等价连接!"); + } + } + + refObj.put(k, v); continue; } } @@ -1585,8 +1624,9 @@ else if (join != null){ j.setAlias(alias); j.setOuter((JSONObject) outer); j.setRequest(requestObj); - if (parentPathObj != null) { - j.setCount(parentPathObj.getInteger("count") != null ? parentPathObj.getInteger("count") : 1); + if (arrKey != null) { + Integer count = parentPathObj.getInteger(JSONRequest.KEY_COUNT); + j.setCount(count == null ? getDefaultQueryCount() : count); } List onList = new ArrayList<>(); @@ -1598,7 +1638,7 @@ else if (join != null){ throw new IllegalArgumentException(e.getKey() + ":value 中 value 值 " + targetPath + " 不合法!必须为引用赋值的路径 '/targetTable/targetKey' !"); } - // 取出引用赋值路径 targetPath 对应的 Table 和 key + // 取出引用赋值路径 targetPath 对应的 Table 和 key index = targetPath.lastIndexOf("/"); String targetKey = index < 0 ? null : targetPath.substring(index + 1); if (StringUtil.isName(targetKey) == false) { @@ -1638,24 +1678,24 @@ else if (join != null){ if (targetObj == null) { throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中路径对应的对象 '" + targetTableKey + "':{} 不存在或值为 null !必须是 {} 这种 JSONObject 格式!"); } - + Join.On on = new Join.On(); on.setKeyAndType(j.getJoinType(), j.getTable(), originKey); if (StringUtil.isName(on.getKey()) == false) { throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 key@ 中 key 值 " + on.getKey() + " 不合法!必须满足英文单词变量名格式!"); } - + on.setOriginKey(originKey); on.setOriginValue((String) refEntry.getValue()); on.setTargetTable(targetTable); on.setTargetAlias(targetAlias); on.setTargetKey(targetKey); - + onList.add(on); } - + j.setOnList(onList); - + joinList.add(j); // onList.add(table + "." + key + " = " + targetTable + "." + targetKey); // ON User.id = Moment.userId @@ -1762,7 +1802,7 @@ public static String replaceArrayChildPath(String parentPath, String valuePath) pos = ps[i+1].contains("/") == false ? ps[i+1] : ps[i+1].substring(0, ps[i+1].indexOf("/")); if ( - //StringUtil.isNumer(pos) && + //StringUtil.isNumer(pos) && vs[i+1].startsWith(pos + "/") == false) { vs[i+1] = pos + "/" + vs[i+1]; } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index fb5713be3..1dcbb7544 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -65,14 +65,14 @@ public int getCachedSQLCount() { public int getExecutedSQLCount() { return executedSQLCount; } - + private long executedSQLDuration = 0; private long sqlResultDuration = 0; @Override public long getExecutedSQLDuration() { return executedSQLDuration; } - + @Override public long getSqlResultDuration() { return sqlResultDuration; @@ -95,7 +95,7 @@ public void putCache(String sql, List list, SQLConfig config) { Log.i(TAG, "saveList sql == null || list == null >> return;"); return; } - + cacheMap.put(sql, list); } @@ -165,7 +165,7 @@ public ResultSet execute(@NotNull Statement statement, String sql) throws Except @Override public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws Exception { long executedSQLStartTime = System.currentTimeMillis(); - + boolean isPrepared = config.isPrepared(); final String sql = config.getSQL(false); @@ -212,7 +212,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws if (isExplain == false) { executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime; } - + result = new JSONObject(true); result.put(JSONResponse.KEY_COUNT, updateCount); result.put("update", updateCount >= 0); @@ -241,7 +241,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws //id,id{}至少一个会有,一定会返回,不用抛异常来阻止关联写操作时前面错误导致后面无条件执行! result.put(JSONResponse.KEY_COUNT, updateCount);//返回修改的记录数 - + String idKey = config.getIdKey(); if (config.getId() != null) { result.put(idKey, config.getId()); @@ -249,7 +249,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws if (config.getIdIn() != null) { result.put(idKey + "[]", config.getIdIn()); } - + return result; case GET: @@ -271,7 +271,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws executedSQLCount ++; executedSQLStartTime = System.currentTimeMillis(); } - rs = executeQuery(config); //FIXME SQL Server 是一次返回两个结果集,包括查询结果和执行计划,需要 moreResults + rs = executeQuery(config); //FIXME SQL Server 是一次返回两个结果集,包括查询结果和执行计划,需要 moreResults if (isExplain == false) { executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime; } @@ -318,23 +318,23 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws if (capacity > 100) { // 有 WHERE 条件,条件越多过滤数据越多,暂时不考虑 @combine:"a | (b & !c)" 里面 | OR 和 ! NOT 条件,太复杂也不是很必要 Map> combine = config.getCombineMap(); - + List andList = combine == null ? null : combine.get("&"); int andCondCount = andList == null ? (config.getWhere() == null ? 0 : config.getWhere().size()) : andList.size(); - + List orList = combine == null ? null : combine.get("|"); int orCondCount = orList == null ? 0 : orList.size(); - + List notList = combine == null ? null : combine.get("!"); int notCondCount = notList == null ? 0 : notList.size(); - + // 有 GROUP BY 分组,字段越少过滤数据越多 String[] group = StringUtil.split(config.getGroup()); int groupCount = group == null ? 0 : group.length; if (groupCount > 0 && Arrays.asList(group).contains(config.getIdKey())) { groupCount = 0; } - + // 有 HAVING 聚合函数,字段越多过滤数据越多,暂时不考虑 @combine:"a | (b & !c)" 里面 | OR 和 ! NOT 条件,太复杂也不是很必要 Map having = config.getHaving(); int havingCount = having == null ? 0 : having.size(); @@ -350,7 +350,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws } } } - + resultList = new ArrayList<>(capacity); } @@ -363,7 +363,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws //