From c479e3bfc27dddda65b699d8927568dbc76c06c2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 21 Jan 2021 15:47:53 +0800 Subject: [PATCH 001/944] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 73b68f069..9877451d9 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ This source code is licensed under the Apache License Version 2.0
English  通用文档 视频教程 - 在线体验 + 在线体验

From d48cbb369ca9e6c48425cbf5e301ac74a5058ec7 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 21 Jan 2021 16:31:44 +0800 Subject: [PATCH 002/944] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9877451d9..6d9909813 100644 --- a/README.md +++ b/README.md @@ -276,7 +276,7 @@ https://github.com/Tencent/APIJSON/issues/187 https://github.com/Tencent/APIJSON/blob/master/Roadmap.md 理论上所有支持 SQL 与 JDBC/ODBC 的软件,都可以用本项目对接 CRUD,待测试:
-[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) +[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...) ### 我要赞赏 如果你喜欢 APIJSON,感觉 APIJSON 帮助到了你,可以点右上角 ⭐Star 支持一下,谢谢 ^_^
From bd481ba3d534b286e95413cc57c82c5a5c2b172e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 21 Jan 2021 16:35:13 +0800 Subject: [PATCH 003/944] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6d9909813..9c74aad60 100644 --- a/README.md +++ b/README.md @@ -276,7 +276,7 @@ https://github.com/Tencent/APIJSON/issues/187 https://github.com/Tencent/APIJSON/blob/master/Roadmap.md 理论上所有支持 SQL 与 JDBC/ODBC 的软件,都可以用本项目对接 CRUD,待测试:
-[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...) +[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...) ### 我要赞赏 如果你喜欢 APIJSON,感觉 APIJSON 帮助到了你,可以点右上角 ⭐Star 支持一下,谢谢 ^_^
From 9d0345929cbfc6281034c112fde409da2de2c85f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 22 Jan 2021 17:32:21 +0800 Subject: [PATCH 004/944] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=8F=91=E7=9A=84=E5=8D=9A=E5=AE=A2=20apijson=E7=AE=80?= =?UTF-8?q?=E5=8D=95=E4=BD=BF=E7=94=A8?= 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 9c74aad60..ed49bc291 100644 --- a/README.md +++ b/README.md @@ -311,6 +311,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [APIJSON对接分布式HTAP数据库TiDB](https://asktug.com/t/htap-tidb/395) +[apijson简单使用](https://www.cnblogs.com/greyzeng/p/14311995.html) + [APIJSON简单部署和使用](https://blog.csdn.net/m450744192/article/details/108462611) [学习自动化接口APIJSON](https://www.jianshu.com/p/981a2a630c7b) From 41b625b8b53ec01ae9e84b57aec8e67efae5c9a5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 14:54:13 +0800 Subject: [PATCH 005/944] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index ed49bc291..32f2b7253 100644 --- a/README.md +++ b/README.md @@ -268,6 +268,11 @@ https://github.com/Tencent/APIJSON/issues/187
+还有为 APIJSON 扫描代码贡献 Bug 的 奇安信代码卫士 和 源伞科技 +

+ + +
感谢大家的贡献。 From 11e8060d5915463e90f27cb01f9755605d036af2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 14:56:48 +0800 Subject: [PATCH 006/944] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 32f2b7253..64f06eb30 100644 --- a/README.md +++ b/README.md @@ -268,11 +268,12 @@ https://github.com/Tencent/APIJSON/issues/187
-还有为 APIJSON 扫描代码贡献 Bug 的 奇安信代码卫士 和 源伞科技 +还有为 APIJSON 扫描代码贡献 Bug 的 [奇安信代码卫士](https://github.com/QiAnXinCodeSafe) 和 [源伞科技](https://www.sourcebrella.com)
- +
+
感谢大家的贡献。 From e9b9e95b61d1c94d2420f59012396ac6c8f7499d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 14:58:17 +0800 Subject: [PATCH 007/944] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 64f06eb30..804405fe4 100644 --- a/README.md +++ b/README.md @@ -270,8 +270,8 @@ https://github.com/Tencent/APIJSON/issues/187
还有为 APIJSON 扫描代码贡献 Bug 的 [奇安信代码卫士](https://github.com/QiAnXinCodeSafe) 和 [源伞科技](https://www.sourcebrella.com)
- - + +

感谢大家的贡献。 From 1750932d7797b4a2416fa17ac5f556a73233293a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 15:00:27 +0800 Subject: [PATCH 008/944] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 804405fe4..235e64571 100644 --- a/README.md +++ b/README.md @@ -268,10 +268,12 @@ https://github.com/Tencent/APIJSON/issues/187
-还有为 APIJSON 扫描代码贡献 Bug 的 [奇安信代码卫士](https://github.com/QiAnXinCodeSafe) 和 [源伞科技](https://www.sourcebrella.com) + +还有为 APIJSON 扫描代码贡献 Bug 的 [奇安信代码卫士](https://github.com/QiAnXinCodeSafe) 和 [源伞科技](https://www.sourcebrella.com) +
- +

感谢大家的贡献。 From 5c3a98634afeda2fe6f6f4c7501b95b0b8e05386 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 15:03:23 +0800 Subject: [PATCH 009/944] Update README.md --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 235e64571..adbeba662 100644 --- a/README.md +++ b/README.md @@ -236,20 +236,20 @@ https://github.com/Tencent/APIJSON/issues/187 主项目 APIJSON 的贡献者们和 生态周边项目 的作者们:
- - - - - - - - - - - - - + height="54" width="54" > + + + + + + + + + + + + +
From 8a19f3738a5d297f021388e1d99d5d776cf77bb9 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 15:05:16 +0800 Subject: [PATCH 010/944] Update README.md --- README.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index adbeba662..cd585559b 100644 --- a/README.md +++ b/README.md @@ -234,6 +234,7 @@ https://github.com/Tencent/APIJSON/issues/187 ### 贡献者们 主项目 APIJSON 的贡献者们和 生态周边项目 的作者们: +
@@ -252,20 +253,20 @@ https://github.com/Tencent/APIJSON/issues/187
- + height="54" width="54" > + + height="54" width="54" > - + height="54" width="54" > + - + height="54" width="54" > + - - + height="54" width="54" > + +

@@ -276,6 +277,7 @@ https://github.com/Tencent/APIJSON/issues/187

+ 感谢大家的贡献。 From 3b5d3a5f12e437f4aa9d20fed28599dcc0a538ea Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 15:07:05 +0800 Subject: [PATCH 011/944] Update README.md --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index cd585559b..bf6a01b20 100644 --- a/README.md +++ b/README.md @@ -218,17 +218,17 @@ https://github.com/Tencent/APIJSON/issues/36 如果您在使用 APIJSON,请让我们知道,您的使用对我们非常重要(按登记顺序排列):
https://github.com/Tencent/APIJSON/issues/187
- - - - - - - - - - - + + + + + + + + + + +
From d2c47d482f6112564226d979b7a72e9f66592d34 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 15:08:45 +0800 Subject: [PATCH 012/944] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index bf6a01b20..1216588cd 100644 --- a/README.md +++ b/README.md @@ -234,7 +234,6 @@ https://github.com/Tencent/APIJSON/issues/187 ### 贡献者们 主项目 APIJSON 的贡献者们和 生态周边项目 的作者们: -
@@ -271,7 +270,6 @@ https://github.com/Tencent/APIJSON/issues/187
还有为 APIJSON 扫描代码贡献 Bug 的 [奇安信代码卫士](https://github.com/QiAnXinCodeSafe) 和 [源伞科技](https://www.sourcebrella.com) -
From 37287d4eebbebabefb4f511ea3dce701f042db9a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 15:11:14 +0800 Subject: [PATCH 013/944] Update README.md --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 1216588cd..5737163be 100644 --- a/README.md +++ b/README.md @@ -218,17 +218,17 @@ https://github.com/Tencent/APIJSON/issues/36 如果您在使用 APIJSON,请让我们知道,您的使用对我们非常重要(按登记顺序排列):
https://github.com/Tencent/APIJSON/issues/187
- - - - - - - - - - - + + + + + + + + + + +
From 9e215311c41b201739f749fef64d64c24c115591 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 15:15:07 +0800 Subject: [PATCH 014/944] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5737163be..b161d29b6 100644 --- a/README.md +++ b/README.md @@ -223,10 +223,10 @@ https://github.com/Tencent/APIJSON/issues/187 - - + +
From ffb19c86ea07b17708d8658f52c88f06452d1a62 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 15:17:02 +0800 Subject: [PATCH 015/944] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b161d29b6..d10a74d25 100644 --- a/README.md +++ b/README.md @@ -225,8 +225,8 @@ https://github.com/Tencent/APIJSON/issues/187 - +
From d2cf5bc3d25f426ebe22c94fac6210d79132d51e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 15:23:35 +0800 Subject: [PATCH 016/944] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d10a74d25..31f3d4940 100644 --- a/README.md +++ b/README.md @@ -272,7 +272,7 @@ https://github.com/Tencent/APIJSON/issues/187 还有为 APIJSON 扫描代码贡献 Bug 的 [奇安信代码卫士](https://github.com/QiAnXinCodeSafe) 和 [源伞科技](https://www.sourcebrella.com)
- +

From 9b82b5ed4768bd9bb30029ec44c5803968c458eb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 23:43:23 +0800 Subject: [PATCH 017/944] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 31f3d4940..7d6f552bc 100644 --- a/README.md +++ b/README.md @@ -269,7 +269,7 @@ https://github.com/Tencent/APIJSON/issues/187

-还有为 APIJSON 扫描代码贡献 Bug 的 [奇安信代码卫士](https://github.com/QiAnXinCodeSafe) 和 [源伞科技](https://www.sourcebrella.com) +还有为 APIJSON 扫描代码贡献 Issue 的 [奇安信代码卫士](https://github.com/QiAnXinCodeSafe) 和 [源伞科技](https://www.sourcebrella.com)
From 1bf71489e3039a0d376264c9328d705e91147a6a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 23:45:16 +0800 Subject: [PATCH 018/944] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7d6f552bc..1d66d754f 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ https://github.com/Tencent/APIJSON/wiki * **解决十大痛点** (APIJSON 大幅提振开发效率、强力杜绝联调扯皮、巧妙规避文档缺陷、非常节省流量带宽 等) * **开发提速巨大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) -* **社区影响力大** (GitHub 9.6K Star 在 350W Java 项目中排名前 150,远超 FLAG, BAT 等国内外绝大部分开源项目) +* **社区影响力大** (GitHub 9.8K Star 在 350W Java 项目中排名前 150,远超 FLAG, BAT 等国内外绝大部分开源项目) * **各项荣誉成就** (腾讯开源五个第一、腾讯首个 GVP 获奖项目、腾讯后端项目 Star 第一、GitHub Java 周榜第一 等) * **多样用户案例** (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等) * **适用场景广泛** (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) From f05df3a1887999769ac6db18cfa8f314a5585165 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 24 Jan 2021 01:29:22 +0800 Subject: [PATCH 019/944] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E6=83=85=E5=86=B5=E4=B8=8B=E6=9C=AA=E5=8F=8A=E6=97=B6=E9=87=8A?= =?UTF-8?q?=E6=94=BE=E8=B5=84=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLExecutor.java | 254 ++++++++++-------- 1 file changed, 143 insertions(+), 111 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index ccfe38f8c..92508640d 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -176,127 +176,142 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); ResultSet rs = null; + List resultList = null; + Map childMap = null; - if (unknowType) { - Statement statement = getStatement(config); - rs = execute(statement, sql); + try { + if (unknowType) { + Statement statement = getStatement(config); + rs = execute(statement, sql); + + result = new JSONObject(true); + int updateCount = statement.getUpdateCount(); + result.put(JSONResponse.KEY_COUNT, updateCount); + result.put("update", updateCount >= 0); + //导致后面 rs.getMetaData() 报错 Operation not allowed after ResultSet closed result.put("moreResults", statement.getMoreResults()); + } + else { + switch (config.getMethod()) { + case HEAD: + case HEADS: + rs = executeQuery(config); - result = new JSONObject(true); - int updateCount = statement.getUpdateCount(); - result.put(JSONResponse.KEY_COUNT, updateCount); - result.put("update", updateCount >= 0); - //导致后面 rs.getMetaData() 报错 Operation not allowed after ResultSet closed result.put("moreResults", statement.getMoreResults()); - } - else { - switch (config.getMethod()) { - case HEAD: - case HEADS: - rs = executeQuery(config); + executedSQLCount ++; - executedSQLCount ++; + result = rs.next() ? AbstractParser.newSuccessResult() + : AbstractParser.newErrorResult(new SQLException("数据库错误, rs.next() 失败!")); + result.put(JSONResponse.KEY_COUNT, rs.getLong(1)); + return result; - result = rs.next() ? AbstractParser.newSuccessResult() - : AbstractParser.newErrorResult(new SQLException("数据库错误, rs.next() 失败!")); - result.put(JSONResponse.KEY_COUNT, rs.getLong(1)); + case POST: + case PUT: + case DELETE: + executedSQLCount ++; - rs.close(); - return result; + int updateCount = executeUpdate(config); - case POST: - case PUT: - case DELETE: - executedSQLCount ++; + result = AbstractParser.newResult(updateCount > 0 ? JSONResponse.CODE_SUCCESS : JSONResponse.CODE_NOT_FOUND + , updateCount > 0 ? JSONResponse.MSG_SUCCEED : "没权限访问或对象不存在!"); - int updateCount = executeUpdate(config); + //id,id{}至少一个会有,一定会返回,不用抛异常来阻止关联写操作时前面错误导致后面无条件执行! + result.put(JSONResponse.KEY_COUNT, updateCount);//返回修改的记录数 + if (config.getId() != null) { + result.put(config.getIdKey(), config.getId()); + } else { + result.put(config.getIdKey() + "[]", config.getWhere(config.getIdKey() + "{}", true)); + } + return result; - result = AbstractParser.newResult(updateCount > 0 ? JSONResponse.CODE_SUCCESS : JSONResponse.CODE_NOT_FOUND - , updateCount > 0 ? JSONResponse.MSG_SUCCEED : "没权限访问或对象不存在!"); + case GET: + case GETS: + result = getCacheItem(sql, position, config.getCache()); + Log.i(TAG, ">>> select result = getCache('" + sql + "', " + position + ") = " + result); + if (result != null) { + cachedSQLCount ++; - //id,id{}至少一个会有,一定会返回,不用抛异常来阻止关联写操作时前面错误导致后面无条件执行! - result.put(JSONResponse.KEY_COUNT, updateCount);//返回修改的记录数 - if (config.getId() != null) { - result.put(config.getIdKey(), config.getId()); - } else { - result.put(config.getIdKey() + "[]", config.getWhere(config.getIdKey() + "{}", true)); - } - return result; + Log.d(TAG, "\n\n select result != null >> return result;" + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n"); + return result; + } - case GET: - case GETS: - result = getCacheItem(sql, position, config.getCache()); - Log.i(TAG, ">>> select result = getCache('" + sql + "', " + position + ") = " + result); - if (result != null) { - cachedSQLCount ++; + rs = executeQuery(config); //FIXME SQL Server 是一次返回两个结果集,包括查询结果和执行计划,需要 moreResults - Log.d(TAG, "\n\n select result != null >> return result;" + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n"); - return result; - } - - rs = executeQuery(config); //FIXME SQL Server 是一次返回两个结果集,包括查询结果和执行计划,需要 moreResults + if (config.isExplain() == false) { //只有 SELECT 才能 EXPLAIN + executedSQLCount ++; + } + break; - if (config.isExplain() == false) { //只有 SELECT 才能 EXPLAIN - executedSQLCount ++; + default://OPTIONS, TRACE等 + Log.e(TAG, "select sql = " + sql + " ; method = " + config.getMethod() + " >> return null;"); + return null; } - break; - - default://OPTIONS, TRACE等 - Log.e(TAG, "select sql = " + sql + " ; method = " + config.getMethod() + " >> return null;"); - return null; } - } - // final boolean cache = config.getCount() != 1; - List resultList = new ArrayList<>(); - // Log.d(TAG, "select cache = " + cache + "; resultList" + (resultList == null ? "=" : "!=") + "null"); + // final boolean cache = config.getCount() != 1; + resultList = new ArrayList<>(); + // Log.d(TAG, "select cache = " + cache + "; resultList" + (resultList == null ? "=" : "!=") + "null"); - int index = -1; + int index = -1; - ResultSetMetaData rsmd = rs.getMetaData(); - final int length = rsmd.getColumnCount(); + ResultSetMetaData rsmd = rs.getMetaData(); + final int length = rsmd.getColumnCount(); - // + childMap = new HashMap<>(); //要存到cacheMap + // WHERE id = ? AND ... 或 WHERE ... AND id = ? 强制排序 remove 再 put,还是重新 getSQL吧 - boolean hasJoin = config.hasJoin(); - int viceColumnStart = length + 1; //第一个副表字段的index - while (rs.next()) { - index ++; - Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n select while (rs.next()){ index = " + index + "\n\n"); + boolean hasJoin = config.hasJoin(); + int viceColumnStart = length + 1; //第一个副表字段的index + while (rs.next()) { + index ++; + Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n select while (rs.next()){ index = " + index + "\n\n"); - JSONObject item = new JSONObject(true); + JSONObject item = new JSONObject(true); - for (int i = 1; i <= length; i++) { + for (int i = 1; i <= length; i++) { - // if (hasJoin && viceColumnStart > length && config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { - // viceColumnStart = i; - // } + // if (hasJoin && viceColumnStart > length && config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { + // viceColumnStart = i; + // } - // bugfix-修复非常规数据库字段,获取表名失败导致输出异常 - if (config.isExplain() == false && hasJoin && viceColumnStart > length) { - List column = config.getColumn(); + // bugfix-修复非常规数据库字段,获取表名失败导致输出异常 + if (config.isExplain() == false && hasJoin && viceColumnStart > length) { + List column = config.getColumn(); - if (column != null && column.isEmpty() == false) { - viceColumnStart = column.size() + 1; - } - else if (config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { - viceColumnStart = i; + if (column != null && column.isEmpty() == false) { + viceColumnStart = column.size() + 1; + } + else if (config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { + viceColumnStart = i; + } } + + item = onPutColumn(config, rs, rsmd, index, item, i, config.isExplain() == false && hasJoin && i >= viceColumnStart ? childMap : null); } - item = onPutColumn(config, rs, rsmd, index, item, i, config.isExplain() == false && hasJoin && i >= viceColumnStart ? childMap : null); - } + resultList = onPutTable(config, rs, rsmd, resultList, index, item); - resultList = onPutTable(config, rs, rsmd, resultList, index, item); + Log.d(TAG, "\n select while (rs.next()) { resultList.put( " + index + ", result); " + + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n"); + } - Log.d(TAG, "\n select while (rs.next()) { resultList.put( " + index + ", result); " - + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n"); + } + finally { + if (rs != null) { + try { + rs.close(); + } + catch (Exception e) { + e.printStackTrace(); + } + } } - rs.close(); + if (resultList == null) { + return null; + } if (unknowType || config.isExplain()) { if (config.isExplain()) { @@ -397,41 +412,51 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); //执行副表的批量查询 并 缓存到 childMap - ResultSet rs = executeQuery(jc); + ResultSet rs = null; + try { + rs = executeQuery(jc); - int index = -1; + int index = -1; - ResultSetMetaData rsmd = rs.getMetaData(); - final int length = rsmd.getColumnCount(); + ResultSetMetaData rsmd = rs.getMetaData(); + final int length = rsmd.getColumnCount(); - JSONObject result; - String cacheSql; - while (rs.next()) { //FIXME 同时有 @ APP JOIN 和 < 等 SQL JOIN 时,next = false 总是无法进入循环,导致缓存失效,可能是连接池或线程问题 - index ++; - Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n executeAppJoin while (rs.next()){ index = " + index + "\n\n"); + JSONObject result; + String cacheSql; + while (rs.next()) { //FIXME 同时有 @ APP JOIN 和 < 等 SQL JOIN 时,next = false 总是无法进入循环,导致缓存失效,可能是连接池或线程问题 + index ++; + Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n executeAppJoin while (rs.next()){ index = " + index + "\n\n"); - result = new JSONObject(true); + result = new JSONObject(true); - for (int i = 1; i <= length; i++) { + for (int i = 1; i <= length; i++) { - result = onPutColumn(jc, rs, rsmd, index, result, i, null); - } + result = onPutColumn(jc, rs, rsmd, index, result, i, null); + } - //每个 result 都要用新的 SQL 来存 childResultMap = onPutTable(config, rs, rsmd, childResultMap, index, result); + //每个 result 都要用新的 SQL 来存 childResultMap = onPutTable(config, rs, rsmd, childResultMap, index, result); - Log.d(TAG, "\n executeAppJoin while (rs.next()) { resultList.put( " + index + ", result); " - + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n"); + Log.d(TAG, "\n executeAppJoin while (rs.next()) { resultList.put( " + index + ", result); " + + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n"); - //缓存到 childMap - cc.putWhere(j.getKey(), result.get(j.getKey()), false); - cacheSql = cc.getSQL(false); - childMap.put(cacheSql, result); + //缓存到 childMap + cc.putWhere(j.getKey(), result.get(j.getKey()), false); + cacheSql = cc.getSQL(false); + childMap.put(cacheSql, result); - Log.d(TAG, ">>> executeAppJoin childMap.put('" + cacheSql + "', result); childMap.size() = " + childMap.size()); + Log.d(TAG, ">>> executeAppJoin childMap.put('" + cacheSql + "', result); childMap.size() = " + childMap.size()); + } + } + finally { + if (rs != null) { + try { + rs.close(); + } + catch (Exception e) { + e.printStackTrace(); + } + } } - - rs.close(); - long endTime = System.currentTimeMillis(); Log.d(TAG, "\n\n executeAppJoin endTime = " + endTime + "; duration = " + (endTime - startTime) @@ -576,6 +601,13 @@ else if (value instanceof Clob) { //SQL Server TEXT 类型 居然走这个 s = br.readLine(); } value = sb.toString(); + + try { + br.close(); + } + catch (Exception e) { + e.printStackTrace(); + } } if (castToJson == false) { From bbcf05e01c724894a5ba0dcddd49a14bbfeb5346 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 24 Jan 2021 01:55:59 +0800 Subject: [PATCH 020/944] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E6=9F=90=E4=BA=9B?= =?UTF-8?q?=E6=83=85=E5=86=B5=E4=B8=8B=E8=A7=A3=E6=9E=90=20=E5=BC=95?= =?UTF-8?q?=E7=94=A8=E8=B5=8B=E5=80=BC=20=E5=87=BA=E9=94=99=20throw=20Null?= =?UTF-8?q?PointerException=20=E6=88=96=20IndexOutOfBoundsException?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractParser.java | 152 +++++++++--------- .../src/main/java/apijson/orm/SQLConfig.java | 2 +- 2 files changed, 78 insertions(+), 76 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 3168cf856..85bbb4c1e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -462,7 +462,7 @@ public static JSONObject parseRequest(String request) throws Exception { @Override public JSONObject parseCorrectRequest(RequestMethod method, String tag, int version, String name, @NotNull JSONObject request , int maxUpdateCount, SQLCreator creator) throws Exception { - + if (RequestMethod.isPublicMethod(method)) { return request;//需要指定JSON结构的get请求可以改为post请求。一般只有对安全性要求高的才会指定,而这种情况用明文的GET方式几乎肯定不安全 } @@ -486,10 +486,10 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers JSONObject target = object; if (object.containsKey(tag) == false) { //tag 是 Table 名或 Table[] - + 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()); @@ -500,15 +500,15 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers } } } - + //获取指定的JSON结构 >>>>>>>>>>>>>> - + //JSONObject clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {} return getVerifier().verifyRequest(method, name, target, request, maxUpdateCount, getGlobleDatabase(), getGlobleSchema(), creator); } - - + + /**新建带状态内容的JSONObject * @param code * @param msg @@ -533,7 +533,7 @@ public static JSONObject extendResult(JSONObject object, int code, String msg) { if (object.containsKey(JSONResponse.KEY_CODE) == false) { object.put(JSONResponse.KEY_CODE, code); } - + String m = StringUtil.getString(object.getString(JSONResponse.KEY_MSG)); if (m.isEmpty() == false) { msg = m + " ;\n " + StringUtil.getString(msg); @@ -679,14 +679,14 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, // TODO 目前只使用 Request 而不使用 Response,所以这里写死用 REQUEST_MAP,以后可能 Response 表也会与 Request 表合并,用字段来区分 String cacheKey = AbstractVerifier.getCacheKeyForRequest(method, tag); SortedMap versionedMap = AbstractVerifier.REQUEST_MAP.get(cacheKey); - + JSONObject result = versionedMap == null ? null : versionedMap.get(Integer.valueOf(version)); if (result == null) { // version <= 0 时使用最新,version > 0 时使用 > version 的最接近版本(最小版本) Set> set = versionedMap == null ? null : versionedMap.entrySet(); - + if (set != null && set.isEmpty() == false) { Entry maxEntry = null; - + for (Entry entry : set) { if (entry == null || entry.getKey() == null || entry.getValue() == null) { continue; @@ -700,30 +700,30 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, if (entry.getKey() < version) { break; } - + maxEntry = entry; } - + result = maxEntry == null ? null : maxEntry.getValue(); } - + if (result != null) { // 加快下次查询,查到值的话组合情况其实是有限的,不属于恶意请求 if (versionedMap == null) { versionedMap = new TreeMap<>((o1, o2) -> { return o2 == null ? -1 : o2.compareTo(o1); // 降序 }); } - + versionedMap.put(Integer.valueOf(version), result); AbstractVerifier.REQUEST_MAP.put(cacheKey, versionedMap); } } - + if (result == null) { if (AbstractVerifier.REQUEST_MAP.isEmpty() == false) { return null; // 已使用 REQUEST_MAP 缓存全部,但没查到 } - + //获取指定的JSON结构 <<<<<<<<<<<<<< SQLConfig config = createSQLConfig().setMethod(GET).setTable(table); config.setPrepared(false); @@ -732,7 +732,7 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, Map where = new HashMap(); where.put("method", method); where.put(JSONRequest.KEY_TAG, tag); - + if (version > 0) { where.put(JSONRequest.KEY_VERSION + "{}", ">=" + version); } @@ -742,12 +742,12 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, //too many connections error: 不try-catch,可以让客户端看到是服务器内部异常 result = getSQLExecutor().execute(config, false); - + // version, method, tag 组合情况太多了,JDK 里又没有 LRUCache,所以要么启动时一次性缓存全部后面只用缓存,要么每次都查数据库 // versionedMap.put(Integer.valueOf(version), result); // AbstractVerifier.REQUEST_MAP.put(cacheKey, versionedMap); } - + return getJSONObject(result, "structure"); //解决返回值套了一层 "structure":{} } @@ -807,10 +807,10 @@ public JSONObject onObjectParse(final JSONObject request int index = parentPath.lastIndexOf("]/"); if (index >= 0) { int total = rp.getIntValue(JSONResponse.KEY_COUNT); - + String pathPrefix = parentPath.substring(0, index) + "]/"; putQueryResult(pathPrefix + JSONResponse.KEY_TOTAL, total); - + //详细的分页信息,主要为 PC 端提供 int count = arrayConfig.getCount(); int page = arrayConfig.getPage(); @@ -818,7 +818,7 @@ public JSONObject onObjectParse(final JSONObject request if (max < 0) { max = 0; } - + JSONObject pagination = new JSONObject(true); pagination.put(JSONResponse.KEY_TOTAL, total); pagination.put(JSONRequest.KEY_COUNT, count); @@ -929,51 +929,52 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name request.remove(JSONRequest.KEY_JOIN); Log.d(TAG, "onArrayParse query = " + query + "; count = " + count + "; page = " + page + "; join = " + join); - if (request.isEmpty()) {//如果条件成立,说明所有的 parentPath/name:request 中request都无效!!! + if (request.isEmpty()) { // 如果条件成立,说明所有的 parentPath/name:request 中request都无效!!! 后续都不执行,没必要还原数组关键词浪费性能 Log.e(TAG, "onArrayParse request.isEmpty() >> return null;"); return null; } + 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 = " + page); - - - //key[]:{Table:{}}中key equals Table时 提取Table - int index = isSubquery || name == null ? -1 : name.lastIndexOf("[]"); - String childPath = index <= 0 ? null : Pair.parseEntry(name.substring(0, index), true).getKey(); // Table-key1-key2... - //判断第一个key,即Table是否存在,如果存在就提取 - String[] childKeys = StringUtil.split(childPath, "-", false); - if (childKeys == null || childKeys.length <= 0 || request.containsKey(childKeys[0]) == false) { - childKeys = null; - } + //key[]:{Table:{}}中key equals Table时 提取Table + int index = isSubquery || name == null ? -1 : name.lastIndexOf("[]"); + String childPath = index <= 0 ? null : Pair.parseEntry(name.substring(0, index), true).getKey(); // Table-key1-key2... + //判断第一个key,即Table是否存在,如果存在就提取 + String[] childKeys = StringUtil.split(childPath, "-", false); + if (childKeys == null || childKeys.length <= 0 || request.containsKey(childKeys[0]) == false) { + childKeys = null; + } - //Table<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - JSONArray response = new JSONArray(); - SQLConfig config = createSQLConfig() - .setMethod(requestMethod) - .setCount(size) - .setPage(page) - .setQuery(query2) - .setJoinList(onJoinParse(join, request)); - JSONObject parent; - //生成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; + //Table<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + response = new JSONArray(); + SQLConfig config = createSQLConfig() + .setMethod(requestMethod) + .setCount(size) + .setPage(page) + .setQuery(query2) + .setJoinList(onJoinParse(join, request)); + + JSONObject parent; + //生成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有意义 } - //key[]:{Table:{}}中key equals Table时 提取Table - response.add(getValue(parent, childKeys)); //null有意义 - } - //Table>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + //Table>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - /* - * 支持引用取值后的数组 + /* + * 支持引用取值后的数组 { "User-id[]": { "User": { @@ -986,18 +987,19 @@ 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); - } - + */ + Object fo = childKeys == null || response.isEmpty() ? null : response.get(0); + if (fo instanceof Boolean || fo instanceof Number || fo instanceof String) { //[{}] 和 [[]] 都没意义 + putQueryResult(path, response); + } + } finally { - //后面还可能用到,要还原 - request.put(JSONRequest.KEY_QUERY, query); - request.put(JSONRequest.KEY_COUNT, count); - request.put(JSONRequest.KEY_PAGE, page); - request.put(JSONRequest.KEY_JOIN, join); + //后面还可能用到,要还原 + request.put(JSONRequest.KEY_QUERY, query); + request.put(JSONRequest.KEY_COUNT, count); + request.put(JSONRequest.KEY_PAGE, page); + request.put(JSONRequest.KEY_JOIN, join); + } if (Log.DEBUG) { Log.i(TAG, "onArrayParse return response = \n" + JSON.toJSONString(response) + "\n>>>>>>>>>>>>>>>\n\n\n"); @@ -1142,8 +1144,9 @@ else if (join != null){ private static final List JOIN_COPY_KEY_LIST; - static {//TODO 不全 + 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_COLUMN); @@ -1151,14 +1154,13 @@ else if (join != null){ 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); } - /** - * 取指定json对象的id集合 + /**取指定 JSON 对象的 id 集合 * @param table * @param key * @param obj - * @param targetKey * @return null ? 全部 : 有限的数组 */ private JSONObject getJoinObject(String table, JSONObject obj, String key) { @@ -1171,8 +1173,8 @@ private JSONObject getJoinObject(String table, JSONObject obj, String key) { return null; } - //取出所有join条件 - JSONObject requestObj = new JSONObject(true);//(JSONObject) obj.clone();// + // 取出所有 join 条件 + JSONObject requestObj = new JSONObject(true); // (JSONObject) obj.clone(); Set set = new LinkedHashSet<>(obj.keySet()); for (String k : set) { if (StringUtil.isEmpty(k, true)) { @@ -1190,7 +1192,7 @@ private JSONObject getJoinObject(String table, JSONObject obj, String key) { continue; } throw new UnsupportedOperationException(table + "." + k + " 不合法!" + JSONRequest.KEY_JOIN - + " 关联的Table中只能有1个 key@:value !"); + + " 关联的Table中只能有1个 key@:value !"); // TODO 支持 join on } if (k.contains("()") == false) { //不需要远程函数 @@ -1393,7 +1395,7 @@ public Object getValueByPath(String valuePath) { if (parent != null) { Log.i(TAG, "getValueByPath >> get from queryResultMap >> return parent.get(keys[keys.length - 1]);"); - target = parent.get(keys[keys.length - 1]); //值为null应该报错NotExistExeption,一般都是id关联,不可为null,否则可能绕过安全机制 + target = keys == null || keys.length <= 0 ? parent : parent.get(keys[keys.length - 1]); //值为null应该报错NotExistExeption,一般都是id关联,不可为null,否则可能绕过安全机制 if (target != null) { Log.i(TAG, "getValueByPath >> getValue >> return target = " + target); return target; diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index ed466e2cf..7e73bdd18 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -112,7 +112,7 @@ public interface SQLConfig { SQLConfig setId(Object id); RequestRole getRole(); - SQLConfig setRole(RequestRole role); + SQLConfig setRole(RequestRole role); // TODO 提供 String 类型的,方便扩展 public boolean isDistinct(); public SQLConfig setDistinct(boolean distinct); From b7d1fef19286e30692ff628f78875e130d1583ca Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 24 Jan 2021 02:55:03 +0800 Subject: [PATCH 021/944] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=85=A8=E5=B1=80?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E5=8F=82=E6=95=B0=E7=9A=84=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractObjectParser.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index f33534d19..8fcd78d80 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -177,7 +177,7 @@ public AbstractObjectParser parse() throws Exception { functionMap = null;//must init childMap = null;//must init - Set> set = new LinkedHashSet>(request.entrySet()); + Set> set = request.isEmpty() ? null : new LinkedHashSet>(request.entrySet()); if (set != null && set.isEmpty() == false) {//判断换取少几个变量的初始化是否值得? if (isTable) {//非Table下必须保证原有顺序!否则 count,page 会丢, total@:"/[]/total" 会在[]:{}前执行! customMap = new LinkedHashMap(); @@ -261,17 +261,17 @@ else if (method == PUT && value instanceof JSONArray } if (isTable) { - if (sqlRequest.get(JSONRequest.KEY_DATABASE) == null && parser.getGlobleDatabase() != null) { + if (parser.getGlobleDatabase() != null && sqlRequest.get(JSONRequest.KEY_DATABASE) == null) { sqlRequest.put(JSONRequest.KEY_DATABASE, parser.getGlobleDatabase()); } - if (sqlRequest.get(JSONRequest.KEY_SCHEMA) == null && parser.getGlobleSchema() != null) { + if (parser.getGlobleSchema() != null && sqlRequest.get(JSONRequest.KEY_SCHEMA) == null) { sqlRequest.put(JSONRequest.KEY_SCHEMA, parser.getGlobleSchema()); } if (isSubquery == false) { //解决 SQL 语法报错,子查询不能 EXPLAIN - if (sqlRequest.get(JSONRequest.KEY_EXPLAIN) == null && parser.getGlobleExplain() != null) { + if (parser.getGlobleExplain() != null && sqlRequest.get(JSONRequest.KEY_EXPLAIN) == null) { sqlRequest.put(JSONRequest.KEY_EXPLAIN, parser.getGlobleExplain()); } - if (sqlRequest.get(JSONRequest.KEY_CACHE) == null && parser.getGlobleCache() != null) { + if (parser.getGlobleCache() != null && sqlRequest.get(JSONRequest.KEY_CACHE) == null) { sqlRequest.put(JSONRequest.KEY_CACHE, parser.getGlobleCache()); } } From 893d3b3fd9cb617b2bcfa17b503b097dede1dfdc Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 24 Jan 2021 22:43:13 +0800 Subject: [PATCH 022/944] =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=8E=A8=E8=8D=90=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20APIJSON=20=E6=8E=A5=E5=8F=A3=E8=B0=83?= =?UTF-8?q?=E8=AF=95=E5=AE=9E=E8=B7=B5=EF=BC=8C=E6=84=9F=E8=B0=A2=E8=85=BE?= =?UTF-8?q?=E8=AE=AF=E5=90=8C=E4=BA=8B=E7=9A=84=E8=B4=A1=E7=8C=AE?= 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 1d66d754f..4981da0d3 100644 --- a/README.md +++ b/README.md @@ -325,6 +325,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [学习自动化接口APIJSON](https://www.jianshu.com/p/981a2a630c7b) +[APIJSON 接口调试实践](https://github.com/Tencent/APIJSON/issues/189) + [APIJSON使用例子总结](https://blog.csdn.net/weixin_41077841/article/details/110518007) [APIJSON 自动化接口和文档的快速开发神器 (一)](https://blog.csdn.net/qq_41829492/article/details/88670940) From bb4896d208e813a3f5eec21f60e1c083621b1f92 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 30 Jan 2021 19:56:35 +0800 Subject: [PATCH 023/944] =?UTF-8?q?=E9=80=9A=E8=BF=87=E5=87=8F=E5=B0=91?= =?UTF-8?q?=E4=B8=8D=E5=BF=85=E8=A6=81=E7=9A=84=20newSQLConfig=20=E5=8F=8A?= =?UTF-8?q?=20getSQL=20=E7=AD=89=E6=AD=A5=E9=AA=A4=E6=9D=A5=E5=A4=A7?= =?UTF-8?q?=E5=B9=85=E6=8F=90=E5=8D=87=E6=95=B0=E7=BB=84=E5=86=85=E4=B8=BB?= =?UTF-8?q?=E8=A1=A8=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 | 248 +++++++++++------- .../main/java/apijson/orm/AbstractParser.java | 22 +- .../java/apijson/orm/AbstractSQLExecutor.java | 32 ++- .../main/java/apijson/orm/SQLExecutor.java | 2 + 4 files changed, 189 insertions(+), 115 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 8fcd78d80..2ac9447e6 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -60,6 +60,8 @@ public AbstractObjectParser setParser(AbstractParser parser) { protected final int type; protected final List joinList; protected final boolean isTable; + protected final boolean isArrayMainTable; + protected final boolean isReuse; protected final String path; protected final String name; protected final String table; @@ -96,6 +98,8 @@ public AbstractObjectParser(@NotNull JSONObject request, String parentPath, Stri this.table = entry.getKey(); this.alias = entry.getValue(); this.isTable = apijson.JSONObject.isTableKey(table); + this.isArrayMainTable = isSubquery == false && this.isTable && this.type == SQLConfig.TYPE_ITEM_CHILD_0 && RequestMethod.isGetMethod(method, true); + this.isReuse = isArrayMainTable && arrayConfig != null && arrayConfig.getPosition() > 0; this.objectCount = 0; this.arrayCount = 0; @@ -118,6 +122,15 @@ public AbstractObjectParser(@NotNull JSONObject request, String parentPath, Stri Log.d(TAG, "AbstractObjectParser isEmpty = " + isEmpty + "; tri = " + tri + "; drop = " + drop); } + protected int position; + public int getPosition() { + return position; + } + public AbstractObjectParser setPosition(int position) { + this.position = position; + return this; + } + private boolean invalidate = false; public void invalidate() { @@ -260,7 +273,7 @@ else if (method == PUT && value instanceof JSONArray // 非Table内的函数会被滞后在onChildParse后调用! onFunctionResponse("-"); } - if (isTable) { + if (isReuse == false && isTable) { if (parser.getGlobleDatabase() != null && sqlRequest.get(JSONRequest.KEY_DATABASE) == null) { sqlRequest.put(JSONRequest.KEY_DATABASE, parser.getGlobleDatabase()); } @@ -297,96 +310,96 @@ else if (method == PUT && value instanceof JSONArray */ @Override public boolean onParse(@NotNull String key, @NotNull Object value) throws Exception { - if (key.endsWith("@")) {//StringUtil.isPath((String) value)) { - - if (value instanceof JSONObject) { // SQL 子查询对象,JSONObject -> SQLConfig.getSQL - String replaceKey = key.substring(0, key.length() - 1);//key{}@ getRealKey - - JSONObject subquery = (JSONObject) value; - String range = subquery.getString(JSONRequest.KEY_SUBQUERY_RANGE); - if (range != null && JSONRequest.SUBQUERY_RANGE_ALL.equals(range) == false && JSONRequest.SUBQUERY_RANGE_ANY.equals(range) == false) { - throw new IllegalArgumentException("子查询 " + path + "/" + key + ":{ range:value } 中 value 只能为 [" + JSONRequest.SUBQUERY_RANGE_ALL + ", " + JSONRequest.SUBQUERY_RANGE_ANY + "] 中的一个!"); - } - + if (key.endsWith("@")) { // StringUtil.isPath((String) value)) { + String replaceKey = key.substring(0, key.length() - 1); + + // [] 内主表 position > 0 时,用来生成 SQLConfig 的键值对全都忽略,不解析 +// if (isReuse == false || replaceKey.endsWith("()") || (replaceKey.startsWith("@") && JSONRequest.TABLE_KEY_LIST.contains(replaceKey) == false)) { + if (value instanceof JSONObject) { // key{}@ getRealKey, SQL 子查询对象,JSONObject -> SQLConfig.getSQL + + JSONObject subquery = (JSONObject) value; + String range = subquery.getString(JSONRequest.KEY_SUBQUERY_RANGE); + if (range != null && JSONRequest.SUBQUERY_RANGE_ALL.equals(range) == false && JSONRequest.SUBQUERY_RANGE_ANY.equals(range) == false) { + throw new IllegalArgumentException("子查询 " + path + "/" + key + ":{ range:value } 中 value 只能为 [" + JSONRequest.SUBQUERY_RANGE_ALL + ", " + JSONRequest.SUBQUERY_RANGE_ANY + "] 中的一个!"); + } - JSONArray arr = parser.onArrayParse(subquery, path, key, true); - JSONObject obj = arr == null || arr.isEmpty() ? null : arr.getJSONObject(0); - if (obj == null) { - throw new Exception("服务器内部错误,解析子查询 " + path + "/" + key + ":{ } 为 Subquery 对象失败!"); - } + JSONArray arr = parser.onArrayParse(subquery, path, key, true); - String from = subquery.getString(JSONRequest.KEY_SUBQUERY_FROM); - JSONObject arrObj = from == null ? null : obj.getJSONObject(from); - 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"); - } - - Subquery s = new Subquery(); - s.setPath(path); - s.setOriginKey(key); - s.setOriginValue(subquery); - - s.setFrom(from); - s.setRange(range); - s.setKey(replaceKey); - s.setConfig(cfg); + JSONObject obj = arr == null || arr.isEmpty() ? null : arr.getJSONObject(0); + if (obj == null) { + throw new Exception("服务器内部错误,解析子查询 " + path + "/" + key + ":{ } 为 Subquery 对象失败!"); + } - key = replaceKey; - value = s; //(range == null || range.isEmpty() ? "" : "range") + "(" + cfg.getSQL(false) + ") "; + String from = subquery.getString(JSONRequest.KEY_SUBQUERY_FROM); + JSONObject arrObj = from == null ? null : obj.getJSONObject(from); + 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"); + } - parser.putQueryResult(AbstractParser.getAbsPath(path, key), s); //字符串引用保证不了安全性 parser.getSQL(cfg)); - } - else if (value instanceof String) { // 引用赋值路径 + Subquery s = new Subquery(); + s.setPath(path); + s.setOriginKey(key); + s.setOriginValue(subquery); - // System.out.println("getObject key.endsWith(@) >> parseRelation = " + parseRelation); - String replaceKey = key.substring(0, key.length() - 1);//key{}@ getRealKey - String targetPath = AbstractParser.getValuePath(type == TYPE_ITEM - ? path : parentPath, new String((String) value)); + s.setFrom(from); + s.setRange(range); + s.setKey(replaceKey); + s.setConfig(cfg); - //先尝试获取,尽量保留缺省依赖路径,这样就不需要担心路径改变 - Object target = onReferenceParse(targetPath); - Log.i(TAG, "onParse targetPath = " + targetPath + "; target = " + target); + key = replaceKey; + value = s; //(range == null || range.isEmpty() ? "" : "range") + "(" + cfg.getSQL(false) + ") "; - if (target == null) {//String#equals(null)会出错 - Log.d(TAG, "onParse target == null >> return true;"); - return true; + parser.putQueryResult(AbstractParser.getAbsPath(path, key), s); //字符串引用保证不了安全性 parser.getSQL(cfg)); } - if (target instanceof Map) { //target可能是从requestObject里取出的 {} - if (isTable || targetPath.endsWith("[]/" + JSONResponse.KEY_INFO) == false) { - Log.d(TAG, "onParse target instanceof Map >> return false;"); - return false; //FIXME 这个判断现在来看是否还有必要?为啥不允许为 JSONObject ?以前可能因为防止二次遍历再解析,现在只有一次遍历 + else if (value instanceof String) { // //key{}@ getRealKey, 引用赋值路径 + // System.out.println("getObject key.endsWith(@) >> parseRelation = " + parseRelation); + String targetPath = AbstractParser.getValuePath(type == TYPE_ITEM + ? path : parentPath, new String((String) value)); + + //先尝试获取,尽量保留缺省依赖路径,这样就不需要担心路径改变 + Object target = onReferenceParse(targetPath); + Log.i(TAG, "onParse targetPath = " + targetPath + "; target = " + target); + + if (target == null) {//String#equals(null)会出错 + Log.d(TAG, "onParse target == null >> return true;"); + return true; } - } - if (targetPath.equals(target)) {//必须valuePath和保证getValueByPath传进去的一致! - Log.d(TAG, "onParse targetPath.equals(target) >>"); - - //非查询关键词 @key 不影响查询,直接跳过 - if (isTable && (key.startsWith("@") == false || JSONRequest.TABLE_KEY_LIST.contains(key))) { - Log.e(TAG, "onParse isTable && (key.startsWith(@) == false" - + " || JSONRequest.TABLE_KEY_LIST.contains(key)) >> return null;"); - return false;//获取不到就不用再做无效的query了。不考虑 Table:{Table:{}}嵌套 - } else { - Log.d(TAG, "onParse isTable(table) == false >> return true;"); - return true;//舍去,对Table无影响 + if (target instanceof Map) { //target可能是从requestObject里取出的 {} + if (isTable || targetPath.endsWith("[]/" + JSONResponse.KEY_INFO) == false) { + Log.d(TAG, "onParse target instanceof Map >> return false;"); + return false; //FIXME 这个判断现在来看是否还有必要?为啥不允许为 JSONObject ?以前可能因为防止二次遍历再解析,现在只有一次遍历 + } } - } - - //直接替换原来的key@:path为key:target - Log.i(TAG, "onParse >> key = replaceKey; value = target;"); - key = replaceKey; - value = target; - Log.d(TAG, "onParse key = " + key + "; value = " + value); - } - else { - throw new IllegalArgumentException(path + "/" + key + ":value 中 value 必须为 依赖路径String 或 SQL子查询JSONObject !"); - } + if (targetPath.equals(target)) {//必须valuePath和保证getValueByPath传进去的一致! + Log.d(TAG, "onParse targetPath.equals(target) >>"); + + //非查询关键词 @key 不影响查询,直接跳过 + if (isTable && (key.startsWith("@") == false || JSONRequest.TABLE_KEY_LIST.contains(key))) { + Log.e(TAG, "onParse isTable && (key.startsWith(@) == false" + + " || JSONRequest.TABLE_KEY_LIST.contains(key)) >> return null;"); + return false;//获取不到就不用再做无效的query了。不考虑 Table:{Table:{}}嵌套 + } else { + Log.d(TAG, "onParse isTable(table) == false >> return true;"); + return true;//舍去,对Table无影响 + } + } + //直接替换原来的key@:path为key:target + Log.i(TAG, "onParse >> key = replaceKey; value = target;"); + key = replaceKey; + value = target; + Log.d(TAG, "onParse key = " + key + "; value = " + value); + } + else { + throw new IllegalArgumentException(path + "/" + key + ":value 中 value 必须为 依赖路径String 或 SQL子查询JSONObject !"); + } +// } } if (key.endsWith("()")) { @@ -426,7 +439,9 @@ else if (isTable && key.startsWith("@") && JSONRequest.TABLE_KEY_LIST.contains(k customMap.put(key, value); } else { + // 导致副表从 1 开始都不查了 if (isReuse == false) { sqlRequest.put(key, value); + // } } return true; @@ -640,8 +655,8 @@ public AbstractObjectParser setSQLConfig() throws Exception { @Override public AbstractObjectParser setSQLConfig(int count, int page, int position) throws Exception { - if (isTable == false) { - return this; + if (isTable == false || isReuse) { + return setPosition(position); } if (sqlConfig == null) { @@ -676,28 +691,30 @@ public AbstractObjectParser executeSQL() throws Exception { //执行SQL操作数据库 if (isTable == false) {//提高性能 sqlReponse = new JSONObject(sqlRequest); - } else { - try { - sqlReponse = onSQLExecute(); - } - catch (NotExistException e) { - // Log.e(TAG, "getObject try { response = getSQLObject(config2); } catch (Exception e) {"); - // if (e instanceof NotExistException) {//非严重异常,有时候只是数据不存在 - // // e.printStackTrace(); - sqlReponse = null;//内部吃掉异常,put到最外层 - // requestObject.put(JSONResponse.KEY_MSG - // , StringUtil.getString(requestObject.get(JSONResponse.KEY_MSG) - // + "; query " + path + " cath NotExistException:" - // + newErrorResult(e).getString(JSONResponse.KEY_MSG))); - // } else { - // throw e; - // } + } + else { + try { + sqlReponse = onSQLExecute(); + + } + catch (NotExistException e) { + // Log.e(TAG, "getObject try { response = getSQLObject(config2); } catch (Exception e) {"); + // if (e instanceof NotExistException) {//非严重异常,有时候只是数据不存在 + // // e.printStackTrace(); + sqlReponse = null;//内部吃掉异常,put到最外层 + // requestObject.put(JSONResponse.KEY_MSG + // , StringUtil.getString(requestObject.get(JSONResponse.KEY_MSG) + // + "; query " + path + " cath NotExistException:" + // + newErrorResult(e).getString(JSONResponse.KEY_MSG))); + // } else { + // throw e; + // } + } } if (drop) {//丢弃Table,只为了向下提供条件 sqlReponse = null; } - } return this; } @@ -799,10 +816,39 @@ public Object onReferenceParse(@NotNull String path) { @Override public JSONObject onSQLExecute() throws Exception { - JSONObject result = parser.executeSQL(sqlConfig, isSubquery); - if (isSubquery == false && result != null) { - parser.putQueryResult(path, result);//解决获取关联数据时requestObject里不存在需要的关联数据 + int position = getPosition(); + + JSONObject result; + if (isArrayMainTable && position > 0) { // 数组主表使用专门的缓存数据 + result = parser.getArrayMainCacheItem(parentPath.substring(0, parentPath.lastIndexOf("[]") + 2), position); } + 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) { + String arrayPath = parentPath.substring(0, parentPath.lastIndexOf("[]") + 2); + + for (int i = 1; i < list.size(); i++) { // 从 1 开始,0 已经处理过 + JSONObject obj = parser.parseCorrectResponse(table, list.get(i)); + list.set(i, obj); + + if (obj != null) { + parser.putQueryResult(arrayPath + "/" + i + "/" + table, obj); //解决获取关联数据时requestObject里不存在需要的关联数据 + } + } + + parser.putArrayMainCache(arrayPath, list); + } + } + + if (isSubquery == false && result != null) { + parser.putQueryResult(path, result);//解决获取关联数据时requestObject里不存在需要的关联数据 + } + } + return result; } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 85bbb4c1e..1f0880128 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -14,7 +14,6 @@ import java.sql.Savepoint; import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; @@ -792,8 +791,8 @@ public JSONObject onObjectParse(final JSONObject request JSONObject response = null; - if (op != null) {//TODO SQL查询结果为空时,functionMap和customMap还有没有意义? - if (arrayConfig == null) {//Common + if (op != null) {//SQL查询结果为空时,functionMap和customMap没有意义 + if (arrayConfig == null) { //Common response = op.setSQLConfig().executeSQL().response(); } else {//Array Item Child @@ -1431,6 +1430,20 @@ public static JSONObject getJSONObject(JSONObject object, String key) { public static final String KEY_CONFIG = "config"; + + protected Map> arrayMainCacheMap = new HashMap<>(); + public void putArrayMainCache(String arrayPath, List mainTableDataList) { + arrayMainCacheMap.put(arrayPath, mainTableDataList); + } + public List getArrayMainCache(String arrayPath) { + return arrayMainCacheMap.get(arrayPath); + } + 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 @@ -1451,8 +1464,9 @@ public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Except } try { - boolean explain = config.isExplain(); JSONObject result; + + boolean explain = config.isExplain(); if (explain) { //如果先执行 explain,则 execute 会死循环,所以只能先执行非 explain config.setExplain(false); //对下面 config.getSQL(false); 生效 JSONObject res = getSQLExecutor().execute(config, false); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 92508640d..47c2d46e5 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -155,7 +155,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws config.setPrepared(prepared); if (StringUtil.isEmpty(sql, true)) { - Log.e(TAG, "select StringUtil.isEmpty(sql, true) >> return null;"); + Log.e(TAG, "execute StringUtil.isEmpty(sql, true) >> return null;"); return null; } @@ -169,7 +169,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws long startTime = System.currentTimeMillis(); Log.d(TAG, "\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" + "\n已生成 " + generatedSQLCount + " 条 SQL" - + "\nselect startTime = " + startTime + + "\nexecute startTime = " + startTime + "\ndatabase = " + StringUtil.getString(config.getDatabase()) + "; schema = " + StringUtil.getString(config.getSchema()) + "; sql = \n" + sql @@ -225,11 +225,11 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws case GET: case GETS: result = getCacheItem(sql, position, config.getCache()); - Log.i(TAG, ">>> select result = getCache('" + sql + "', " + position + ") = " + result); + Log.i(TAG, ">>> execute result = getCache('" + sql + "', " + position + ") = " + result); if (result != null) { cachedSQLCount ++; - Log.d(TAG, "\n\n select result != null >> return result;" + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n"); + Log.d(TAG, "\n\n execute result != null >> return result;" + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n"); return result; } @@ -241,7 +241,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws break; default://OPTIONS, TRACE等 - Log.e(TAG, "select sql = " + sql + " ; method = " + config.getMethod() + " >> return null;"); + Log.e(TAG, "execute sql = " + sql + " ; method = " + config.getMethod() + " >> return null;"); return null; } } @@ -266,7 +266,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws int viceColumnStart = length + 1; //第一个副表字段的index while (rs.next()) { index ++; - Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n select while (rs.next()){ index = " + index + "\n\n"); + Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n execute while (rs.next()){ index = " + index + "\n\n"); JSONObject item = new JSONObject(true); @@ -293,7 +293,7 @@ else if (config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { resultList = onPutTable(config, rs, rsmd, resultList, index, item); - Log.d(TAG, "\n select while (rs.next()) { resultList.put( " + index + ", result); " + Log.d(TAG, "\n execute while (rs.next()) { resultList.put( " + index + ", result); " + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n"); } @@ -345,12 +345,24 @@ else if (config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { } putCache(sql, resultList, config.getCache()); - Log.i(TAG, ">>> select putCache('" + sql + "', resultList); resultList.size() = " + resultList.size()); + Log.i(TAG, ">>> execute putCache('" + sql + "', resultList); resultList.size() = " + resultList.size()); + // 数组主表对象额外一次返回全部,方便 Parser 缓存来提高性能 + + result = position >= resultList.size() ? new JSONObject() : resultList.get(position); + if (position == 0 && resultList.size() > 1 && result != null && result.isEmpty() == false) { + // 不是 main 不会直接执行,count=1 返回的不会超过 1 && config.isMain() && config.getCount() != 1 + Log.i(TAG, ">>> execute position == 0 && resultList.size() > 1 && result != null && result.isEmpty() == false" + + " >> result = new JSONObject(result); result.put(KEY_RAW_LIST, resultList);"); + + result = new JSONObject(result); + result.put(KEY_RAW_LIST, resultList); + } + long endTime = System.currentTimeMillis(); - Log.d(TAG, "\n\n select endTime = " + endTime + "; duration = " + (endTime - startTime) + Log.d(TAG, "\n\n execute endTime = " + endTime + "; duration = " + (endTime - startTime) + "\n return resultList.get(" + position + ");" + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n"); - return position >= resultList.size() ? new JSONObject() : resultList.get(position); + return result; } diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java index b2b0425d5..9fe5917a7 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java @@ -22,6 +22,8 @@ */ public interface SQLExecutor { + String KEY_RAW_LIST = "@RAW@LIST"; // 避免和字段命名冲突,不用 $RAW@LIST$ 是因为 $ 会在 fastjson 内部转义,浪费性能 + /**保存缓存 * @param sql * @param map From 2d193df9992aee4ed1fc020b34fbc5c87a330e4a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 31 Jan 2021 01:24:53 +0800 Subject: [PATCH 024/944] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index ab37d9f31..09d127be1 100644 --- a/Document.md +++ b/Document.md @@ -371,6 +371,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
"!" - OUTTER 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":{},
   "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"}}})

⑤ 每一层都加当前用户名:
["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":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... 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;sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"Comment":{"@column":"date%3bsum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... 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)"}})
From e73a1a9385a31955300bddff23c54d17a89b44ee Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 31 Jan 2021 01:26:19 +0800 Subject: [PATCH 025/944] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 09d127be1..727187cfe 100644 --- a/Document.md +++ b/Document.md @@ -371,6 +371,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
"!" - OUTTER 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":{},
   "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"}}})

⑤ 每一层都加当前用户名:
["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;sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"Comment":{"@column":"date%3bsum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... 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;sum(if(userId%2=0,1,0))",
"@group":"date",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Comment":{"@column":"date%3bsum(if(userId%252=0,1,0))","@group":"date","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... 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)"}})
From 3ce23f7fcea50be963a4e008c3b8aa14911b4ea2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 31 Jan 2021 01:26:58 +0800 Subject: [PATCH 026/944] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 727187cfe..8479881eb 100644 --- a/Document.md +++ b/Document.md @@ -371,6 +371,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
"!" - OUTTER 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":{},
   "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"}}})

⑤ 每一层都加当前用户名:
["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;sum(if(userId%2=0,1,0))",
"@group":"date",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Comment":{"@column":"date%3bsum(if(userId%252=0,1,0))","@group":"date","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... 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;sum(if(userId%2=0,1,0))",
"@group":"date",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Comment":{"@column":"date%3bsum(if(userId%252=0,1,0))","@group":"date","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date,sum(if(userId%2=0,1,0)) ... GROUP BY date 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)"}})
From a890072145d9bf7aa7c713a40b2ab6992f0c8159 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 31 Jan 2021 01:29:00 +0800 Subject: [PATCH 027/944] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 8479881eb..202f9ff7f 100644 --- a/Document.md +++ b/Document.md @@ -371,6 +371,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
"!" - OUTTER 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":{},
   "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"}}})

⑤ 每一层都加当前用户名:
["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;sum(if(userId%2=0,1,0))",
"@group":"date",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Comment":{"@column":"date%3bsum(if(userId%252=0,1,0))","@group":"date","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date,sum(if(userId%2=0,1,0)) ... GROUP BY date 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":"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/{"[]":{"Comment":{"@column":"left(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 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)"}})
From 859cdf5f60dbf97f24141eb37faaab91538d9db8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 31 Jan 2021 01:30:23 +0800 Subject: [PATCH 028/944] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 202f9ff7f..e064a8a86 100644 --- a/Document.md +++ b/Document.md @@ -371,6 +371,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
"!" - OUTTER 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":{},
   "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"}}})

⑤ 每一层都加当前用户名:
["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":"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/{"[]":{"Comment":{"@column":"left(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 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/{"[]":{"Comment":{"@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)"}})
From 5a73b23b7bdff764bf3579917f025ab9c18f099f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 31 Jan 2021 01:32:21 +0800 Subject: [PATCH 029/944] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index e064a8a86..f21c8153a 100644 --- a/Document.md +++ b/Document.md @@ -371,6 +371,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
"!" - OUTTER 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":{},
   "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"}}})

⑤ 每一层都加当前用户名:
["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/{"[]":{"Comment":{"@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)"}})
From cf1cca0254a47cf7d8a4f0b25fc56ba9c5ef1130 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 31 Jan 2021 02:24:37 +0800 Subject: [PATCH 030/944] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20"toId%":=20"0,10"?= =?UTF-8?q?=20=E7=AD=89=E8=BF=9E=E7=BB=AD=E8=8C=83=E5=9B=B4=E6=8A=A5?= =?UTF-8?q?=E9=94=99=20value=20=E7=B1=BB=E5=9E=8B=E4=B8=8D=E5=90=88?= =?UTF-8?q?=E6=B3=95=EF=BC=9B=E8=A7=A3=E5=86=B3=20"id{}@":=20"[]/Moment/pr?= =?UTF-8?q?aiseUserIdList"=20=E7=AD=89=E5=BC=95=E7=94=A8=E8=B5=8B=E5=80=BC?= =?UTF-8?q?=E7=9A=84=E5=80=BC=E6=9C=89=E6=97=B6=E7=B1=BB=E5=9E=8B=E4=B8=BA?= =?UTF-8?q?=20List=20=E6=97=B6=E6=8A=A5=E9=94=99=20ArrayList=20cannot=20be?= =?UTF-8?q?=20cast=20to=20JSONArray=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractSQLConfig.java | 10 ++++++++-- .../src/main/java/apijson/orm/AbstractVerifier.java | 3 ++- 2 files 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 e6f3fd9cc..169fe6d4e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1920,7 +1920,10 @@ public String getRegExpString(String key, String value, boolean ignoreCase) { * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getBetweenString(String key, Object value) throws IllegalArgumentException { + public String getBetweenString(String key, 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 ""; } @@ -1960,7 +1963,7 @@ public String getBetweenString(String key, Object[] values, int type) throws Ill throw new IllegalArgumentException(key + "%:value 中 value 不合法!类型为 String 时必须包括1个逗号 , 且左右两侧都有值!类型为 String[] 里面每个元素要符合前面类型为 String 的规则 !"); } - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + "(" + getBetweenString(key, vs[0], vs[1]) + ")"; + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + "(" + getBetweenString(key, (Object) vs[0], (Object) vs[1]) + ")"; } return getCondition(Logic.isNot(type), condition); @@ -2149,6 +2152,9 @@ public String getExistsString(String key, Object value, String rawSQL) throws Ex */ @JSONField(serialize = false) public String getContainString(String key, 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 ""; } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 2e14f0881..b85c1e19f 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -262,7 +262,8 @@ public boolean verifyAccess(SQLConfig config) throws Exception { //key!{}:[] 或 其它没有明确id的条件 等 可以和key{}:list组合。类型错误就报错 requestId = (Number) config.getWhere(visitorIdkey, true);//JSON里数值不能保证是Long,可能是Integer - JSONArray requestIdArray = (JSONArray) config.getWhere(visitorIdkey + "{}", true);//不能是 &{}, |{} 不要传,直接{} + @SuppressWarnings("unchecked") + Collection requestIdArray = (Collection) config.getWhere(visitorIdkey + "{}", true);//不能是 &{}, |{} 不要传,直接{} if (requestId != null) { if (requestIdArray == null) { requestIdArray = new JSONArray(); From 6831cb6a6d9b0db24b612628d5dc31243493b7a4 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 31 Jan 2021 04:20:26 +0800 Subject: [PATCH 031/944] =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E5=AE=89=E5=85=A8?= =?UTF-8?q?=EF=BC=9A=E5=AF=B9=20DELETE=20=E5=92=8C=20PUT=20=E5=BC=BA?= =?UTF-8?q?=E5=88=B6=E5=8A=A0=20LIMIT=EF=BC=9B=E7=AE=80=E5=8C=96=E5=8C=85?= =?UTF-8?q?=E5=90=AB=E9=80=89=E9=A1=B9=E7=9A=84=E5=86=99=E6=B3=95=EF=BC=9A?= =?UTF-8?q?=E8=A7=A3=E5=86=B3=20"key<>":=20"a"=20=E8=BF=99=E7=A7=8D?= =?UTF-8?q?=E5=8C=85=E5=90=AB=E5=AD=97=E7=AC=A6=E4=B8=B2=E7=9A=84=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E6=8A=A5=E9=94=99=20Data=20truncation:=20Invalid=20JS?= =?UTF-8?q?ON=20text=EF=BC=8C=E5=8E=9F=E6=9D=A5=E5=BF=85=E9=A1=BB=E9=87=8C?= =?UTF-8?q?=E9=9D=A2=E5=86=8D=E7=94=A8=20""=20=E5=8C=85=E8=A3=85=E4=B8=80?= =?UTF-8?q?=E6=AC=A1=EF=BC=8CJSON=20=E4=B8=AD=E8=BF=98=E5=BE=97=E8=BD=AC?= =?UTF-8?q?=E4=B9=89=EF=BC=8C=E7=8E=B0=E5=9C=A8=E7=9B=B4=E6=8E=A5=E5=86=99?= =?UTF-8?q?=E5=8D=B3=E5=8F=AF=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apijson/orm/AbstractObjectParser.java | 4 +-- .../java/apijson/orm/AbstractSQLConfig.java | 29 +++++++++++++------ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 2ac9447e6..b40c9ca0b 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -650,7 +650,7 @@ public SQLConfig newSQLConfig(boolean isProcedure) throws Exception { */ @Override public AbstractObjectParser setSQLConfig() throws Exception { - return setSQLConfig(1, 0, 0); + return setSQLConfig(RequestMethod.isQueryMethod(method) ? 1 : 0, 0, 0); } @Override @@ -668,7 +668,7 @@ public AbstractObjectParser setSQLConfig(int count, int page, int position) thro return this; } } - sqlConfig.setCount(count).setPage(page).setPosition(position); + sqlConfig.setCount(sqlConfig.getCount() <= 0 ? count : sqlConfig.getCount()).setPage(page).setPosition(position); parser.onVerifyRole(sqlConfig); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 169fe6d4e..e6b08a494 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1284,11 +1284,11 @@ public String getLimitString() { public static String getLimitString(int page, int count, boolean isTSQL) { int offset = getOffset(page, count); - if (isTSQL) { + if (isTSQL) { // OFFSET FECTH 中所有关键词都不可省略 return " OFFSET " + offset + " ROWS FETCH FIRST " + count + " ROWS ONLY"; } - return " LIMIT " + count + " OFFSET " + offset; + return " LIMIT " + count + (offset <= 0 ? "" : " OFFSET " + offset); // DELETE, UPDATE 不支持 OFFSET } //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @@ -2179,20 +2179,23 @@ public String getContainString(String key, Object[] childs, int type) throws Ill String condition = ""; if (childs != null) { for (int i = 0; i < childs.length; i++) { - if (childs[i] != null) { - if (childs[i] instanceof JSON) { + Object c = childs[i]; + if (c != null) { + if (c instanceof JSON) { throw new IllegalArgumentException(key + "<>:value 中value类型不能为JSON!"); } condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)); if (isPostgreSQL()) { - condition += (getKey(key) + " @> " + getValue(newJSONArray(childs[i]))); //operator does not exist: jsonb @> character varying "[" + childs[i] + "]"); + condition += (getKey(key) + " @> " + getValue(newJSONArray(c))); //operator does not exist: jsonb @> character varying "[" + c + "]"); } else if (isOracle()) { - condition += ("json_textcontains(" + getKey(key) + ", '$', " + getValue(childs[i].toString()) + ")"); + condition += ("json_textcontains(" + getKey(key) + ", '$', " + getValue(c.toString()) + ")"); } else { - condition += ("json_contains(" + getKey(key) + ", " + getValue(childs[i].toString()) + ")"); + boolean isNum = c instanceof Number; + String v = (isNum ? "" : "\"") + childs[i] + (isNum ? "" : "\""); + condition += ("json_contains(" + getKey(key) + ", " + getValue(v) + ")"); } } } @@ -2390,9 +2393,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); + return "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) + config.getLimitString(); case DELETE: - return "DELETE FROM " + tablePath + config.getWhereString(true); + return "DELETE FROM " + tablePath + config.getWhereString(true) + config.getLimitString(); default: String explain = (config.isExplain() ? (config.isSQLServer() || config.isOracle() ? "SET STATISTICS PROFILE ON " : "EXPLAIN ") : ""); if (config.isTest() && RequestMethod.isGetMethod(config.getMethod(), true)) { @@ -2635,6 +2638,10 @@ public static SQLConfig newSQLConfig(RequestMethod method, String table, String throw new NotExistException(TAG + ": newSQLConfig idIn instanceof List >> 去掉无效 id 后 newIdIn.isEmpty()"); } idIn = newIdIn; + + if (method == DELETE || method == PUT) { + config.setCount(newIdIn.size()); + } } //对id和id{}处理,这两个一定会作为条件 @@ -2670,6 +2677,10 @@ else if (id instanceof Subquery) {} throw new NotExistException(TAG + ": newSQLConfig idIn != null && (((List) idIn).contains(id) == false"); } } + + if (method == DELETE || method == PUT) { + config.setCount(1); + } } From a406242a81f2b303a1c55e6a4f5c3c835e62e53a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 31 Jan 2021 19:35:52 +0800 Subject: [PATCH 032/944] =?UTF-8?q?=E3=80=90=E6=80=A7=E8=83=BD=E3=80=91?= =?UTF-8?q?=E9=80=9A=E8=BF=87=E7=BC=93=E5=AD=98=E5=8F=8A=E5=A4=8D=E7=94=A8?= =?UTF-8?q?=E6=95=B0=E7=BB=84=E4=B8=BB=E8=A1=A8=20ObjectParser=20=E6=9D=A5?= =?UTF-8?q?=E5=A4=A7=E5=B9=85=E6=8F=90=E5=8D=87=E6=9F=A5=E8=AF=A2=E6=95=B0?= =?UTF-8?q?=E7=BB=84=E7=9A=84=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 | 457 ++++++++-------- .../main/java/apijson/orm/AbstractParser.java | 41 +- .../java/apijson/orm/AbstractSQLConfig.java | 501 +++++++++--------- .../main/java/apijson/orm/ObjectParser.java | 8 +- .../src/main/java/apijson/orm/Parser.java | 2 +- 5 files changed, 528 insertions(+), 481 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index b40c9ca0b..f5a2e079f 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -61,12 +61,6 @@ public AbstractObjectParser setParser(AbstractParser parser) { protected final List joinList; protected final boolean isTable; protected final boolean isArrayMainTable; - protected final boolean isReuse; - protected final String path; - protected final String name; - protected final String table; - protected final String alias; - protected final boolean tri; /** @@ -80,26 +74,23 @@ public AbstractObjectParser setParser(AbstractParser parser) { * @param name * @throws Exception */ - public AbstractObjectParser(@NotNull JSONObject request, String parentPath, String name, SQLConfig arrayConfig, boolean isSubquery) throws Exception { + public AbstractObjectParser(@NotNull JSONObject request, String parentPath, SQLConfig arrayConfig + , boolean isSubquery, boolean isTable, boolean isArrayMainTable) throws Exception { if (request == null) { throw new IllegalArgumentException(TAG + ".ObjectParser request == null!!!"); } this.request = request; this.parentPath = parentPath; + this.arrayConfig = arrayConfig; this.isSubquery = isSubquery; this.type = arrayConfig == null ? 0 : arrayConfig.getType(); this.joinList = arrayConfig == null ? null : arrayConfig.getJoinList(); - this.name = name; - this.path = AbstractParser.getAbsPath(parentPath, name); - - apijson.orm.Entry entry = Pair.parseEntry(name, true); - this.table = entry.getKey(); - this.alias = entry.getValue(); - this.isTable = apijson.JSONObject.isTableKey(table); - this.isArrayMainTable = isSubquery == false && this.isTable && this.type == SQLConfig.TYPE_ITEM_CHILD_0 && RequestMethod.isGetMethod(method, true); - this.isReuse = isArrayMainTable && arrayConfig != null && arrayConfig.getPosition() > 0; + + 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.objectCount = 0; this.arrayCount = 0; @@ -117,11 +108,10 @@ public AbstractObjectParser(@NotNull JSONObject request, String parentPath, Stri request.remove(KEY_DROP); } - - Log.d(TAG, "AbstractObjectParser table = " + table + "; isTable = " + isTable); - Log.d(TAG, "AbstractObjectParser isEmpty = " + isEmpty + "; tri = " + tri + "; drop = " + drop); } + + protected int position; public int getPosition() { return position; @@ -148,6 +138,12 @@ public boolean isBreakParse() { return breakParse || isInvalidate(); } + protected String name; + protected String table; + protected String alias; + protected boolean isReuse; + protected String parentName; + protected String path; protected JSONObject response; protected JSONObject sqlRequest; @@ -177,118 +173,135 @@ public boolean isBreakParse() { * @throws Exception */ @Override - public AbstractObjectParser parse() throws Exception { + public AbstractObjectParser parse(String name, boolean isReuse) throws Exception { if (isInvalidate() == false) { + this.isReuse = isReuse; + this.name = name; + this.path = AbstractParser.getAbsPath(parentPath, name); + + 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 + sqlReponse = null;//must init - sqlRequest = new JSONObject(true);//must init + if (isReuse == false) { + sqlRequest = new JSONObject(true);//must init - sqlReponse = null;//must init - customMap = null;//must init - functionMap = null;//must init - childMap = null;//must init - - Set> set = request.isEmpty() ? null : new LinkedHashSet>(request.entrySet()); - if (set != null && set.isEmpty() == false) {//判断换取少几个变量的初始化是否值得? - if (isTable) {//非Table下必须保证原有顺序!否则 count,page 会丢, total@:"/[]/total" 会在[]:{}前执行! - customMap = new LinkedHashMap(); - childMap = new LinkedHashMap(); - } - functionMap = new LinkedHashMap>();//必须执行 - - - //条件<<<<<<<<<<<<<<<<<<< - List whereList = null; - if (method == PUT) { //这里只有PUTArray需要处理 || method == DELETE) { - String[] combine = StringUtil.split(request.getString(KEY_COMBINE)); - if (combine != null) { - String w; - for (int i = 0; i < combine.length; i++) { //去除 &,|,! 前缀 - w = combine[i]; - if (w != null && (w.startsWith("&") || w.startsWith("|") || w.startsWith("!"))) { - combine[i] = w.substring(1); + customMap = null;//must init + functionMap = null;//must init + childMap = null;//must init + + Set> set = request.isEmpty() ? null : new LinkedHashSet>(request.entrySet()); + if (set != null && set.isEmpty() == false) {//判断换取少几个变量的初始化是否值得? + if (isTable) {//非Table下必须保证原有顺序!否则 count,page 会丢, total@:"/[]/total" 会在[]:{}前执行! + customMap = new LinkedHashMap(); + childMap = new LinkedHashMap(); + } + functionMap = new LinkedHashMap>();//必须执行 + + //条件<<<<<<<<<<<<<<<<<<< + List whereList = null; + if (method == PUT) { //这里只有PUTArray需要处理 || method == DELETE) { + String[] combine = StringUtil.split(request.getString(KEY_COMBINE)); + if (combine != null) { + String w; + for (int i = 0; i < combine.length; i++) { //去除 &,|,! 前缀 + w = combine[i]; + if (w != null && (w.startsWith("&") || w.startsWith("|") || w.startsWith("!"))) { + combine[i] = w.substring(1); + } } } + //Arrays.asList()返回值不支持add方法! + whereList = new ArrayList(Arrays.asList(combine != null ? combine : new String[]{})); + whereList.add(apijson.JSONRequest.KEY_ID); + whereList.add(apijson.JSONRequest.KEY_ID_IN); } - //Arrays.asList()返回值不支持add方法! - whereList = new ArrayList(Arrays.asList(combine != null ? combine : new String[]{})); - whereList.add(apijson.JSONRequest.KEY_ID); - whereList.add(apijson.JSONRequest.KEY_ID_IN); - } - //条件>>>>>>>>>>>>>>>>>>> + //条件>>>>>>>>>>>>>>>>>>> - String key; - Object value; - int index = 0; + String key; + Object value; + int index = 0; - for (Entry entry : set) { - if (isBreakParse()) { - break; - } + for (Entry entry : set) { + if (isBreakParse()) { + break; + } - value = entry.getValue(); - if (value == null) { - continue; - } - key = entry.getKey(); + value = entry.getValue(); + if (value == null) { + continue; + } + key = entry.getKey(); - try { - if (key.startsWith("@") || key.endsWith("@")) { - if (onParse(key, value) == false) { - invalidate(); + try { + if (key.startsWith("@") || key.endsWith("@")) { + if (onParse(key, value) == false) { + invalidate(); + } } - } - else if (value instanceof JSONObject) { // JSONObject,往下一级提取 - if (childMap != null) { // 添加到childMap,最后再解析 - childMap.put(key, (JSONObject)value); + else if (value instanceof JSONObject) { // JSONObject,往下一级提取 + if (childMap != null) { // 添加到childMap,最后再解析 + childMap.put(key, (JSONObject)value); + } + else { // 直接解析并替换原来的,[]:{} 内必须直接解析,否则会因为丢掉count等属性,并且total@:"/[]/total"必须在[]:{} 后! + response.put(key, onChildParse(index, key, (JSONObject)value)); + index ++; + } } - else { // 直接解析并替换原来的,[]:{} 内必须直接解析,否则会因为丢掉count等属性,并且total@:"/[]/total"必须在[]:{} 后! - response.put(key, onChildParse(index, key, (JSONObject)value)); - index ++; + else if ((method == POST || method == PUT) && value instanceof JSONArray + && JSONRequest.isTableArray(key)) { // JSONArray,批量新增或修改,往下一级提取 + onTableArrayParse(key, (JSONArray) value); } - } - 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 - onPUTArrayParse(key, (JSONArray) value); - } - else { // JSONArray或其它Object,直接填充 - if (onParse(key, value) == false) { - invalidate(); + else if (method == PUT && value instanceof JSONArray + && (whereList == null || whereList.contains(key) == false)) { // PUT JSONArray + onPUTArrayParse(key, (JSONArray) value); } + else { // JSONArray或其它Object,直接填充 + if (onParse(key, value) == false) { + invalidate(); + } + } + } catch (Exception e) { + if (tri == false) { + throw e; // 不忽略错误,抛异常 + } + invalidate(); // 忽略错误,还原request } - } catch (Exception e) { - if (tri == false) { - throw e; // 不忽略错误,抛异常 - } - invalidate(); // 忽略错误,还原request } - } - // 非Table内的函数会被滞后在onChildParse后调用! onFunctionResponse("-"); - } - - if (isReuse == false && isTable) { - if (parser.getGlobleDatabase() != null && sqlRequest.get(JSONRequest.KEY_DATABASE) == null) { - sqlRequest.put(JSONRequest.KEY_DATABASE, parser.getGlobleDatabase()); - } - if (parser.getGlobleSchema() != null && sqlRequest.get(JSONRequest.KEY_SCHEMA) == null) { - sqlRequest.put(JSONRequest.KEY_SCHEMA, parser.getGlobleSchema()); } - if (isSubquery == false) { //解决 SQL 语法报错,子查询不能 EXPLAIN - if (parser.getGlobleExplain() != null && sqlRequest.get(JSONRequest.KEY_EXPLAIN) == null) { - sqlRequest.put(JSONRequest.KEY_EXPLAIN, parser.getGlobleExplain()); + + if (isTable) { + if (parser.getGlobleDatabase() != null && sqlRequest.get(JSONRequest.KEY_DATABASE) == null) { + sqlRequest.put(JSONRequest.KEY_DATABASE, parser.getGlobleDatabase()); } - if (parser.getGlobleCache() != null && sqlRequest.get(JSONRequest.KEY_CACHE) == null) { - sqlRequest.put(JSONRequest.KEY_CACHE, parser.getGlobleCache()); + if (parser.getGlobleSchema() != null && sqlRequest.get(JSONRequest.KEY_SCHEMA) == null) { + sqlRequest.put(JSONRequest.KEY_SCHEMA, parser.getGlobleSchema()); + } + if (isSubquery == false) { //解决 SQL 语法报错,子查询不能 EXPLAIN + if (parser.getGlobleExplain() != null && sqlRequest.get(JSONRequest.KEY_EXPLAIN) == null) { + sqlRequest.put(JSONRequest.KEY_EXPLAIN, parser.getGlobleExplain()); + } + if (parser.getGlobleCache() != null && sqlRequest.get(JSONRequest.KEY_CACHE) == null) { + sqlRequest.put(JSONRequest.KEY_CACHE, parser.getGlobleCache()); + } } } } + + if (isTable) { // 非Table内的函数会被滞后在onChildParse后调用 + onFunctionResponse("-"); + } + } if (isInvalidate()) { @@ -311,95 +324,93 @@ else if (method == PUT && value instanceof JSONArray @Override public boolean onParse(@NotNull String key, @NotNull Object value) throws Exception { if (key.endsWith("@")) { // StringUtil.isPath((String) value)) { - String replaceKey = key.substring(0, key.length() - 1); - // [] 内主表 position > 0 时,用来生成 SQLConfig 的键值对全都忽略,不解析 -// if (isReuse == false || replaceKey.endsWith("()") || (replaceKey.startsWith("@") && JSONRequest.TABLE_KEY_LIST.contains(replaceKey) == false)) { - if (value instanceof JSONObject) { // key{}@ getRealKey, SQL 子查询对象,JSONObject -> SQLConfig.getSQL + if (value instanceof JSONObject) { // key{}@ getRealKey, SQL 子查询对象,JSONObject -> SQLConfig.getSQL + String replaceKey = key.substring(0, key.length() - 1); - JSONObject subquery = (JSONObject) value; - String range = subquery.getString(JSONRequest.KEY_SUBQUERY_RANGE); - if (range != null && JSONRequest.SUBQUERY_RANGE_ALL.equals(range) == false && JSONRequest.SUBQUERY_RANGE_ANY.equals(range) == false) { - throw new IllegalArgumentException("子查询 " + path + "/" + key + ":{ range:value } 中 value 只能为 [" + JSONRequest.SUBQUERY_RANGE_ALL + ", " + JSONRequest.SUBQUERY_RANGE_ANY + "] 中的一个!"); - } + JSONObject subquery = (JSONObject) value; + String range = subquery.getString(JSONRequest.KEY_SUBQUERY_RANGE); + if (range != null && JSONRequest.SUBQUERY_RANGE_ALL.equals(range) == false && JSONRequest.SUBQUERY_RANGE_ANY.equals(range) == false) { + throw new IllegalArgumentException("子查询 " + path + "/" + key + ":{ range:value } 中 value 只能为 [" + JSONRequest.SUBQUERY_RANGE_ALL + ", " + JSONRequest.SUBQUERY_RANGE_ANY + "] 中的一个!"); + } - JSONArray arr = parser.onArrayParse(subquery, path, key, true); + JSONArray arr = parser.onArrayParse(subquery, path, key, true); - JSONObject obj = arr == null || arr.isEmpty() ? null : arr.getJSONObject(0); - if (obj == null) { - throw new Exception("服务器内部错误,解析子查询 " + path + "/" + key + ":{ } 为 Subquery 对象失败!"); - } + JSONObject obj = arr == null || arr.isEmpty() ? null : arr.getJSONObject(0); + if (obj == null) { + throw new Exception("服务器内部错误,解析子查询 " + path + "/" + key + ":{ } 为 Subquery 对象失败!"); + } - String from = subquery.getString(JSONRequest.KEY_SUBQUERY_FROM); - JSONObject arrObj = from == null ? null : obj.getJSONObject(from); - 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"); - } + String from = subquery.getString(JSONRequest.KEY_SUBQUERY_FROM); + JSONObject arrObj = from == null ? null : obj.getJSONObject(from); + 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"); + } + + Subquery s = new Subquery(); + s.setPath(path); + s.setOriginKey(key); + s.setOriginValue(subquery); + + s.setFrom(from); + s.setRange(range); + s.setKey(replaceKey); + s.setConfig(cfg); - Subquery s = new Subquery(); - s.setPath(path); - s.setOriginKey(key); - s.setOriginValue(subquery); + key = replaceKey; + value = s; //(range == null || range.isEmpty() ? "" : "range") + "(" + cfg.getSQL(false) + ") "; + + parser.putQueryResult(AbstractParser.getAbsPath(path, key), s); //字符串引用保证不了安全性 parser.getSQL(cfg)); + } + else if (value instanceof String) { // //key{}@ getRealKey, 引用赋值路径 + String replaceKey = key.substring(0, key.length() - 1); - s.setFrom(from); - s.setRange(range); - s.setKey(replaceKey); - s.setConfig(cfg); + // System.out.println("getObject key.endsWith(@) >> parseRelation = " + parseRelation); + String targetPath = AbstractParser.getValuePath(type == TYPE_ITEM ? path : parentPath, new String((String) value)); - key = replaceKey; - value = s; //(range == null || range.isEmpty() ? "" : "range") + "(" + cfg.getSQL(false) + ") "; + //先尝试获取,尽量保留缺省依赖路径,这样就不需要担心路径改变 + Object target = onReferenceParse(targetPath); + Log.i(TAG, "onParse targetPath = " + targetPath + "; target = " + target); - parser.putQueryResult(AbstractParser.getAbsPath(path, key), s); //字符串引用保证不了安全性 parser.getSQL(cfg)); + if (target == null) {//String#equals(null)会出错 + Log.d(TAG, "onParse target == null >> return true;"); + return true; } - else if (value instanceof String) { // //key{}@ getRealKey, 引用赋值路径 - // System.out.println("getObject key.endsWith(@) >> parseRelation = " + parseRelation); - String targetPath = AbstractParser.getValuePath(type == TYPE_ITEM - ? path : parentPath, new String((String) value)); - - //先尝试获取,尽量保留缺省依赖路径,这样就不需要担心路径改变 - Object target = onReferenceParse(targetPath); - Log.i(TAG, "onParse targetPath = " + targetPath + "; target = " + target); - - if (target == null) {//String#equals(null)会出错 - Log.d(TAG, "onParse target == null >> return true;"); - return true; + if (target instanceof Map) { //target可能是从requestObject里取出的 {} + if (isTable || targetPath.endsWith("[]/" + JSONResponse.KEY_INFO) == false) { + Log.d(TAG, "onParse target instanceof Map >> return false;"); + return false; //FIXME 这个判断现在来看是否还有必要?为啥不允许为 JSONObject ?以前可能因为防止二次遍历再解析,现在只有一次遍历 } - if (target instanceof Map) { //target可能是从requestObject里取出的 {} - if (isTable || targetPath.endsWith("[]/" + JSONResponse.KEY_INFO) == false) { - Log.d(TAG, "onParse target instanceof Map >> return false;"); - return false; //FIXME 这个判断现在来看是否还有必要?为啥不允许为 JSONObject ?以前可能因为防止二次遍历再解析,现在只有一次遍历 - } + } + if (targetPath.equals(target)) {//必须valuePath和保证getValueByPath传进去的一致! + Log.d(TAG, "onParse targetPath.equals(target) >>"); + + //非查询关键词 @key 不影响查询,直接跳过 + if (isTable && (key.startsWith("@") == false || JSONRequest.TABLE_KEY_LIST.contains(key))) { + Log.e(TAG, "onParse isTable && (key.startsWith(@) == false" + + " || JSONRequest.TABLE_KEY_LIST.contains(key)) >> return null;"); + return false;//获取不到就不用再做无效的query了。不考虑 Table:{Table:{}}嵌套 + } else { + Log.d(TAG, "onParse isTable(table) == false >> return true;"); + return true;//舍去,对Table无影响 } - if (targetPath.equals(target)) {//必须valuePath和保证getValueByPath传进去的一致! - Log.d(TAG, "onParse targetPath.equals(target) >>"); - - //非查询关键词 @key 不影响查询,直接跳过 - if (isTable && (key.startsWith("@") == false || JSONRequest.TABLE_KEY_LIST.contains(key))) { - Log.e(TAG, "onParse isTable && (key.startsWith(@) == false" - + " || JSONRequest.TABLE_KEY_LIST.contains(key)) >> return null;"); - return false;//获取不到就不用再做无效的query了。不考虑 Table:{Table:{}}嵌套 - } else { - Log.d(TAG, "onParse isTable(table) == false >> return true;"); - return true;//舍去,对Table无影响 - } - } + } - //直接替换原来的key@:path为key:target - Log.i(TAG, "onParse >> key = replaceKey; value = target;"); - key = replaceKey; - value = target; - Log.d(TAG, "onParse key = " + key + "; value = " + value); - } - else { - throw new IllegalArgumentException(path + "/" + key + ":value 中 value 必须为 依赖路径String 或 SQL子查询JSONObject !"); - } -// } + //直接替换原来的key@:path为key:target + Log.i(TAG, "onParse >> key = replaceKey; value = target;"); + key = replaceKey; + value = target; + Log.d(TAG, "onParse key = " + key + "; value = " + value); + } + else { + throw new IllegalArgumentException(path + "/" + key + ":value 中 value 必须为 依赖路径String 或 SQL子查询JSONObject !"); + } } if (key.endsWith("()")) { @@ -410,21 +421,24 @@ else if (value instanceof String) { // //key{}@ getRealKey, 引用赋值路径 String k = key.substring(0, key.length() - 2); String type; //远程函数比较少用,一般一个Table:{}内用到也就一两个,所以这里用 "-","0","+" 更直观,转用 -1,0,1 对性能提升不大。 - if (k.endsWith("-")) { //不能封装到functionMap后批量执行,否则会导致非Table内的 key-():function() 在onChildParse后执行! + boolean isMinus = k.endsWith("-"); + if (isMinus) { //不能封装到functionMap后批量执行,否则会导致非Table内的 key-():function() 在onChildParse后执行! type = "-"; k = k.substring(0, k.length() - 1); - parseFunction(k, (String) value, parentPath, name, request); + if (isTable == false) { + parseFunction(k, (String) value, parentPath, name, request); + } + } + else if (k.endsWith("+")) { + type = "+"; + k = k.substring(0, k.length() - 1); } else { - if (k.endsWith("+")) { - type = "+"; - k = k.substring(0, k.length() - 1); - } - else { - type = "0"; - } + type = "0"; + } + if (isMinus == false || isTable) { //远程函数比较少用,一般一个Table:{}内用到也就一两个,所以这里循环里new出来对性能影响不大。 Map map = functionMap.get(type); if (map == null) { @@ -439,9 +453,7 @@ else if (isTable && key.startsWith("@") && JSONRequest.TABLE_KEY_LIST.contains(k customMap.put(key, value); } else { - // 导致副表从 1 开始都不查了 if (isReuse == false) { sqlRequest.put(key, value); - // } } return true; @@ -590,7 +602,7 @@ public void onTableArrayParse(String key, JSONArray value) throws Exception { String idKey = parser.createSQLConfig().getIdKey(); //Table[]: [{}] arrayConfig 为 null boolean isNeedVerifyContent = parser.isNeedVerifyContent(); - + for (int i = 0; i < valueArray.size(); i++) { //只要有一条失败,则抛出异常,全部失败 //TODO 改成一条多 VALUES 的 SQL 性能更高,报错也更会更好处理,更人性化 JSONObject item; @@ -693,28 +705,27 @@ public AbstractObjectParser executeSQL() throws Exception { sqlReponse = new JSONObject(sqlRequest); } else { - try { - sqlReponse = onSQLExecute(); - - } - catch (NotExistException e) { - // Log.e(TAG, "getObject try { response = getSQLObject(config2); } catch (Exception e) {"); - // if (e instanceof NotExistException) {//非严重异常,有时候只是数据不存在 - // // e.printStackTrace(); - sqlReponse = null;//内部吃掉异常,put到最外层 - // requestObject.put(JSONResponse.KEY_MSG - // , StringUtil.getString(requestObject.get(JSONResponse.KEY_MSG) - // + "; query " + path + " cath NotExistException:" - // + newErrorResult(e).getString(JSONResponse.KEY_MSG))); - // } else { - // throw e; - // } - } + try { + sqlReponse = onSQLExecute(); } - - if (drop) {//丢弃Table,只为了向下提供条件 - sqlReponse = null; + catch (NotExistException e) { + // Log.e(TAG, "getObject try { response = getSQLObject(config2); } catch (Exception e) {"); + // if (e instanceof NotExistException) {//非严重异常,有时候只是数据不存在 + // // e.printStackTrace(); + sqlReponse = null;//内部吃掉异常,put到最外层 + // requestObject.put(JSONResponse.KEY_MSG + // , StringUtil.getString(requestObject.get(JSONResponse.KEY_MSG) + // + "; query " + path + " cath NotExistException:" + // + newErrorResult(e).getString(JSONResponse.KEY_MSG))); + // } else { + // throw e; + // } } + } + + if (drop) {//丢弃Table,只为了向下提供条件 + sqlReponse = null; + } return this; } @@ -799,10 +810,16 @@ public void onChildResponse() throws Exception { if (set != null) { int index = 0; for (Entry entry : set) { - if (entry != null) { - response.put(entry.getKey(), onChildParse(index, entry.getKey(), entry.getValue())); - index ++; + Object child = entry == null ? null : onChildParse(index, entry.getKey(), entry.getValue()); + if (child == null + || (child instanceof JSONObject && ((JSONObject) child).isEmpty()) + || (child instanceof JSONArray && ((JSONArray) child).isEmpty()) + ) { + continue; } + + response.put(entry.getKey(), child ); + index ++; } } } @@ -834,21 +851,21 @@ public JSONObject onSQLExecute() throws Exception { for (int i = 1; i < list.size(); i++) { // 从 1 开始,0 已经处理过 JSONObject obj = parser.parseCorrectResponse(table, list.get(i)); list.set(i, obj); - + if (obj != null) { parser.putQueryResult(arrayPath + "/" + i + "/" + table, obj); //解决获取关联数据时requestObject里不存在需要的关联数据 } } - + parser.putArrayMainCache(arrayPath, list); } } - + if (isSubquery == false && result != null) { parser.putQueryResult(path, result);//解决获取关联数据时requestObject里不存在需要的关联数据 } } - + return result; } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 1f0880128..55ca32b0c 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -752,6 +752,8 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, + protected Map arrayObjectParserCacheMap = new HashMap<>(); + // protected SQLConfig itemConfig; /**获取单个对象,该对象处于parentObject内 * @param parentPath parentObject的路径 @@ -774,9 +776,10 @@ public JSONObject onObjectParse(final JSONObject request } int type = arrayConfig == null ? 0 : arrayConfig.getType(); + int position = arrayConfig == null ? 0 : arrayConfig.getPosition(); String[] arr = StringUtil.split(parentPath, "/"); - if (arrayConfig == null || arrayConfig.getPosition() == 0) { + if (position == 0) { int d = arr == null ? 1 : arr.length + 1; if (queryDepth < d) { queryDepth = d; @@ -786,12 +789,24 @@ public JSONObject onObjectParse(final JSONObject request } } } + + boolean isTable = apijson.JSONObject.isTableKey(name); + boolean isArrayMainTable = isSubquery == false && isTable && type == SQLConfig.TYPE_ITEM_CHILD_0 && arrayConfig != null && RequestMethod.isGetMethod(arrayConfig.getMethod(), true); + boolean isReuse = isArrayMainTable && position > 0; - ObjectParser op = createObjectParser(request, parentPath, name, arrayConfig, isSubquery).parse(); - - + ObjectParser op = null; + 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(); } @@ -799,9 +814,12 @@ public JSONObject onObjectParse(final JSONObject request int query = arrayConfig.getQuery(); //total 这里不能用arrayConfig.getType(),因为在createObjectParser.onChildParse传到onObjectParse时已被改掉 - if (type == SQLConfig.TYPE_ITEM_CHILD_0 && query != JSONRequest.QUERY_TABLE - && arrayConfig.getPosition() == 0) { + 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) { @@ -842,14 +860,21 @@ public JSONObject onObjectParse(final JSONObject request response = null;//不再往后查询 } else { response = op - .setSQLConfig(arrayConfig.getCount(), arrayConfig.getPage(), arrayConfig.getPosition()) + .setSQLConfig(arrayConfig.getCount(), arrayConfig.getPage(), position) .executeSQL() .response(); // itemConfig = op.getConfig(); } } - op.recycle(); + if (isArrayMainTable) { + if (position == 0) { // 提取并缓存数组主表的列表数据 + arrayObjectParserCacheMap.put(parentPath.substring(0, parentPath.lastIndexOf("[]") + 2), op); + } + } +// else { +// op.recycle(); +// } op = null; } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index e6b08a494..6defdd1e1 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -84,7 +84,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { // * 和 / 不能同时出现,防止 /* */ 段注释! # 和 -- 不能出现,防止行注释! ; 不能出现,防止隔断SQL语句!空格不能出现,防止 CRUD,DROP,SHOW TABLES等语句! private static final Pattern PATTERN_RANGE; private static final Pattern PATTERN_FUNCTION; - + /** * 表名映射,隐藏真实表名,对安全要求很高的表可以这么做 */ @@ -96,8 +96,8 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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 改成更好的正则,校验前面为单词,中间为操作符,后面为值 - - + + TABLE_KEY_MAP = new HashMap(); TABLE_KEY_MAP.put(Table.class.getSimpleName(), Table.TABLE_NAME); TABLE_KEY_MAP.put(Column.class.getSimpleName(), Column.TABLE_NAME); @@ -530,13 +530,13 @@ public String getHavingString(boolean hasPrefix) { if (keys == null || keys.length <= 0) { return StringUtil.isEmpty(joinHaving, true) ? "" : (hasPrefix ? " HAVING " : "") + joinHaving; } - + String quote = getQuote(); String tableAlias = getAliasWithQuote(); List raw = getRaw(); boolean containRaw = raw != null && raw.contains(KEY_HAVING); - + String expression; String method; //暂时不允许 String prefix; @@ -560,7 +560,7 @@ public String getHavingString(boolean hasPrefix) { + "} catch (Exception e) = " + e.getMessage()); } } - + if (expression.length() > 50) { throw new UnsupportedOperationException("@having:value 的 value 中字符串 " + expression + " 不合法!" + "不允许传超过 50 个字符的函数或表达式!请用 @raw 简化传参!"); @@ -958,13 +958,13 @@ public String getColumnString(boolean inSQLJoin) throws Exception { method = expression.substring(0, start); boolean distinct = i <= 0 && method.startsWith(PREFFIX_DISTINCT); String fun = distinct ? method.substring(PREFFIX_DISTINCT.length()) : method; - + if (fun.isEmpty() == false && StringUtil.isName(fun) == false) { throw new IllegalArgumentException("字符 " + method + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中SQL函数名 function 必须符合正则表达式 ^[0-9a-zA-Z_]+$ !"); } - + } boolean isColumn = start < 0; @@ -1049,13 +1049,13 @@ else if (StringUtil.isName(origin)) { 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 (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...\"" @@ -2393,9 +2393,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.getLimitString(); + return "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); case DELETE: - return "DELETE FROM " + tablePath + config.getWhereString(true) + config.getLimitString(); + 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)) { @@ -2615,15 +2615,9 @@ public static SQLConfig newSQLConfig(RequestMethod method, String table, String String userIdKey = callback.getUserIdKey(database, schema, table); String userIdInKey = userIdKey + "{}"; - Object idIn = request.get(idInKey); //可能是 id{}:">0" - - if (method == POST && request.get(idKey) == null) { - Object newId = callback.newId(method, database, schema, table); // null 表示数据库自增 id - if (newId != null) { - request.put(idKey, newId); - } - } + //对id和id{}处理,这两个一定会作为条件 + Object idIn = request.get(idInKey); //可能是 id{}:">0" if (idIn instanceof List) { // 排除掉 0, 负数, 空字符串 等无效 id 值 List ids = ((List) idIn); List newIdIn = new ArrayList<>(); @@ -2638,14 +2632,18 @@ public static SQLConfig newSQLConfig(RequestMethod method, String table, String throw new NotExistException(TAG + ": newSQLConfig idIn instanceof List >> 去掉无效 id 后 newIdIn.isEmpty()"); } idIn = newIdIn; - + if (method == DELETE || method == PUT) { config.setCount(newIdIn.size()); } } - - //对id和id{}处理,这两个一定会作为条件 + Object id = request.get(idKey); + boolean hasId = id != null; + if (method == POST && hasId == false) { + id = callback.newId(method, database, schema, table); // null 表示数据库自增 id + } + if (id != null) { //null无效 if (id instanceof Number) { if (((Number) id).longValue() <= 0) { //一定没有值 @@ -2677,7 +2675,7 @@ else if (id instanceof Subquery) {} throw new NotExistException(TAG + ": newSQLConfig idIn != null && (((List) idIn).contains(id) == false"); } } - + if (method == DELETE || method == PUT) { config.setCount(1); } @@ -2695,271 +2693,276 @@ else if (id instanceof Subquery) {} String raw = request.getString(KEY_RAW); String json = request.getString(KEY_JSON); - //强制作为条件且放在最前面优化性能 - request.remove(idKey); - request.remove(idInKey); - //关键词 - request.remove(KEY_ROLE); - request.remove(KEY_EXPLAIN); - request.remove(KEY_CACHE); - request.remove(KEY_DATABASE); - request.remove(KEY_SCHEMA); - request.remove(KEY_COMBINE); - request.remove(KEY_FROM); - request.remove(KEY_COLUMN); - request.remove(KEY_GROUP); - request.remove(KEY_HAVING); - request.remove(KEY_ORDER); - request.remove(KEY_RAW); - request.remove(KEY_JSON); - - String[] rawArr = StringUtil.split(raw); - config.setRaw(rawArr == null || rawArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(rawArr))); - - Map tableWhere = new LinkedHashMap();//保证顺序好优化 WHERE id > 1 AND name LIKE... - - //已经remove了id和id{},以及@key - Set set = request.keySet(); //前面已经判断request是否为空 - if (method == POST) { //POST操作 - if (idIn != null) { - throw new IllegalArgumentException("POST 请求中不允许传 " + idInKey + " !"); - } - - if (set != null && set.isEmpty() == false) { //不能直接return,要走完下面的流程 - String[] columns = set.toArray(new String[]{}); - - Collection valueCollection = request.values(); - Object[] values = valueCollection == null ? null : valueCollection.toArray(); - - if (values == null || values.length != columns.length) { - throw new Exception("服务器内部错误:\n" + TAG - + " newSQLConfig values == null || values.length != columns.length !"); + try { + //强制作为条件且放在最前面优化性能 + request.remove(idKey); + request.remove(idInKey); + //关键词 + request.remove(KEY_ROLE); + request.remove(KEY_EXPLAIN); + request.remove(KEY_CACHE); + request.remove(KEY_DATABASE); + request.remove(KEY_SCHEMA); + request.remove(KEY_COMBINE); + request.remove(KEY_FROM); + request.remove(KEY_COLUMN); + request.remove(KEY_GROUP); + request.remove(KEY_HAVING); + request.remove(KEY_ORDER); + request.remove(KEY_RAW); + request.remove(KEY_JSON); + + String[] rawArr = StringUtil.split(raw); + config.setRaw(rawArr == null || rawArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(rawArr))); + + Map tableWhere = new LinkedHashMap();//保证顺序好优化 WHERE id > 1 AND name LIKE... + + //已经remove了id和id{},以及@key + Set set = request.keySet(); //前面已经判断request是否为空 + if (method == POST) { //POST操作 + if (idIn != null) { + throw new IllegalArgumentException("POST 请求中不允许传 " + idInKey + " !"); } - column = (id == null ? "" : idKey + ",") + 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数量为准 + if (set != null && set.isEmpty() == false) { //不能直接return,要走完下面的流程 + String[] columns = set.toArray(new String[]{}); - items = new ArrayList<>(size); - items.add(id); //idList.get(i)); //第0个就是id + Collection valueCollection = request.values(); + Object[] values = valueCollection == null ? null : valueCollection.toArray(); - for (int j = 1; j < size; j++) { - items.add(values[j-1]); //从第1个开始,允许"null" + if (values == null || values.length != columns.length) { + throw new Exception("服务器内部错误:\n" + TAG + + " newSQLConfig values == null || values.length != columns.length !"); } - } + column = (id == null ? "" : idKey + ",") + StringUtil.getString(columns); //set已经判断过不为空 - valuess.add(items); - config.setValues(valuess); - } - } - else { //非POST操作 - final boolean isWhere = method != PUT;//除了POST,PUT,其它全是条件!!! - - //条件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - List whereList = null; + 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数量为准 - Map> combineMap = new LinkedHashMap<>(); - List andList = new ArrayList<>(); - List orList = new ArrayList<>(); - List notList = new ArrayList<>(); + items = new ArrayList<>(size); + items.add(id); //idList.get(i)); //第0个就是id - //强制作为条件且放在最前面优化性能 - if (id != null) { - tableWhere.put(idKey, id); - andList.add(idKey); - } - if (idIn != null) { - tableWhere.put(idInKey, idIn); - andList.add(idInKey); - } + for (int j = 1; j < size; j++) { + items.add(values[j-1]); //从第1个开始,允许"null" + } + } - String[] ws = StringUtil.split(combine); - if (ws != null) { - if (method == DELETE || method == GETS || method == HEADS) { - throw new IllegalArgumentException("DELETE,GETS,HEADS 请求不允许传 @combine:value !"); + valuess.add(items); + config.setValues(valuess); } - 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 { //非POST操作 + final boolean isWhere = method != PUT;//除了POST,PUT,其它全是条件!!! + + //条件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + 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 !"); + } + 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); - } } - } + //条件>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - //条件>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + Map tableContent = new LinkedHashMap(); + Object value; + for (String key : set) { + value = request.get(key); - Map tableContent = new LinkedHashMap(); - Object value; - for (String key : set) { - value = request.get(key); - - if (value instanceof Map) {//只允许常规Object - throw new IllegalArgumentException("不允许 " + key + " 等任何key的value类型为 {JSONObject} !"); - } + if (value instanceof Map) {//只允许常规Object + throw new IllegalArgumentException("不允许 " + key + " 等任何key的value类型为 {JSONObject} !"); + } - //解决AccessVerifier新增userId没有作为条件,而是作为内容,导致PUT,DELETE出错 - if (isWhere) { - tableWhere.put(key, value); - if (whereList == null || whereList.contains(key) == false) { - andList.add(key); + //解决AccessVerifier新增userId没有作为条件,而是作为内容,导致PUT,DELETE出错 + if (isWhere) { + tableWhere.put(key, value); + if (whereList == null || whereList.contains(key) == false) { + andList.add(key); + } + } + else if (whereList != null && whereList.contains(key)) { + tableWhere.put(key, value); + } + else { + tableContent.put(key, value);//一样 instanceof JSONArray ? JSON.toJSONString(value) : value); } } - else if (whereList != null && whereList.contains(key)) { - tableWhere.put(key, value); - } - else { - tableContent.put(key, value);//一样 instanceof JSONArray ? JSON.toJSONString(value) : value); - } - } - combineMap.put("&", andList); - combineMap.put("|", orList); - combineMap.put("!", notList); - config.setCombine(combineMap); + combineMap.put("&", andList); + combineMap.put("|", orList); + combineMap.put("!", notList); + config.setCombine(combineMap); - config.setContent(tableContent); - } + config.setContent(tableContent); + } - List cs = new ArrayList<>(); + List cs = new ArrayList<>(); - List rawList = config.getRaw(); - boolean containColumnRaw = rawList != null && rawList.contains(KEY_COLUMN); + List rawList = config.getRaw(); + 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); + 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()); } - } catch (Exception e) { - Log.e(TAG, "newSQLConfig config instanceof AbstractSQLConfig >> try { " - + " rawColumnSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, column); " - + "} catch (Exception e) = " + e.getMessage()); - } - } - - boolean distinct = column == null || rawColumnSQL != null ? false : column.startsWith(PREFFIX_DISTINCT); - if (rawColumnSQL == null) { - String[] fks = StringUtil.split(distinct ? column.substring(PREFFIX_DISTINCT.length()) : column, ";"); // key0,key1;fun0(key0,...);fun1(key0,...);key3;fun2(key0,...) - if (fks != null) { - String[] ks; - for (String fk : fks) { - if (containColumnRaw) { - try { - String rawSQL = config.getRawSQL(KEY_COLUMN, fk); - if (rawSQL != null) { - cs.add(rawSQL); - continue; + } + + boolean distinct = column == null || rawColumnSQL != null ? false : column.startsWith(PREFFIX_DISTINCT); + if (rawColumnSQL == null) { + String[] fks = StringUtil.split(distinct ? column.substring(PREFFIX_DISTINCT.length()) : column, ";"); // key0,key1;fun0(key0,...);fun1(key0,...);key3;fun2(key0,...) + if (fks != null) { + 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()); } - } catch (Exception e) { - Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { " - + " String rawSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, fk); ... " - + "} catch (Exception e) = " + e.getMessage()); } - } - if (fk.contains("(")) { // fun0(key0,...) - cs.add(fk); - } - else { //key0,key1... - ks = StringUtil.split(fk); - if (ks != null && ks.length > 0) { - cs.addAll(Arrays.asList(ks)); + if (fk.contains("(")) { // fun0(key0,...) + cs.add(fk); + } + else { //key0,key1... + ks = StringUtil.split(fk); + if (ks != null && ks.length > 0) { + cs.addAll(Arrays.asList(ks)); + } } } } } - } - 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.setId(id); - //在 tableWhere 第0个 config.setIdIn(idIn); - - config.setRole(RequestRole.get(role)); - 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 等要合并 - - //后面还可能用到,要还原 - //id或id{}条件 - request.put(idKey, id); - request.put(idInKey, idIn); - //关键词 - request.put(KEY_DATABASE, database); - request.put(KEY_ROLE, role); - request.put(KEY_EXPLAIN, explain); - request.put(KEY_CACHE, cache); - request.put(KEY_SCHEMA, schema); - request.put(KEY_COMBINE, combine); - request.put(KEY_FROM, from); - request.put(KEY_COLUMN, column); - request.put(KEY_GROUP, group); - request.put(KEY_HAVING, having); - request.put(KEY_ORDER, order); - request.put(KEY_RAW, raw); - request.put(KEY_JSON, json); + 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.setId(id); + //在 tableWhere 第0个 config.setIdIn(idIn); + + config.setRole(RequestRole.get(role)); + 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 等要合并 + + } + finally {//后面还可能用到,要还原 + //id或id{}条件 + if (hasId) { + request.put(idKey, id); + } + request.put(idInKey, idIn); + //关键词 + request.put(KEY_DATABASE, database); + request.put(KEY_ROLE, role); + request.put(KEY_EXPLAIN, explain); + request.put(KEY_CACHE, cache); + request.put(KEY_SCHEMA, schema); + request.put(KEY_COMBINE, combine); + request.put(KEY_FROM, from); + request.put(KEY_COLUMN, column); + request.put(KEY_GROUP, group); + request.put(KEY_HAVING, having); + request.put(KEY_ORDER, order); + request.put(KEY_RAW, raw); + request.put(KEY_JSON, json); + } return config; } diff --git a/APIJSONORM/src/main/java/apijson/orm/ObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/ObjectParser.java index a43a2d879..452ced293 100755 --- a/APIJSONORM/src/main/java/apijson/orm/ObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/ObjectParser.java @@ -20,14 +20,15 @@ */ public interface ObjectParser { - /**解析成员 * response重新赋值 - * @param config 传递给第0个Table + * @param parentPath + * @param name + * @param isReuse * @return null or this * @throws Exception */ - ObjectParser parse() throws Exception; + ObjectParser parse(String name, boolean isReuse) throws Exception; /**调用 parser 的 sqlExecutor 来解析结果 * @param method @@ -159,4 +160,5 @@ public interface ObjectParser { Map> getFunctionMap(); Map getChildMap(); + } diff --git a/APIJSONORM/src/main/java/apijson/orm/Parser.java b/APIJSONORM/src/main/java/apijson/orm/Parser.java index 4e09c5738..dee098b29 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Parser.java +++ b/APIJSONORM/src/main/java/apijson/orm/Parser.java @@ -93,7 +93,7 @@ JSONObject parseCorrectRequest(RequestMethod method, String tag, int version, St */ Object onFunctionParse(String key, String function, String parentPath, String currentName, JSONObject currentObject) throws Exception; - ObjectParser createObjectParser(JSONObject request, String parentPath, String name, SQLConfig arrayConfig, boolean isSubquery) throws Exception; + ObjectParser createObjectParser(JSONObject request, String parentPath, SQLConfig arrayConfig, boolean isSubquery, boolean isTable, boolean isArrayMainTable) throws Exception; int getDefaultQueryCount(); int getMaxQueryPage(); From 9cd448713a97163159f21bbd712f214c6420d254 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 1 Feb 2021 00:03:37 +0800 Subject: [PATCH 033/944] =?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.6.0?= 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 6802c6753..2ae05b340 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.5.1 + 4.6.0 jar APIJSONORM From 68f847e81f4987fe975e305c063e3bf6d088cade Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 2 Feb 2021 16:12:55 +0800 Subject: [PATCH 034/944] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index f21c8153a..f80e36c6a 100644 --- a/Document.md +++ b/Document.md @@ -370,7 +370,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
"!" - OUTTER 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":{},
   "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"}}})

⑤ 每一层都加当前用户名:
["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
"!" - OUTTER 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)"}})
From d564cfca003456076b574784d16d79665c3d4a71 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 2 Feb 2021 16:18:24 +0800 Subject: [PATCH 035/944] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index f80e36c6a..aeab529fb 100644 --- a/Document.md +++ b/Document.md @@ -370,7 +370,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
"!" - OUTTER 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
"!" - OUTTER 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)"}})
From e59238e0222d40d9227afb6cd6212eece109acbe Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 2 Feb 2021 16:20:25 +0800 Subject: [PATCH 036/944] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index aeab529fb..f6ff8dc19 100644 --- a/Document.md +++ b/Document.md @@ -370,7 +370,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
"!" - OUTTER 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
"!" - OUTTER 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)"}})
From 47c896a1adef34fc6ba14eb85b43f7af12deecff Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 3 Feb 2021 21:49:10 +0800 Subject: [PATCH 037/944] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=8F=91=E7=9A=84=E6=96=87=E7=AB=A0=20"apijson=E7=AE=80?= =?UTF-8?q?=E5=8D=95demo"?= 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 4981da0d3..8e10555ef 100644 --- a/README.md +++ b/README.md @@ -319,6 +319,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [APIJSON对接分布式HTAP数据库TiDB](https://asktug.com/t/htap-tidb/395) +[apijson简单demo](https://blog.csdn.net/dmw412724/article/details/113558115) + [apijson简单使用](https://www.cnblogs.com/greyzeng/p/14311995.html) [APIJSON简单部署和使用](https://blog.csdn.net/m450744192/article/details/108462611) From 1ba92a4bdd48660617b3d146d6fd33495a9984e8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 5 Feb 2021 15:33:04 +0800 Subject: [PATCH 038/944] Update Roadmap.md --- Roadmap.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Roadmap.md b/Roadmap.md index 6f0e3e5b7..55ce594fb 100644 --- a/Roadmap.md +++ b/Roadmap.md @@ -191,6 +191,9 @@ https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/ ### 提高性能 +最近的两次大幅提升性能相关优化及 Release
+[4.6.0【性能】大幅提升数组内主表查询性能](https://github.com/Tencent/APIJSON/releases/tag/4.6.0)
+[4.4.5【性能】大幅提升增删改的性能](https://github.com/Tencent/APIJSON/releases)
#### 解析 JSON From ae4e52039f07b8ed03366a7d485429b473d03dc6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 5 Feb 2021 15:39:55 +0800 Subject: [PATCH 039/944] Update Roadmap.md --- Roadmap.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Roadmap.md b/Roadmap.md index 55ce594fb..74c3b4ca4 100644 --- a/Roadmap.md +++ b/Roadmap.md @@ -191,9 +191,9 @@ https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/ ### 提高性能 -最近的两次大幅提升性能相关优化及 Release
+20200205 更新:最近的两次大幅提升性能相关优化及 Release
[4.6.0【性能】大幅提升数组内主表查询性能](https://github.com/Tencent/APIJSON/releases/tag/4.6.0)
-[4.4.5【性能】大幅提升增删改的性能](https://github.com/Tencent/APIJSON/releases)
+[4.4.5【性能】大幅提升增删改的性能](https://github.com/Tencent/APIJSON/releases/tag/4.4.5)
#### 解析 JSON @@ -222,6 +222,9 @@ APIJSON 代码经过商业分析软件 [源伞Pinpoint](https://www.sourcebrella https://github.com/Tencent/APIJSON/issues/48
但我们需要再接再厉,尽可能做到 99.999% 可靠度,降低使用风险,让用户放心和安心。
+20200205 更新:已经解决了 [源伞科技](https://www.sourcebrella.com) 以上报告中的大部分问题 及 [奇安信代码卫士](https://github.com/QiAnXinCodeSafe) 发现的部分问题 +https://github.com/Tencent/APIJSON/issues/created_by/QiAnXinCodeSafe + #### 减少 Bug ##### [APIAuto](https://github.com/TommyLemon/APIAuto) 上统计的 bug @@ -241,6 +244,9 @@ https://gitee.com/TommyLemon/UnitAuto
### 完善文档 +20200205 更新:最近完善及更新了通用文档、上手文档、图文入门文档等,还对首页引导文档加了导航目录 +https://github.com/Tencent/APIJSON/blob/master/Navigation.md + #### 中文文档 ##### 通用文档 @@ -294,6 +300,8 @@ https://github.com/APIJSON/APIJSON-Demo
https://github.com/Tencent/APIJSON#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90 +20200205 更新:最近首页相关推荐新增了 1 篇官方发的文章和 6 篇用户发的文章 +https://github.com/Tencent/APIJSON/blob/master/README.md#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90 #### 登记正在使用 APIJSON 的公司或项目 From 3847d2b3c242009d7cf77f88944fb15f353dfd73 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 6 Feb 2021 16:03:34 +0800 Subject: [PATCH 040/944] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e10555ef..b48279c5c 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON -

🏆 码云最有价值开源项目
🚀 后端接口和文档自动化,前端(客户端) 定制返回JSON的数据和结构!

+

🏆 码云最有价值开源项目
🚀 后端接口和文档自动化,前端(客户端) 定制返回 JSON 的数据和结构!

From e7058a000487a9ff7a6c8f143528997ee0dc85fb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 6 Feb 2021 16:30:03 +0800 Subject: [PATCH 041/944] =?UTF-8?q?"=E4=B8=BA=E4=BB=80=E4=B9=88=E4=B8=80?= =?UTF-8?q?=E5=AE=9A=E8=A6=81=E8=B4=A1=E7=8C=AE=E4=BB=A3=E7=A0=81=EF=BC=9F?= =?UTF-8?q?"=20=E6=96=B0=E5=A2=9E=E7=AE=80=E5=8E=86=E4=B8=8E=E9=9D=A2?= =?UTF-8?q?=E8=AF=95=E7=9B=B8=E5=85=B3=E8=AF=B4=E6=98=8E?= 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 af41fa23b..321f0e142 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,7 +32,7 @@ APIJSON 已开发近 4 年,在此感谢所有开发者对于 APIJSON 的喜欢 加入 APIJSON ,共同打造一个更棒的自动化 ORM 库!🍾🎉 ### 为什么一定要贡献代码? -贡献代码可以避免你碰到以下麻烦:
+APIJSON 作为腾讯开源的知名热门项目,贡献代码除了可以给简历加亮点、为面试加分,还可以避免你碰到以下麻烦:
1.你在 APIJSON 上更改的代码其他人看不到,不能帮你发现 Bug,更不可能帮你修复 Bug 甚至优化代码
2.作者和其它贡献者可能不兼容你更改的代码,导致你的项目在升级 APIJSON 版本后在功能甚至编译上出错
3.你需要自己维护你的代码,每次升级 APIJSON 版本时,你都需要下载 APIJSON 新代码再合并你自己的更改
From 4858f02d3f41f318e91ecb264d36b6c865b95c93 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 6 Feb 2021 19:46:09 +0800 Subject: [PATCH 042/944] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E8=A1=A8=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E5=BD=93=E8=A1=A8=E5=90=8D=E6=9C=89=E5=88=AB=E5=90=8D?= =?UTF-8?q?=E6=97=B6=E8=A2=AB=E5=BD=93=E6=88=90=E6=99=AE=E9=80=9A=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E5=AF=BC=E8=87=B4=E6=9F=A5=E4=B8=8D=E5=88=B0=E6=95=B0?= =?UTF-8?q?=E6=8D=AE?= 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/AbstractParser.java | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index f5a2e079f..f83c9e8bb 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -853,7 +853,7 @@ public JSONObject onSQLExecute() throws Exception { list.set(i, obj); if (obj != null) { - parser.putQueryResult(arrayPath + "/" + i + "/" + table, obj); //解决获取关联数据时requestObject里不存在需要的关联数据 + parser.putQueryResult(arrayPath + "/" + i + "/" + name, obj); //解决获取关联数据时requestObject里不存在需要的关联数据 } } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 55ca32b0c..9fb17ee05 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -790,7 +790,11 @@ public JSONObject onObjectParse(final JSONObject request } } - boolean isTable = apijson.JSONObject.isTableKey(name); + apijson.orm.Entry entry = Pair.parseEntry(name, true); + String table = entry.getKey(); //Comment + // String alias = entry.getValue(); //to + + boolean isTable = apijson.JSONObject.isTableKey(table); boolean isArrayMainTable = isSubquery == false && isTable && type == SQLConfig.TYPE_ITEM_CHILD_0 && arrayConfig != null && RequestMethod.isGetMethod(arrayConfig.getMethod(), true); boolean isReuse = isArrayMainTable && position > 0; From cb025812658ea8097bd3b1f17f1e48572a9baf15 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 7 Feb 2021 10:38:52 +0800 Subject: [PATCH 043/944] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20Star=20=E6=95=B0?= =?UTF-8?q?=E4=B8=BA=2010K?= 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 b48279c5c..eed57eac4 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ https://github.com/Tencent/APIJSON/wiki * **解决十大痛点** (APIJSON 大幅提振开发效率、强力杜绝联调扯皮、巧妙规避文档缺陷、非常节省流量带宽 等) * **开发提速巨大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) -* **社区影响力大** (GitHub 9.8K Star 在 350W Java 项目中排名前 150,远超 FLAG, BAT 等国内外绝大部分开源项目) +* **社区影响力大** (GitHub 10K Star 在 350W Java 项目中排名前 150,远超 FLAG, BAT 等国内外绝大部分开源项目) * **各项荣誉成就** (腾讯开源五个第一、腾讯首个 GVP 获奖项目、腾讯后端项目 Star 第一、GitHub Java 周榜第一 等) * **多样用户案例** (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等) * **适用场景广泛** (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) From 5877fcb27986db1550be6cd4810749ea4109f69c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 10 Feb 2021 15:03:33 +0800 Subject: [PATCH 044/944] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=99=BB=E8=AE=B0?= =?UTF-8?q?=E7=94=A8=E6=88=B7=20=E6=8A=95=E6=8A=95=E7=A7=91=E6=8A=80-?= =?UTF-8?q?=E8=A1=8C=E4=B8=9A=E9=A2=86=E5=85=88=E7=9A=84=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E5=9E=8B=E9=87=91=E8=9E=8D=E7=A7=91=E6=8A=80=E5=85=AC=E5=8F=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index eed57eac4..c254f948f 100644 --- a/README.md +++ b/README.md @@ -229,6 +229,7 @@ https://github.com/Tencent/APIJSON/issues/187 +
From 41fedcfe75027da8883bb800be1f9e49dbb0556e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 10 Feb 2021 16:35:16 +0800 Subject: [PATCH 045/944] =?UTF-8?q?=E4=B8=BA=E7=94=A8=E6=88=B7=20=E6=8A=95?= =?UTF-8?q?=E6=8A=95=E7=A7=91=E6=8A=80-=E8=A1=8C=E4=B8=9A=E9=A2=86?= =?UTF-8?q?=E5=85=88=E7=9A=84=E5=B9=B3=E5=8F=B0=E5=9E=8B=E9=87=91=E8=9E=8D?= =?UTF-8?q?=E7=A7=91=E6=8A=80=E5=85=AC=E5=8F=B8=20=E6=9B=B4=E9=80=82?= =?UTF-8?q?=E5=90=88=E5=B1=95=E7=A4=BA=E7=9A=84=20logo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index c254f948f..f60d6029d 100644 --- a/README.md +++ b/README.md @@ -218,18 +218,18 @@ https://github.com/Tencent/APIJSON/issues/36 如果您在使用 APIJSON,请让我们知道,您的使用对我们非常重要(按登记顺序排列):
https://github.com/Tencent/APIJSON/issues/187

- - - - - - - - - - - - + + + + + + + + + + + +
From 22d1861739113acf8770ae7b059c0e1afebfb26f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Feb 2021 15:58:53 +0800 Subject: [PATCH 046/944] =?UTF-8?q?=E9=80=9A=E7=94=A8=E6=96=87=E6=A1=A3?= =?UTF-8?q?=EF=BC=9A=E6=96=B0=E5=A2=9E=E5=AF=B9=E4=B8=87=E8=83=BD=E9=80=9A?= =?UTF-8?q?=E7=94=A8=20API=20=E5=AF=B9=E5=BA=94=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E5=A2=9E=E5=88=A0=E6=94=B9=E6=9F=A5=E7=9A=84=20SQL=20=E8=AF=B4?= =?UTF-8?q?=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Document.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Document.md b/Document.md index f6ff8dc19..fe2d02702 100644 --- a/Document.md +++ b/Document.md @@ -330,13 +330,13 @@  方法及说明 | URL | Request | Response ------------ | ------------ | ------------ | ------------ -GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 235 的 Moment:
{
   "Moment":{
     "id":235
   }
} | {
   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
   }
} | {
   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
   }
}
后端校验通过后自动解析为 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"
} GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,其它同GET | 同GET HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,其它同HEAD | 同HEAD -POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如一个 id = 38710 的 User 发布一个新 Moment:
{
   "Moment":{
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}

批量:
{
   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[]"
}
| 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "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{} 至少传一个

例如修改 id = 235 的 Moment 的 content:
{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id | 同POST -DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如批量删除 id = 100,110,120 的 Comment:
{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
} | {
   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由后端生成,不能传

例如一个 id = 38710 的 User 发布一个新 Moment:
{
   "Moment":{
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Moment(userId,content) VALUES(38710,'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"
}
例如
{
   "Moment":{
     "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{} 至少传一个

例如修改 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 | 同POST +DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如批量删除 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"
} 1.TableName指要查询的数据库表Table的名称字符串。第一个字符为大写字母,剩下的字符要符合英语字母、数字、下划线中的任何一种。对应的值的类型为JSONObject,结构是 {...},里面放的是Table的字段(列名)。下同。
From 00f6843babab2b6425087f55f4ad73e0f65307ec Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Feb 2021 16:11:37 +0800 Subject: [PATCH 047/944] Update Document.md --- Document.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Document.md b/Document.md index fe2d02702..3eb2bfd3a 100644 --- a/Document.md +++ b/Document.md @@ -334,9 +334,9 @@ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {< 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"
} GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,其它同GET | 同GET HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,其它同HEAD | 同HEAD -POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如一个 id = 38710 的 User 发布一个新 Moment:
{
   "Moment":{
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Moment(userId,content) VALUES(38710,'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"
}
例如
{
   "Moment":{
     "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{} 至少传一个

例如修改 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 | 同POST -DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如批量删除 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 发布一个新 Moment:
{
   "Moment":{
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Moment(userId,content) VALUES(38710,'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"
}
例如
{
   "Moment":{
     "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 | 同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"
} 1.TableName指要查询的数据库表Table的名称字符串。第一个字符为大写字母,剩下的字符要符合英语字母、数字、下划线中的任何一种。对应的值的类型为JSONObject,结构是 {...},里面放的是Table的字段(列名)。下同。
From 784051b6211d3e61571824d29f4e5de1cf2dc131 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Feb 2021 16:15:09 +0800 Subject: [PATCH 048/944] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 3eb2bfd3a..eb980951e 100644 --- a/Document.md +++ b/Document.md @@ -334,7 +334,7 @@ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {< 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"
} 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 发布一个新 Moment:
{
   "Moment":{
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Moment(userId,content) VALUES(38710,'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"
}
例如
{
   "Moment":{
     "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 发布一个新 Moment:
{
   "Moment":{
     "content":"APIJSON,let interfaces and documents go to hell !",
     "pictureList":["http://apijson.cn/images/APIJSON_GVPAwardCertificate4Tencent-small.png"]
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Moment(userId,content,pictureList) VALUES(38710,'APIJSON,let interfaces and documents go to hell !','["http://apijson.cn/images/APIJSON_GVPAwardCertificate4Tencent-small.png"]')`

批量:
{
   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"
}
例如
{
   "Moment":{
     "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 | 同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"
} From b5daa2258b9ac9a1b9aec74b9390e582e78e49e8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Feb 2021 16:20:21 +0800 Subject: [PATCH 049/944] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index eb980951e..6de724607 100644 --- a/Document.md +++ b/Document.md @@ -334,7 +334,7 @@ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {< 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"
} 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 发布一个新 Moment:
{
   "Moment":{
     "content":"APIJSON,let interfaces and documents go to hell !",
     "pictureList":["http://apijson.cn/images/APIJSON_GVPAwardCertificate4Tencent-small.png"]
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Moment(userId,content,pictureList) VALUES(38710,'APIJSON,let interfaces and documents go to hell !','["http://apijson.cn/images/APIJSON_GVPAwardCertificate4Tencent-small.png"]')`

批量:
{
   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"
}
例如
{
   "Moment":{
     "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"
}
后端校验通过后自动解析为 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 | 同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"
} From 8012b9376746482fdc1653a8b8d6eda2fde18e10 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Feb 2021 16:21:50 +0800 Subject: [PATCH 050/944] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 6de724607..acc2a0c95 100644 --- a/Document.md +++ b/Document.md @@ -335,7 +335,7 @@ HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | 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 | 同POST +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 | 同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"
} From 46f82aa8ac7be054f3cf563611b203a3de7ede55 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Feb 2021 16:26:02 +0800 Subject: [PATCH 051/944] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index acc2a0c95..45cd44b5f 100644 --- a/Document.md +++ b/Document.md @@ -334,7 +334,7 @@ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {< 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"
} 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"
} +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:[]" //Comment[] 表示所有项全部统一设置,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 | 同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"
} From 1784f812e5ca1a507beedec3cae61f84a001c8d8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Feb 2021 16:27:03 +0800 Subject: [PATCH 052/944] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 45cd44b5f..1bd3e2980 100644 --- a/Document.md +++ b/Document.md @@ -334,7 +334,7 @@ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {< 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"
} 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:[]" //Comment[] 表示所有项全部统一设置,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"
}
后端校验通过后自动解析为 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:[]" //Comment[] 表示全部统一设置,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 | 同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"
} From 48bb2409ece893827d0df2316d8be6faf4a49370 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Feb 2021 16:31:31 +0800 Subject: [PATCH 053/944] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 1bd3e2980..5c3e763bf 100644 --- a/Document.md +++ b/Document.md @@ -334,7 +334,7 @@ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {< 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"
} 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:[]" //Comment[] 表示全部统一设置,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"
}
后端校验通过后自动解析为 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:[]" //Comment[] 表示 id{}:[] 指定记录全部统一设置,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 | 同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"
} From d064196adde941754c954a986bf611234af03f55 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Feb 2021 16:33:23 +0800 Subject: [PATCH 054/944] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 5c3e763bf..51f4f6fd3 100644 --- a/Document.md +++ b/Document.md @@ -334,7 +334,7 @@ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {< 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"
} 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:[]" //Comment[] 表示 id{}:[] 指定记录全部统一设置,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"
}
后端校验通过后自动解析为 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:[]" //Comment[] 对应有 id{}:[] 的对象,表示指定记录全部统一设置;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 | 同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"
} From 42ad76397c99a7b26286cc7b46e150c797a48445 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Feb 2021 16:39:53 +0800 Subject: [PATCH 055/944] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 51f4f6fd3..748919fbf 100644 --- a/Document.md +++ b/Document.md @@ -334,7 +334,7 @@ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {< 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"
} 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:[]" //Comment[] 对应有 id{}:[] 的对象,表示指定记录全部统一设置;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"
}
后端校验通过后自动解析为 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:[]" //Comment[] 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置;Comment:[] 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置
}
后端校验通过后自动解析为 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 | 同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"
} From 706bf929fbd5063050f645bc8d5cd287565247a4 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Feb 2021 16:42:48 +0800 Subject: [PATCH 056/944] Update Document.md --- Document.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Document.md b/Document.md index 748919fbf..701932c9b 100644 --- a/Document.md +++ b/Document.md @@ -334,8 +334,8 @@ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {< 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"
} 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:[]" //Comment[] 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置;Comment:[] 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置
}
后端校验通过后自动解析为 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 | 同POST +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"
} From b11f138432bb31d40801d3ef11eb883c591ac8ff Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 26 Feb 2021 16:35:17 +0800 Subject: [PATCH 057/944] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f60d6029d..7fa0cad82 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON -

🏆 码云最有价值开源项目
🚀 后端接口和文档自动化,前端(客户端) 定制返回 JSON 的数据和结构!

+

🏆 码云最有价值开源项目
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构!

From b8c6ba1bbb067eab117da7048277a1a165d8eaac Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 7 Mar 2021 23:10:01 +0800 Subject: [PATCH 058/944] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE=E5=8F=8A=E4=BD=9C?= =?UTF-8?q?=E8=80=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 7fa0cad82..41a7ac460 100644 --- a/README.md +++ b/README.md @@ -252,9 +252,12 @@ https://github.com/Tencent/APIJSON/issues/187
+ + + + + + + + + + + + + + +


From bf1fd02fa95771e8cc0980528381badc1ad635a6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 7 Mar 2021 23:13:34 +0800 Subject: [PATCH 059/944] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=A4=9A=E4=B8=AA=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E5=8F=8A=E4=BD=9C=E8=80=85?= 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 41a7ac460..2fd4547a3 100644 --- a/README.md +++ b/README.md @@ -282,7 +282,7 @@ https://github.com/Tencent/APIJSON/issues/187 - +
From 3b82a7f783a07e19983dfc3554386588dd41a2ad Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 11 Mar 2021 13:09:40 +0800 Subject: [PATCH 060/944] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=AF=B9=E8=B4=A1?= =?UTF-8?q?=E7=8C=AE=E8=80=85=E7=9A=84=E8=AF=B4=E6=98=8E=EF=BC=8C=E5=8C=85?= =?UTF-8?q?=E6=8B=AC=E7=9F=A5=E4=B9=8E=E5=9F=BA=E7=A1=80=E7=A0=94=E5=8F=91?= =?UTF-8?q?=E6=9E=B6=E6=9E=84=E5=B8=88sunxiaoguang=E5=92=8C=E7=8E=B0?= =?UTF-8?q?=E5=B1=85=E7=BE=8E=E5=9B=BD=E6=B4=9B=E6=9D=89=E7=9F=B6=E7=9A=84?= =?UTF-8?q?ruoranw?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 321f0e142..374f0c5ee 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,19 +7,19 @@ 非常感谢以下几位贡献者对于 APIJSON 的做出的贡献: -- [ruoranw](https://github.com/ruoranw) +- [ruoranw](https://github.com/ruoranw)(现居美国洛杉矶) - [zhoulingfengofcd](https://github.com/zhoulingfengofcd) - [Zerounary](https://github.com/Zerounary) - [vincentCheng](https://github.com/vincentCheng) - [justinfengchen](https://github.com/justinfengchen) - [linlwqq](https://github.com/linlwqq) -- [redcatmiss](https://github.com/redcatmiss) +- [redcatmiss](https://github.com/redcatmiss)(社保科技员工) - [linbren](https://github.com/linbren) - [jinzhongjian](https://github.com/jinzhongjian) - [CoolGeo2016](https://github.com/CoolGeo2016) - [1906522096](https://github.com/1906522096) - [github-ganyu](https://github.com/github-ganyu) -- [sunxiaoguang](https://github.com/sunxiaoguang) +- [sunxiaoguang](https://github.com/sunxiaoguang)(知乎基础研发架构师) #### 其中特别致谢:
justinfengchen 提交的 6 个 Commits, 对 APIJSON 做出了 3,130 增加和 0 处删减(截止 2020/11/04 日);
From da1b21d1662ce3ebf8dee4beebb73e4e59b23571 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 19 Mar 2021 15:35:37 +0800 Subject: [PATCH 061/944] update users and contributors --- README-English.md | 86 ++++++++++++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 34 deletions(-) diff --git a/README-English.md b/README-English.md index 21b650aba..9d5cf49be 100644 --- a/README-English.md +++ b/README-English.md @@ -303,17 +303,18 @@ 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) @@ -322,35 +323,52 @@ If you have any questions or suggestions, you can [create an issue](https://gith Here are the contributers of this project and authors of other projects for ecosystem of APIJSON:
- - - - - - - - - - - - + height="54" width="54" > + + + + + + + + + + + + +
+ - + height="54" width="54" > + + + height="54" width="54" > - + height="54" width="54" > + - + height="54" width="54" > + + + + + - - + height="54" width="54" > + + + + + + + + + + +

Thanks to all contributers of APIJSON! From a32d5f420ee816472777471c312aaf223ed6f77c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Mar 2021 16:25:14 +0800 Subject: [PATCH 062/944] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=AF=B9=20Response?= =?UTF-8?q?=20=E6=A0=A1=E9=AA=8C=E5=8F=AA=E5=AF=B9=E6=9C=80=E5=A4=96?= =?UTF-8?q?=E5=B1=82=E7=94=9F=E6=95=88=EF=BC=9B=E7=A7=BB=E9=99=A4=E5=B7=B2?= =?UTF-8?q?=E5=BA=9F=E5=BC=83=E7=9A=84=20StructureUtil=20=E5=92=8C=20Test?= =?UTF-8?q?=20=E7=9A=84=E7=9B=B8=E5=85=B3=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/AbstractVerifier.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index b85c1e19f..3ef1d5c4a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -70,7 +70,6 @@ import apijson.orm.model.SysColumn; import apijson.orm.model.SysTable; import apijson.orm.model.Table; -import apijson.orm.model.Test; import apijson.orm.model.TestRecord; /**校验器(权限、请求参数、返回结果等) @@ -121,7 +120,6 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { 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))); - SYSTEM_ACCESS_MAP.put(Test.class.getSimpleName(), getAccessMap(Test.class.getAnnotation(MethodAccess.class))); if (Log.DEBUG) { SYSTEM_ACCESS_MAP.put(Table.class.getSimpleName(), getAccessMap(Table.class.getAnnotation(MethodAccess.class))); @@ -724,7 +722,12 @@ public static JSONObject verifyResponse(@NotNull final RequestMethod method, fin } //解析 - return parse(method, name, target, response, database, schema, idKeyCallback, creator, callback != null ? callback : new OnParseCallback() {}); + return parse(method, name, target, response, database, schema, idKeyCallback, creator, callback != null ? callback : new OnParseCallback() { + @Override + protected JSONObject onParseJSONObject(String key, JSONObject tobj, JSONObject robj) throws Exception { + return verifyResponse(method, key, tobj, robj, database, schema, idKeyCallback, creator, callback); + } + }); } From 0a2cbc111a58791ce261747da9b929a9d0617274 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Mar 2021 17:30:17 +0800 Subject: [PATCH 063/944] =?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.6.1?= 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 2ae05b340..cb3b5333e 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.6.0 + 4.6.1 jar APIJSONORM From ba6884446594f0b705a23b5d4e9eec509ef24e2f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Mar 2021 17:54:25 +0800 Subject: [PATCH 064/944] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=B7=B2=E5=BA=9F?= =?UTF-8?q?=E5=BC=83=E7=9A=84=20Test=20=E7=B1=BB=E7=9A=84=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=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 | 2 -- .../src/main/java/apijson/orm/model/Test.java | 17 ----------------- 2 files changed, 19 deletions(-) delete mode 100755 APIJSONORM/src/main/java/apijson/orm/model/Test.java diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 6defdd1e1..242224102 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -68,7 +68,6 @@ import apijson.orm.model.SysColumn; import apijson.orm.model.SysTable; import apijson.orm.model.Table; -import apijson.orm.model.Test; import apijson.orm.model.TestRecord; /**config sql for JSON Request @@ -111,7 +110,6 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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(Test.class.getSimpleName()); CONFIG_TABLE_LIST.add(Access.class.getSimpleName()); CONFIG_TABLE_LIST.add(Document.class.getSimpleName()); CONFIG_TABLE_LIST.add(TestRecord.class.getSimpleName()); diff --git a/APIJSONORM/src/main/java/apijson/orm/model/Test.java b/APIJSONORM/src/main/java/apijson/orm/model/Test.java deleted file mode 100755 index 5b887e383..000000000 --- a/APIJSONORM/src/main/java/apijson/orm/model/Test.java +++ /dev/null @@ -1,17 +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; - -/**条件测试。最早可能 4.5.0 移除。AbstractSQLConfig 已支持 SELECT 2>1 这种简单条件表达式, - * 相当于是 SELECT 后只有 WHERE 条件表达式,其它全都没有,这样就可以去掉仅用来动态执行校验逻辑 Test 表了。 - * @author Lemon - */ -@Deprecated -@MethodAccess(POST = {}, PUT = {}, DELETE = {}) -public class Test { -} From 35aee2c9031a37e70f4fd98bdf70616fcda3a4fe Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 27 Mar 2021 11:18:06 +0800 Subject: [PATCH 065/944] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=8F=91=E7=9A=84=E6=96=87=E7=AB=A0=20=E5=85=A8=E5=9B=BD?= =?UTF-8?q?=E8=A1=8C=E6=94=BF=E5=8C=BA=E5=88=92=E6=95=B0=E6=8D=AE=E6=8A=93?= =?UTF-8?q?=E5=8F=96=E4=B8=8E=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://www.mdeditor.tw/pl/gCVk --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2fd4547a3..b3afbb6f6 100644 --- a/README.md +++ b/README.md @@ -354,6 +354,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [APIJSON复杂业务深入实践(类似12306订票系统)](https://blog.csdn.net/aa330233789/article/details/105309571) +[全国行政区划数据抓取与处理](https://www.mdeditor.tw/pl/gCVk) ### 生态项目 [APIJSON-Demo](https://github.com/APIJSON/APIJSON-Demo) APIJSON 各种语言、各种框架 的 使用示例项目、上手文档、测试数据 SQL 文件 等 From 386a02f2f3baeb30a9fd6b31a1e1fabf27f67f32 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 27 Mar 2021 16:47:56 +0800 Subject: [PATCH 066/944] =?UTF-8?q?=E3=80=90=E5=AE=89=E5=85=A8=E3=80=91?= =?UTF-8?q?=EF=BC=9A=E5=8A=A0=E5=BC=BA=E5=AF=B9=20JOIN=20=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E9=94=AE=E5=80=BC=E5=AF=B9=E7=9A=84=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=EF=BC=9B=E5=8A=A0=E5=BC=BA=E5=AF=B9=E5=91=BD=E5=90=8D=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 --- .../src/main/java/apijson/StringUtil.java | 21 ++++-- .../main/java/apijson/orm/AbstractParser.java | 70 ++++++++++++++----- .../java/apijson/orm/AbstractSQLConfig.java | 9 +-- .../src/main/java/apijson/orm/Join.java | 17 +++-- 4 files changed, 85 insertions(+), 32 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index 7f8fa831c..e32b41dac 100755 --- a/APIJSONORM/src/main/java/apijson/StringUtil.java +++ b/APIJSONORM/src/main/java/apijson/StringUtil.java @@ -399,29 +399,36 @@ public static boolean isNumberOrAlpha(String s) { * @return */ public static boolean isName(String s) { - return s != null && PATTERN_NAME.matcher(s).matches(); + if (s == null || s.isEmpty()) { + return false; + } + + String first = s.substring(0, 1); + if ("_".equals(first) == false && PATTERN_ALPHA.matcher(first).matches() == false) { + return false; + } + + return s.length() <= 1 ? true : PATTERN_NAME.matcher(s.substring(1)).matches(); } /**判断是否为首字母大写的代码名称 * @param s * @return */ public static boolean isBigName(String s) { - s = getString(s); - if (s.isEmpty() || PATTERN_ALPHA_BIG.matcher(s.substring(0, 1)).matches() == false) { + if (s == null || s.isEmpty() || PATTERN_ALPHA_BIG.matcher(s.substring(0, 1)).matches() == false) { return false; } - return s.length() <= 1 ? true : isName(s.substring(1)); + return s.length() <= 1 ? true : PATTERN_NAME.matcher(s.substring(1)).matches(); } /**判断是否为首字母小写的代码名称 * @param s * @return */ public static boolean isSmallName(String s) { - s = getString(s); - if (s.isEmpty() || PATTERN_ALPHA_SMALL.matcher(s.substring(0, 1)).matches() == false) { + if (s == null || s.isEmpty() || PATTERN_ALPHA_SMALL.matcher(s.substring(0, 1)).matches() == false) { return false; } - return s.length() <= 1 ? true : isName(s.substring(1)); + return s.length() <= 1 ? true : PATTERN_NAME.matcher(s.substring(1)).matches(); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 9fb17ee05..a22c05d5c 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1062,7 +1062,7 @@ else if (join != null){ Set> set = joinMap == null ? null : joinMap.entrySet(); if (set == null || set.isEmpty()) { - Log.e(TAG, "doJoin set == null || set.isEmpty() >> return null;"); + Log.e(TAG, "onJoinParse set == null || set.isEmpty() >> return null;"); return null; } @@ -1092,7 +1092,7 @@ else if (join != null){ int index = path.indexOf("/"); if (index < 0) { - throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中value不合法!" + throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 值 " + path + " 不合法!" + "必须为 &/Table0/key0, ( ) <> () * @@ -1105,37 +1105,70 @@ else if (join != null){ String tableKey = index < 0 ? null : path.substring(0, index); //User:owner 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/key0, 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 + " 不合法!必须满足英文单词变量名格式!"); + // } + + targetTable = targetTableKey; // 主表不允许别名 + if (StringUtil.isName(targetTable) == false) { + throw new IllegalArgumentException("/" + path + ":'/targetTable/targetKey' 中 targetTable 值 " + targetTable + " 不合法!必须满足大写字母开头的表对象英文单词 key 格式!"); + } + //对引用的JSONObject添加条件 - targetObj = request.getJSONObject(targetTable); + try { + targetObj = request.getJSONObject(targetTableKey); + } + catch (Exception e2) { + throw new IllegalArgumentException("/" + path + ":'/targetTable/targetKey' 中路径对应的 '" + targetTableKey + "':value 中 value 类型不合法!必须是 {} 这种 JSONObject 格式!" + e2.getMessage()); + } + if (targetObj == null) { - throw new IllegalArgumentException(targetTable + "." + targetKey - + ":'/targetTable/targetKey' 中路径对应的对象不存在!"); + throw new IllegalArgumentException("/" + path + ":'/targetTable/targetKey' 中路径对应的对象 '" + targetTableKey + "':{} 不存在或值为 null !必须是 {} 这种 JSONObject 格式!"); } tableObj.put(key, tableObj.remove(key)); //保证和SQLExcecutor缓存的Config里where顺序一致,生成的SQL也就一致 @@ -1147,11 +1180,16 @@ else if (join != null){ j.setJoinType(joinType); j.setTable(table); j.setAlias(alias); - j.setTargetName(targetTable); + 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() + " 不合法!必须满足英文单词变量名格式!"); + } joinList.add(j); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 242224102..72b3b32ef 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2489,7 +2489,8 @@ public String getJoinString() throws Exception { String sql = null; SQLConfig jc; String jt; - String tn; + String tt; + String ta; for (Join j : joinList) { if (j.isAppJoin()) { // APP JOIN,只是作为一个标记,执行完主表的查询后自动执行副表的查询 User.id IN($commentIdList) continue; @@ -2502,7 +2503,7 @@ public String getJoinString() throws Exception { jc.setPrepared(isPrepared()); jt = StringUtil.isEmpty(jc.getAlias(), true) ? jc.getTable() : jc.getAlias(); - tn = j.getTargetName(); + tt = j.getTargetTable(); //如果要强制小写,则可在子类重写这个方法再 toLowerCase // if (DATABASE_POSTGRESQL.equals(getDatabase())) { @@ -2522,7 +2523,7 @@ public String getJoinString() throws Exception { 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 + tn + quote + "." + quote + j.getTargetKey() + quote; + + quote + tt + quote + "." + quote + j.getTargetKey() + quote; jc.setMain(false).setKeyPrefix(true); pvl.addAll(jc.getPreparedValueList()); @@ -2537,7 +2538,7 @@ public String getJoinString() throws Exception { case "(": // ANTI JOIN: A & ! B case ")": // FOREIGN JOIN: B & ! A sql = " INNER JOIN " + jc.getTablePath() - + " ON " + quote + jt + quote + "." + quote + j.getKey() + quote + " = " + quote + tn + quote + "." + quote + j.getTargetKey() + quote; + + " ON " + quote + jt + quote + "." + quote + j.getKey() + quote + " = " + quote + tt + quote + "." + quote + j.getTargetKey() + quote; break; default: throw new UnsupportedOperationException( diff --git a/APIJSONORM/src/main/java/apijson/orm/Join.java b/APIJSONORM/src/main/java/apijson/orm/Join.java index 168e3e77d..01761037a 100644 --- a/APIJSONORM/src/main/java/apijson/orm/Join.java +++ b/APIJSONORM/src/main/java/apijson/orm/Join.java @@ -25,7 +25,8 @@ public class Join { private String table; //User private String alias; //owner private String key; //id - private String targetName; // Moment + private String targetTable; // Moment + private String targetAlias; //main private String targetKey; // userId private JSONObject outter; @@ -91,11 +92,17 @@ public String getKey() { public void setKey(String key) { this.key = key; } - public String getTargetName() { - return targetName; + public void setTargetTable(String targetTable) { + this.targetTable = targetTable; } - public void setTargetName(String targetName) { - this.targetName = targetName; + public String getTargetTable() { + return targetTable; + } + public void setTargetAlias(String targetAlias) { + this.targetAlias = targetAlias; + } + public String getTargetAlias() { + return targetAlias; } public String getTargetKey() { return targetKey; From bae6febdbb4abe612692234b2ee2715c6fad8647 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 27 Mar 2021 19:26:06 +0800 Subject: [PATCH 067/944] =?UTF-8?q?=E3=80=90=E5=AE=89=E5=85=A8=E3=80=91?= =?UTF-8?q?=EF=BC=9A=E8=B0=83=E7=94=A8=20SQL=20=E5=87=BD=E6=95=B0=E5=8F=AA?= =?UTF-8?q?=E5=85=81=E8=AE=B8=E7=94=A8=E5=90=8E=E7=AB=AF=E5=B7=B2=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E7=9A=84=EF=BC=8C=E9=81=BF=E5=85=8D=20sleep(10)=20?= =?UTF-8?q?=E8=BF=99=E7=A7=8D=E5=91=BD=E4=BB=A4=E5=87=BD=E6=95=B0=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E6=95=B0=E6=8D=AE=E5=BA=93=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 217 +++++++++++++++++- 1 file changed, 207 insertions(+), 10 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 72b3b32ef..0fdba0b81 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -92,6 +92,8 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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 改成更好的正则,校验前面为单词,中间为操作符,后面为值 @@ -122,7 +124,185 @@ public abstract class AbstractSQLConfig implements SQLConfig { DATABASE_LIST.add(DATABASE_ORACLE); DATABASE_LIST.add(DATABASE_DB2); + 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("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...) + } @@ -581,11 +761,19 @@ public String getHavingString(boolean hasPrefix) { } method = expression.substring(0, start); - - if (StringUtil.isName(method) == false) { - throw new IllegalArgumentException("字符 " + method + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中SQL函数名 function 必须符合正则表达式 ^[0-9a-zA-Z_]+$ !"); + 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) { + throw new IllegalArgumentException("字符 " + method + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); + } } suffix = expression.substring(end + 1, expression.length()); @@ -957,10 +1145,19 @@ public String getColumnString(boolean inSQLJoin) throws Exception { boolean distinct = i <= 0 && method.startsWith(PREFFIX_DISTINCT); String fun = distinct ? method.substring(PREFFIX_DISTINCT.length()) : method; - if (fun.isEmpty() == false && StringUtil.isName(fun) == false) { - throw new IllegalArgumentException("字符 " + method + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中SQL函数名 function 必须符合正则表达式 ^[0-9a-zA-Z_]+$ !"); + 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 + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); + } } } @@ -2490,7 +2687,7 @@ public String getJoinString() throws Exception { SQLConfig jc; String jt; String tt; - String ta; + // 主表不用别名 String ta; for (Join j : joinList) { if (j.isAppJoin()) { // APP JOIN,只是作为一个标记,执行完主表的查询后自动执行副表的查询 User.id IN($commentIdList) continue; From 786d326e6f5f5da2dad2e31490c91bec88ff34f5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 27 Mar 2021 19:41:59 +0800 Subject: [PATCH 068/944] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E6=95=B4=E5=90=88?= =?UTF-8?q?=20APIJSON=20=E5=92=8C=E5=BE=AE=E6=9C=8D=E5=8A=A1=E6=A1=86?= =?UTF-8?q?=E6=9E=B6=20light4j=20=E7=9A=84=20Demo(=E5=90=8C=E6=97=B6?= =?UTF-8?q?=E6=8E=A5=E5=85=A5=E4=BA=86=20Redis)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/xlongwei/light4j --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b3afbb6f6..5fed97412 100644 --- a/README.md +++ b/README.md @@ -385,6 +385,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [ApiJsonByJFinal](https://gitee.com/zhiyuexin/ApiJsonByJFinal) 整合 APIJSON 和 JFinal 的 Demo +[light4j](https://github.com/xlongwei/light4j) 整合 APIJSON 和微服务框架 light-4j 的 Demo,同时接入了 Redis + [SpringServer1.2-APIJSON](https://github.com/Airforce-1/SpringServer1.2-APIJSON) 智慧党建服务器端,提供 上传 和 下载 文件的接口 [apijson-builder](https://github.com/pengxianggui/apijson-builder) 一个方便为 APIJSON 构建 RESTful 请求的 JavaScript 库 From fe65470b5c0a819da1097a8658a5ec1cec3babb9 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 5 Apr 2021 19:24:42 +0800 Subject: [PATCH 069/944] =?UTF-8?q?SQL=20=E5=87=BD=E6=95=B0=E7=99=BD?= =?UTF-8?q?=E5=90=8D=E5=8D=95=E6=96=B0=E5=A2=9E=20length=EF=BC=9Bkey$=20?= =?UTF-8?q?=E6=A8=A1=E7=B3=8A=E6=90=9C=E7=B4=A2=E4=B8=8D=E5=85=81=E8=AE=B8?= =?UTF-8?q?=E8=BF=9E=E7=BB=AD=E7=9A=84=20%=EF=BC=9BAbstractSQLExecutor=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20getKey=20=E6=96=B9=E6=B3=95=EF=BC=9B?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=BE=85=E5=AE=9E=E7=8E=B0=E5=85=B3=E9=94=AE?= =?UTF-8?q?=E8=AF=8D=20@null=EF=BC=9B=E5=88=A0=E9=99=A4=20Structure.java,?= =?UTF-8?q?=20Operation=20=E4=B8=AD=20NECESSARY,=20DISALLOW=20=E7=AD=89?= =?UTF-8?q?=E5=B7=B2=E5=BA=9F=E5=BC=83=E7=9A=84=E9=83=A8=E5=88=86=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/JSONObject.java | 1 + .../java/apijson/orm/AbstractSQLConfig.java | 14 +- .../java/apijson/orm/AbstractSQLExecutor.java | 11 +- .../java/apijson/orm/AbstractVerifier.java | 35 +-- .../src/main/java/apijson/orm/Operation.java | 15 +- .../src/main/java/apijson/orm/Structure.java | 241 ------------------ 6 files changed, 25 insertions(+), 292 deletions(-) delete mode 100755 APIJSONORM/src/main/java/apijson/orm/Structure.java diff --git a/APIJSONORM/src/main/java/apijson/JSONObject.java b/APIJSONORM/src/main/java/apijson/JSONObject.java index 75ca4256d..026d63efb 100755 --- a/APIJSONORM/src/main/java/apijson/JSONObject.java +++ b/APIJSONORM/src/main/java/apijson/JSONObject.java @@ -133,6 +133,7 @@ public JSONObject setUserIdIn(List list) { public static final String KEY_DROP = "@drop"; //丢弃,不返回,TODO 应该通过 fastjson 的 ignore 之类的机制来处理,避免导致下面的对象也不返回 // 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_ROLE = "@role"; //角色,拥有对某些数据的某些操作的权限 public static final String KEY_DATABASE = "@database"; //数据库类型,默认为MySQL diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 0fdba0b81..6f61b9c19 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -143,6 +143,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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 开始处的空格 @@ -1013,6 +1014,8 @@ 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 "*" @@ -2011,10 +2014,15 @@ public String getSearchString(String key, Object[] values, int type) throws Ille String condition = ""; for (int i = 0; i < values.length; i++) { - if (values[i] instanceof String == false) { - throw new IllegalArgumentException(key + "$:value 中value的类型只能为String或String[]!"); + Object v = values[i]; + if (v instanceof String == false) { + throw new IllegalArgumentException(key + "$:value 中 value 的类型只能为 String 或 String[]!"); + } + if (((String) v).contains("%%")) { + throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + " 中包含 %% !不允许有连续的 % !"); } - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getLikeString(key, values[i]); + + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getLikeString(key, v); } return getCondition(Logic.isNot(type), condition); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 47c2d46e5..a3ff117cf 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -249,7 +249,8 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws // final boolean cache = config.getCount() != 1; - resultList = new ArrayList<>(); + // TODO 设置初始容量为查到的数据量,解决频繁扩容导致的延迟,貌似只有 rs.last 取 rs.getRow() ? 然后又得 rs.beforeFirst 重置位置以便下方取值 + resultList = new ArrayList<>(config.getCount() <= 0 ? Parser.MAX_QUERY_COUNT : config.getCount()); // Log.d(TAG, "select cache = " + cache + "; resultList" + (resultList == null ? "=" : "!=") + "null"); int index = -1; @@ -504,7 +505,7 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r //已改为 rsmd.getTableName(columnIndex) 支持副表不传 @column , 但如何判断是副表?childMap != null // String lable = rsmd.getColumnLabel(columnIndex); // int dotIndex = lable.indexOf("."); - String lable = rsmd.getColumnLabel(columnIndex);//dotIndex < 0 ? lable : lable.substring(dotIndex + 1); + String lable = getKey(config, rs, rsmd, tablePosition, table, columnIndex, childMap); String childTable = childMap == null ? null : rsmd.getTableName(columnIndex); //dotIndex < 0 ? null : lable.substring(0, dotIndex); @@ -567,6 +568,12 @@ protected List onPutTable(@NotNull SQLConfig config, @NotNull Result return resultList; } + + + protected String getKey(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd + , final int tablePosition, @NotNull JSONObject table, final int columnIndex, Map childMap) throws Exception { + return rsmd.getColumnLabel(columnIndex); // dotIndex < 0 ? lable : lable.substring(dotIndex + 1); + } 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 { diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 3ef1d5c4a..633b5d645 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -12,11 +12,9 @@ import static apijson.RequestMethod.HEADS; import static apijson.RequestMethod.POST; import static apijson.RequestMethod.PUT; -import static apijson.orm.Operation.DISALLOW; import static apijson.orm.Operation.EXIST; import static apijson.orm.Operation.INSERT; import static apijson.orm.Operation.MUST; -import static apijson.orm.Operation.NECESSARY; import static apijson.orm.Operation.REFUSE; import static apijson.orm.Operation.REMOVE; import static apijson.orm.Operation.REPLACE; @@ -96,6 +94,7 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { @NotNull public static final Map> REQUEST_MAP; + // 正则匹配的别名快捷方式,例如用 "PHONE" 代替 "^((13[0-9])|(15[^4,\\D])|(18[0-2,5-9])|(17[0-9]))\\d{8}$" @NotNull public static final Map COMPILE_MAP; static { @@ -110,8 +109,6 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { OPERATION_KEY_LIST.add(REMOVE.name()); OPERATION_KEY_LIST.add(MUST.name()); OPERATION_KEY_LIST.add(REFUSE.name()); - OPERATION_KEY_LIST.add(NECESSARY.name()); - OPERATION_KEY_LIST.add(DISALLOW.name()); SYSTEM_ACCESS_MAP = new HashMap>(); @@ -776,8 +773,6 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, String remove = StringUtil.getNoBlankString(target.getString(REMOVE.name())); String must = StringUtil.getNoBlankString(target.getString(MUST.name())); String refuse = StringUtil.getNoBlankString(target.getString(REFUSE.name())); - String necessary = StringUtil.getNoBlankString(target.getString(NECESSARY.name())); - String disallow = StringUtil.getNoBlankString(target.getString(DISALLOW.name())); // 移除字段<<<<<<<<<<<<<<<<<<< @@ -798,15 +793,6 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, + " 里面不能缺少 " + s + " 等[" + must + "]内的任何字段!"); } } - - String[] necessarys = StringUtil.split(necessary); - List necessaryList = necessarys == null ? new ArrayList() : Arrays.asList(necessarys); - for (String s : necessaryList) { - if (real.get(s) == null) {//可能传null进来,这里还会通过 real.containsKey(s) == false) { - throw new IllegalArgumentException(method + "请求," + name - + " 里面不能缺少 " + s + " 等[" + necessary + "]内的任何字段!"); - } - } //判断必要字段是否都有>>>>>>>>>>>>>>>>>>> @@ -879,21 +865,6 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, refuseList.addAll(Arrays.asList(refuses)); } } - - List disallowList = new ArrayList(); - if ("!".equals(disallow)) {//所有非necessary,改成 !necessary 更好 - for (String key : rkset) {//对@key放行,@role,@column,自定义@position等 - if (key != null && key.startsWith("@") == false - && necessaryList.contains(key) == false && objKeySet.contains(key) == false) { - disallowList.add(key); - } - } - } else { - String[] disallows = StringUtil.split(disallow); - if (disallows != null && disallows.length > 0) { - disallowList.addAll(Arrays.asList(disallows)); - } - } //解析不允许的字段>>>>>>>>>>>>>>>>>>> @@ -903,10 +874,6 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, throw new IllegalArgumentException(method + "请求," + name + " 里面不允许传 " + rk + " 等" + StringUtil.getString(refuseList) + "内的任何字段!"); } - if (disallowList.contains(rk)) { //不允许的字段 - throw new IllegalArgumentException(method + "请求," + name - + " 里面不允许传 " + rk + " 等" + StringUtil.getString(disallowList) + "内的任何字段!"); - } if (rk == null) { //无效的key real.remove(rk); diff --git a/APIJSONORM/src/main/java/apijson/orm/Operation.java b/APIJSONORM/src/main/java/apijson/orm/Operation.java index a0e2278b1..e6461bab5 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Operation.java +++ b/APIJSONORM/src/main/java/apijson/orm/Operation.java @@ -5,7 +5,7 @@ package apijson.orm; -/**对请求JSON的操作 +/**对请求 JSON 的操作 * @author Lemon */ public enum Operation { @@ -14,21 +14,12 @@ public enum Operation { * "key0,key1,key2..." */ MUST, - /** - * @deprecated 用 MUST 代替,最早可能 4.5.0 移除 - */ - NECESSARY, /** * 不允许传的字段,结构是 * "key0,key1,key2..." */ REFUSE, - /** - * @deprecated 用 REFUSE 代替,最早可能 4.5.0 移除 - */ - DISALLOW, - /**TODO 是否应该把数组类型写成 BOOLEANS, NUMBERS 等复数单词,以便抽取 enum ?扩展用 VERIFY 或 INSERT/UPDATE 远程函数等 * 验证是否符合预设的类型: @@ -47,7 +38,7 @@ public enum Operation { * "id": "NUMBER", //id 类型必须为 NUMBER * "pictureList": "URL[]", //pictureList 类型必须为 URL[] * } - * @see {@link Structure#type(String, String, Object, boolean)} + * @see {@link AbstractVerifier#verifyType(String, String, Object, boolean)} */ TYPE, @@ -61,7 +52,7 @@ public enum Operation { * } * 例如 * { - * "phone~": "PHONE", //phone 必须满足 PHONE 的格式 + * "phone~": "PHONE", //phone 必须满足 PHONE 的格式,配置见 {@link AbstractVerifier#COMPILE_MAP} * "status{}": [1,2,3], //status 必须在给出的范围内 * "balance&{}":">0,<=10000" //必须满足 balance>0 & balance<=10000 * } diff --git a/APIJSONORM/src/main/java/apijson/orm/Structure.java b/APIJSONORM/src/main/java/apijson/orm/Structure.java deleted file mode 100755 index b5d146f90..000000000 --- a/APIJSONORM/src/main/java/apijson/orm/Structure.java +++ /dev/null @@ -1,241 +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; - -import java.util.Map; -import java.util.regex.Pattern; - -import javax.activation.UnsupportedDataTypeException; - -import com.alibaba.fastjson.JSONObject; - -import apijson.JSON; -import apijson.Log; -import apijson.NotNull; -import apijson.RequestMethod; -import apijson.orm.AbstractSQLConfig.Callback; -import apijson.orm.AbstractSQLConfig.IdCallback; - -/**结构类。已整合进 AbstractVerifier,最快 4.5.0 移除 - * 增删改查: OPERATION(ADD,REPLACE,PUT,REMOVE) OPERATION:{key0:value0, key1:value1 ...} - * 对值校验: VERIFY:{key0:value0, key1:value1 ...} (key{}:range,key$:"%m%"等) - * 对值重复性校验: UNIQUE:"key0:, key1 ..." (UNIQUE:"phone,email" 等) - * @author Lemon - */ -@Deprecated -public class Structure { - public static final String TAG = "Structure"; - - public static final Map COMPILE_MAP = AbstractVerifier.COMPILE_MAP; - - private Structure() {} - - - /**从request提取target指定的内容 - * @param method - * @param name - * @param target - * @param request - * @param creator - * @return - * @throws Exception - */ - public static JSONObject parseRequest(@NotNull final RequestMethod method, final String name - , final JSONObject target, final JSONObject request, final SQLCreator creator) throws Exception { - return parseRequest(method, name, target, request, Parser.MAX_UPDATE_COUNT, creator); - } - /**从request提取target指定的内容 - * @param method - * @param name - * @param target - * @param request - * @param maxUpdateCount - * @param creator - * @return - * @throws Exception - */ - public static JSONObject parseRequest(@NotNull final RequestMethod method, final String name - , final JSONObject target, final JSONObject request, final int maxUpdateCount, final SQLCreator creator) throws Exception { - return parseRequest(method, name, target, request, maxUpdateCount, null, null, null, creator); - } - /**从request提取target指定的内容 - * @param method - * @param name - * @param target - * @param request - * @param maxUpdateCount - * @param idKey - * @param userIdKey - * @param creator - * @return - * @throws Exception - */ - public static JSONObject parseRequest(@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 { - - return AbstractVerifier.verifyRequest(method, name, target, request, maxUpdateCount, database, schema, idCallback, creator); - } - - /**校验并将response转换为指定的内容和结构 - * @param method - * @param name - * @param target - * @param response - * @param callback - * @param creator - * @return - * @throws Exception - */ - public static JSONObject parseResponse(@NotNull final RequestMethod method, final String name - , final JSONObject target, final JSONObject response, SQLCreator creator, OnParseCallback callback) throws Exception { - return parseResponse(method, name, target, response, null, null, null, creator, callback); - } - /**校验并将response转换为指定的内容和结构 - * @param method - * @param name - * @param target - * @param response - * @param idKey - * @param callback - * @param creator - * @return - * @throws Exception - */ - public static JSONObject parseResponse(@NotNull final RequestMethod method, final String name - , final JSONObject target, final JSONObject response, final String database, final String schema - , final Callback idKeyCallback, SQLCreator creator, OnParseCallback callback) throws Exception { - - Log.i(TAG, "parseResponse method = " + method + "; name = " + name - + "; target = \n" + JSON.toJSONString(target) - + "\n response = \n" + JSON.toJSONString(response)); - - if (target == null || response == null) {// || target.isEmpty() { - Log.i(TAG, "parseRequest target == null || response == null >> return response;"); - return response; - } - - //解析 - return parse(method, name, target, response, database, schema, idKeyCallback, creator, callback != null ? callback : new OnParseCallback() {}); - } - - - /**对request和response不同的解析用callback返回 - * @param method - * @param name - * @param target - * @param real - * @param creator - * @param callback - * @return - * @throws Exception - */ - public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real - , SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { - return parse(method, name, target, real, null, null, null, creator, callback); - } - - /**对request和response不同的解析用callback返回 - * @param method - * @param name - * @param target - * @param real - * @param idKey - * @param userIdKey - * @param creator - * @param callback - * @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 { - return AbstractVerifier.parse(method, name, target, real, database, schema, idCallback, creator, callback); - } - - - - /**验证值类型 - * @param tk - * @param tv {@link Operation} - * @param real - * @throws Exception - */ - public static void type(@NotNull String tk, Object tv, @NotNull JSONObject real) throws UnsupportedDataTypeException { - if (tv instanceof String == false) { - throw new UnsupportedDataTypeException("服务器内部错误," + tk + ":value 的value不合法!" - + "Request表校验规则中 TYPE:{ key:value } 中的value只能是String类型!"); - } - - type(tk, (String) tv, real.get(tk)); - } - /**验证值类型 - * @param tk - * @param tv {@link Operation} - * @param rv - * @throws Exception - */ - public static void type(@NotNull String tk, @NotNull String tv, Object rv) throws UnsupportedDataTypeException { - type(tk, tv, rv, false); - } - /**验证值类型 - * @param tk - * @param tv {@link Operation} - * @param rv - * @param isInArray - * @throws Exception - */ - public static void type(@NotNull String tk, @NotNull String tv, Object rv, boolean isInArray) throws UnsupportedDataTypeException { - AbstractVerifier.verifyType(tk, tv, rv, isInArray); - } - - - /**验证是否存在 - * @param table - * @param key - * @param value - * @throws Exception - */ - public static void verifyExist(String table, String key, Object value, long exceptId, @NotNull SQLCreator creator) throws Exception { - AbstractVerifier.verifyExist(table, key, value, exceptId, creator); - } - - /**验证是否重复 - * @param table - * @param key - * @param value - * @throws Exception - */ - public static void verifyRepeat(String table, String key, Object value, @NotNull SQLCreator creator) throws Exception { - verifyRepeat(table, key, value, 0, creator); - } - - /**验证是否重复 - * @param table - * @param key - * @param value - * @param exceptId 不包含id - * @throws Exception - */ - public static void verifyRepeat(String table, String key, Object value, long exceptId, @NotNull SQLCreator creator) throws Exception { - verifyRepeat(table, key, value, exceptId, null, creator); - } - - /**验证是否重复 - * TODO 与 AbstractVerifier.verifyRepeat 代码重复,需要简化 - * @param table - * @param key - * @param value - * @param exceptId 不包含id - * @param idKey - * @param creator - * @throws Exception - */ - public static void verifyRepeat(String table, String key, Object value, long exceptId, String idKey, @NotNull SQLCreator creator) throws Exception { - AbstractVerifier.verifyRepeat(table, key, value, exceptId, idKey, creator); - } - - -} From 8a6672b5f95ac8bb447742018068e842213d2469 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 5 Apr 2021 19:33:53 +0800 Subject: [PATCH 070/944] =?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.6.6?= 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 cb3b5333e..b892374e2 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.6.1 + 4.6.6 jar APIJSONORM From 60564651608a2abb487047a8b9b475356dabcbe0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 5 Apr 2021 20:44:52 +0800 Subject: [PATCH 071/944] =?UTF-8?q?=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9AAPIJSON=20=E7=9A=84=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E6=8F=92=E4=BB=B6=EF=BC=8C=E6=94=AF=E6=8C=81=20?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=90=8D=E6=98=A0=E5=B0=84=20=E5=92=8C=20!ke?= =?UTF-8?q?y=20=E5=8F=8D=E9=80=89=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/APIJSON/apijson-column --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5fed97412..7c3ccc83e 100644 --- a/README.md +++ b/README.md @@ -363,6 +363,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [apijson-framework](https://github.com/APIJSON/apijson-framework) APIJSON 服务端框架,可通过 Maven, Gradle 等远程依赖 +[apijson-column](https://github.com/APIJSON/apijson-column) APIJSON 的字段插件,支持 字段名映射 和 !key 反选字段 + [APIAuto](https://github.com/TommyLemon/APIAuto) 敏捷开发最强大易用的 HTTP 接口工具,机器学习零代码测试、生成代码与静态检查、生成文档与光标悬浮注释 [UnitAuto](https://github.com/TommyLemon/UnitAuto) 机器学习单元测试平台,零代码、全方位、自动化 测试 方法/函数 的正确性和可用性 From 3d210639c6d20b94679cd1268b549b64fa35288a Mon Sep 17 00:00:00 2001 From: iceewei Date: Mon, 5 Apr 2021 22:27:10 +0800 Subject: [PATCH 072/944] =?UTF-8?q?=E9=94=99=E8=AF=AF=E7=A0=81=E5=8F=AA?= =?UTF-8?q?=E5=9C=A8=E6=9C=80=E5=A4=96=E5=B1=82=E8=BF=94=E5=9B=9E=EF=BC=8C?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E7=BB=9F=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLExecutor.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index a3ff117cf..324cd6350 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -210,8 +210,11 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws int updateCount = executeUpdate(config); - result = AbstractParser.newResult(updateCount > 0 ? JSONResponse.CODE_SUCCESS : JSONResponse.CODE_NOT_FOUND - , updateCount > 0 ? JSONResponse.MSG_SUCCEED : "没权限访问或对象不存在!"); + result = new JSONObject(); + if (config.getMethod() == RequestMethod.DELETE) { + result = AbstractParser.newResult(updateCount > 0 ? JSONResponse.CODE_SUCCESS : JSONResponse.CODE_NOT_FOUND, + updateCount > 0 ? JSONResponse.MSG_SUCCEED : "没权限访问或对象不存在!"); + } //id,id{}至少一个会有,一定会返回,不用抛异常来阻止关联写操作时前面错误导致后面无条件执行! result.put(JSONResponse.KEY_COUNT, updateCount);//返回修改的记录数 @@ -717,8 +720,8 @@ public Connection getConnection(@NotNull SQLConfig config) throws Exception { connection = connectionMap.get(config.getDatabase()); if (connection == null || connection.isClosed()) { Log.i(TAG, "select connection " + (connection == null ? " = null" : ("isClosed = " + connection.isClosed()))) ; - // PostgreSQL 不允许 cross-database - connection = DriverManager.getConnection(config.getDBUri(), config.getDBAccount(), config.getDBPassword()); + // PostgreSQL 不允许 cross-database + connection = DriverManager.getConnection(config.getDBUri(), config.getDBAccount(), config.getDBPassword()); connectionMap.put(config.getDatabase(), connection); } @@ -823,14 +826,14 @@ public ResultSet executeQuery(@NotNull SQLConfig config) throws Exception { public int executeUpdate(@NotNull SQLConfig config) throws Exception { PreparedStatement s = getStatement(config); int count = s.executeUpdate(); //PreparedStatement 不用传 SQL - + if (config.getMethod() == RequestMethod.POST && config.getId() == null) { //自增id ResultSet rs = s.getGeneratedKeys(); if (rs != null && rs.next()) { config.setId(rs.getLong(1));//返回插入的主键id } } - + return count; } From 737aa738efb8fb5ca362b537c8adc49b9d225f9c Mon Sep 17 00:00:00 2001 From: iceewei Date: Mon, 5 Apr 2021 22:29:18 +0800 Subject: [PATCH 073/944] =?UTF-8?q?=E9=94=99=E8=AF=AF=E7=A0=81=E5=8F=AA?= =?UTF-8?q?=E5=9C=A8=E6=9C=80=E5=A4=96=E5=B1=82=E8=BF=94=E5=9B=9E=EF=BC=8C?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E7=BB=9F=E4=B8=80?= 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 insertion(+) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 324cd6350..043251a56 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -212,6 +212,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws result = new JSONObject(); if (config.getMethod() == RequestMethod.DELETE) { + // 特别地,针对DELETE请求,如果需要提示code,可以用内部的code来判断。其余请求类型统一使用外层错误码。 result = AbstractParser.newResult(updateCount > 0 ? JSONResponse.CODE_SUCCESS : JSONResponse.CODE_NOT_FOUND, updateCount > 0 ? JSONResponse.MSG_SUCCEED : "没权限访问或对象不存在!"); } From 635d2413683b3e1eecb18a7af50cde7fb42be681 Mon Sep 17 00:00:00 2001 From: iceewei Date: Tue, 6 Apr 2021 15:37:16 +0800 Subject: [PATCH 074/944] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E4=BF=A1=E6=81=AF=E6=8E=A7=E5=88=B6=E9=9D=99=E6=80=81?= =?UTF-8?q?=E5=8F=98=E9=87=8F=EF=BC=8C=E6=94=B9=E9=80=A0=E4=B8=A4=E5=B1=82?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E7=A0=81=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AbstractParser类增加了isPrintErrorLog静态变量,暴露给用户控制错误信息抛出 - 内外两层错误码改造,增加抛出异常逻辑(updateCount <= 0) --- .../src/main/java/apijson/orm/AbstractParser.java | 8 ++++++-- .../main/java/apijson/orm/AbstractSQLExecutor.java | 12 ++++++------ Document.md | 4 ++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index a22c05d5c..be8ef09e3 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -301,6 +301,9 @@ public JSONObject parseResponse(String request) { private int queryDepth; + // 打印异常日志的标识。线上环境比较敏感,可以通过切换该变量来控制异常栈抛出、错误日志打印。保守起见,该值默认为false。 + public static boolean isPrintErrorLog = false; + /**解析请求json并获取对应结果 * @param request * @return requestObject @@ -383,11 +386,12 @@ public JSONObject parseResponse(JSONObject request) { long endTime = System.currentTimeMillis(); long duration = endTime - startTime; - if (Log.DEBUG) { //用 | 替代 /,避免 APIJSON ORM,APIAuto 等解析路径错误 + if (isPrintErrorLog) { //用 | 替代 /,避免 APIJSON ORM,APIAuto 等解析路径错误 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); if (error != null) { + Log.d(TAG, String.format("onObjectParse error, error is %s", error.getMessage())); requestObject.put("throw", error.getClass().getName()); requestObject.put("trace", error.getStackTrace()); } @@ -397,7 +401,7 @@ public JSONObject parseResponse(JSONObject request) { //会不会导致原来的session = null? session = null; - if (Log.DEBUG) { + if (isPrintErrorLog) { Log.d(TAG, "\n\n\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n " + requestMethod + "/parseResponse request = \n" + requestString + "\n\n"); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 043251a56..85b4162dc 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -27,6 +27,7 @@ import java.util.Map.Entry; import java.util.Set; +import apijson.orm.exception.NotExistException; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; @@ -209,14 +210,13 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws executedSQLCount ++; int updateCount = executeUpdate(config); - - result = new JSONObject(); - if (config.getMethod() == RequestMethod.DELETE) { - // 特别地,针对DELETE请求,如果需要提示code,可以用内部的code来判断。其余请求类型统一使用外层错误码。 - result = AbstractParser.newResult(updateCount > 0 ? JSONResponse.CODE_SUCCESS : JSONResponse.CODE_NOT_FOUND, - updateCount > 0 ? JSONResponse.MSG_SUCCEED : "没权限访问或对象不存在!"); + if (updateCount <= 0) { + throw new NotExistException("没权限访问或对象不存在!"); } + // 更新成功后收集结果。例如更新操作成功时,返回count(affected rows)、id字段 + result = new JSONObject(true); + //id,id{}至少一个会有,一定会返回,不用抛异常来阻止关联写操作时前面错误导致后面无条件执行! result.put(JSONResponse.KEY_COUNT, updateCount);//返回修改的记录数 if (config.getId() != null) { diff --git a/Document.md b/Document.md index 701932c9b..c7e7be53c 100644 --- a/Document.md +++ b/Document.md @@ -318,7 +318,7 @@ 3.请求中的 / 需要转义。JSONRequest.java已经用URLEncoder.encode转义,不需要再写;但如果是浏览器或Postman等直接输入url/request,需要把request中的所有 / 都改成 %252F 。下同。
4.code,指返回结果中的状态码,200表示成功,其它都是错误码,值全部都是HTTP标准状态码。下同。
5.msg,指返回结果中的状态信息,对成功结果或错误原因的详细说明。下同。
-6.code和msg总是在返回结果的同一层级成对出现。对所有请求的返回结果都会在最外层有一对总结式code和msg。对非GET类型的请求,返回结果里面的每个JSONObject里都会有一对code和msg说明这个JSONObject的状态。下同。
+6.code和msg总是在返回结果的同一层级成对出现。对所有请求的返回结果都会在最外层有一对总结式code和msg。下同。
7.id等字段对应的值仅供说明,不一定是数据库里存在的,请求里用的是真实存在的值。下同。
@@ -334,7 +334,7 @@ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {< 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"
} 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"
} +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"
} From 873afa8470d189648e5e5dc30fc0827da01746c5 Mon Sep 17 00:00:00 2001 From: iceewei Date: Tue, 6 Apr 2021 16:51:57 +0800 Subject: [PATCH 075/944] =?UTF-8?q?fix:=20=E6=B7=BB=E5=8A=A0=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E4=BF=A1=E6=81=AF=E6=8E=A7=E5=88=B6=E9=9D=99=E6=80=81?= =?UTF-8?q?=E5=8F=98=E9=87=8F=EF=BC=8C=E6=94=B9=E9=80=A0=E4=B8=A4=E5=B1=82?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E7=A0=81=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AbstractParser类增加了isPrintErrorLog静态变量,暴露给用户控制错误信息抛出 - 内外两层错误码改造,增加抛出异常逻辑(updateCount <= 0) --- APIJSONORM/pom.xml | 2 +- APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index b892374e2..4c1041bc9 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.6.6 + 4.6.7 jar APIJSONORM diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 85b4162dc..8966d0132 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -214,7 +214,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws throw new NotExistException("没权限访问或对象不存在!"); } - // 更新成功后收集结果。例如更新操作成功时,返回count(affected rows)、id字段 + // updateCount>0时收集结果。例如更新操作成功时,返回count(affected rows)、id字段 result = new JSONObject(true); //id,id{}至少一个会有,一定会返回,不用抛异常来阻止关联写操作时前面错误导致后面无条件执行! From b67280d8c7765115ef0537715bd9eaf7b9a91017 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 6 Apr 2021 17:42:02 +0800 Subject: [PATCH 076/944] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=9D=A5=E8=87=AA=E8=85=BE=E8=AE=AF=20CSIG=20?= =?UTF-8?q?=E7=9A=84=E5=90=8C=E4=BA=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/212 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7c3ccc83e..ed92e297d 100644 --- a/README.md +++ b/README.md @@ -242,6 +242,7 @@ https://github.com/Tencent/APIJSON/issues/187 + From 1a75bfba0e8a542f8921ba6d20d920302089d932 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 6 Apr 2021 17:52:01 +0800 Subject: [PATCH 077/944] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E6=96=B0=E5=A2=9E=20=E8=85=BE=E8=AE=AF=E5=90=8E?= =?UTF-8?q?=E5=8F=B0=E5=B7=A5=E7=A8=8B=E5=B8=88=20fineday009?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/edit/master/CONTRIBUTING.md --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 374f0c5ee..0900ca076 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,10 +10,11 @@ - [ruoranw](https://github.com/ruoranw)(现居美国洛杉矶) - [zhoulingfengofcd](https://github.com/zhoulingfengofcd) - [Zerounary](https://github.com/Zerounary) +- [fineday009](https://github.com/fineday009)(腾讯后台工程师) - [vincentCheng](https://github.com/vincentCheng) - [justinfengchen](https://github.com/justinfengchen) - [linlwqq](https://github.com/linlwqq) -- [redcatmiss](https://github.com/redcatmiss)(社保科技员工) +- [redcatmiss](https://github.com/redcatmiss)(社保科技后端工程师) - [linbren](https://github.com/linbren) - [jinzhongjian](https://github.com/jinzhongjian) - [CoolGeo2016](https://github.com/CoolGeo2016) From 8cf170c0bfb71052a09baad4e43029954352442a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 7 Apr 2021 01:28:36 +0800 Subject: [PATCH 078/944] =?UTF-8?q?AbstractSQLExecutor=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=A2=9E=E5=88=A0=E6=94=B9=E6=9C=AA=E6=88=90=E5=8A=9F?= =?UTF-8?q?=E4=B9=9F=E6=9C=AA=E6=8A=9B=E5=BC=82=E5=B8=B8=E7=9A=84=20code?= =?UTF-8?q?=20=E5=92=8C=20msg=EF=BC=9BAbstractParser=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E5=8F=8A=E5=93=8D=E5=BA=94=E7=9A=84=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E6=89=93=E5=8D=B0=EF=BC=9BAbstractSQLConfig=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=20key$=20=E7=9A=84=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractParser.java | 29 +++++++++---------- .../java/apijson/orm/AbstractSQLConfig.java | 7 +++-- .../java/apijson/orm/AbstractSQLExecutor.java | 5 ++-- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index be8ef09e3..7f3a2f3c2 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -48,6 +48,11 @@ public abstract class AbstractParser implements Parser, ParserCreator, VerifierCreator, SQLCreator { protected static final String TAG = "AbstractParser"; + /** + * 打印大数据量日志的标识。线上环境比较敏感,可以通过切换该变量来控制异常栈抛出、错误日志打印。保守起见,该值默认为false。 + * 与 {@link Log#DEBUG} 任何一个为 true 都会打印关键的接口请求及响应信息。 + */ + public static boolean IS_PRINT_BIG_LOG = false; /** * method = null @@ -301,9 +306,6 @@ public JSONObject parseResponse(String request) { private int queryDepth; - // 打印异常日志的标识。线上环境比较敏感,可以通过切换该变量来控制异常栈抛出、错误日志打印。保守起见,该值默认为false。 - public static boolean isPrintErrorLog = false; - /**解析请求json并获取对应结果 * @param request * @return requestObject @@ -386,12 +388,11 @@ public JSONObject parseResponse(JSONObject request) { long endTime = System.currentTimeMillis(); long duration = endTime - startTime; - if (isPrintErrorLog) { //用 | 替代 /,避免 APIJSON ORM,APIAuto 等解析路径错误 + if (Log.DEBUG) { requestObject.put("sql:generate|cache|execute|maxExecute", getSQLExecutor().getGeneratedSQLCount() + "|" + getSQLExecutor().getCachedSQLCount() + "|" + getSQLExecutor().getExecutedSQLCount() + "|" + getMaxSQLCount()); requestObject.put("depth:count|max", queryDepth + "|" + getMaxQueryDepth()); requestObject.put("time:start|duration|end", startTime + "|" + duration + "|" + endTime); if (error != null) { - Log.d(TAG, String.format("onObjectParse error, error is %s", error.getMessage())); requestObject.put("throw", error.getClass().getName()); requestObject.put("trace", error.getStackTrace()); } @@ -399,17 +400,15 @@ public JSONObject parseResponse(JSONObject request) { onClose(); - //会不会导致原来的session = null? session = null; - - if (isPrintErrorLog) { - Log.d(TAG, "\n\n\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n " - + requestMethod + "/parseResponse request = \n" + requestString + "\n\n"); - - Log.d(TAG, "parseResponse return response = \n" + JSON.toJSONString(requestObject) - + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n\n"); + System.err.println("\n\n\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n " + + TAG + ".DEBUG: " + requestMethod + "/parseResponse request = \n" + requestString + "\n\n"); + + if (Log.DEBUG || IS_PRINT_BIG_LOG || error != null) { // 日志仅存服务器,所以不太敏感,而且这些日志虽然量大但非常重要,对排查 bug 很关键 + System.err.println(TAG + ".DEBUG: " + requestMethod + "/parseResponse return response = \n" + JSON.toJSONString(requestObject) + "\n\n"); } - Log.d(TAG, "parseResponse endTime = " + endTime + "; duration = " + duration - + ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n\n"); + + System.err.println(TAG + ".DEBUG: " + requestMethod + "/parseResponse endTime = " + endTime + "; duration = " + duration + + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n\n"); return res; } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 6f61b9c19..968128011 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2018,9 +2018,12 @@ public String getSearchString(String key, Object[] values, int type) throws Ille if (v instanceof String == false) { throw new IllegalArgumentException(key + "$:value 中 value 的类型只能为 String 或 String[]!"); } - if (((String) v).contains("%%")) { - throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + " 中包含 %% !不允许有连续的 % !"); + if (((String) v).isEmpty()) { // 允许查空格 StringUtil.isEmpty((String) v, true) + 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); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 8966d0132..68bd8fe7e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -27,7 +27,6 @@ import java.util.Map.Entry; import java.util.Set; -import apijson.orm.exception.NotExistException; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; @@ -211,11 +210,11 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws int updateCount = executeUpdate(config); if (updateCount <= 0) { - throw new NotExistException("没权限访问或对象不存在!"); + throw new IllegalAccessException("没权限访问或对象不存在!"); // NotExistException 会被 catch 转为成功状态 } // updateCount>0时收集结果。例如更新操作成功时,返回count(affected rows)、id字段 - result = new JSONObject(true); + result = AbstractParser.newSuccessResult(); // TODO 对 APIAuto 及其它现有的前端/客户端影响比较大,暂时还是返回 code 和 msg,5.0 再移除 new JSONObject(true); //id,id{}至少一个会有,一定会返回,不用抛异常来阻止关联写操作时前面错误导致后面无条件执行! result.put(JSONResponse.KEY_COUNT, updateCount);//返回修改的记录数 From 7d6226bdd976885aa59796b2958f658cf36c3e16 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 7 Apr 2021 01:43:07 +0800 Subject: [PATCH 079/944] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E6=96=B0=E5=A2=9E=20=E8=85=BE=E8=AE=AF=20CSIG=20?= =?UTF-8?q?=E5=90=8C=E4=BA=8B=20fineday009?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0900ca076..091950a18 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,11 +10,11 @@ - [ruoranw](https://github.com/ruoranw)(现居美国洛杉矶) - [zhoulingfengofcd](https://github.com/zhoulingfengofcd) - [Zerounary](https://github.com/Zerounary) -- [fineday009](https://github.com/fineday009)(腾讯后台工程师) +- [fineday009](https://github.com/fineday009)(腾讯工程师) - [vincentCheng](https://github.com/vincentCheng) - [justinfengchen](https://github.com/justinfengchen) - [linlwqq](https://github.com/linlwqq) -- [redcatmiss](https://github.com/redcatmiss)(社保科技后端工程师) +- [redcatmiss](https://github.com/redcatmiss)(社保科技工程师) - [linbren](https://github.com/linbren) - [jinzhongjian](https://github.com/jinzhongjian) - [CoolGeo2016](https://github.com/CoolGeo2016) From f4d8775acba7b0ed4145e270a12a2c5bff8413b8 Mon Sep 17 00:00:00 2001 From: 403f <1292451605@qq.com> Date: Sat, 10 Apr 2021 22:44:04 +0800 Subject: [PATCH 080/944] =?UTF-8?q?=E5=AF=B9JSONResponse.java=E4=B8=AD?= =?UTF-8?q?=E7=9A=84formatHyphen=E6=96=B9=E6=B3=95=E7=9A=84=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/JSONResponse.java | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/JSONResponse.java b/APIJSONORM/src/main/java/apijson/JSONResponse.java index 5cc3a0b6e..ae60fd247 100755 --- a/APIJSONORM/src/main/java/apijson/JSONResponse.java +++ b/APIJSONORM/src/main/java/apijson/JSONResponse.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Set; +import java.util.StringTokenizer; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; @@ -502,23 +503,17 @@ public static String formatColon(@NotNull String key) { * @return */ public static String formatHyphen(@NotNull String key, boolean firstCase) { - boolean first = true; - int index; - String name = ""; - String part; - do { - index = key.indexOf("-"); - part = index < 0 ? key : key.substring(0, index); - - name += firstCase && first == false ? StringUtil.firstCase(part, true) : part; - key = key.substring(index + 1); - first = false; - } - while (index >= 0); + StringTokenizer parts = new StringTokenizer(key, "-"); + name += parts.nextToken(); + while(parts.hasMoreTokens()) + { + String part = parts.nextToken(); + name += firstCase ? StringUtil.firstCase(part, true) : part; + } - return name; + return name; } From 8646eab6f4ef379f404ebaab23501aaa71755ab6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 13 Apr 2021 22:49:51 +0800 Subject: [PATCH 081/944] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E4=BB=AC?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20403f=EF=BC=8C=E6=84=9F=E8=B0=A2=E5=85=B3?= =?UTF-8?q?=E4=BA=8E=E6=80=A7=E8=83=BD=E4=BC=98=E5=8C=96=E7=9A=84=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=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/217 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ed92e297d..dabe909ad 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,7 @@ https://github.com/Tencent/APIJSON/issues/187 + From ef41ebb2cb61ddaeb5a4d6ccfc01abf2f0d22135 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 13 Apr 2021 22:52:56 +0800 Subject: [PATCH 082/944] =?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=20403f=EF=BC=8C=E6=84=9F=E8=B0=A2?= =?UTF-8?q?=E5=85=B3=E4=BA=8E=E6=80=A7=E8=83=BD=E4=BC=98=E5=8C=96=E7=9A=84?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=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/217 --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 091950a18..69ea4ac49 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,7 @@ ## Acknowledgements -非常感谢以下几位贡献者对于 APIJSON 的做出的贡献: +非常感谢以下贡献者们对于 APIJSON 的做出的贡献: - [ruoranw](https://github.com/ruoranw)(现居美国洛杉矶) - [zhoulingfengofcd](https://github.com/zhoulingfengofcd) @@ -21,6 +21,7 @@ - [1906522096](https://github.com/1906522096) - [github-ganyu](https://github.com/github-ganyu) - [sunxiaoguang](https://github.com/sunxiaoguang)(知乎基础研发架构师) +- [403f](https://github.com/Tencent/APIJSON/pull/217) #### 其中特别致谢:
justinfengchen 提交的 6 个 Commits, 对 APIJSON 做出了 3,130 增加和 0 处删减(截止 2020/11/04 日);
From 6f80013878331b19a689e6b0bbafcdf0e87b4aa6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 17 Apr 2021 17:56:27 +0800 Subject: [PATCH 083/944] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20JOIN=20=E5=89=AF?= =?UTF-8?q?=E8=A1=A8=E6=9C=89=20=E5=BC=95=E7=94=A8=E8=B5=8B=E5=80=BC=20?= =?UTF-8?q?=E5=A4=96=E7=9A=84=E6=9D=A1=E4=BB=B6=E6=97=B6=E5=9B=A0=E4=B8=BA?= =?UTF-8?q?=E7=BC=93=E5=AD=98=20SQL=20WHERE=20=E4=B8=AD=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E9=A1=BA=E5=BA=8F=E4=B8=8D=E4=B8=80=E8=87=B4=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E5=A4=9A=E4=BD=99=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractParser.java | 14 +++++++++++++- .../main/java/apijson/orm/AbstractSQLConfig.java | 4 +++- .../main/java/apijson/orm/AbstractSQLExecutor.java | 8 ++++---- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 7f3a2f3c2..d068104bf 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1174,7 +1174,19 @@ else if (join != null){ throw new IllegalArgumentException("/" + path + ":'/targetTable/targetKey' 中路径对应的对象 '" + targetTableKey + "':{} 不存在或值为 null !必须是 {} 这种 JSONObject 格式!"); } - tableObj.put(key, tableObj.remove(key)); //保证和SQLExcecutor缓存的Config里where顺序一致,生成的SQL也就一致 + // 保证和 SQLExcecutor 缓存的 Config 里 where 顺序一致,生成的 SQL 也就一致 <<<<<<<<< + // AbstractSQLConfig.newSQLConfig 中强制把 id, id{}, userId, userId{} 放到了最前面 tableObj.put(key, tableObj.remove(key)); + + if (tableObj.size() > 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); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 968128011..4ed74b0f5 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1570,7 +1570,9 @@ public AbstractSQLConfig putWhere(String key, Object value, boolean prior) { combine = getCombine(); List andList = combine.get("&"); if (value == null) { - andList.remove(key); + if (andList != null) { + andList.remove(key); + } } else if (andList == null || andList.contains(key) == false) { int i = 0; diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 68bd8fe7e..05521ba0f 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -408,8 +408,8 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map //替换为 "id{}": [userId1, userId2, userId3...] - jc.putWhere(j.getOriginKey(), null, false); - jc.putWhere(j.getKey() + "{}", targetValueList, false); + jc.putWhere(j.getOriginKey(), null, false); // remove orginKey + jc.putWhere(j.getKey() + "{}", targetValueList, true); // add orginKey{} jc.setMain(true).setPreparedValueList(new ArrayList<>()); @@ -456,7 +456,7 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n"); //缓存到 childMap - cc.putWhere(j.getKey(), result.get(j.getKey()), false); + cc.putWhere(j.getKey(), result.get(j.getKey()), true); cacheSql = cc.getSQL(false); childMap.put(cacheSql, result); @@ -531,7 +531,7 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r if (childConfig != null && childTable.equalsIgnoreCase(childConfig.getSQLTable())) { - childConfig.putWhere(j.getKey(), table.get(j.getTargetKey()), false); + childConfig.putWhere(j.getKey(), table.get(j.getTargetKey()), true); childSql = childConfig.getSQL(false); if (StringUtil.isEmpty(childSql, true)) { From b900c5819c3bc641845fffce3e205da6b64bccda Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 18 Apr 2021 01:38:29 +0800 Subject: [PATCH 084/944] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20>=20RIGHT=20JOIN,?= =?UTF-8?q?=20^=20SIDE=20JOIN,=20!=20ANTI=20JOIN,=20)=20FOREIGN=20JOIN=20?= =?UTF-8?q?=E7=AD=89=E4=B8=8D=E8=BF=94=E5=9B=9E=E5=89=AF=E8=A1=A8=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=EF=BC=9B=E8=A7=A3=E5=86=B3=20|=20FULL=20JOIN=20?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E7=9A=84=E5=89=AF=E8=A1=A8=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E9=83=A8=E5=88=86=E6=98=AF=E9=94=99=E7=9A=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 17 +++--- .../java/apijson/orm/AbstractSQLExecutor.java | 18 +++++-- .../src/main/java/apijson/orm/Join.java | 52 +++++++++++++++---- 3 files changed, 67 insertions(+), 20 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 4ed74b0f5..d4ac822f8 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1769,8 +1769,8 @@ else if ("!".equals(ce.getKey())) { if (isAntiJoin) { // ( ANTI JOIN: A & ! B newWs += " ( " + ( isWsEmpty ? "" : ws + AND ) + NOT + " ( " + js + " ) " + " ) "; } - else if (isForeignJoin) { // ) FOREIGN JOIN: B & ! A - newWs += " ( " + " ( " + js + " ) " + ( isWsEmpty ? "" : AND + NOT + ws ) + " ) "; + 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) 返回结果不一样,后者往往更多 @@ -3203,7 +3203,7 @@ public static SQLConfig parseJoin(RequestMethod method, SQLConfig config, List resultList, Map continue; } - jc = j.getJoinConfig(); cc = j.getCacheConfig(); //这里用config改了getSQL后再还原很麻烦,所以提前给一个config2更好 + if (cc == null) { + if (Log.DEBUG) { + throw new NullPointerException("服务器内部错误, executeAppJoin cc == null ! 导致不能缓存 @ APP JOIN 的副表数据!"); + } + continue; + } + + jc = j.getJoinConfig(); //取出 "id@": "@/User/userId" 中所有 userId 的值 List targetValueList = new ArrayList<>(); @@ -543,15 +550,18 @@ 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); } - - finalTable.put(lable, getValue(config, rs, rsmd, tablePosition, table, columnIndex, lable, childMap)); - + return table; } diff --git a/APIJSONORM/src/main/java/apijson/orm/Join.java b/APIJSONORM/src/main/java/apijson/orm/Join.java index 01761037a..70c4401b9 100644 --- a/APIJSONORM/src/main/java/apijson/orm/Join.java +++ b/APIJSONORM/src/main/java/apijson/orm/Join.java @@ -162,7 +162,48 @@ else if (originKey.endsWith("<>")) { } + public boolean isAppJoin() { + return "@".equals(getJoinType()); + } + public boolean isLeftJoin() { + return "<".equals(getJoinType()); + } + public boolean isRightJoin() { + return ">".equals(getJoinType()); + } + public boolean isCrossJoin() { + return "*".equals(getJoinType()); + } + public boolean isInnerJoin() { + return "&".equals(getJoinType()); + } + public boolean isFullJoin() { + String jt = getJoinType(); + return "".equals(jt) || "|".equals(jt); + } + public boolean isOuterJoin() { + return "!".equals(getJoinType()); + } + public boolean isSideJoin() { + return "^".equals(getJoinType()); + } + public boolean isAntiJoin() { + return "(".equals(getJoinType()); + } + public boolean isForeignJoin() { + return ")".equals(getJoinType()); + } + public boolean isLeftOrRightJoin() { + String jt = getJoinType(); + return "<".equals(jt) || ">".equals(jt); + } + + public boolean canCacheViceTable() { + String jt = getJoinType(); + return "@".equals(jt) || "<".equals(jt) || ">".equals(jt) || "&".equals(jt) || "*".equals(jt) || ")".equals(jt); + } + public boolean isSQLJoin() { return ! isAppJoin(); } @@ -171,22 +212,13 @@ public static boolean isSQLJoin(Join j) { return j != null && j.isSQLJoin(); } - public boolean isAppJoin() { - return "@".equals(getJoinType()); - } - public static boolean isAppJoin(Join j) { return j != null && j.isAppJoin(); } - - public boolean isLeftOrRightJoin() { - return "<".equals(getJoinType()) || ">".equals(getJoinType()); - } - + public static boolean isLeftOrRightJoin(Join j) { return j != null && j.isLeftOrRightJoin(); } - } From 4ebc995f91e0ca42cc206de0ef555393d6be8b87 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 18 Apr 2021 01:45:59 +0800 Subject: [PATCH 085/944] =?UTF-8?q?=E8=B0=83=E6=95=B4=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=8F=91=E6=96=87=E7=9A=84=E9=93=BE=E6=8E=A5=EF=BC=9A=E5=85=A8?= =?UTF-8?q?=E5=9B=BD=E8=A1=8C=E6=94=BF=E5=8C=BA=E5=88=92=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=8A=93=E5=8F=96=E4=B8=8E=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://my.oschina.net/hwxia/blog/4999897 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dabe909ad..5194f1b3e 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 * 自动生成文档,不用再编写和维护 * 自动校验权限、自动管理版本、自动防 SQL 注入 * 开放 API 无需划分版本,始终保持兼容 -* 支持增删改查、模糊搜索、正则匹配、远程函数等 +* 支持增删改查、复杂查询、跨库连表、远程函数等
@@ -356,7 +356,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [APIJSON复杂业务深入实践(类似12306订票系统)](https://blog.csdn.net/aa330233789/article/details/105309571) -[全国行政区划数据抓取与处理](https://www.mdeditor.tw/pl/gCVk) +[全国行政区划数据抓取与处理](https://my.oschina.net/hwxia/blog/4999897) ### 生态项目 [APIJSON-Demo](https://github.com/APIJSON/APIJSON-Demo) APIJSON 各种语言、各种框架 的 使用示例项目、上手文档、测试数据 SQL 文件 等 From 838b3b08c8b7353748380c6c03a3b1dc08af6263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A1=BE=E5=8A=A0=E6=98=A5?= Date: Tue, 20 Apr 2021 10:56:20 +0800 Subject: [PATCH 086/944] =?UTF-8?q?fix:=20@explain=E5=9C=A8=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=96=B9=E6=B3=95=E5=BA=94=E7=94=A8=E7=9A=84=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit explain只应用在select请求中,如果是更新请求 不需要执行explain,但可以返回sql语句 issue #218 closes #218 --- .../main/java/apijson/orm/AbstractParser.java | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index d068104bf..697c145b7 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1512,6 +1512,8 @@ 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) { @@ -1549,19 +1551,27 @@ public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Except JSONObject result; boolean explain = config.isExplain(); - if (explain) { //如果先执行 explain,则 execute 会死循环,所以只能先执行非 explain + if (explain) { + //如果先执行 explain,则 execute 会死循环,所以只能先执行非 explain config.setExplain(false); //对下面 config.getSQL(false); 生效 JSONObject res = getSQLExecutor().execute(config, false); - config.setExplain(explain); - JSONObject explainResult = config.isMain() && config.getPosition() != 0 ? null : getSQLExecutor().execute(config, false); + //如果是查询方法,才能执行explain + if (RequestMethod.isQueryMethod(config.getMethod())){ + config.setExplain(explain); + JSONObject explainResult = config.isMain() && config.getPosition() != 0 ? null : getSQLExecutor().execute(config, false); - if (explainResult == null) { - result = res; - } - else { + if (explainResult == null) { + result = res; + } + else { + result = new JSONObject(true); + result.put(KEY_EXPLAIN, explainResult); + result.putAll(res); + } + }else{//如果是更新请求,不执行explain,但可以返回sql result = new JSONObject(true); - result.put(KEY_EXPLAIN, explainResult); + result.put(KEY_SQL, config.getSQL(false)); result.putAll(res); } } From 93efd13a8b3b7974d4e9b864dcfd6f56d10b0c8d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 20 Apr 2021 23:23:08 +0800 Subject: [PATCH 087/944] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20gujiachun=EF=BC=8C=E6=84=9F=E8=B0=A2=E8=A7=A3?= =?UTF-8?q?=E5=86=B3=E5=AF=B9=E5=A2=9E=E5=88=A0=E6=94=B9=E7=94=A8=20@expla?= =?UTF-8?q?in=20=E7=9A=84=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/219 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5194f1b3e..d120c0289 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,7 @@ https://github.com/Tencent/APIJSON/issues/187 + From 3c9084479876748055ebe048d388098ef05c3e23 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 20 Apr 2021 23:25:00 +0800 Subject: [PATCH 088/944] =?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=20gujiachun=EF=BC=8C=E6=84=9F?= =?UTF-8?q?=E8=B0=A2=E8=A7=A3=E5=86=B3=E5=AF=B9=E5=A2=9E=E5=88=A0=E6=94=B9?= =?UTF-8?q?=E7=94=A8=20@explain=20=E7=9A=84=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/219 --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 69ea4ac49..8ef74b84e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,8 @@ - [1906522096](https://github.com/1906522096) - [github-ganyu](https://github.com/github-ganyu) - [sunxiaoguang](https://github.com/sunxiaoguang)(知乎基础研发架构师) -- [403f](https://github.com/Tencent/APIJSON/pull/217) +- [403f](https://github.com/403f) +- [gujiachun](https://github.com/gujiachun) #### 其中特别致谢:
justinfengchen 提交的 6 个 Commits, 对 APIJSON 做出了 3,130 增加和 0 处删减(截止 2020/11/04 日);
From 68d94d43b291eae6f51f5db7ace8d8ee308c7cf3 Mon Sep 17 00:00:00 2001 From: gdjs2 Date: Thu, 22 Apr 2021 07:43:03 +0000 Subject: [PATCH 089/944] Using Arrays.toString() to deal with methods array --- .../src/main/java/apijson/orm/AbstractFunctionParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java index d712b15f5..3debe096a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java @@ -167,7 +167,7 @@ public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull Str String[] methods = StringUtil.split(row.getString("methods")); List ml = methods == null || methods.length <= 0 ? null : Arrays.asList(methods); if (ml != null && ml.contains(parser.getMethod().toString()) == false) { - throw new UnsupportedOperationException("不允许 method = " + parser.getMethod() + " 的请求调用远程函数 " + fb.getMethod() + " ! 必须满足 method 在 " + methods + "内 !"); + throw new UnsupportedOperationException("不允许 method = " + parser.getMethod() + " 的请求调用远程函数 " + fb.getMethod() + " ! 必须满足 method 在 " + Arrays.toString(methods) + "内 !"); } try { From ab5c047d3160c6cc788aae8e8d536f4773b42099 Mon Sep 17 00:00:00 2001 From: Rkyzzy <982993741@qq.com> Date: Thu, 22 Apr 2021 17:22:34 +0800 Subject: [PATCH 090/944] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8StringBuilder?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BA=86=E5=AD=97=E7=AC=A6=E4=B8=B2=E6=8B=BC?= =?UTF-8?q?=E6=8E=A5=20=E5=B0=86StringUtil.java=E7=B1=BB=E4=B8=AD=E4=B8=89?= =?UTF-8?q?=E5=A4=84=E5=AD=97=E7=AC=A6=E4=B8=B2=E7=9B=B4=E6=8E=A5=E6=8B=BC?= =?UTF-8?q?=E6=8E=A5=E4=BC=98=E5=8C=96=E4=B8=BA=E4=BD=BF=E7=94=A8StringBui?= =?UTF-8?q?lder=E6=8B=BC=E6=8E=A5=20issue=20#182?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/StringUtil.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index 7f8fa831c..fed6d9933 100755 --- a/APIJSONORM/src/main/java/apijson/StringUtil.java +++ b/APIJSONORM/src/main/java/apijson/StringUtil.java @@ -118,7 +118,7 @@ public static String getString(Object[] array, String split) { * @return */ public static String getString(Object[] array, String split, boolean ignoreEmptyItem) { - String s = ""; + StringBuilder s = new StringBuilder(""); if (array != null) { if (split == null) { split = ","; @@ -127,10 +127,10 @@ public static String getString(Object[] array, String split, boolean ignoreEmpty if (ignoreEmptyItem && isEmpty(array[i], true)) { continue; } - s += ((i > 0 ? split : "") + array[i]); + s.append(((i > 0 ? split : "") + array[i])); } } - return getString(s); + return getString(s.toString()); } //获取string,为null时返回"" >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -540,20 +540,19 @@ public static String getNumber(String s, boolean onlyStart) { return ""; } - String numberString = ""; + StringBuilder numberString = new StringBuilder(""); String single; for (int i = 0; i < s.length(); i++) { single = s.substring(i, i + 1); if (isNumer(single)) { - numberString += single; + numberString.append(single); } else { if (onlyStart) { - return numberString; + return numberString.toString(); } } } - - return numberString; + return numberString.toString(); } //提取特殊字符>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -643,14 +642,16 @@ public static String getPrice(String price, int formatType) { } //单独写到getCorrectPrice? <<<<<<<<<<<<<<<<<<<<<< - String correctPrice = ""; + String correctPrice; + StringBuilder correctPriceBuilder = new StringBuilder(""); String s; for (int i = 0; i < price.length(); i++) { s = price.substring(i, i + 1); if (".".equals(s) || isNumer(s)) { - correctPrice += s; + correctPriceBuilder.append(s); } } + correctPrice = correctPriceBuilder.toString(); //单独写到getCorrectPrice? >>>>>>>>>>>>>>>>>>>>>> Log.i(TAG, "getPrice <<<<<<<<<<<<<<<<<< correctPrice = " + correctPrice); From a8b388b0bab9b7a0c5288bd1813b27902fc8bf37 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 23 Apr 2021 10:56:06 +0800 Subject: [PATCH 091/944] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8ef74b84e..bae8c1cc4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,7 +62,7 @@ APIJSON 作为腾讯开源的知名热门项目,贡献代码除了可以给简 我们除了希望听到您的反馈和建议外,我们也希望您接受代码形式的直接帮助,对我们的 GitHub 发出 Pull Request 请求。 -以下是具体步骤: +以下是具体步骤:(注意本步骤将不会自动记录贡献者的 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 5826b45a82dfd7caa321d4e23be90f337b69aeee Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 23 Apr 2021 10:57:48 +0800 Subject: [PATCH 092/944] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bae8c1cc4..40cee5241 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,7 +62,7 @@ APIJSON 作为腾讯开源的知名热门项目,贡献代码除了可以给简 我们除了希望听到您的反馈和建议外,我们也希望您接受代码形式的直接帮助,对我们的 GitHub 发出 Pull Request 请求。 -以下是具体步骤:(注意本步骤将不会自动记录贡献者的 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)) +以下是具体步骤:(注意本步骤将不会自动把贡献者添加到 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 373dae0fc8656cafdcab5ebe3ac1a0cde478ab5c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 23 Apr 2021 10:59:04 +0800 Subject: [PATCH 093/944] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 40cee5241..30e47f32c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,7 +62,7 @@ APIJSON 作为腾讯开源的知名热门项目,贡献代码除了可以给简 我们除了希望听到您的反馈和建议外,我们也希望您接受代码形式的直接帮助,对我们的 GitHub 发出 Pull Request 请求。 -以下是具体步骤:(注意本步骤将不会自动把贡献者添加到 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 本项目,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 46436c5cc0dc7971ae34757da787cf9df7b00235 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 23 Apr 2021 11:00:33 +0800 Subject: [PATCH 094/944] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 30e47f32c..a6bef6ca7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,7 +62,7 @@ APIJSON 作为腾讯开源的知名热门项目,贡献代码除了可以给简 我们除了希望听到您的反馈和建议外,我们也希望您接受代码形式的直接帮助,对我们的 GitHub 发出 Pull Request 请求。 -以下是具体步骤:(注意本步骤未 Fork 本项目,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)) +以下是具体步骤:(如果使用本步骤,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 08a3125adf96509ae8361095c18e54818e2e4711 Mon Sep 17 00:00:00 2001 From: kxlv2000 <49295281+kxlv2000@users.noreply.github.com> Date: Fri, 23 Apr 2021 15:26:26 +0800 Subject: [PATCH 095/944] =?UTF-8?q?=E5=B0=9D=E8=AF=95=E4=BF=AE=E5=A4=8D=20?= =?UTF-8?q?oracle=20select=20=E5=88=86=E9=A1=B5=E8=AF=AD=E6=B3=95=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index d4ac822f8..4f036900e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1473,17 +1473,17 @@ public String getLimitString() { if (count <= 0 || RequestMethod.isHeadMethod(getMethod(), true)) { return ""; } - return getLimitString(getPage(), getCount(), isOracle() || isSQLServer() || isDb2()); + return getLimitString(getPage(), getCount(), isOracle() || isSQLServer() || isDb2(), isOracle()); } /**获取限制数量 * @param limit * @return */ - public static String getLimitString(int page, int count, boolean isTSQL) { + public static String getLimitString(int page, int count, boolean isTSQL, boolean isOracle) { int offset = getOffset(page, count); - if (isTSQL) { // OFFSET FECTH 中所有关键词都不可省略 - return " OFFSET " + offset + " ROWS FETCH FIRST " + count + " ROWS ONLY"; + if (isTSQL) { // OFFSET FECTH 中所有关键词都不可省略, 另外 Oracle 数据库使用子查询加 where 分页 + 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 @@ -2613,8 +2613,12 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { config.setPreparedValueList(new ArrayList()); String column = config.getColumnString(); - return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config); - } + 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(); + }else + return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config); + } } /**获取条件SQL字符串 @@ -2641,7 +2645,7 @@ private static String getConditionString(String column, String table, AbstractSQ //no need to optimize // if (config.getPage() <= 0 || ID.equals(column.trim())) { - return condition + config.getLimitString(); + return config.isOracle()? condition:condition + config.getLimitString(); // } // // From c38ad8cebbb49374e4d31bde3f6846c5343a8da8 Mon Sep 17 00:00:00 2001 From: Rkyzzy <982993741@qq.com> Date: Sat, 24 Apr 2021 21:27:23 +0800 Subject: [PATCH 096/944] =?UTF-8?q?=E4=BD=BF=E7=94=A8entrySet=E8=BF=AD?= =?UTF-8?q?=E4=BB=A3=E5=99=A8=E6=9B=BF=E4=BB=A3keySet=E8=BF=AD=E4=BB=A3?= =?UTF-8?q?=E5=99=A8=E6=8F=90=E9=AB=98=E6=95=88=E7=8E=87=20#48?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractParser.java | 4 ++-- .../java/apijson/orm/AbstractSQLConfig.java | 24 ++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 9fb17ee05..b45619369 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1392,10 +1392,10 @@ public Object getValueByPath(String valuePath) { } //取出key被valuePath包含的result,再从里面获取key对应的value - Set set = queryResultMap.keySet(); JSONObject parent = null; String[] keys = null; - for (String path : set) { + for (Map.Entry entry : queryResultMap.entrySet()){ + String path = entry.getKey(); if (valuePath.startsWith(path + "/")) { try { parent = (JSONObject) queryResultMap.get(path); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 6defdd1e1..bdbadc864 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1341,16 +1341,17 @@ public Object getWhere(String key, boolean exactMatch) { return where == null ? null : where.get(key); } - Set set = key == null || where == null ? null : where.keySet(); - if (set != null) { - synchronized (where) { - if (where != null) { - int index; - for (String k : set) { - index = k.indexOf(key); - if (index >= 0 && StringUtil.isName(k.substring(index)) == false) { - return where.get(k); - } + if (key == null || where == null){ + return null; + } + synchronized (where) { + if (where != null) { + int index; + for (Map.Entry entry : where.entrySet()) { + String k = entry.getKey(); + index = k.indexOf(key); + if (index >= 0 && StringUtil.isName(k.substring(index)) == false) { + return where.get(k); } } } @@ -2289,7 +2290,8 @@ public String getSetString(RequestMethod method, Map content, bo Object value; String idKey = getIdKey(); - for (String key : set) { + for (Map.Entry entry : content.entrySet()) { + String key = entry.getKey(); //避免筛选到全部 value = key == null ? null : content.get(key); if (key == null || idKey.equals(key)) { continue; From 6b5ecb2f1dde23ba56dae93dbd905906116a59fc Mon Sep 17 00:00:00 2001 From: Rkyzzy <982993741@qq.com> Date: Sat, 24 Apr 2021 22:42:35 +0800 Subject: [PATCH 097/944] =?UTF-8?q?=E4=BD=BF=E7=94=A8entrySet=E4=BB=A3?= =?UTF-8?q?=E6=9B=BFkeySet=E6=8F=90=E9=AB=98=E6=95=88=E7=8E=87=20#48?= 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 b45619369..98052f7ee 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1398,7 +1398,7 @@ public Object getValueByPath(String valuePath) { String path = entry.getKey(); if (valuePath.startsWith(path + "/")) { try { - parent = (JSONObject) queryResultMap.get(path); + parent = (JSONObject) entry.getValue(); } catch (Exception e) { Log.e(TAG, "getValueByPath try { parent = (JSONObject) queryResultMap.get(path); } catch { " + "\n parent not instanceof JSONObject!"); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index bdbadc864..81acbf455 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1351,7 +1351,7 @@ public Object getWhere(String key, boolean exactMatch) { String k = entry.getKey(); index = k.indexOf(key); if (index >= 0 && StringUtil.isName(k.substring(index)) == false) { - return where.get(k); + return entry.getValue(); } } } @@ -2304,7 +2304,7 @@ public String getSetString(RequestMethod method, Map content, bo } else { keyType = 0; //注意重置类型,不然不该加减的字段会跟着加减 } - value = content.get(key); + value = entry.getValue(); key = getRealKey(method, key, false, true, verifyName); setString += (isFirst ? "" : ", ") + (getKey(key) + " = " + (keyType == 1 ? getAddString(key, value) : (keyType == 2 From 0fcbdfab03aa9bcb8e0a35dc7276f327a34e7aca Mon Sep 17 00:00:00 2001 From: Rkyzzy <982993741@qq.com> Date: Sun, 25 Apr 2021 01:26:57 +0800 Subject: [PATCH 098/944] further wrote javadoc --- .../src/main/java/apijson/StringUtil.java | 23 ++++++++++++------- .../main/java/apijson/orm/AbstractParser.java | 4 +++- .../java/apijson/orm/AbstractSQLConfig.java | 14 +++++++---- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index fed6d9933..23e49f181 100755 --- a/APIJSONORM/src/main/java/apijson/StringUtil.java +++ b/APIJSONORM/src/main/java/apijson/StringUtil.java @@ -111,11 +111,13 @@ public static String getString(Object[] array, boolean ignoreEmptyItem) { public static String getString(Object[] array, String split) { return getString(array, split, false); } + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/182 /**获取string,为null则返回"" - * @param array - * @param split - * @param ignoreEmptyItem - * @return + * @param array -the str array given + * @param split -the token used to split + * @param ignoreEmptyItem -whether to ignore empty item or not + * @return {@link #getString(Object[], String, boolean)} + *

Here we replace the simple "+" way of concatenating with Stringbuilder 's append

*/ public static String getString(Object[] array, String split, boolean ignoreEmptyItem) { StringBuilder s = new StringBuilder(""); @@ -530,10 +532,13 @@ public static String getNumber(CharSequence cs) { public static String getNumber(String s) { return getNumber(s, false); } + + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/182 /**去掉string内所有非数字类型字符 - * @param s + * @param s -string passed in * @param onlyStart 中间有非数字时只获取前面的数字 - * @return + * @return limit String + *

Here we replace the simple "+" way of concatenating with Stringbuilder 's append

*/ public static String getNumber(String s, boolean onlyStart) { if (isNotEmpty(s, true) == false) { @@ -631,10 +636,12 @@ public static String getCorrectEmail(String email) { public static String getPrice(String price) { return getPrice(price, PRICE_FORMAT_DEFAULT); } + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/182 /**获取价格,保留两位小数 - * @param price + * @param price -price passed in * @param formatType 添加单位(元) - * @return + * @return limit String + *

Here we replace the simple "+" way of concatenating with Stringbuilder 's append

*/ public static String getPrice(String price, int formatType) { if (isNotEmpty(price, true) == false) { diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 98052f7ee..3684c9dce 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1375,9 +1375,11 @@ public synchronized void putQueryResult(String path, Object result) { queryResultMap.put(path, result); // } } + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 /**根据路径获取值 - * @param valuePath + * @param valuePath -the path need to get value * @return parent == null ? valuePath : parent.get(keys[keys.length - 1]) + *

use entrySet+getValue() to replace keySet+get() to enhance efficiency

*/ @Override public Object getValueByPath(String valuePath) { diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 81acbf455..51152f81f 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1329,10 +1329,12 @@ public AbstractSQLConfig setCombine(Map> combine) { public Object getWhere(String key) { return getWhere(key, false); } + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 /** - * @param key - * @param exactMatch + * @param key - the key passed in + * @param exactMatch - whether it is exact match * @return + *

use entrySet+getValue() to replace keySet+get() to enhance efficiency

*/ @JSONField(serialize = false) @Override @@ -2273,11 +2275,13 @@ public static JSONArray newJSONArray(Object obj) { public String getSetString() throws Exception { return getSetString(getMethod(), getContent(), ! isTest()); } + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 /**获取SET - * @param method - * @param content + * @param method -the method used + * @param content -the content map * @return - * @throws Exception + * @throws Exception + *

use entrySet+getValue() to replace keySet+get() to enhance efficiency

*/ @JSONField(serialize = false) public String getSetString(RequestMethod method, Map content, boolean verifyName) throws Exception { From 39f21f6f0f5a2428bac546e70abe391a68d73afa Mon Sep 17 00:00:00 2001 From: gdjs2 Date: Sun, 25 Apr 2021 04:12:46 +0000 Subject: [PATCH 099/944] Format the code; Use valueOf(String) instead of the deprecated Long(String) --- .../src/main/java/apijson/JSONResponse.java | 18 +++++++----------- .../java/apijson/orm/AbstractVerifier.java | 2 +- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/JSONResponse.java b/APIJSONORM/src/main/java/apijson/JSONResponse.java index ae60fd247..d979c7328 100755 --- a/APIJSONORM/src/main/java/apijson/JSONResponse.java +++ b/APIJSONORM/src/main/java/apijson/JSONResponse.java @@ -505,16 +505,12 @@ public static String formatColon(@NotNull String key) { public static String formatHyphen(@NotNull String key, boolean firstCase) { String name = ""; - StringTokenizer parts = new StringTokenizer(key, "-"); - name += parts.nextToken(); - while(parts.hasMoreTokens()) - { - String part = parts.nextToken(); - name += firstCase ? StringUtil.firstCase(part, true) : part; - } - - return name; + StringTokenizer parts = new StringTokenizer(key, "-"); + name += parts.nextToken(); + while(parts.hasMoreTokens()) { + String part = parts.nextToken(); + name += firstCase ? StringUtil.firstCase(part, true) : part; + } + return name; } - - } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 633b5d645..926a037f8 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -277,7 +277,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception { if (id instanceof Number == false) {//不能准确地判断Long,可能是Integer throw new UnsupportedDataTypeException(table + ".id类型错误,id类型必须是Long!"); } - if (list.contains(new Long("" + id)) == false) {//Integer等转为Long才能正确判断。强转崩溃 + if (list.contains(Long.valueOf("" + id)) == false) {//Integer等转为Long才能正确判断。强转崩溃 throw new IllegalAccessException(visitorIdkey + " = " + id + " 的 " + table + " 不允许 " + role.name() + " 用户的 " + method.name() + " 请求!"); } From 6d8b7d3289b1304c55a498bdb87db62b1fb06df5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 26 Apr 2021 16:22:22 +0800 Subject: [PATCH 100/944] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=B8=80=E4=B8=AA=20?= =?UTF-8?q?PHP=20=E7=89=88=E6=9C=AC=E7=9A=84=20APIJSON=EF=BC=9AAPIJSON-php?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/xianglong111/APIJSON-php --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d120c0289..e6619a2dd 100644 --- a/README.md +++ b/README.md @@ -380,6 +380,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-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-node](https://github.com/kevinaskin/apijson-node) Node.ts 版 APIJSON,提供 nestjs 和 typeorm 的 Demo,支持 MySQL, PostgreSQL, SQL Server, Oracle From c6eb4463608a0d2bc0fddda1e0c9a6ff0c3253d7 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 28 Apr 2021 11:26:53 +0800 Subject: [PATCH 101/944] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e6619a2dd..07cc655db 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON -

🏆 码云最有价值开源项目
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构!

+

🏆 腾讯内外四个奖项、腾讯开源五个第一
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构!

From 2162ac6b5a1787b11382fbee97d327bcffaf0865 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 28 Apr 2021 11:35:04 +0800 Subject: [PATCH 102/944] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8C=85=E6=8B=AC?= =?UTF-8?q?=E6=9D=A5=E8=87=AA=20SUSTech=20=E5=9C=A8=E5=86=85=E7=9A=84=204?= =?UTF-8?q?=20=E4=B8=AA=E8=B4=A1=E7=8C=AE=E8=80=85?= 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 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 07cc655db..13c64d960 100644 --- a/README.md +++ b/README.md @@ -240,6 +240,7 @@ https://github.com/Tencent/APIJSON/issues/187 height="54" width="54" > + @@ -249,6 +250,8 @@ https://github.com/Tencent/APIJSON/issues/187 + + From 905fb1139ff5bea868dbb6049982653f169e6566 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 28 Apr 2021 11:41:40 +0800 Subject: [PATCH 103/944] =?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=8C=85=E6=8B=AC=E6=9D=A5=E8=87=AA?= =?UTF-8?q?=20=E8=85=BE=E8=AE=AF=E3=80=81SUSTech=20=E7=9A=84=204=20?= =?UTF-8?q?=E4=BA=BA=EF=BC=8C=E9=9D=9E=E5=B8=B8=E6=84=9F=E8=B0=A2=E5=A4=A7?= =?UTF-8?q?=E5=AE=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/edit/master/CONTRIBUTING.md --- CONTRIBUTING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a6bef6ca7..011ee9502 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,6 +7,7 @@ 非常感谢以下贡献者们对于 APIJSON 的做出的贡献: +- [TommyLemon](https://github.com/TommyLemon)(腾讯工程师) - [ruoranw](https://github.com/ruoranw)(现居美国洛杉矶) - [zhoulingfengofcd](https://github.com/zhoulingfengofcd) - [Zerounary](https://github.com/Zerounary) @@ -23,6 +24,9 @@ - [sunxiaoguang](https://github.com/sunxiaoguang)(知乎基础研发架构师) - [403f](https://github.com/403f) - [gujiachun](https://github.com/gujiachun) +- [gdjs2](https://github.com/gdjs2) +- [Rkyzzy](https://github.com/Rkyzzy)(SUSTech) +- [kxlv2000](https://github.com/kxlv2000)(SUSTech) #### 其中特别致谢:
justinfengchen 提交的 6 个 Commits, 对 APIJSON 做出了 3,130 增加和 0 处删减(截止 2020/11/04 日);
From 95932f7687dbfb7b2b4e3166621805fc5020ce01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A1=BE=E5=8A=A0=E6=98=A5?= Date: Thu, 29 Apr 2021 16:27:50 +0800 Subject: [PATCH 104/944] =?UTF-8?q?=E5=8E=BB=E9=99=A4final=E5=85=B3?= =?UTF-8?q?=E9=94=AE=E5=AD=97=EF=BC=8C=E6=96=B9=E4=BE=BF=E4=B8=9A=E5=8A=A1?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=87=8D=E6=96=B0=E5=AE=9A=E4=B9=89ok?= =?UTF-8?q?=E3=80=81code=E3=80=81msg=E5=AD=97=E6=AE=B5=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/JSONResponse.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/JSONResponse.java b/APIJSONORM/src/main/java/apijson/JSONResponse.java index ae60fd247..33da83ca6 100755 --- a/APIJSONORM/src/main/java/apijson/JSONResponse.java +++ b/APIJSONORM/src/main/java/apijson/JSONResponse.java @@ -57,9 +57,9 @@ public JSONResponse(JSONObject object) { public static final String MSG_SERVER_ERROR = "Internal Server Error!"; //服务器内部错误 - public static final String KEY_OK = "ok"; - public static final String KEY_CODE = "code"; - public static final String KEY_MSG = "msg"; + public static String KEY_OK = "ok"; + public static String KEY_CODE = "code"; + public static String KEY_MSG = "msg"; public static final String KEY_COUNT = "count"; public static final String KEY_TOTAL = "total"; public static final String KEY_INFO = "info"; //详细的分页信息 From eb5fe3204a3ac5ef499311888fe88397de099b02 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 4 May 2021 17:06:06 +0800 Subject: [PATCH 105/944] =?UTF-8?q?=E2=80=9C=E8=85=BE=E8=AE=AF=E5=86=85?= =?UTF-8?q?=E5=A4=96=E5=9B=9B=E4=B8=AA=E5=A5=96=E9=A1=B9=E2=80=9D=20?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=20=E2=80=9C=E8=85=BE=E8=AE=AF=E5=86=85?= =?UTF-8?q?=E5=A4=96=E4=BA=94=E4=B8=AA=E5=A5=96=E9=A1=B9=E2=80=9D?= 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 13c64d960..fd6dbd232 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON -

🏆 腾讯内外四个奖项、腾讯开源五个第一
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构!

+

🏆 腾讯内外五个奖项、腾讯开源五个第一
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构!

From 6a0871ffea25fe9ca848c0bd4d171d76185343a7 Mon Sep 17 00:00:00 2001 From: smallhowcao Date: Thu, 13 May 2021 15:53:44 +0800 Subject: [PATCH 106/944] =?UTF-8?q?=E4=BF=AE=E6=94=B9=20Document=20=20?= =?UTF-8?q?=E6=96=87=E6=A1=A3=20OUTER=20JOIN=20=E6=8B=BC=E5=86=99=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改 Document 文档 OUTER JOIN 拼写错误 --- Document-English.md | 2 +- Document.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Document-English.md b/Document-English.md index 9dd2eb141..eb737bb4b 100644 --- a/Document-English.md +++ b/Document-English.md @@ -42,7 +42,7 @@ 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
"!" - OUTTER 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 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)"}})
diff --git a/Document.md b/Document.md index c7e7be53c..d1fd5dbce 100644 --- a/Document.md +++ b/Document.md @@ -370,7 +370,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
"!" - OUTTER 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",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@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)"}})
From 0aa7281398bf52aa71dac3dad9d447d055778c4f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 13 May 2021 16:39:20 +0800 Subject: [PATCH 107/944] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E3=80=90=E8=85=BE?= =?UTF-8?q?=E8=AE=AF=E7=8A=80=E7=89=9B=E9=B8=9F=E5=BC=80=E6=BA=90=E4=BA=BA?= =?UTF-8?q?=E6=89=8D=E5=9F=B9=E5=85=BB=E8=AE=A1=E5=88=92=E3=80=91=E5=85=AC?= =?UTF-8?q?=E5=91=8A?= 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 fd6dbd232..70fc32d17 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[【腾讯犀牛鸟开源人才培养计划】开始啦!加入我们开源共建吧~](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
From b3704c9f75a63f9db878519f76851eb55ec376c2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 13 May 2021 20:07:21 +0800 Subject: [PATCH 108/944] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=EF=BC=8C=E6=84=9F=E8=B0=A2=E8=85=BE?= =?UTF-8?q?=E8=AE=AF=E5=90=8C=E4=BA=8B=E7=9A=84=E8=B4=A1=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 70fc32d17..c178329b6 100644 --- a/README.md +++ b/README.md @@ -259,6 +259,7 @@ https://github.com/Tencent/APIJSON/issues/187 +
From d78967a05d8b6a51ae3f7f28266b8cd87f2cc42f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 13 May 2021 20:08:32 +0800 Subject: [PATCH 109/944] =?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=E8=85=BE=E8=AE=AF=E5=B7=A5=E7=A8=8B?= =?UTF-8?q?=E5=B8=88=20caohao-php=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 --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 011ee9502..ac57d8d9d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,6 +27,8 @@ - [gdjs2](https://github.com/gdjs2) - [Rkyzzy](https://github.com/Rkyzzy)(SUSTech) - [kxlv2000](https://github.com/kxlv2000)(SUSTech) +- [caohao-php](https://github.com/caohao-php)(腾讯工程师) + #### 其中特别致谢:
justinfengchen 提交的 6 个 Commits, 对 APIJSON 做出了 3,130 增加和 0 处删减(截止 2020/11/04 日);
From 5750a556be3cb220848192527d6699306932e5a5 Mon Sep 17 00:00:00 2001 From: Eno Yao Date: Fri, 14 May 2021 16:29:16 +0800 Subject: [PATCH 110/944] Update README-English.md Update README-English.md --- README-English.md | 63 +++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/README-English.md b/README-English.md index 9d5cf49be..eab64c560 100644 --- a/README-English.md +++ b/README-English.md @@ -75,19 +75,22 @@ With APIJSON, client developers will no longer be suffered from possible errors Server developers no longer need to worry about compatibility of APIs and documents with legacy apps. ### Examples: + #### Get a User Request: -


+
+```json
 {
   "User":{
   }
 }
-
+``` [Click here to test](http://apijson.cn:8080/get/{"User":{}}) Response: -

+
+```json
 {
   "User":{
     "id":38710,
@@ -106,13 +109,14 @@ Response:
   "code":200,
   "msg":"success"
 }
-
- +```
#### Get an Array of Users + Request: -

+
+```json
 {
   "[]":{
     "count":3,             //just get 3 results
@@ -121,12 +125,13 @@ Request:
     }
   }
 }
-
+``` [Click here to test](http://apijson.cn:8080/get/{"[]":{"count":3,"User":{"@column":"id,name"}}}) Response: -

+
+```json
 {
   "[]":[
     {
@@ -151,7 +156,7 @@ Response:
   "code":200,
   "msg":"success"
 }
-
+```
@@ -188,28 +193,28 @@ In the menu at the right, click libs, right click apijson-orm.jar,click add as l Open apijson.demo.server.DemoSQLConfig. In line 40-61, change return values of `getDBUri`,`getDBAccount`,`getDBPassword`,`getSchema` to your own database.
-

+```java
+@Override
+public String getDBUri() {
+    //TODO: Change the return value to your own
+    return DATABASE_POSTGRESQL.equalsIgnoreCase(getDatabase()) ? "jdbc:postgresql://localhost:5432/postgres" : "jdbc:mysql://192.168.71.146:3306/";
+}
+@Override
+public String getDBAccount() {
+    //TODO: Change the return value to your own
+    return DATABASE_POSTGRESQL.equalsIgnoreCase(getDatabase()) ? "postgres" : "root";
+}
 @Override
-	public String getDBUri() {
-		//TODO: Change the return value to your own
-		return DATABASE_POSTGRESQL.equalsIgnoreCase(getDatabase()) ? "jdbc:postgresql://localhost:5432/postgres" : "jdbc:mysql://192.168.71.146:3306/";
-	}
-	@Override
-	public String getDBAccount() {
+public String getDBPassword() {
     //TODO: Change the return value to your own
-		return DATABASE_POSTGRESQL.equalsIgnoreCase(getDatabase()) ? "postgres" : "root";
-	}
-	@Override
-	public String getDBPassword() {
-  	//TODO: Change the return value to your own
-		return DATABASE_POSTGRESQL.equalsIgnoreCase(getDatabase()) ? null : "root"; 
-	}
-	@Override
-	public String getSchema() {
-		String s = super.getSchema();
-		return StringUtil.isEmpty(s, true) ? "thea" : s; //TODO: Change the return value to your own. For here,change "thea" to "your database's name"
-	}
-
+ return DATABASE_POSTGRESQL.equalsIgnoreCase(getDatabase()) ? null : "root"; +} +@Override +public String getSchema() { + String s = super.getSchema(); + return StringUtil.isEmpty(s, true) ? "thea" : s; //TODO: Change the return value to your own. For here,change "thea" to "your database's name" +} +``` **Note**: Instead of this step, you can also [import your database](#2.2). From 3ac696efb732daf4100badabb9ce256d48632015 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 14 May 2021 18:40:50 +0800 Subject: [PATCH 111/944] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E4=BB=AC?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=85=BE=E8=AE=AF=20AlloyTeam=20=E7=9A=84?= =?UTF-8?q?=E5=90=8C=E4=BA=8B=EF=BC=8C=E9=9D=9E=E5=B8=B8=E6=84=9F=E8=B0=A2?= =?UTF-8?q?=E5=AF=B9=E8=8B=B1=E6=96=87=E6=96=87=E6=A1=A3=E7=9A=84=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/235 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c178329b6..a5d11536e 100644 --- a/README.md +++ b/README.md @@ -259,7 +259,9 @@ https://github.com/Tencent/APIJSON/issues/187 + +
From dedd0d16b6c7c085fa1fc259df212049eb966d9f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 14 May 2021 18:41:52 +0800 Subject: [PATCH 112/944] =?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=E8=85=BE=E8=AE=AF=20AlloyTeam=20?= =?UTF-8?q?=E7=9A=84=E5=90=8C=E4=BA=8B=EF=BC=8C=E9=9D=9E=E5=B8=B8=E6=84=9F?= =?UTF-8?q?=E8=B0=A2=E5=AF=B9=E8=8B=B1=E6=96=87=E6=96=87=E6=A1=A3=E7=9A=84?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/235 --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ac57d8d9d..b20176fcb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,6 +28,7 @@ - [Rkyzzy](https://github.com/Rkyzzy)(SUSTech) - [kxlv2000](https://github.com/kxlv2000)(SUSTech) - [caohao-php](https://github.com/caohao-php)(腾讯工程师) +- [Wscats](https://github.com/Wscats)(腾讯工程师) #### 其中特别致谢:
From 20b16a86933fb1d616ca431f96e9e4a0bccf6958 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 15 May 2021 22:23:06 +0800 Subject: [PATCH 113/944] Update README.md --- README.md | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index a5d11536e..edda5555d 100644 --- a/README.md +++ b/README.md @@ -56,11 +56,6 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 前端再也不用和后端沟通接口或文档问题了!再也不会被文档各种错误坑了!
后端再也不用为了兼容旧接口写新版接口和文档了!再也不会被前端随时随地没完没了地烦了! -

- -

- - ### 特点功能 #### 对于前端 @@ -134,13 +129,9 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这
### APIJSON 分享演讲 -https://github.com/TommyLemon/StaticResources/tree/master/APIJSON/Share/GiteeGVPMeetup2020
-https://my.oschina.net/u/4570368/blog/4818203
-https://my.oschina.net/gitosc/blog/4864607
- -
-演讲录播视频 https://www.bilibili.com/video/BV1Tv411t74v?p=4 +![image](https://user-images.githubusercontent.com/5738175/118364689-18440980-b5cc-11eb-9291-51053954f805.png) +
From 2577f02f9e741150a61a8d7df890ca03d235a825 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 00:01:20 +0800 Subject: [PATCH 114/944] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index edda5555d..c90935626 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这
### APIJSON 分享演讲 -https://www.bilibili.com/video/BV1Tv411t74v?p=4 +https://www.bilibili.com/video/BV1Tv411t74v ![image](https://user-images.githubusercontent.com/5738175/118364689-18440980-b5cc-11eb-9291-51053954f805.png) From 8519c1b3d61f6c651362c8c900930a5a51d73ea6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 11:36:36 +0800 Subject: [PATCH 115/944] Update CONTRIBUTING.md --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b20176fcb..ebef62c74 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,14 +5,14 @@ ## Acknowledgements -非常感谢以下贡献者们对于 APIJSON 的做出的贡献: +非常感谢以下贡献者们对于 APIJSON 本项目做出的贡献: - [TommyLemon](https://github.com/TommyLemon)(腾讯工程师) - [ruoranw](https://github.com/ruoranw)(现居美国洛杉矶) - [zhoulingfengofcd](https://github.com/zhoulingfengofcd) -- [Zerounary](https://github.com/Zerounary) +- [Zerounary](https://github.com/Zerounary)(还贡献了 APIJSONParser) - [fineday009](https://github.com/fineday009)(腾讯工程师) -- [vincentCheng](https://github.com/vincentCheng) +- [vincentCheng](https://github.com/vincentCheng)(还贡献了 apijson-doc) - [justinfengchen](https://github.com/justinfengchen) - [linlwqq](https://github.com/linlwqq) - [redcatmiss](https://github.com/redcatmiss)(社保科技工程师) From 81632232ffcf67e22eb71b6643992b40ddb00f7f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 11:38:31 +0800 Subject: [PATCH 116/944] Update CONTRIBUTING.md --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ebef62c74..4687648ed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,9 +10,9 @@ - [TommyLemon](https://github.com/TommyLemon)(腾讯工程师) - [ruoranw](https://github.com/ruoranw)(现居美国洛杉矶) - [zhoulingfengofcd](https://github.com/zhoulingfengofcd) -- [Zerounary](https://github.com/Zerounary)(还贡献了 APIJSONParser) -- [fineday009](https://github.com/fineday009)(腾讯工程师) -- [vincentCheng](https://github.com/vincentCheng)(还贡献了 apijson-doc) +- [Zerounary](https://github.com/Zerounary)(还开源了 APIJSONParser) +- [fineday009](https://github.com/fineday009)(腾讯工程师,还贡献了 apijson-framework, APIJSON-Demo) +- [vincentCheng](https://github.com/vincentCheng)(还开源了 apijson-doc) - [justinfengchen](https://github.com/justinfengchen) - [linlwqq](https://github.com/linlwqq) - [redcatmiss](https://github.com/redcatmiss)(社保科技工程师) From 0de66738791ddb8a732b8b22d0da67b3a4fa3014 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 11:39:21 +0800 Subject: [PATCH 117/944] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4687648ed..1638d3cae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ 非常感谢以下贡献者们对于 APIJSON 本项目做出的贡献: -- [TommyLemon](https://github.com/TommyLemon)(腾讯工程师) +- [TommyLemon](https://github.com/TommyLemon)(腾讯工程师,还开源了 apijson-framework, APIJSON-Demo, apijson-column 等) - [ruoranw](https://github.com/ruoranw)(现居美国洛杉矶) - [zhoulingfengofcd](https://github.com/zhoulingfengofcd) - [Zerounary](https://github.com/Zerounary)(还开源了 APIJSONParser) From 6ef703230fca0b1eec22fd6786914d5bd564a17f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 11:41:20 +0800 Subject: [PATCH 118/944] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1638d3cae..7613337a3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ 非常感谢以下贡献者们对于 APIJSON 本项目做出的贡献: - [TommyLemon](https://github.com/TommyLemon)(腾讯工程师,还开源了 apijson-framework, APIJSON-Demo, apijson-column 等) -- [ruoranw](https://github.com/ruoranw)(现居美国洛杉矶) +- [ruoranw](https://github.com/ruoranw)(现居美国洛杉矶,还开源了 APIJSONdocs) - [zhoulingfengofcd](https://github.com/zhoulingfengofcd) - [Zerounary](https://github.com/Zerounary)(还开源了 APIJSONParser) - [fineday009](https://github.com/fineday009)(腾讯工程师,还贡献了 apijson-framework, APIJSON-Demo) From 43da615fad71d6f6f3a6b0863958a4fc24b24f86 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 13:02:45 +0800 Subject: [PATCH 119/944] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c90935626..507121400 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON -

🏆 腾讯内外五个奖项、腾讯开源五个第一
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构!

+

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

From deaa91d806ab57c83da29ecba170172bd56631a8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 13:03:15 +0800 Subject: [PATCH 120/944] Update README-English.md --- README-English.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README-English.md b/README-English.md index eab64c560..596b7c11c 100644 --- a/README-English.md +++ b/README-English.md @@ -6,7 +6,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON -

🏆Gitee Most Valuable Project
🚀A JSON Transmission Protocol and an ORM Library for providing APIs and Documents automatically.

+

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

From 3b419c231b8d20dc367d83b27d85b90a2db146d0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 13:03:29 +0800 Subject: [PATCH 121/944] Update README-English.md --- README-English.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README-English.md b/README-English.md index 596b7c11c..9ed377326 100644 --- a/README-English.md +++ b/README-English.md @@ -6,7 +6,7 @@ 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.

+

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

From 575f58b9cbf98ac7ef19f8cb81a042bc028e2b83 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 13:11:17 +0800 Subject: [PATCH 122/944] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 507121400..87a109268 100644 --- a/README.md +++ b/README.md @@ -139,11 +139,11 @@ https://www.bilibili.com/video/BV1Tv411t74v 前后端 关于接口的 开发、文档、联调 等 10 大痛点解析
https://github.com/Tencent/APIJSON/wiki -* **解决十大痛点** (APIJSON 大幅提振开发效率、强力杜绝联调扯皮、巧妙规避文档缺陷、非常节省流量带宽 等) +* **解决十大痛点** (APIJSON 可帮助用户 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等) * **开发提速巨大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) -* **社区影响力大** (GitHub 10K Star 在 350W Java 项目中排名前 150,远超 FLAG, BAT 等国内外绝大部分开源项目) -* **各项荣誉成就** (腾讯开源五个第一、腾讯首个 GVP 获奖项目、腾讯后端项目 Star 第一、GitHub Java 周榜第一 等) +* **社区影响力大** (GitHub 1W+ Star 在 450W Java 项目中排名前 140,远超 FLAG, BAT 等国内外绝大部分开源项目) +* **各项荣誉成就** (腾讯内三个奖项、腾讯外两个奖项、腾讯后端项目 Star 第一、登上 GitHub Java 日周月榜 等) * **多样用户案例** (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等) * **适用场景广泛** (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) * **周边生态丰富** (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) @@ -152,7 +152,7 @@ https://github.com/Tencent/APIJSON/wiki * **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动控制权限、自动校验参数、自动防SQL注入等) * **灵活定制业务** (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) * **高质可靠代码** (代码严谨规范,商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 Bug 率低至 0.15%) -* **兼容各种项目** (对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo,协议不限 HTTP,与其它库无冲突) +* **兼容各种项目** (协议不限 HTTP,与其它库无冲突,对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo) * **工程轻量小巧** (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1) * **多年持续迭代** (自 2016 年开源至今已连续维护 4 年,累计 2000+ Commits、70+ Releases,不断更新迭代中...) From cc61ca64841184324babea280312f8d53e18947d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 13:13:23 +0800 Subject: [PATCH 123/944] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 87a109268..8a321b8e8 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 ### APIJSON 分享演讲 https://www.bilibili.com/video/BV1Tv411t74v -![image](https://user-images.githubusercontent.com/5738175/118364689-18440980-b5cc-11eb-9291-51053954f805.png) +![image](http://apijson.cn/images/comparison/APIJSON_vs_PreviousWays.jpg)
@@ -143,7 +143,7 @@ https://github.com/Tencent/APIJSON/wiki * **开发提速巨大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * **社区影响力大** (GitHub 1W+ Star 在 450W Java 项目中排名前 140,远超 FLAG, BAT 等国内外绝大部分开源项目) -* **各项荣誉成就** (腾讯内三个奖项、腾讯外两个奖项、腾讯后端项目 Star 第一、登上 GitHub Java 日周月榜 等) +* **各项荣誉成就** (腾讯内三个奖项、腾讯外两个奖项、腾讯后端项目 Star 第一、多次登上 GitHub Java 日周月榜 等) * **多样用户案例** (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等) * **适用场景广泛** (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) * **周边生态丰富** (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) From 1c867f761637f56d22565db6bbfca7a5477382f0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 13:14:13 +0800 Subject: [PATCH 124/944] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8a321b8e8..95fa3dc26 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 的数据和结构

@@ -51,10 +51,10 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 能大幅降低开发和沟通成本,简化开发流程,缩短开发周期。
适合中小型前后端分离的项目,尤其是 BaaS、Serverless、互联网创业项目和企业自用项目。
-通过万能的 API,前端可以定制任何数据、任何结构!
-大部分 HTTP 请求后端再也不用写接口了,更不用写文档了!
-前端再也不用和后端沟通接口或文档问题了!再也不会被文档各种错误坑了!
-后端再也不用为了兼容旧接口写新版接口和文档了!再也不会被前端随时随地没完没了地烦了! +通过万能的 API,前端可以定制任何数据、任何结构。
+大部分 HTTP 请求后端再也不用写接口了,更不用写文档了。
+前端再也不用和后端沟通接口或文档问题了。再也不会被文档各种错误坑了。
+后端再也不用为了兼容旧接口写新版接口和文档了。再也不会被前端随时随地没完没了地烦了。 ### 特点功能 From 9f64a46dc7dac1160fdb67e28c4c01a33eb66c69 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 14:21:24 +0800 Subject: [PATCH 125/944] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 95fa3dc26..0a969deb8 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ https://github.com/Tencent/APIJSON/wiki * **解决十大痛点** (APIJSON 可帮助用户 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等) * **开发提速巨大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) -* **社区影响力大** (GitHub 1W+ Star 在 450W Java 项目中排名前 140,远超 FLAG, BAT 等国内外绝大部分开源项目) +* **社区影响力大** (GitHub 1W+ Star 在 350W Java 项目中排名前 140,远超 FLAG, BAT 等国内外绝大部分开源项目) * **各项荣誉成就** (腾讯内三个奖项、腾讯外两个奖项、腾讯后端项目 Star 第一、多次登上 GitHub Java 日周月榜 等) * **多样用户案例** (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等) * **适用场景广泛** (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) From 358afde0b581f4be371f984422aa6289d38575e1 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 18 May 2021 12:00:58 +0800 Subject: [PATCH 126/944] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0a969deb8..79b18a8f9 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ https://www.bilibili.com/video/BV1Tv411t74v 前后端 关于接口的 开发、文档、联调 等 10 大痛点解析
https://github.com/Tencent/APIJSON/wiki -* **解决十大痛点** (APIJSON 可帮助用户 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等) +* **解决多个痛点** (APIJSON 可帮助用户 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等) * **开发提速巨大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * **社区影响力大** (GitHub 1W+ Star 在 350W Java 项目中排名前 140,远超 FLAG, BAT 等国内外绝大部分开源项目) From f8592c5b274b0b4c0d4ab4b63bff4a8ff0e769a7 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 18 May 2021 12:01:33 +0800 Subject: [PATCH 127/944] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 79b18a8f9..0b5af5af4 100644 --- a/README.md +++ b/README.md @@ -136,10 +136,10 @@ https://www.bilibili.com/video/BV1Tv411t74v
### 为什么选择 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 等国内外绝大部分开源项目) From 97942228dd3b49713dcad9952dd13fb896741f49 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 18 May 2021 12:03:05 +0800 Subject: [PATCH 128/944] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 0b5af5af4..6e1f8a87a 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,6 @@ https://github.com/Tencent/APIJSON/wiki * **开发提速巨大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * **社区影响力大** (GitHub 1W+ Star 在 350W Java 项目中排名前 140,远超 FLAG, BAT 等国内外绝大部分开源项目) -* **各项荣誉成就** (腾讯内三个奖项、腾讯外两个奖项、腾讯后端项目 Star 第一、多次登上 GitHub Java 日周月榜 等) * **多样用户案例** (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等) * **适用场景广泛** (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) * **周边生态丰富** (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) From 1fca1270da303431ba00f329a7851e993d2f89b8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 18 May 2021 12:05:32 +0800 Subject: [PATCH 129/944] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6e1f8a87a..7adf5e1b7 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ https://www.bilibili.com/video/BV1Tv411t74v https://github.com/Tencent/APIJSON/wiki * **解决十个痛点** (APIJSON 可帮助用户 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等) -* **开发提速巨大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) +* **开发提速很大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * **社区影响力大** (GitHub 1W+ Star 在 350W Java 项目中排名前 140,远超 FLAG, BAT 等国内外绝大部分开源项目) * **多样用户案例** (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等) From a719ceb2d7433c163f87695c7568efcda3e7cdef Mon Sep 17 00:00:00 2001 From: kxlv2000 <49295281+kxlv2000@users.noreply.github.com> Date: Wed, 19 May 2021 21:54:05 +0800 Subject: [PATCH 130/944] =?UTF-8?q?=E4=BC=98=E5=8C=96=20system.err.printli?= =?UTF-8?q?n=20=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit issure #232 --- .../main/java/apijson/orm/AbstractParser.java | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index e109b5a04..1525941b9 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -48,11 +48,24 @@ public abstract class AbstractParser implements Parser, ParserCreator, VerifierCreator, SQLCreator { protected static final String TAG = "AbstractParser"; + /** + * 可以通过切换该变量来控制是否打印关键的接口请求内容。保守起见,该值默认为false。 + * 与 {@link Log#DEBUG} 任何一个为 true 都会打印关键的接口请求内容。 + */ + public static boolean IS_PRINT_REQUEST_STRING_LOG = true; + /** * 打印大数据量日志的标识。线上环境比较敏感,可以通过切换该变量来控制异常栈抛出、错误日志打印。保守起见,该值默认为false。 * 与 {@link Log#DEBUG} 任何一个为 true 都会打印关键的接口请求及响应信息。 */ - public static boolean IS_PRINT_BIG_LOG = false; + public static boolean IS_PRINT_BIG_LOG = true; + + /** + * 可以通过切换该变量来控制是否打印关键的接口请求结束时间。保守起见,该值默认为false。 + * 与 {@link Log#DEBUG} 任何一个为 true 都会打印关键的接口请求结束时间。 + */ + public static boolean IS_PRINT_REQUEST_ENDTIME_LOG = true; + /** * method = null @@ -400,16 +413,18 @@ public JSONObject parseResponse(JSONObject request) { onClose(); - System.err.println("\n\n\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n " - + TAG + ".DEBUG: " + requestMethod + "/parseResponse request = \n" + requestString + "\n\n"); - - if (Log.DEBUG || IS_PRINT_BIG_LOG || error != null) { // 日志仅存服务器,所以不太敏感,而且这些日志虽然量大但非常重要,对排查 bug 很关键 - System.err.println(TAG + ".DEBUG: " + requestMethod + "/parseResponse return response = \n" + JSON.toJSONString(requestObject) + "\n\n"); + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/232 + if (IS_PRINT_REQUEST_STRING_LOG||Log.DEBUG||error != null) { + Log.sl("\n\n\n",'<',""); + Log.fd(TAG , requestMethod + "/parseResponse request = \n" + requestString + "\n\n"); + } + if (IS_PRINT_BIG_LOG||Log.DEBUG||error != null) { // 日志仅存服务器,所以不太敏感,而且这些日志虽然量大但非常重要,对排查 bug 很关键 + Log.fd(TAG,requestMethod + "/parseResponse return response = \n" + JSON.toJSONString(requestObject) + "\n\n"); + } + if (IS_PRINT_REQUEST_ENDTIME_LOG||Log.DEBUG||error != null) { + Log.fd(TAG , requestMethod + "/parseResponse endTime = " + endTime + "; duration = " + duration); + Log.sl("",'>',"\n\n\n"); } - - System.err.println(TAG + ".DEBUG: " + requestMethod + "/parseResponse endTime = " + endTime + "; duration = " + duration - + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n\n"); - return res; } From 204ffe55fa873a08abe09e397299324498a1d40f Mon Sep 17 00:00:00 2001 From: kxlv2000 <49295281+kxlv2000@users.noreply.github.com> Date: Wed, 19 May 2021 22:09:27 +0800 Subject: [PATCH 131/944] =?UTF-8?q?=E4=BC=98=E5=8C=96=20system.err.printli?= =?UTF-8?q?n=20=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit issue Tencent#232 --- APIJSONORM/src/main/java/apijson/Log.java | 19 +++++++++++++++++++ .../main/java/apijson/orm/AbstractParser.java | 6 +++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/Log.java b/APIJSONORM/src/main/java/apijson/Log.java index d37691f8e..eecc7e1c2 100755 --- a/APIJSONORM/src/main/java/apijson/Log.java +++ b/APIJSONORM/src/main/java/apijson/Log.java @@ -22,6 +22,25 @@ public static void d(String TAG, String msg) { } } + /** + * Forced debug + * @param TAG tag + * @param msg debug messages + */ + public static void fd(String TAG, String msg) { + System.err.println(TAG + ".DEBUG: " + msg); + } + + /** + * Generate separation line + * @param pre prefix + * @param symbol used for generating separation line + * @param post postfix + */ + public static void sl(String pre,char symbol ,String post) { + System.err.println(pre+new String(new char[48]).replace('\u0000', symbol)+post); + } + /** * @param TAG * @param msg diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 1525941b9..c46e82e4e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -52,19 +52,19 @@ public abstract class AbstractParser implements Parser, ParserCreator, * 可以通过切换该变量来控制是否打印关键的接口请求内容。保守起见,该值默认为false。 * 与 {@link Log#DEBUG} 任何一个为 true 都会打印关键的接口请求内容。 */ - public static boolean IS_PRINT_REQUEST_STRING_LOG = true; + public static boolean IS_PRINT_REQUEST_STRING_LOG = false; /** * 打印大数据量日志的标识。线上环境比较敏感,可以通过切换该变量来控制异常栈抛出、错误日志打印。保守起见,该值默认为false。 * 与 {@link Log#DEBUG} 任何一个为 true 都会打印关键的接口请求及响应信息。 */ - public static boolean IS_PRINT_BIG_LOG = true; + public static boolean IS_PRINT_BIG_LOG = false; /** * 可以通过切换该变量来控制是否打印关键的接口请求结束时间。保守起见,该值默认为false。 * 与 {@link Log#DEBUG} 任何一个为 true 都会打印关键的接口请求结束时间。 */ - public static boolean IS_PRINT_REQUEST_ENDTIME_LOG = true; + public static boolean IS_PRINT_REQUEST_ENDTIME_LOG = false; /** From 4116e5e5385c1d700bb64451f33b8bd11fcec64c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 20 May 2021 00:37:31 +0800 Subject: [PATCH 132/944] =?UTF-8?q?=E6=96=B0=E5=A2=9E=204=20=E4=B8=AA?= =?UTF-8?q?=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE=EF=BC=9A1=20=E4=B8=AA?= =?UTF-8?q?=E7=AC=AC=E4=B8=89=E6=96=B9=20SQL=20Parser=20=E5=92=8C=203=20?= =?UTF-8?q?=E4=B8=AA=20Demo=20=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7adf5e1b7..75108017e 100644 --- a/README.md +++ b/README.md @@ -382,18 +382,26 @@ 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-node](https://github.com/kevinaskin/apijson-node) Node.ts 版 APIJSON,提供 nestjs 和 typeorm 的 Demo,支持 MySQL, PostgreSQL, SQL Server, Oracle +[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 等 [APIJSONParser](https://github.com/Zerounary/APIJSONParser) 第三方 APIJSON 解析器,将 JSON 动态解析成 SQL -[ApiJsonByJFinal](https://gitee.com/zhiyuexin/ApiJsonByJFinal) 整合 APIJSON 和 JFinal 的 Demo +[FfApiJson](https://gitee.com/own_3_0/ff-api-json) 用 JSON 格式直接生成 SQL,借鉴 APIJSON 支持多数据源 + +[apijson-sample](https://gitee.com/greyzeng/apijson-sample) APIJSON 简单使用 Demo 及教程 + +[apijson-examples](https://gitee.com/drone/apijson-examples) APIJSON 的前端、业务后端、管理后端 Demo [light4j](https://github.com/xlongwei/light4j) 整合 APIJSON 和微服务框架 light-4j 的 Demo,同时接入了 Redis [SpringServer1.2-APIJSON](https://github.com/Airforce-1/SpringServer1.2-APIJSON) 智慧党建服务器端,提供 上传 和 下载 文件的接口 +[apijson-examples](https://gitee.com/drone/apijson-examples) APIJSON 的前端、业务后端、管理后端 Demo + +[ApiJsonByJFinal](https://gitee.com/zhiyuexin/ApiJsonByJFinal) 整合 APIJSON 和 JFinal 的 Demo + [apijson-builder](https://github.com/pengxianggui/apijson-builder) 一个方便为 APIJSON 构建 RESTful 请求的 JavaScript 库 [AbsGrade](https://github.com/APIJSON/AbsGrade) 列表级联算法,支持微信朋友圈单层评论、QQ空间双层评论、百度网盘多层(无限层)文件夹等 From 6c690cd4c5b98a7de5f389134138cd06ff34cb0c Mon Sep 17 00:00:00 2001 From: Rkyzzy <982993741@qq.com> Date: Thu, 20 May 2021 19:40:17 +0800 Subject: [PATCH 133/944] =?UTF-8?q?fix:=20=E6=9B=B4=E6=96=B0=E4=B8=BA?= =?UTF-8?q?=E6=9C=80=E6=96=B0=E7=9A=84=E6=89=8B=E6=9C=BA=E5=8F=B7=E6=AD=A3?= =?UTF-8?q?=E5=88=99=E8=A1=A8=E8=BE=BE=E5=BC=8F=E5=8C=B9=E9=85=8D=20issue#?= =?UTF-8?q?240?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/StringUtil.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index fc9552de4..671f04ae0 100755 --- a/APIJSONORM/src/main/java/apijson/StringUtil.java +++ b/APIJSONORM/src/main/java/apijson/StringUtil.java @@ -311,7 +311,8 @@ public static boolean isNotEmpty(String s, boolean trim) { PATTERN_ALPHA_BIG = Pattern.compile("^[A-Z]+$"); PATTERN_ALPHA_SMALL = Pattern.compile("^[a-z]+$"); PATTERN_NAME = Pattern.compile("^[0-9a-zA-Z_]+$");//已用55个中英字符测试通过 - PATTERN_PHONE = Pattern.compile("^((13[0-9])|(15[^4,\\D])|(18[0-2,5-9])|(17[0-9]))\\d{8}$"); + //PATTERN_PHONE = Pattern.compile("^((13[0-9])|(15[^4,\\D])|(18[0-2,5-9])|(17[0-9]))\\d{8}$"); + PATTERN_PHONE = Pattern.compile("^1(?:3\\d{3}|5[^4\\D]\\d{2}|8\\d{3}|7(?:[0-35-9]\\d{2}|4(?:0\\d|1[0-2]|9\\d))|9[0-35-9]\\d{2}|6[2567]\\d{2}|4(?:(?:10|4[01])\\d{3}|[68]\\d{4}|[579]\\d{2}))\\d{6}$"); 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]+$"); From 89fb9bf7cd299aafa189d044d83f17651b41ba3d Mon Sep 17 00:00:00 2001 From: Rkyzzy <982993741@qq.com> Date: Thu, 20 May 2021 19:44:31 +0800 Subject: [PATCH 134/944] =?UTF-8?q?fix=20=E6=9B=B4=E6=96=B0=E4=BA=86?= =?UTF-8?q?=E6=9C=80=E6=96=B0=E7=9A=84=E6=89=8B=E6=9C=BA=E6=AD=A3=E5=88=99?= =?UTF-8?q?=E8=A1=A8=E8=BE=BE=E5=BC=8F=E5=8C=B9=E9=85=8D=E6=B3=95=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/StringUtil.java | 1 - 1 file changed, 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index 671f04ae0..8d3c32798 100755 --- a/APIJSONORM/src/main/java/apijson/StringUtil.java +++ b/APIJSONORM/src/main/java/apijson/StringUtil.java @@ -311,7 +311,6 @@ public static boolean isNotEmpty(String s, boolean trim) { PATTERN_ALPHA_BIG = Pattern.compile("^[A-Z]+$"); PATTERN_ALPHA_SMALL = Pattern.compile("^[a-z]+$"); PATTERN_NAME = Pattern.compile("^[0-9a-zA-Z_]+$");//已用55个中英字符测试通过 - //PATTERN_PHONE = Pattern.compile("^((13[0-9])|(15[^4,\\D])|(18[0-2,5-9])|(17[0-9]))\\d{8}$"); PATTERN_PHONE = Pattern.compile("^1(?:3\\d{3}|5[^4\\D]\\d{2}|8\\d{3}|7(?:[0-35-9]\\d{2}|4(?:0\\d|1[0-2]|9\\d))|9[0-35-9]\\d{2}|6[2567]\\d{2}|4(?:(?:10|4[01])\\d{3}|[68]\\d{4}|[579]\\d{2}))\\d{6}$"); 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}$)"); From 8cffe1301fbc83173188e90ba61955cbf499ef25 Mon Sep 17 00:00:00 2001 From: Rkyzzy <982993741@qq.com> Date: Thu, 20 May 2021 19:47:44 +0800 Subject: [PATCH 135/944] =?UTF-8?q?fix=20=E6=9B=B4=E6=96=B0=E4=BA=86?= =?UTF-8?q?=E6=9C=80=E6=96=B0=E7=9A=84=E6=89=8B=E6=9C=BA=E5=8F=B7=E6=AD=A3?= =?UTF-8?q?=E5=88=99=E8=A1=A8=E8=BE=BE=E5=BC=8F=E5=8C=B9=E9=85=8D=E6=B3=95?= =?UTF-8?q?=E5=88=99\=20issue=20#240?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/StringUtil.java | 1 + 1 file changed, 1 insertion(+) diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index 8d3c32798..9fac4e34e 100755 --- a/APIJSONORM/src/main/java/apijson/StringUtil.java +++ b/APIJSONORM/src/main/java/apijson/StringUtil.java @@ -311,6 +311,7 @@ public static boolean isNotEmpty(String s, boolean trim) { PATTERN_ALPHA_BIG = Pattern.compile("^[A-Z]+$"); PATTERN_ALPHA_SMALL = Pattern.compile("^[a-z]+$"); PATTERN_NAME = Pattern.compile("^[0-9a-zA-Z_]+$");//已用55个中英字符测试通过 + //newest phone regex expression reference https://github.com/VincentSit/ChinaMobilePhoneNumberRegex PATTERN_PHONE = Pattern.compile("^1(?:3\\d{3}|5[^4\\D]\\d{2}|8\\d{3}|7(?:[0-35-9]\\d{2}|4(?:0\\d|1[0-2]|9\\d))|9[0-35-9]\\d{2}|6[2567]\\d{2}|4(?:(?:10|4[01])\\d{3}|[68]\\d{4}|[579]\\d{2}))\\d{6}$"); 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}$)"); From e73841dd919112d947ce67e1ee2fc1be7a3fc375 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 27 May 2021 18:00:02 +0800 Subject: [PATCH 136/944] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=96=87=E7=AB=A0=20?= =?UTF-8?q?APIJSON=E6=95=99=E7=A8=8B=EF=BC=88=E4=B8=80=EF=BC=89=EF=BC=9A?= =?UTF-8?q?=E4=B8=8A=E6=89=8Bapijson=E9=A1=B9=E7=9B=AE=EF=BC=8C=E5=AD=A6?= =?UTF-8?q?=E4=B9=A0apijson=E8=AF=AD=E6=B3=95=EF=BC=8C=E5=B9=B6=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E6=8C=81=E4=B9=85=E5=B1=82=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://zhuanlan.zhihu.com/p/375681893 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 75108017e..952541e0a 100644 --- a/README.md +++ b/README.md @@ -337,6 +337,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [APIJSON对接分布式HTAP数据库TiDB](https://asktug.com/t/htap-tidb/395) +[APIJSON教程(一):上手apijson项目,学习apijson语法,并实现持久层配置](https://zhuanlan.zhihu.com/p/375681893) + [apijson简单demo](https://blog.csdn.net/dmw412724/article/details/113558115) [apijson简单使用](https://www.cnblogs.com/greyzeng/p/14311995.html) From f93c1f314aa1764e2c12bb90d41994c01d2d0d30 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 7 Jun 2021 00:16:43 +0800 Subject: [PATCH 137/944] English README: optimize format of request json --- README-English.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README-English.md b/README-English.md index 9ed377326..68e8ad53f 100644 --- a/README-English.md +++ b/README-English.md @@ -79,7 +79,7 @@ Server developers no longer need to worry about compatibility of APIs and docume #### Get a User Request: -```json +```js { "User":{  } @@ -90,7 +90,7 @@ Request: Response: -```json +```js { "User":{ "id":38710, @@ -116,7 +116,7 @@ Response: Request: -```json +```js { "[]":{   "count":3,             //just get 3 results @@ -131,7 +131,7 @@ Request: Response: -```json +```js { "[]":[ { From a9efd6ddcfd11800fea6264560b8fe021c3f2159 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 7 Jun 2021 00:17:53 +0800 Subject: [PATCH 138/944] English README: fix online testing url --- README-English.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README-English.md b/README-English.md index 68e8ad53f..770afb08d 100644 --- a/README-English.md +++ b/README-English.md @@ -160,7 +160,7 @@ Response:
-[Test it online](http://apijson.cn/)
+[Test it online](http://apijson.cn/api)
![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON_Auto_get.jpg) From 3b525d218450eebc4e4945d6e425a536b383b899 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 7 Jun 2021 00:25:36 +0800 Subject: [PATCH 139/944] English README: add new contributors --- README-English.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README-English.md b/README-English.md index 770afb08d..40ab91fa7 100644 --- a/README-English.md +++ b/README-English.md @@ -331,17 +331,26 @@ Here are the contributers of this project and authors of other projects for ecos height="54" width="54" > + + + + + + + + +
From 0fc409b90132527aed936083c2454f317b1bd2c4 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 9 Jun 2021 22:11:35 +0800 Subject: [PATCH 140/944] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 952541e0a..0083f1814 100644 --- a/README.md +++ b/README.md @@ -129,9 +129,14 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这
### APIJSON 分享演讲 +APIJSON-零代码接口与文档 ORM 库(国际开源谷 Gitee Meetup)
https://www.bilibili.com/video/BV1Tv411t74v ![image](http://apijson.cn/images/comparison/APIJSON_vs_PreviousWays.jpg) +APIJSON和APIAuto-零代码开发和测试(QECon 全球软件质量&效能大会)
+https://www.bilibili.com/video/BV1yv411p7Y4 +wecom-temp-377bbd0daf5aed716baf7ebcb003d94c +
From 7e6097a1e2a63334272edd33b77789cda94e5eab Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 9 Jun 2021 22:27:11 +0800 Subject: [PATCH 141/944] =?UTF-8?q?=E5=88=86=E4=BA=AB=E6=BC=94=E8=AE=B2?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20QECon=20=E5=85=A8=E7=90=83=E8=BD=AF?= =?UTF-8?q?=E4=BB=B6=E8=B4=A8=E9=87=8F&=E6=95=88=E8=83=BD=E5=A4=A7?= =?UTF-8?q?=E4=BC=9A=20=E7=9A=84=E9=9B=B6=E4=BB=A3=E7=A0=81=E5=BC=80?= =?UTF-8?q?=E5=8F=91=E5=92=8C=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://mp.weixin.qq.com/s/plcRVRcIN_1l59jCigNjhQ --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0083f1814..4ed8b8a29 100644 --- a/README.md +++ b/README.md @@ -129,12 +129,17 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这
### APIJSON 分享演讲 -APIJSON-零代码接口与文档 ORM 库(国际开源谷 Gitee Meetup)
+#### APIJSON-零代码接口与文档 ORM 库(国际开源谷 Gitee Meetup) + https://www.bilibili.com/video/BV1Tv411t74v + ![image](http://apijson.cn/images/comparison/APIJSON_vs_PreviousWays.jpg) -APIJSON和APIAuto-零代码开发和测试(QECon 全球软件质量&效能大会)
+ +#### APIJSON 和 APIAuto-零代码开发和测试(QECon 全球软件质量&效能大会) + https://www.bilibili.com/video/BV1yv411p7Y4 + wecom-temp-377bbd0daf5aed716baf7ebcb003d94c From 81da25d53389e14fa81c5091cb7595af592ff45d Mon Sep 17 00:00:00 2001 From: jun0315 <1072505283@qq.com> Date: Fri, 11 Jun 2021 13:53:07 +0800 Subject: [PATCH 142/944] feat:log print current time --- APIJSONORM/src/main/java/apijson/Log.java | 43 +++++++++++++++++++---- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/Log.java b/APIJSONORM/src/main/java/apijson/Log.java index eecc7e1c2..e3f64f86d 100755 --- a/APIJSONORM/src/main/java/apijson/Log.java +++ b/APIJSONORM/src/main/java/apijson/Log.java @@ -5,20 +5,49 @@ package apijson; +import java.text.SimpleDateFormat; + /**测试用Log * @modifier Lemon */ public class Log { public static boolean DEBUG = true; - + + //默认的时间格式 + public static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS"); + + /** + * modify date format + * @param dateFormatString + */ + public static void setDateFormat(String dateFormatString) { + dateFormat = new SimpleDateFormat(dateFormatString); + } + + /** + * log info by level tag and msg + * @param TAG + * @param msg + * @param level + */ + public static void logInfo(String TAG, String msg, String level){ + if(level.equals("DEBUG") || level .equals("ERROR") ||level.equals("WARN")){ + System.err.println(dateFormat.format(System.currentTimeMillis()) + ": " + TAG + "." + level + ": " + msg); + } + else if(level.equals("VERBOSE") || level .equals("INFO") ){ + System.out.println(dateFormat.format(System.currentTimeMillis()) + ": " + TAG + "." + level + ": " + msg); + } + } + + /** * @param TAG * @param msg */ public static void d(String TAG, String msg) { if (DEBUG) { - System.err.println(TAG + ".DEBUG: " + msg); + logInfo(TAG,msg,"DEBUG"); } } @@ -28,7 +57,7 @@ public static void d(String TAG, String msg) { * @param msg debug messages */ public static void fd(String TAG, String msg) { - System.err.println(TAG + ".DEBUG: " + msg); + logInfo(TAG,msg,"DEBUG"); } /** @@ -47,7 +76,7 @@ public static void sl(String pre,char symbol ,String post) { */ public static void v(String TAG, String msg) { if (DEBUG) { - System.out.println(TAG + ".VERBOSE: " + msg); + logInfo(TAG,msg,"VERBOSE"); } } @@ -57,7 +86,7 @@ public static void v(String TAG, String msg) { */ public static void i(String TAG, String msg) { if (DEBUG) { - System.out.println(TAG + ".INFO: " + msg); + logInfo(TAG,msg,"INFO"); } } @@ -67,7 +96,7 @@ public static void i(String TAG, String msg) { */ public static void e(String TAG, String msg) { if (DEBUG) { - System.err.println(TAG + ".ERROR: " + msg); + logInfo(TAG,msg,"ERROR"); } } @@ -77,7 +106,7 @@ public static void e(String TAG, String msg) { */ public static void w(String TAG, String msg) { if (DEBUG) { - System.err.println(TAG + ".WARN: " + msg); + logInfo(TAG,msg,"WARN"); } } From 7ade5730df83fb59216d1b7830e7df9ba2b7196d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 11 Jun 2021 16:30:23 +0800 Subject: [PATCH 143/944] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=EF=BC=8C=E6=84=9F=E8=B0=A2=E4=B8=BA?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E6=89=93=E5=8D=B0=E6=96=B0=E5=A2=9E=E5=AF=B9?= =?UTF-8?q?=E5=BA=94=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/250 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4ed8b8a29..49ec6ac0b 100644 --- a/README.md +++ b/README.md @@ -261,6 +261,7 @@ https://github.com/Tencent/APIJSON/issues/187 +
Date: Fri, 11 Jun 2021 16:31:48 +0800 Subject: [PATCH 144/944] =?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=20jun0315=EF=BC=8C=E6=84=9F?= =?UTF-8?q?=E8=B0=A2=E4=B8=BA=E6=97=A5=E5=BF=97=E6=89=93=E5=8D=B0=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=AF=B9=E5=BA=94=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/250 --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7613337a3..0c39db52c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,6 +29,7 @@ - [kxlv2000](https://github.com/kxlv2000)(SUSTech) - [caohao-php](https://github.com/caohao-php)(腾讯工程师) - [Wscats](https://github.com/Wscats)(腾讯工程师) +- [jun0315](https://github.com/jun0315) #### 其中特别致谢:
From b39dda60aedc32c0d41fad81df0fa966dd59e0c9 Mon Sep 17 00:00:00 2001 From: iceewei Date: Mon, 14 Jun 2021 22:59:40 +0800 Subject: [PATCH 145/944] =?UTF-8?q?=E6=96=B0=E5=A2=9Edatasource=E5=85=B3?= =?UTF-8?q?=E9=94=AE=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/JSONObject.java | 27 +++++--- .../apijson/orm/AbstractObjectParser.java | 4 ++ .../main/java/apijson/orm/AbstractParser.java | 15 ++++- .../java/apijson/orm/AbstractSQLConfig.java | 51 ++++++++++++-- .../java/apijson/orm/AbstractVerifier.java | 67 ++++++++++++++++--- .../src/main/java/apijson/orm/Parser.java | 1 + .../src/main/java/apijson/orm/SQLConfig.java | 7 +- 7 files changed, 148 insertions(+), 24 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/JSONObject.java b/APIJSONORM/src/main/java/apijson/JSONObject.java index 026d63efb..925735bb9 100755 --- a/APIJSONORM/src/main/java/apijson/JSONObject.java +++ b/APIJSONORM/src/main/java/apijson/JSONObject.java @@ -137,9 +137,10 @@ public JSONObject setUserIdIn(List list) { public static final String KEY_ROLE = "@role"; //角色,拥有对某些数据的某些操作的权限 public static final String KEY_DATABASE = "@database"; //数据库类型,默认为MySQL + public static final String KEY_SCHEMA = "@schema"; //数据库,Table在非默认schema内时需要声明 + public static final String KEY_DATASOURCE = "@datasource"; //数据源 public static final String KEY_EXPLAIN = "@explain"; //分析 true/false public static final String KEY_CACHE = "@cache"; //缓存 RAM/ROM/ALL - public static final String KEY_SCHEMA = "@schema"; //数据库,Table在非默认schema内时需要声明 public static final String KEY_COLUMN = "@column"; //查询的Table字段或SQL函数 public static final String KEY_FROM = "@from"; //FROM语句 public static final String KEY_COMBINE = "@combine"; //条件组合,每个条件key前面可以放&,|,!逻辑关系 "id!{},&sex,!name&$" @@ -154,9 +155,10 @@ public JSONObject setUserIdIn(List list) { TABLE_KEY_LIST = new ArrayList(); TABLE_KEY_LIST.add(KEY_ROLE); TABLE_KEY_LIST.add(KEY_DATABASE); + TABLE_KEY_LIST.add(KEY_SCHEMA); + TABLE_KEY_LIST.add(KEY_DATASOURCE); TABLE_KEY_LIST.add(KEY_EXPLAIN); TABLE_KEY_LIST.add(KEY_CACHE); - TABLE_KEY_LIST.add(KEY_SCHEMA); TABLE_KEY_LIST.add(KEY_COLUMN); TABLE_KEY_LIST.add(KEY_FROM); TABLE_KEY_LIST.add(KEY_COMBINE); @@ -216,6 +218,20 @@ public JSONObject setRole(String role) { public JSONObject setDatabase(String database) { return puts(KEY_DATABASE, database); } + /**set schema where table was puts + * @param schema + * @return this + */ + public JSONObject setSchema(String schema) { + return puts(KEY_SCHEMA, schema); + } + /**set datasource where table was puts + * @param datasource + * @return this + */ + public JSONObject setDatasource(String datasource) { + return puts(KEY_DATASOURCE, datasource); + } /**set if return explain informations * @param explain * @return @@ -243,13 +259,6 @@ public JSONObject setCache(Integer cache) { public JSONObject setCache(String cache) { return puts(KEY_CACHE, cache); } - /**set schema where table was puts - * @param schema - * @return this - */ - public JSONObject setSchema(String schema) { - return puts(KEY_SCHEMA, schema); - } /**set keys need to be returned * @param keys key0, key1, key2 ... diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index f83c9e8bb..5244abc9a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -287,6 +287,10 @@ else if (method == PUT && value instanceof JSONArray if (parser.getGlobleSchema() != null && sqlRequest.get(JSONRequest.KEY_SCHEMA) == null) { sqlRequest.put(JSONRequest.KEY_SCHEMA, parser.getGlobleSchema()); } + 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()); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index c46e82e4e..84bbca2b7 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -199,6 +199,16 @@ public AbstractParser setGlobleSchema(String globleSchema) { public String getGlobleSchema() { return globleSchema; } + protected String globleDatasource; + @Override + public String getGlobleDatasource() { + return globleDatasource; + } + public AbstractParser setGlobleDatasource(String globleDatasource) { + this.globleDatasource = globleDatasource; + return this; + } + protected Boolean globleExplain; public AbstractParser setGlobleExplain(Boolean globleExplain) { this.globleExplain = globleExplain; @@ -361,12 +371,14 @@ public JSONObject parseResponse(JSONObject request) { 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)); requestObject.remove(JSONRequest.KEY_FORMAT); requestObject.remove(JSONRequest.KEY_DATABASE); requestObject.remove(JSONRequest.KEY_SCHEMA); + requestObject.remove(JSONRequest.KEY_DATASOURCE); requestObject.remove(JSONRequest.KEY_EXPLAIN); requestObject.remove(JSONRequest.KEY_CACHE); } catch (Exception e) { @@ -1245,6 +1257,7 @@ else if (join != null){ 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); @@ -1464,7 +1477,7 @@ public Object getValueByPath(String valuePath) { //取出key被valuePath包含的result,再从里面获取key对应的value JSONObject parent = null; String[] keys = null; - for (Map.Entry entry : queryResultMap.entrySet()){ + for (Entry entry : queryResultMap.entrySet()){ String path = entry.getKey(); if (valuePath.startsWith(path + "/")) { try { diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index efedbe3e8..d963fea55 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -9,6 +9,7 @@ import static apijson.JSONObject.KEY_COLUMN; import static apijson.JSONObject.KEY_COMBINE; import static apijson.JSONObject.KEY_DATABASE; +import static apijson.JSONObject.KEY_DATASOURCE; import static apijson.JSONObject.KEY_EXPLAIN; import static apijson.JSONObject.KEY_FROM; import static apijson.JSONObject.KEY_GROUP; @@ -335,6 +336,7 @@ public String getUserIdKey() { private boolean distinct = false; private String database; //表所在的数据库类型 private String schema; //表所在的数据库名 + private String datasource; //数据源 private String table; //表名 private String alias; //表别名 private String group; //分组方式的字符串数组,','分隔 @@ -549,6 +551,17 @@ public AbstractSQLConfig setSchema(String schema) { this.schema = schema; return this; } + + @Override + public String getDatasource() { + return datasource; + } + @Override + public SQLConfig setDatasource(String datasource) { + this.datasource = datasource; + return this; + } + /**请求传进来的Table名 * @return * @see {@link #getSQLTable()} @@ -1547,7 +1560,7 @@ public Object getWhere(String key, boolean exactMatch) { synchronized (where) { if (where != null) { int index; - for (Map.Entry entry : where.entrySet()) { + for (Entry entry : where.entrySet()) { String k = entry.getKey(); index = k.indexOf(key); if (index >= 0 && StringUtil.isName(k.substring(index)) == false) { @@ -2502,7 +2515,7 @@ public String getSetString(RequestMethod method, Map content, bo Object value; String idKey = getIdKey(); - for (Map.Entry entry : content.entrySet()) { + for (Entry entry : content.entrySet()) { String key = entry.getKey(); //避免筛选到全部 value = key == null ? null : content.get(key); if (key == null || idKey.equals(key)) { @@ -2811,12 +2824,14 @@ 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); config.setAlias(alias); config.setDatabase(database); //不删,后面表对象还要用的,必须放在 parseJoin 前 config.setSchema(schema); //不删,后面表对象还要用的 + config.setDatasource(datasource); //不删,后面表对象还要用的 if (isProcedure) { return config; @@ -3387,21 +3402,39 @@ 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 + * @param schema + * @param table + * @return + */ + String getIdKey(String database, String schema, String datasource, String table); - /**获取 User 的主键名 + /**已废弃,最早 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 + * @param table + * @return + */ + String getUserIdKey(String database, String schema, String datasource, String table); } public static interface Callback extends IdCallback { @@ -3434,11 +3467,21 @@ 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); + } @Override 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); + } @Override public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception { diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 926a037f8..30aeb5f15 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -109,8 +109,8 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { OPERATION_KEY_LIST.add(REMOVE.name()); OPERATION_KEY_LIST.add(MUST.name()); OPERATION_KEY_LIST.add(REFUSE.name()); - - + + SYSTEM_ACCESS_MAP = new HashMap>(); SYSTEM_ACCESS_MAP.put(Access.class.getSimpleName(), getAccessMap(Access.class.getAnnotation(MethodAccess.class))); @@ -170,10 +170,18 @@ 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; } @Override + public String getUserIdKey(String database, String schema, String datasource, String table) { + return getUserIdKey(database, schema, table); + } + @Override public Object newId(RequestMethod method, String database, String schema, String table) { return System.currentTimeMillis(); } @@ -515,6 +523,23 @@ public static JSONObject verifyRequest(@NotNull final RequestMethod method, fina 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 { + return verifyRequest(method, name, target, request, maxUpdateCount, database, schema, null, idCallback, creator); + } + /**从request提取target指定的内容 + * @param method + * @param name + * @param target + * @param request + * @param maxUpdateCount + * @param idKey + * @param userIdKey + * @param creator + * @return + * @throws Exception + */ + 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 { Log.i(TAG, "verifyRequest method = " + method + "; name = " + name + "; target = \n" + JSON.toJSONString(target) @@ -546,14 +571,18 @@ public JSONObject onParseJSONObject(String key, JSONObject tobj, JSONObject robj } else if (apijson.JSONObject.isTableKey(key)) { String db = request.getString(apijson.JSONObject.KEY_DATABASE); String sh = request.getString(apijson.JSONObject.KEY_SCHEMA); + String ds = request.getString(apijson.JSONObject.KEY_DATASOURCE); if (StringUtil.isEmpty(db, false)) { db = database; } if (StringUtil.isEmpty(sh, false)) { sh = schema; } + if (StringUtil.isEmpty(ds, false)) { + ds = datasource; + } - String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, key); + String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, ds, key); String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey; if (method == RequestMethod.POST) { @@ -564,7 +593,7 @@ public JSONObject onParseJSONObject(String key, JSONObject tobj, JSONObject robj if (RequestMethod.isQueryMethod(method) == false) { verifyId(method.name(), name, key, robj, finalIdKey, maxUpdateCount, true); - String userIdKey = idCallback == null ? null : idCallback.getUserIdKey(db, sh, key); + String userIdKey = idCallback == null ? null : idCallback.getUserIdKey(db, sh, ds, key); String finalUserIdKey = StringUtil.isEmpty(userIdKey, false) ? apijson.JSONObject.KEY_USER_ID : userIdKey; verifyId(method.name(), name, key, robj, finalUserIdKey, maxUpdateCount, false); } @@ -742,14 +771,14 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, , SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { return parse(method, name, target, real, null, null, null, creator, callback); } - /**对request和response不同的解析用callback返回 * @param method * @param name * @param target * @param real - * @param idKey - * @param userIdKey + * @param database + * @param schema + * @param idCallback * @param creator * @param callback * @return @@ -757,6 +786,24 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, */ 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返回 + * @param method + * @param name + * @param target + * @param real + * @param database + * @param schema + * @param datasource + * @param idCallback + * @param creator + * @param callback + * @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 { if (target == null) { return null; } @@ -913,12 +960,16 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, String db = real.getString(apijson.JSONObject.KEY_DATABASE); String sh = real.getString(apijson.JSONObject.KEY_SCHEMA); + String ds = real.getString(apijson.JSONObject.KEY_DATASOURCE); if (StringUtil.isEmpty(db, false)) { db = database; } if (StringUtil.isEmpty(sh, false)) { sh = schema; } + if (StringUtil.isEmpty(ds, false)) { + ds = datasource; + } String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, name); String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey; @@ -977,7 +1028,7 @@ private static JSONObject operate(Operation opt, JSONObject targetChild, JSONObj if (tk == null || OPERATION_KEY_LIST.contains(tk)) { continue; } - + tv = e.getValue(); if (opt == TYPE) { diff --git a/APIJSONORM/src/main/java/apijson/orm/Parser.java b/APIJSONORM/src/main/java/apijson/orm/Parser.java index dee098b29..6e0af5368 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Parser.java +++ b/APIJSONORM/src/main/java/apijson/orm/Parser.java @@ -124,6 +124,7 @@ JSONObject parseCorrectRequest(RequestMethod method, String tag, int version, St RequestRole getGlobleRole(); String getGlobleDatabase(); String getGlobleSchema(); + String getGlobleDatasource(); Boolean getGlobleExplain(); String getGlobleCache(); diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index 7e73bdd18..9abce83b9 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -120,10 +120,13 @@ public interface SQLConfig { String getDatabase(); SQLConfig setDatabase(String database); - String getQuote(); - String getSchema(); SQLConfig setSchema(String schema); + + String getDatasource(); + SQLConfig setDatasource(String datasource); + + String getQuote(); /**请求传进来的Table名 * @return From ccec4b836b25cdd646ac184775924379de0b0087 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 15 Jun 2021 00:03:02 +0800 Subject: [PATCH 146/944] =?UTF-8?q?=E5=88=86=E9=A1=B5=EF=BC=9A=E8=A7=A3?= =?UTF-8?q?=E5=86=B3=20query=3D2=20=E4=B8=8D=E5=85=BC=E5=AE=B9=20=E4=B8=BB?= =?UTF-8?q?=E8=A1=A8=20@column:"fun()"=20=E8=BF=99=E7=A7=8D=E5=8C=85?= =?UTF-8?q?=E5=90=AB=20SQL=20=E5=87=BD=E6=95=B0=E7=9A=84=E5=86=99=E6=B3=95?= =?UTF-8?q?=EF=BC=9BSQL=20=E5=87=BD=E6=95=B0=EF=BC=9A=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E5=8F=B3=E6=8B=AC=E5=8F=B7=20)=20=E7=9A=84=E4=BD=8D=E7=BD=AE?= =?UTF-8?q?=E4=BB=8E=20=20indexOf=20=E6=94=B9=E4=B8=BA=20lastIndexOf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index d963fea55..758b49cbd 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -768,7 +768,7 @@ public String getHavingString(boolean hasPrefix) { continue; } - int end = expression.indexOf(")"); + int end = expression.lastIndexOf(")"); if (start >= end) { throw new IllegalArgumentException("字符 " + expression + " 不合法!" + "@having:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); @@ -1040,13 +1040,28 @@ 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); - if (StringUtil.isName(origin) == false || (alias != null && StringUtil.isName(alias) == false)) { - throw new IllegalArgumentException("HEAD请求: 预编译模式下 @column:value 中 value里面用 , 分割的每一项" + + if (alias != null && StringUtil.isName(alias) == false) { + throw new IllegalArgumentException("HEAD请求: 字符 " + alias + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); } + + if (StringUtil.isName(origin) == false) { + int start = origin.indexOf("("); + if (start < 0 || origin.lastIndexOf(")") <= start) { + 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个单词!并且不要有多余的空格!"); + } + } } } - return SQL.count(column != null && column.size() == 1 ? getKey(Pair.parseEntry(column.get(0), true).getKey()) : "*"); + + return SQL.count(column != null && column.size() == 1 && StringUtil.isName(column.get(0)) ? getKey(column.get(0)) : "*"); case POST: if (column == null || column.isEmpty()) { throw new IllegalArgumentException("POST 请求必须在Table内设置要保存的 key:value !"); @@ -1151,7 +1166,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { int start = expression.indexOf("("); int end = 0; if (start >= 0) { - end = expression.indexOf(")"); + end = expression.lastIndexOf(")"); if (start >= end) { throw new IllegalArgumentException("字符 " + expression + " 不合法!" + "@column:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); @@ -2254,7 +2269,7 @@ else if (range instanceof String) {//非Number类型需要客户端拼接成 < ' if (rawSQL != null) { int index = rawSQL == null ? -1 : rawSQL.indexOf("("); - condition = (index >= 0 && index < rawSQL.indexOf(")") ? "" : getKey(k) + " ") + rawSQL; + condition = (index >= 0 && index < rawSQL.lastIndexOf(")") ? "" : getKey(k) + " ") + rawSQL; } // 还是只支持整段为 Raw SQL 比较好 @@ -2299,7 +2314,7 @@ else if (isPrepared() && (c.contains("--") || PATTERN_RANGE.matcher(c).matches() index = c == null ? -1 : c.indexOf("("); condition += ((i <= 0 ? "" : (logic.isAnd() ? AND : OR)) //连接方式 - + (index >= 0 && index < c.indexOf(")") ? "" : getKey(k) + " ") //函数和非函数条件 + + (index >= 0 && index < c.lastIndexOf(")") ? "" : getKey(k) + " ") //函数和非函数条件 + c); // 还是只支持整段为 Raw SQL 比较好 (appendRaw && index > 0 ? rawSQL : "") + c); //单个条件,如果有 Raw SQL 则按原来位置拼接 } } From db7916845292034950f846a4dec3a6022ae3ff40 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 15 Jun 2021 00:51:16 +0800 Subject: [PATCH 147/944] =?UTF-8?q?=E5=88=86=E9=A1=B5:=20=E5=9C=A8?= =?UTF-8?q?=E5=88=86=E9=A1=B5=E8=AF=A6=E6=83=85=20info=20=E5=86=85?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E6=9F=A5=E8=AF=A2=E8=AE=A1=E5=88=92=20@expla?= =?UTF-8?q?in?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractParser.java | 5 + .../java/apijson/orm/AbstractSQLConfig.java | 11 +- .../java/apijson/orm/AbstractSQLExecutor.java | 173 +++++++++--------- 3 files changed, 100 insertions(+), 89 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 84bbca2b7..7051b3e06 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -871,6 +871,10 @@ public JSONObject onObjectParse(final JSONObject request } JSONObject pagination = new JSONObject(true); + Object explain = rp.get(JSONResponse.KEY_EXPLAIN); + if (explain instanceof JSONObject) { + pagination.put(JSONResponse.KEY_EXPLAIN, explain); + } pagination.put(JSONResponse.KEY_TOTAL, total); pagination.put(JSONRequest.KEY_COUNT, count); pagination.put(JSONRequest.KEY_PAGE, page); @@ -878,6 +882,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) { diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 758b49cbd..2eabffc62 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2647,12 +2647,13 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { config.setPreparedValueList(new ArrayList()); String column = config.getColumnString(); - if(config.isOracle()){ + 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(); - }else - return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config); + 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(); + } } /**获取条件SQL字符串 @@ -2679,7 +2680,7 @@ private static String getConditionString(String column, String table, AbstractSQ //no need to optimize // if (config.getPage() <= 0 || ID.equals(column.trim())) { - return config.isOracle()? condition:condition + config.getLimitString(); + return condition; // config.isOracle() ? condition : condition + config.getLimitString(); // } // // diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 6008c4b13..15b178be4 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -148,21 +148,24 @@ public ResultSet execute(@NotNull Statement statement, String sql) throws Except */ @Override public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws Exception { - boolean prepared = config.isPrepared(); + boolean isPrepared = config.isPrepared(); final String sql = config.getSQL(false); - config.setPrepared(prepared); + config.setPrepared(isPrepared); if (StringUtil.isEmpty(sql, true)) { Log.e(TAG, "execute StringUtil.isEmpty(sql, true) >> return null;"); return null; } + + boolean isExplain = config.isExplain(); + boolean isHead = RequestMethod.isHeadMethod(config.getMethod(), true); final int position = config.getPosition(); JSONObject result; - if (config.isExplain() == false) { + if (isExplain == false) { generatedSQLCount ++; } @@ -192,17 +195,6 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws } else { switch (config.getMethod()) { - case HEAD: - case HEADS: - rs = executeQuery(config); - - executedSQLCount ++; - - result = rs.next() ? AbstractParser.newSuccessResult() - : AbstractParser.newErrorResult(new SQLException("数据库错误, rs.next() 失败!")); - result.put(JSONResponse.KEY_COUNT, rs.getLong(1)); - return result; - case POST: case PUT: case DELETE: @@ -224,10 +216,12 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws result.put(config.getIdKey() + "[]", config.getWhere(config.getIdKey() + "{}", true)); } return result; - + case GET: case GETS: - result = getCacheItem(sql, position, config.getCache()); + case HEAD: + case HEADS: + result = isHead ? null : getCacheItem(sql, position, config.getCache()); Log.i(TAG, ">>> execute result = getCache('" + sql + "', " + position + ") = " + result); if (result != null) { cachedSQLCount ++; @@ -238,7 +232,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws rs = executeQuery(config); //FIXME SQL Server 是一次返回两个结果集,包括查询结果和执行计划,需要 moreResults - if (config.isExplain() == false) { //只有 SELECT 才能 EXPLAIN + if (isExplain == false) { //只有 SELECT 才能 EXPLAIN executedSQLCount ++; } break; @@ -249,58 +243,68 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws } } + + if (isExplain == false && isHead) { + if (rs.next() == false) { + return AbstractParser.newErrorResult(new SQLException("数据库错误, rs.next() 失败!")); + } + result = AbstractParser.newSuccessResult(); + result.put(JSONResponse.KEY_COUNT, rs.getLong(1)); + resultList = new ArrayList<>(1); + resultList.add(result); + } + else { + // final boolean cache = config.getCount() != 1; + // TODO 设置初始容量为查到的数据量,解决频繁扩容导致的延迟,貌似只有 rs.last 取 rs.getRow() ? 然后又得 rs.beforeFirst 重置位置以便下方取值 + resultList = new ArrayList<>(config.getCount() <= 0 ? Parser.MAX_QUERY_COUNT : config.getCount()); + // Log.d(TAG, "select cache = " + cache + "; resultList" + (resultList == null ? "=" : "!=") + "null"); - // final boolean cache = config.getCount() != 1; - // TODO 设置初始容量为查到的数据量,解决频繁扩容导致的延迟,貌似只有 rs.last 取 rs.getRow() ? 然后又得 rs.beforeFirst 重置位置以便下方取值 - resultList = new ArrayList<>(config.getCount() <= 0 ? Parser.MAX_QUERY_COUNT : config.getCount()); - // Log.d(TAG, "select cache = " + cache + "; resultList" + (resultList == null ? "=" : "!=") + "null"); - - int index = -1; + int index = -1; - ResultSetMetaData rsmd = rs.getMetaData(); - final int length = rsmd.getColumnCount(); + ResultSetMetaData rsmd = rs.getMetaData(); + final int length = rsmd.getColumnCount(); - // + childMap = new HashMap<>(); //要存到cacheMap + // WHERE id = ? AND ... 或 WHERE ... AND id = ? 强制排序 remove 再 put,还是重新 getSQL吧 - boolean hasJoin = config.hasJoin(); - int viceColumnStart = length + 1; //第一个副表字段的index - while (rs.next()) { - index ++; - Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n execute while (rs.next()){ index = " + index + "\n\n"); + boolean hasJoin = config.hasJoin(); + int viceColumnStart = length + 1; //第一个副表字段的index + while (rs.next()) { + index ++; + Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n execute while (rs.next()){ index = " + index + "\n\n"); - JSONObject item = new JSONObject(true); + JSONObject item = new JSONObject(true); - for (int i = 1; i <= length; i++) { + for (int i = 1; i <= length; i++) { - // if (hasJoin && viceColumnStart > length && config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { - // viceColumnStart = i; - // } + // if (hasJoin && viceColumnStart > length && config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { + // viceColumnStart = i; + // } - // bugfix-修复非常规数据库字段,获取表名失败导致输出异常 - if (config.isExplain() == false && hasJoin && viceColumnStart > length) { - List column = config.getColumn(); + // bugfix-修复非常规数据库字段,获取表名失败导致输出异常 + if (isExplain == false && hasJoin && viceColumnStart > length) { + List column = config.getColumn(); - if (column != null && column.isEmpty() == false) { - viceColumnStart = column.size() + 1; - } - else if (config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { - viceColumnStart = i; + if (column != null && column.isEmpty() == false) { + viceColumnStart = column.size() + 1; + } + else if (config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { + viceColumnStart = i; + } } - } - item = onPutColumn(config, rs, rsmd, index, item, i, config.isExplain() == false && hasJoin && i >= viceColumnStart ? childMap : null); - } + item = onPutColumn(config, rs, rsmd, index, item, i, isExplain == false && hasJoin && i >= viceColumnStart ? childMap : null); + } - resultList = onPutTable(config, rs, rsmd, resultList, index, item); + resultList = onPutTable(config, rs, rsmd, resultList, index, item); - Log.d(TAG, "\n execute while (rs.next()) { resultList.put( " + index + ", result); " - + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n"); + Log.d(TAG, "\n execute while (rs.next()) { resultList.put( " + index + ", result); " + + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n"); + } } - } finally { if (rs != null) { @@ -317,50 +321,51 @@ else if (config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { return null; } - if (unknowType || config.isExplain()) { - if (config.isExplain()) { + if (unknowType || isExplain) { + if (isExplain) { if (result == null) { result = new JSONObject(true); } - boolean explain = config.isExplain(); config.setExplain(false); result.put("sql", config.getSQL(false)); - config.setExplain(explain); - config.setPrepared(prepared); + config.setExplain(isExplain); + config.setPrepared(isPrepared); } result.put("list", resultList); return result; } + + if (isHead == false) { + // @ APP JOIN 查询副表并缓存到 childMap <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - // @ APP JOIN 查询副表并缓存到 childMap <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + executeAppJoin(config, resultList, childMap); - executeAppJoin(config, resultList, childMap); + // @ APP JOIN 查询副表并缓存到 childMap >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - // @ APP JOIN 查询副表并缓存到 childMap >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + //子查询 SELECT Moment.*, Comment.id 中的 Comment 内字段 + Set> set = childMap.entrySet(); - //子查询 SELECT Moment.*, Comment.id 中的 Comment 内字段 - Set> set = childMap.entrySet(); + // + for (Entry entry : set) { + List l = new ArrayList<>(); + l.add(entry.getValue()); + putCache(entry.getKey(), l, JSONRequest.CACHE_ROM); + } - // - for (Entry entry : set) { - List l = new ArrayList<>(); - l.add(entry.getValue()); - putCache(entry.getKey(), l, JSONRequest.CACHE_ROM); - } + putCache(sql, resultList, config.getCache()); + Log.i(TAG, ">>> execute putCache('" + sql + "', resultList); resultList.size() = " + resultList.size()); - putCache(sql, resultList, config.getCache()); - Log.i(TAG, ">>> execute putCache('" + sql + "', resultList); resultList.size() = " + resultList.size()); + // 数组主表对象额外一次返回全部,方便 Parser 缓存来提高性能 - // 数组主表对象额外一次返回全部,方便 Parser 缓存来提高性能 - - result = position >= resultList.size() ? new JSONObject() : resultList.get(position); - if (position == 0 && resultList.size() > 1 && result != null && result.isEmpty() == false) { - // 不是 main 不会直接执行,count=1 返回的不会超过 1 && config.isMain() && config.getCount() != 1 - Log.i(TAG, ">>> execute position == 0 && resultList.size() > 1 && result != null && result.isEmpty() == false" - + " >> result = new JSONObject(result); result.put(KEY_RAW_LIST, resultList);"); + result = position >= resultList.size() ? new JSONObject() : resultList.get(position); + if (position == 0 && resultList.size() > 1 && result != null && result.isEmpty() == false) { + // 不是 main 不会直接执行,count=1 返回的不会超过 1 && config.isMain() && config.getCount() != 1 + Log.i(TAG, ">>> execute position == 0 && resultList.size() > 1 && result != null && result.isEmpty() == false" + + " >> result = new JSONObject(result); result.put(KEY_RAW_LIST, resultList);"); - result = new JSONObject(result); - result.put(KEY_RAW_LIST, resultList); + result = new JSONObject(result); + result.put(KEY_RAW_LIST, resultList); + } } long endTime = System.currentTimeMillis(); @@ -396,7 +401,7 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map } continue; } - + jc = j.getJoinConfig(); //取出 "id@": "@/User/userId" 中所有 userId 的值 @@ -550,7 +555,7 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r } } } - + } Object value = getValue(config, rs, rsmd, tablePosition, table, columnIndex, lable, childMap); @@ -561,7 +566,7 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r } finalTable.put(lable, value); } - + return table; } @@ -581,7 +586,7 @@ protected List onPutTable(@NotNull SQLConfig config, @NotNull Result return resultList; } - + 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 { From 550dfe9d26868102b13b1afa09e37a0e6cd6fa66 Mon Sep 17 00:00:00 2001 From: JackJay <471771548@qq.com> Date: Thu, 17 Jun 2021 18:50:31 +0800 Subject: [PATCH 148/944] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E5=88=86?= =?UTF-8?q?=E9=A1=B5=E6=9F=A5=E8=AF=A2query=3D2,=E4=B8=94@raw=E5=85=B3?= =?UTF-8?q?=E9=94=AE=E5=AD=97=E6=89=A7=E8=A1=8CSQL=E5=87=BD=E6=95=B0date?= =?UTF-8?q?=5Fformat=E7=9A=84Bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 当分页查询query=2,且@raw关键字执行SQL函数date_format时,格式化字符串(例:%Y-%m-%d %H:%i:%s)中含有冒号会导致解析出错 -在getColumnString方法中,Method为HEAD,HEADS循环拼接SQL时,排除RAW_MAP中包含的值,使SQL拼接成功,执行数量查询。 --- .../java/apijson/orm/AbstractSQLConfig.java | 6978 +++++++++-------- 1 file changed, 3549 insertions(+), 3429 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 2eabffc62..755209951 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -71,3439 +71,3559 @@ import apijson.orm.model.Table; import apijson.orm.model.TestRecord; -/**config sql for JSON Request +/** + * config sql for JSON Request + * * @author Lemon */ public abstract class AbstractSQLConfig implements SQLConfig { - private static final String TAG = "AbstractSQLConfig"; - - public static String DEFAULT_DATABASE = DATABASE_MYSQL; - public static String DEFAULT_SCHEMA = "sys"; - public static String PREFFIX_DISTINCT = "DISTINCT "; - - // * 和 / 不能同时出现,防止 /* */ 段注释! # 和 -- 不能出现,防止行注释! ; 不能出现,防止隔断SQL语句!空格不能出现,防止 CRUD,DROP,SHOW TABLES等语句! - private static final Pattern PATTERN_RANGE; - private static final Pattern PATTERN_FUNCTION; - - /** - * 表名映射,隐藏真实表名,对安全要求很高的表可以这么做 - */ - public static final Map 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 改成更好的正则,校验前面为单词,中间为操作符,后面为值 - - - TABLE_KEY_MAP = new HashMap(); - TABLE_KEY_MAP.put(Table.class.getSimpleName(), Table.TABLE_NAME); - TABLE_KEY_MAP.put(Column.class.getSimpleName(), Column.TABLE_NAME); - TABLE_KEY_MAP.put(PgClass.class.getSimpleName(), PgClass.TABLE_NAME); - TABLE_KEY_MAP.put(PgAttribute.class.getSimpleName(), PgAttribute.TABLE_NAME); - TABLE_KEY_MAP.put(SysTable.class.getSimpleName(), SysTable.TABLE_NAME); - TABLE_KEY_MAP.put(SysColumn.class.getSimpleName(), SysColumn.TABLE_NAME); - TABLE_KEY_MAP.put(ExtendedProperty.class.getSimpleName(), ExtendedProperty.TABLE_NAME); - - 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()); - - - DATABASE_LIST = new ArrayList<>(); - DATABASE_LIST.add(DATABASE_MYSQL); - DATABASE_LIST.add(DATABASE_POSTGRESQL); - DATABASE_LIST.add(DATABASE_SQLSERVER); - DATABASE_LIST.add(DATABASE_ORACLE); - DATABASE_LIST.add(DATABASE_DB2); - - - 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...) - - } - - - @Override - public boolean limitSQLCount() { - return Log.DEBUG == false || AbstractVerifier.SYSTEM_ACCESS_MAP.containsKey(getTable()) == false; - } - - @NotNull - @Override - public String getIdKey() { - return KEY_ID; - } - @NotNull - @Override - public String getUserIdKey() { - return KEY_USER_ID; - } - - - private Object id; //Table的id - private RequestMethod method; //操作方法 - private boolean prepared = true; //预编译 - private boolean main = true; - /** - * TODO 被关联的表通过就忽略关联的表?(这个不行 User:{"sex@":"/Comment/toId"}) - */ - private RequestRole role; //发送请求的用户的角色 - private boolean distinct = false; - private String database; //表所在的数据库类型 - private String schema; //表所在的数据库名 - private String datasource; //数据源 - private String table; //表名 - private String alias; //表别名 - private String group; //分组方式的字符串数组,','分隔 - private String having; //聚合函数的字符串数组,','分隔 - private String order; //排序方式的字符串数组,','分隔 - private List raw; //需要保留原始 SQL 的字段,','分隔 - private List json; //需要转为 JSON 的字段,','分隔 - private Subquery from; //子查询临时表 - private List column; //表内字段名(或函数名,仅查询操作可用)的字符串数组,','分隔 - private List> values; //对应表内字段的值的字符串数组,','分隔 - private Map content; //Request内容,key:value形式,column = content.keySet(),values = content.values() - private Map where; //筛选条件,key:value形式 - private Map> combine; //条件组合,{ "&":[key], "|":[key], "!":[key] } - - - //array item <<<<<<<<<< - private int count; //Table数量 - private int page; //Table所在页码 - private int position; //Table在[]中的位置 - private int query; //JSONRequest.query - private int type; //ObjectParser.type - private int cache; - private boolean explain; - - private List joinList; //连表 配置列表 - //array item >>>>>>>>>> - private boolean test; //测试 - - private String procedure; - - public SQLConfig setProcedure(String procedure) { - this.procedure = procedure; - return this; - } - public String getProcedure() { - return procedure; - } - - public AbstractSQLConfig(RequestMethod method) { - setMethod(method); - } - public AbstractSQLConfig(RequestMethod method, String table) { - this(method); - setTable(table); - } - public AbstractSQLConfig(RequestMethod method, int count, int page) { - this(method); - setCount(count); - setPage(page); - } - - @NotNull - @Override - public RequestMethod getMethod() { - if (method == null) { - method = GET; - } - return method; - } - @Override - public AbstractSQLConfig setMethod(RequestMethod method) { - this.method = method; - return this; - } - @Override - public boolean isPrepared() { - return prepared; - } - @Override - public AbstractSQLConfig setPrepared(boolean prepared) { - this.prepared = prepared; - return this; - } - @Override - public boolean isMain() { - return main; - } - @Override - public AbstractSQLConfig setMain(boolean main) { - this.main = main; - return this; - } - - - @Override - public Object getId() { - return id; - } - @Override - public AbstractSQLConfig setId(Object id) { - this.id = id; - return this; - } - - @Override - public RequestRole 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) { - this.role = role; - return this; - } - - @Override - public boolean isDistinct() { - return distinct; - } - @Override - public SQLConfig setDistinct(boolean distinct) { - this.distinct = distinct; - return this; - } - - @Override - public String getDatabase() { - return database; - } - @Override - public SQLConfig setDatabase(String database) { - this.database = database; - return this; - } - /** - * @return db == null ? DEFAULT_DATABASE : db - */ - @NotNull - public String getSQLDatabase() { - String db = getDatabase(); - return db == null ? DEFAULT_DATABASE : db; // "" 表示已设置,不需要用全局默认的 StringUtil.isEmpty(db, false)) { - } - - @Override - public boolean isMySQL() { - return isMySQL(getSQLDatabase()); - } - public static boolean isMySQL(String db) { - return DATABASE_MYSQL.equals(db); - } - @Override - public boolean isPostgreSQL() { - return isPostgreSQL(getSQLDatabase()); - } - public static boolean isPostgreSQL(String db) { - return DATABASE_POSTGRESQL.equals(db); - } - @Override - public boolean isSQLServer() { - return isSQLServer(getSQLDatabase()); - } - public static boolean isSQLServer(String db) { - return DATABASE_SQLSERVER.equals(db); - } - @Override - public boolean isOracle() { - return isOracle(getSQLDatabase()); - } - public static boolean isOracle(String db) { - return DATABASE_ORACLE.equals(db); - } - @Override - public boolean isDb2() { - return isDb2(getSQLDatabase()); - } - public static boolean isDb2(String db) { - return DATABASE_DB2.equals(db); - } - - @Override - public String getQuote() { - return isMySQL() ? "`" : "\""; - } - - @Override - public String getSchema() { - return schema; - } - /** - * @param sqlTable - * @return - */ - @NotNull - public String getSQLSchema() { - String table = getTable(); - //强制,避免因为全局默认的 @schema 自动填充进来,导致这几个类的 schema 为 sys 等其它值 - if (Table.TAG.equals(table) || Column.TAG.equals(table)) { - return SCHEMA_INFORMATION; //MySQL, PostgreSQL, SQL Server 都有的 - } - if (PgClass.TAG.equals(table) || PgAttribute.TAG.equals(table)) { - return ""; //PostgreSQL 的 pg_class 和 pg_attribute 表好像不属于任何 Schema - } - if (SysTable.TAG.equals(table) || SysColumn.TAG.equals(table) || ExtendedProperty.TAG.equals(table)) { - return SCHEMA_SYS; //SQL Server 在 sys 中的属性比 information_schema 中的要全,能拿到注释 - } - - String sch = getSchema(); - return sch == null ? DEFAULT_SCHEMA : sch; - } - @Override - public AbstractSQLConfig setSchema(String schema) { - if (schema != null) { - String quote = getQuote(); - String s = schema.startsWith(quote) && schema.endsWith(quote) ? schema.substring(1, schema.length() - 1) : schema; - if (StringUtil.isEmpty(s, true) == false && StringUtil.isName(s) == false) { - throw new IllegalArgumentException("@schema:value 中value必须是1个单词!"); - } - } - this.schema = schema; - return this; - } - - @Override - public String getDatasource() { - return datasource; - } - @Override - public SQLConfig setDatasource(String datasource) { - this.datasource = datasource; - return this; - } - - /**请求传进来的Table名 - * @return - * @see {@link #getSQLTable()} - */ - @Override - public String getTable() { - return table; - } - /**数据库里的真实Table名 - * 通过 {@link #TABLE_KEY_MAP} 映射 - * @return - */ - @JSONField(serialize = false) - @Override - public String getSQLTable() { - // String t = TABLE_KEY_MAP.containsKey(table) ? TABLE_KEY_MAP.get(table) : table; - //如果要强制小写,则可在子类重写这个方法再 toLowerCase return DATABASE_POSTGRESQL.equals(getDatabase()) ? t.toLowerCase() : t; - return TABLE_KEY_MAP.containsKey(table) ? TABLE_KEY_MAP.get(table) : table; - } - @JSONField(serialize = false) - @Override - public String getTablePath() { - String q = getQuote(); - - String sch = getSQLSchema(); - String sqlTable = getSQLTable(); - - return (StringUtil.isEmpty(sch, true) ? "" : q + sch + q + ".") + q + sqlTable + q + ( isKeyPrefix() ? " AS " + getAliasWithQuote() : ""); - } - @Override - public AbstractSQLConfig setTable(String table) { //Table已经在Parser中校验,所以这里不用防SQL注入 - this.table = table; - return this; - } - - @Override - public String getAlias() { - return alias; - } - @Override - public AbstractSQLConfig setAlias(String alias) { - this.alias = alias; - return this; - } - public String getAliasWithQuote() { - String a = getAlias(); - if (StringUtil.isEmpty(a, true)) { - a = getTable(); - } - String q = getQuote(); - //getTable 不能小写,因为Verifier用大小写敏感的名称判断权限 - //如果要强制小写,则可在子类重写这个方法再 toLowerCase return q + (DATABASE_POSTGRESQL.equals(getDatabase()) ? a.toLowerCase() : a) + q; - return q + a + q; - } - - @Override - public String getGroup() { - return group; - } - public AbstractSQLConfig setGroup(String... keys) { - return setGroup(StringUtil.getString(keys)); - } - @Override - public AbstractSQLConfig setGroup(String group) { - this.group = group; - return this; - } - @JSONField(serialize = false) - 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()); - } - - c = ((AbstractSQLConfig) cfg).getGroupString(false); - if (StringUtil.isEmpty(c, true) == false) { - joinGroup += (first ? "" : ", ") + c; - first = false; - } - - } - } - - - group = StringUtil.getTrimedString(group); - String[] keys = StringUtil.split(group); - if (keys == null || keys.length <= 0) { - return StringUtil.isEmpty(joinGroup, true) ? "" : (hasPrefix ? " GROUP BY " : "") + joinGroup; - } - - for (int i = 0; i < keys.length; i++) { - if (isPrepared()) { //不能通过 ? 来代替,因为SQLExecutor statement.setString后 GROUP BY 'userId' 有单引号,只能返回一条数据,必须去掉单引号才行! - if (StringUtil.isName(keys[i]) == false) { - throw new IllegalArgumentException("@group:value 中 value里面用 , 分割的每一项都必须是1个单词!并且不要有空格!"); - } - } - - keys[i] = getKey(keys[i]); - } - - return (hasPrefix ? " GROUP BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinGroup, ", "); - } - - @Override - public String getHaving() { - return having; - } - public AbstractSQLConfig setHaving(String... conditions) { - return setHaving(StringUtil.getString(conditions)); - } - @Override - public AbstractSQLConfig setHaving(String having) { - this.having = having; - return this; - } - /**TODO @having 改为默认 | 或连接,且支持 @having: { "key1>": 1, "key{}": "length(key2)>0", "@combine": "key1,key2" } - * @return HAVING conditoin0 AND condition1 OR condition2 ... - */ - @JSONField(serialize = false) - 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()); - } - - c = ((AbstractSQLConfig) cfg).getHavingString(false); - if (StringUtil.isEmpty(c, true) == false) { - joinHaving += (first ? "" : ", ") + c; - first = false; - } - - } - } - - String[] keys = StringUtil.split(getHaving(), ";"); - if (keys == null || keys.length <= 0) { - return StringUtil.isEmpty(joinHaving, true) ? "" : (hasPrefix ? " HAVING " : "") + joinHaving; - } - - String quote = getQuote(); - String tableAlias = getAliasWithQuote(); - - List raw = getRaw(); - boolean containRaw = raw != null && raw.contains(KEY_HAVING); - - String expression; - String method; - //暂时不允许 String prefix; - String suffix; - - //fun0(arg0,arg1,...);fun1(arg0,arg1,...) - for (int i = 0; i < keys.length; i++) { - - //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()); - } - } - - if (expression.length() > 50) { - throw new UnsupportedOperationException("@having:value 的 value 中字符串 " + expression + " 不合法!" - + "不允许传超过 50 个字符的函数或表达式!请用 @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 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) { - throw new IllegalArgumentException("字符 " + method + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 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 + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); - } - - 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() ? tableAlias + "." : "") + origin; - } - } - - keys[i] = method + "(" + StringUtil.getString(ckeys) + ")" + suffix; - } - - //TODO 支持 OR, NOT 参考 @combine:"&key0,|key1,!key2" - return (hasPrefix ? " HAVING " : "") + StringUtil.concat(StringUtil.getString(keys, AND), joinHaving, AND); - } - - @Override - public String getOrder() { - return order; - } - public AbstractSQLConfig setOrder(String... conditions) { - return setOrder(StringUtil.getString(conditions)); - } - @Override - public AbstractSQLConfig setOrder(String order) { - this.order = order; - return this; - } - @JSONField(serialize = false) - 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()); - } - - c = ((AbstractSQLConfig) cfg).getOrderString(false); - if (StringUtil.isEmpty(c, true) == false) { - joinOrder += (first ? "" : ", ") + c; - first = false; - } - - } - } - - - String order = StringUtil.getTrimedString(getOrder()); - // SELECT * FROM sys.Moment ORDER BY userId ASC, rand(); 前面的 userId ASC 和后面的 rand() 都有效 - // if ("rand()".equals(order)) { - // return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(order, joinOrder, ", "); - // } - - if (getCount() > 0 && (isOracle() || isSQLServer() || isDb2())) { // Oracle, SQL Server, DB2 的 OFFSET 必须加 ORDER BY - - // String[] ss = StringUtil.split(order); - if (StringUtil.isEmpty(order, true)) { //SQL Server 子查询内必须指定 OFFSET 才能用 ORDER BY - String idKey = getIdKey(); - if (StringUtil.isEmpty(idKey, true)) { - idKey = "id"; //ORDER BY NULL 不行,SQL Server 会报错,必须要有排序,才能使用 OFFSET FETCH,如果没有 idKey,请求中指定 @order 即可 - } - order = idKey; //让数据库调控默认升序还是降序 + "+"; - } - - //不用这么全面,毕竟没有语法问题还浪费性能,如果有其它问题,让前端传的 JSON 直接加上 @order 来解决 - // boolean contains = false; - // if (ss != null) { - // for (String s : ss) { - // if (s != null && s.startsWith(idKey)) { - // s = s.substring(idKey.length()); - // if ("+".equals(s) || "-".equals(s)) {// || " ASC ".equals(s) || " DESC ".equals(s)) { - // contains = true; - // break; - // } - // } - // } - // } - - // if (contains == false) { - // order = (ss == null || ss.length <= 0 ? "" : order + ",") + idKey + "+"; - // } - } - - - String[] keys = StringUtil.split(order); - if (keys == null || keys.length <= 0) { - return StringUtil.isEmpty(joinOrder, true) ? "" : (hasPrefix ? " ORDER BY " : "") + joinOrder; - } - - for (int i = 0; i < keys.length; i++) { - String item = keys[i]; - if ("rand()".equals(item)) { - continue; - } - - int index = item.endsWith("+") ? item.length() - 1 : -1; //StringUtil.split返回数组中,子项不会有null - String sort; - if (index < 0) { - index = item.endsWith("-") ? item.length() - 1 : -1; - sort = index <= 0 ? "" : " DESC "; - } - else { - sort = " ASC "; - } - - String origin = index < 0 ? item : item.substring(0, index); - - if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! - //这里既不对origin trim,也不对 ASC/DESC ignoreCase,希望前端严格传没有任何空格的字符串过来,减少传输数据量,节约服务器性能 - if (StringUtil.isName(origin) == false) { - throw new IllegalArgumentException("预编译模式下 @order:value 中 " + item + " 不合法! value 里面用 , 分割的" - + "每一项必须是 随机函数 rand() 或 column+ / column- 且其中 column 必须是 1 个单词!并且不要有多余的空格!"); - } - } - - keys[i] = getKey(origin) + sort; - } - - return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinOrder, ", "); - } - - @Override - public List getRaw() { - return raw; - } - @Override - public SQLConfig setRaw(List raw) { - this.raw = raw; - return this; - } - - /**获取原始 SQL 片段 - * @param key - * @param value - * @return - * @throws Exception - */ - @Override - public String getRawSQL(String key, Object value) throws Exception { - List rawList = getRaw(); - boolean containRaw = rawList != null && rawList.contains(key); - if (containRaw && value instanceof String == false) { - throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" - + "对应的 " + key + ":value 中 value 类型只能为 String!"); - } - - 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)) { - return (String) value; - } - } - - return rawSQL; - } - - - @Override - public List getJson() { - return json; - } - @Override - public AbstractSQLConfig setJson(List json) { - this.json = json; - return this; - } - - - @Override - public Subquery getFrom() { - return from; - } - @Override - public AbstractSQLConfig setFrom(Subquery from) { - this.from = from; - return this; - } - - @Override - public List getColumn() { - return column; - } - @Override - public AbstractSQLConfig setColumn(List column) { - this.column = column; - return this; - } - @JSONField(serialize = false) - public String getColumnString() throws Exception { - return getColumnString(false); - } - @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) { - String origin; - String alias; - int index; - for (String c : column) { - index = c.lastIndexOf(":"); //StringUtil.split返回数组中,子项不会有null - origin = index < 0 ? c : c.substring(0, index); - alias = index < 0 ? null : c.substring(index + 1); - - if (alias != null && StringUtil.isName(alias) == false) { - throw new IllegalArgumentException("HEAD请求: 字符 " + alias + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" - + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); - } - - if (StringUtil.isName(origin) == false) { - int start = origin.indexOf("("); - if (start < 0 || origin.lastIndexOf(")") <= start) { - 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个单词!并且不要有多余的空格!"); - } - } - } - } - - return SQL.count(column != null && column.size() == 1 && StringUtil.isName(column.get(0)) ? getKey(column.get(0)) : "*"); - case POST: - if (column == null || column.isEmpty()) { - throw new IllegalArgumentException("POST 请求必须在Table内设置要保存的 key:value !"); - } - - String s = ""; - boolean pfirst = true; - for (String c : column) { - if (isPrepared() && StringUtil.isName(c) == false) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! - throw new IllegalArgumentException("POST请求: 每一个 key:value 中的key都必须是1个单词!"); - } - s += ((pfirst ? "" : ",") + getKey(c)); - - pfirst = false; - } - - return "(" + s + ")"; - case GET: - case GETS: - boolean isQuery = RequestMethod.isQueryMethod(method); //TODO 这个有啥用?上面应是 getMethod 的值 GET 和 GETS 了。 - String joinColumn = ""; - if (isQuery && joinList != null) { - SQLConfig ecfg; - SQLConfig cfg; - String c; - boolean first = true; - for (Join j : joinList) { - 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; - first = false; - } - - inSQLJoin = true; - } - } - - String tableAlias = getAliasWithQuote(); - - // String c = StringUtil.getString(column); //id,name;json_length(contactIdList):contactCount;... - - String[] keys = column == null ? null : column.toArray(new String[]{}); //StringUtil.split(c, ";"); - if (keys == null || keys.length <= 0) { - - boolean noColumn = column != null && inSQLJoin; - String mc = isKeyPrefix() == false ? (noColumn ? "" : "*") : (noColumn ? "" : tableAlias + ".*"); - - return StringUtil.concat(mc, joinColumn, ", ", true); - } - - - 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]; - - if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 - if ("".equals(RAW_MAP.get(expression)) || RAW_MAP.containsValue(expression)) { // newSQLConfig 提前处理好的 - continue; - } - - // 简单点, 后台配置就带上 AS - // int index = expression.lastIndexOf(":"); - // String alias = expression.substring(index+1); - // boolean hasAlias = StringUtil.isName(alias); - // String pre = index > 0 && hasAlias ? expression.substring(0, index) : expression; - // if (RAW_MAP.containsValue(pre) || "".equals(RAW_MAP.get(pre))) { // newSQLConfig 提前处理好的 - // expression = pre + (hasAlias ? " AS " + alias : ""); - // continue; - // } - } - - if (expression.length() > 50) { - throw new UnsupportedOperationException("@column:value 的 value 中字符串 " + expression + " 不合法!" - + "不允许传超过 50 个字符的函数或表达式!请用 @raw 简化传参!"); - } - - - 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,...) 这种格式!"); - } - - method = expression.substring(0, start); - boolean distinct = i <= 0 && method.startsWith(PREFFIX_DISTINCT); - String fun = distinct ? method.substring(PREFFIX_DISTINCT.length()) : method; - - 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 + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); - } - } - - } - - boolean isColumn = start < 0; - - String[] ckeys = StringUtil.split(isColumn ? expression : expression.substring(start + 1, end)); - String quote = getQuote(); - - // 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); - - distinct = j <= 0 && origin.startsWith(PREFFIX_DISTINCT); - if (distinct) { - origin = origin.substring(PREFFIX_DISTINCT.length()); - } - - 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 { - // 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 个空格!其它情况不允许空格!"); - } - } - } - - //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 (isName && isKeyPrefix()) { - ckeys[j] = tableAlias + "." + origin; - // if (isColumn) { - // ckeys[j] += " AS " + quote + (isMain() ? "" : tableAlias + ".") + (StringUtil.isEmpty(alias, true) ? origin : alias) + quote; - // } - if (isColumn && StringUtil.isEmpty(alias, true) == false) { - ckeys[j] += " AS " + quote + alias + quote; - } - } else { - ckeys[j] = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); - } - - if (distinct) { - ckeys[j] = PREFFIX_DISTINCT + ckeys[j]; - } - } - // } - - } - - 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); - - 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 + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); - } - - 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); - // } - } - - } - - 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!" - ); - } - } - - - @Override - public List> getValues() { - return values; - } - @JSONField(serialize = false) - public String getValuesString() { - String s = ""; - if (values != null && values.size() > 0) { - Object[] items = new Object[values.size()]; - List vs; - for (int i = 0; i < values.size(); i++) { - vs = values.get(i); - if (vs == null) { - continue; - } - - items[i] = "("; - for (int j = 0; j < vs.size(); j++) { - items[i] += ((j <= 0 ? "" : ",") + getValue(vs.get(j))); - } - items[i] += ")"; - } - s = StringUtil.getString(items); - } - return s; - } - @Override - public AbstractSQLConfig setValues(List> valuess) { - this.values = valuess; - return this; - } - - @Override - public Map getContent() { - return content; - } - @Override - public AbstractSQLConfig setContent(Map content) { - this.content = content; - return this; - } - - @Override - public int getCount() { - return count; - } - @Override - public AbstractSQLConfig setCount(int count) { - this.count = count; - return this; - } - @Override - public int getPage() { - return page; - } - @Override - public AbstractSQLConfig setPage(int page) { - this.page = page; - return this; - } - @Override - public int getPosition() { - return position; - } - @Override - public AbstractSQLConfig setPosition(int position) { - this.position = position; - return this; - } - - @Override - public int getQuery() { - return query; - } - @Override - public AbstractSQLConfig setQuery(int query) { - this.query = query; - return this; - } - @Override - public int getType() { - return type; - } - @Override - public AbstractSQLConfig setType(int type) { - this.type = type; - return this; - } - - @Override - public int getCache() { - return cache; - } - @Override - public AbstractSQLConfig setCache(int cache) { - this.cache = cache; - return this; - } - - public AbstractSQLConfig setCache(String cache) { - return setCache(getCache(cache)); - } - public static int getCache(String cache) { - int cache2; - if (cache == null) { - cache2 = JSONRequest.CACHE_ALL; - } - else { - // if (isSubquery) { - // throw new IllegalArgumentException("子查询内不支持传 " + JSONRequest.KEY_CACHE + "!"); - // } - - switch (cache) { - case "0": - case JSONRequest.CACHE_ALL_STRING: - cache2 = JSONRequest.CACHE_ALL; - break; - case "1": - case JSONRequest.CACHE_ROM_STRING: - cache2 = JSONRequest.CACHE_ROM; - break; - case "2": - case JSONRequest.CACHE_RAM_STRING: - cache2 = JSONRequest.CACHE_RAM; - break; - default: - throw new IllegalArgumentException(JSONRequest.KEY_CACHE + ":value 中 value 的值不合法!必须在 [0,1,2] 或 [ALL, ROM, RAM] 内 !"); - } - } - return cache2; - } - - @Override - public boolean isExplain() { - return explain; - } - @Override - public AbstractSQLConfig setExplain(boolean explain) { - this.explain = explain; - return this; - } - - @Override - public List getJoinList() { - return joinList; - } - @Override - public SQLConfig setJoinList(List joinList) { - this.joinList = joinList; - return this; - } - @Override - public boolean hasJoin() { - return joinList != null && joinList.isEmpty() == false; - } - - - @Override - public boolean isTest() { - return test; - } - @Override - public AbstractSQLConfig setTest(boolean test) { - this.test = test; - return this; - } - - /**获取初始位置offset - * @return - */ - @JSONField(serialize = false) - public int getOffset() { - return getOffset(getPage(), getCount()); - } - /**获取初始位置offset - * @param page - * @param count - * @return - */ - public static int getOffset(int page, int count) { - return page*count; - } - /**获取限制数量 - * @return - */ - @JSONField(serialize = false) - public String getLimitString() { - if (count <= 0 || RequestMethod.isHeadMethod(getMethod(), true)) { - return ""; - } - return getLimitString(getPage(), getCount(), isOracle() || isSQLServer() || isDb2(), isOracle()); - } - /**获取限制数量 - * @param limit - * @return - */ - public static String getLimitString(int page, int count, boolean isTSQL, boolean isOracle) { - 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 " LIMIT " + count + (offset <= 0 ? "" : " OFFSET " + offset); // DELETE, UPDATE 不支持 OFFSET - } - - //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - @Override - public Map getWhere() { - return where; - } - @Override - public AbstractSQLConfig setWhere(Map where) { - this.where = where; - return this; - } - @NotNull - @Override - public Map> getCombine() { - List andList = combine == null ? null : combine.get("&"); - if (andList == null) { - andList = where == null ? new ArrayList() : new ArrayList(where.keySet()); - if (combine == null) { - combine = new HashMap<>(); - } - combine.put("&", andList); - } - return combine; - } - @Override - public AbstractSQLConfig setCombine(Map> combine) { - this.combine = combine; - return this; - } - /** - * noFunctionChar = false - * @param key - * @return - */ - @JSONField(serialize = false) - @Override - public Object getWhere(String key) { - return getWhere(key, false); - } - //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 - /** - * @param key - the key passed in - * @param exactMatch - whether it is exact match - * @return - *

use entrySet+getValue() to replace keySet+get() to enhance efficiency

- */ - @JSONField(serialize = false) - @Override - public Object getWhere(String key, boolean exactMatch) { - if (exactMatch) { - return where == null ? null : where.get(key); - } - - 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(); - } - } - } - } - return null; - } - @Override - public AbstractSQLConfig putWhere(String key, Object value, boolean prior) { - if (key != null) { - if (where == null) { - where = new LinkedHashMap(); - } - if (value == null) { - where.remove(key); - } else { - where.put(key, value); - } - - combine = getCombine(); - List andList = combine.get("&"); - if (value == null) { - if (andList != null) { - andList.remove(key); - } - } - else if (andList == null || andList.contains(key) == false) { - int i = 0; - if (andList == null) { - andList = new ArrayList<>(); - } - else if (prior && andList.isEmpty() == false) { - - String idKey = getIdKey(); - String idInKey = idKey + "{}"; - String userIdKey = getUserIdKey(); - String userIdInKey = userIdKey + "{}"; - - if (andList.contains(idKey)) { - i ++; - } - if (andList.contains(idInKey)) { - i ++; - } - if (andList.contains(userIdKey)) { - i ++; - } - if (andList.contains(userIdInKey)) { - i ++; - } - } - - if (prior) { - andList.add(i, key); //userId的优先级不能比id高 0, key); - } else { - andList.add(key); //AbstractSQLExecutor.onPutColumn里getSQL,要保证缓存的SQL和查询的SQL里 where 的 key:value 顺序一致 - } - } - combine.put("&", andList); - } - return this; - } - - /**获取WHERE - * @return - * @throws Exception - */ - @JSONField(serialize = false) - @Override - public String getWhereString(boolean hasPrefix) throws Exception { - return getWhereString(hasPrefix, getMethod(), getWhere(), getCombine(), getJoinList(), ! isTest()); - } - /**获取WHERE - * @param method - * @param where - * @return - * @throws Exception - */ - @JSONField(serialize = false) - public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, Map> combine, List joinList, boolean verifyName) throws Exception { - Set>> combineSet = combine == null ? null : combine.entrySet(); - if (combineSet == null || combineSet.isEmpty()) { - Log.w(TAG, "getWhereString combineSet == null || combineSet.isEmpty() >> return \"\";"); - return ""; - } - - List keyList; - - String whereString = ""; - - boolean isCombineFirst = true; - int logic; - - boolean isItemFirst; - String c; - String cs; - - for (Entry> ce : combineSet) { - keyList = ce == null ? null : ce.getValue(); - if (keyList == null || keyList.isEmpty()) { - continue; - } - - if ("|".equals(ce.getKey())) { - logic = Logic.TYPE_OR; - } - else if ("!".equals(ce.getKey())) { - logic = Logic.TYPE_NOT; - } - else { - logic = Logic.TYPE_AND; - } - - - isItemFirst = true; - cs = ""; - for (String key : keyList) { - c = getWhereItem(key, where.get(key), method, verifyName); - - if (StringUtil.isEmpty(c, true)) {//避免SQL条件连接错误 - continue; - } - - cs += (isItemFirst ? "" : (Logic.isAnd(logic) ? AND : OR)) + "(" + c + ")"; - - isItemFirst = false; - } - - if (StringUtil.isEmpty(cs, true)) {//避免SQL条件连接错误 - continue; - } - - whereString += (isCombineFirst ? "" : AND) + (Logic.isNot(logic) ? NOT : "") + " ( " + cs + " ) "; - isCombineFirst = false; - } - - - 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) - 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 s = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; - - if (s.isEmpty() && RequestMethod.isQueryMethod(method) == false) { - throw new UnsupportedOperationException("写操作请求必须带条件!!!"); - } - - return s; - } - - /** - * @param key - * @param value - * @param method - * @param verifyName - * @return - * @throws Exception - */ - 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;"); - return null; - } - if (key.endsWith("@")) {//引用 - // key = key.substring(0, key.lastIndexOf("@")); - throw new IllegalArgumentException(TAG + ".getWhereItem: 字符 " + key + " 不合法!"); - } - - // 原始 SQL 片段 - String rawSQL = getRawSQL(key, value); - - int keyType; - if (key.endsWith("$")) { - keyType = 1; - } - else if (key.endsWith("~")) { - keyType = key.charAt(key.length() - 2) == '*' ? -2 : 2; //FIXME StringIndexOutOfBoundsException - } - else if (key.endsWith("%")) { - keyType = 3; - } - else if (key.endsWith("{}")) { - keyType = 4; - } - else if (key.endsWith("}{")) { - keyType = 5; - } - else if (key.endsWith("<>")) { - keyType = 6; - } - else if (key.endsWith(">=")) { - keyType = 7; - } - else if (key.endsWith("<=")) { - keyType = 8; - } - else if (key.endsWith(">")) { - keyType = 9; - } - else if (key.endsWith("<")) { - keyType = 10; - } else { // else绝对不能省,避免再次踩坑! keyType = 0; 写在for循环外面都没注意! - keyType = 0; - } - - key = getRealKey(method, key, false, true, verifyName); - - switch (keyType) { - case 1: - return getSearchString(key, value, rawSQL); - case -2: - case 2: - return getRegExpString(key, value, keyType < 0, rawSQL); - case 3: - return getBetweenString(key, value, rawSQL); - case 4: - return getRangeString(key, value, rawSQL); - case 5: - return getExistsString(key, value, rawSQL); - case 6: - return getContainString(key, value, rawSQL); - case 7: - return getCompareString(key, value, ">=", rawSQL); - case 8: - return getCompareString(key, value, "<=", rawSQL); - case 9: - return getCompareString(key, value, ">", rawSQL); - case 10: - return getCompareString(key, value, "<", rawSQL); - default: // TODO MySQL JSON类型的字段对比 key='[]' 会无结果! key LIKE '[1, 2, 3]' //TODO MySQL , 后面有空格! - return getEqualString(key, 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) { - throw new IllegalArgumentException(key + ":value 中value不合法!非PUT请求只支持 [Boolean, Number, String] 内的类型 !"); - } - - boolean not = key.endsWith("!"); // & | 没有任何意义,写法多了不好控制 - if (not) { - key = key.substring(0, key.length() - 1); - } - if (StringUtil.isName(key) == false) { - throw new IllegalArgumentException(key + ":value 中key不合法!不支持 ! 以外的逻辑符 !"); - } - - return getKey(key) + (not ? " != " : " = ") + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(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] 内的类型 !"); - } - if (StringUtil.isName(key) == false) { - throw new IllegalArgumentException(key + type + ":value 中key不合法!比较运算 [>, <, >=, <=] 不支持 [&, !, |] 中任何逻辑运算符 !"); - } - - return getKey(key) + " " + type + " " + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(value))); - } - - public String getKey(String key) { - if (isTest()) { - if (key.contains("'")) { // || key.contains("#") || key.contains("--")) { - throw new IllegalArgumentException("参数 " + key + " 不合法!key 中不允许有单引号 ' !"); - } - return getSQLValue(key).toString(); - } - - return getSQLKey(key); - } - public String getSQLKey(String key) { - String q = getQuote(); - return (isKeyPrefix() ? getAliasWithQuote() + "." : "") + q + key + q; - } - - /** - * 使用prepareStatement预编译,值为 ? ,后续动态set进去 - */ - private List preparedValueList = new ArrayList<>(); - private Object getValue(@NotNull Object value) { - if (isPrepared()) { - preparedValueList.add(value); - return "?"; - } - return getSQLValue(value); - } - public Object getSQLValue(@NotNull Object value) { - // return (value instanceof Number || value instanceof Boolean) && DATABASE_POSTGRESQL.equals(getDatabase()) ? value : "'" + value + "'"; - return (value instanceof Number || value instanceof Boolean) ? value : "'" + value + "'"; //MySQL 隐式转换用不了索引 - } - - @Override - public List getPreparedValueList() { - return preparedValueList; - } - @Override - public AbstractSQLConfig setPreparedValueList(List preparedValueList) { - this.preparedValueList = preparedValueList; - return this; - } - - //$ search <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - /**search key match value - * @param in - * @return {@link #getSearchString(String, Object[], int)} - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getSearchString(String key, 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, "getSearchString key = " + key); - - JSONArray arr = newJSONArray(value); - if (arr.isEmpty()) { - return ""; - } - return getSearchString(key, arr.toArray(), logic.getType()); - } - /**search key match values - * @param in - * @return LOGIC [ key LIKE 'values[i]' ] - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getSearchString(String key, Object[] values, int type) throws IllegalArgumentException { - if (values == null || values.length <= 0) { - return ""; - } - - String condition = ""; - 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[]!"); - } - if (((String) v).isEmpty()) { // 允许查空格 StringUtil.isEmpty((String) v, true) - 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); - } - - return getCondition(Logic.isNot(type), condition); - } - - /**WHERE key LIKE 'value' - * @param key - * @param value - * @return key LIKE 'value' - */ - @JSONField(serialize = false) - public String getLikeString(String key, Object value) { - return getKey(key) + " LIKE " + getValue(value); - } - - //$ search >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - - //~ regexp <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - /**search key match RegExp values - * @param key - * @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 { - 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, "getRegExpString key = " + key); - - JSONArray arr = newJSONArray(value); - if (arr.isEmpty()) { - return ""; - } - return getRegExpString(key, arr.toArray(), logic.getType(), ignoreCase); - } - /**search key match RegExp values - * @param key - * @param values - * @param type - * @param ignoreCase - * @return LOGIC [ key REGEXP 'values[i]' ] - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getRegExpString(String key, Object[] values, int type, boolean ignoreCase) throws IllegalArgumentException { - if (values == null || values.length <= 0) { - return ""; - } - - String condition = ""; - for (int i = 0; i < values.length; i++) { - if (values[i] instanceof String == false) { - throw new IllegalArgumentException(key + "$:value 中value的类型只能为String或String[]!"); - } - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getRegExpString(key, (String) values[i], ignoreCase); - } - - return getCondition(Logic.isNot(type), condition); - } - - /**WHERE key REGEXP 'value' - * @param key - * @param value - * @param ignoreCase - * @return key REGEXP 'value' - */ - @JSONField(serialize = false) - public String getRegExpString(String key, String value, boolean ignoreCase) { - if (isPostgreSQL()) { - return getKey(key) + " ~" + (ignoreCase ? "* " : " ") + getValue(value); - } - if (isOracle()) { - return "regexp_like(" + getKey(key) + ", " + getValue(value) + (ignoreCase ? ", 'i'" : ", 'c'") + ")"; - } - return getKey(key) + " REGEXP " + (ignoreCase ? "" : "BINARY ") + getValue(value); - } - //~ regexp >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - - //% between <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - /**WHERE key BETWEEN 'start' AND 'end' - * @param key - * @param value 'start,end' - * @return LOGIC [ key BETWEEN 'start' AND 'end' ] - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getBetweenString(String key, 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, "getBetweenString key = " + key); - - JSONArray arr = newJSONArray(value); - if (arr.isEmpty()) { - return ""; - } - return getBetweenString(key, arr.toArray(), logic.getType()); - } - - /**WHERE key BETWEEN 'start' AND 'end' - * @param key - * @param value 'start,end' TODO 在 '1,2' 和 ['1,2', '3,4'] 基础上新增支持 [1, 2] 和 [[1,2], [3,4]] ? - * @return LOGIC [ key BETWEEN 'start' AND 'end' ] - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getBetweenString(String key, Object[] values, int type) throws IllegalArgumentException { - if (values == null || values.length <= 0) { - return ""; - } - - String condition = ""; - String[] vs; - for (int i = 0; i < values.length; i++) { - if (values[i] instanceof String == false) { - 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 的规则 !"); - } - - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + "(" + getBetweenString(key, (Object) vs[0], (Object) vs[1]) + ")"; - } - - return getCondition(Logic.isNot(type), condition); - } - - /**WHERE key BETWEEN 'start' AND 'end' - * @param key - * @param value 'start,end' TODO 在 '1,2' 和 ['1,2', '3,4'] 基础上新增支持 [1, 2] 和 [[1,2], [3,4]] ? - * @return key BETWEEN 'start' AND 'end' - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getBetweenString(String key, 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 的规则 !"); - } - return getKey(key) + " BETWEEN " + getValue(start) + AND + getValue(end); - } - - - //% between >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - - //{} range <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - - /**WHERE key > 'key0' AND key <= 'key1' AND ... - * @param key - * @param range "condition0,condition1..." - * @return key condition0 AND key condition1 AND ... - * @throws Exception - */ - @JSONField(serialize = false) - public String getRangeString(String key, Object range, String rawSQL) throws Exception { - Log.i(TAG, "getRangeString key = " + key); - if (range == null) {//依赖的对象都没有给出有效值,这个存在无意义。如果是客户端传的,那就能在客户端确定了。 - throw new NotExistException(TAG + "getRangeString(" + key + ", " + range - + ") range == null"); - } - - Logic logic = new Logic(key); - String k = logic.getKey(); - Log.i(TAG, "getRangeString k = " + k); - - if (range instanceof List) { - if (rawSQL != null) { - throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + "{} 不合法!" - + "Raw SQL 不支持 key{}:[] 这种键值对!"); - } - - if (logic.isOr() || logic.isNot()) { - List l = (List) range; - if (logic.isNot() && l.isEmpty()) { - return ""; // key!{}: [] 这个条件无效,加到 SQL 语句中 key IN() 会报错,getInString 里不好处理 - } - return getKey(k) + getInString(k, l.toArray(), logic.isNot()); - } - throw new IllegalArgumentException(key + "{}\":[] 中 {} 前面的逻辑运算符错误!只能用'|','!'中的一种 !"); - } - else if (range instanceof String) {//非Number类型需要客户端拼接成 < 'value0', >= 'value1'这种 - String condition = ""; - String[] cs = rawSQL != null ? null : StringUtil.split((String) range, false); - - if (rawSQL != null) { - int index = rawSQL == null ? -1 : 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; - 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); - } - 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 + " !不允许连续减号 -- !不允许空格!"); - } - - 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 则按原来位置拼接 - } - } - if (condition.isEmpty()) { - return ""; - } - - return getCondition(logic.isNot(), condition); - } - 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() - + "!range 只能是 用','分隔条件的字符串 或者 可取选项JSONArray!"); - } - /**WHERE key IN ('key0', 'key1', ... ) - * @param in - * @return IN ('key0', 'key1', ... ) - * @throws NotExistException - */ - @JSONField(serialize = false) - public String getInString(String key, 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])); - } - } - if (condition.isEmpty()) {//条件如果存在必须执行,不能忽略。条件为空会导致出错,又很难保证条件不为空(@:条件),所以还是这样好 - throw new NotExistException(TAG + ".getInString(" + key + ", [], " + not - + ") >> condition.isEmpty() >> IN()"); - } - return (not ? NOT : "") + " IN (" + condition + ")"; - } - //{} range >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - //}{ exists <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - /**WHERE EXISTS subquery - * 如果合并到 getRangeString,一方面支持不了 [1,2,2] 和 ">1" (转成 EXISTS(SELECT IN ) 需要static newSQLConfig,但它不能传入子类实例,除非不是 static),另一方面多了子查询临时表性能会比 IN 差 - * @param key - * @param value - * @return EXISTS ALL(SELECT ...) - * @throws NotExistException - */ - @JSONField(serialize = false) - public String getExistsString(String key, Object value, String rawSQL) throws Exception { - if (rawSQL != null) { - throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key}{ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); - } - if (value == null) { - return ""; - } - if (value instanceof Subquery == false) { - throw new IllegalArgumentException(key + "}{:subquery 类型为" + value.getClass().getSimpleName() - + "!subquery 只能是 子查询JSONObejct!"); - } - - Logic logic = new Logic(key); - key = logic.getKey(); - Log.i(TAG, "getExistsString key = " + key); - - return (logic.isNot() ? NOT : "") + " EXISTS " + getSubqueryString((Subquery) value); - } - //}{ exists >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - //<> contain <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - /**WHERE key contains value - * @param key - * @param value - * @return {@link #getContainString(String, Object[], int)} - * @throws NotExistException - */ - @JSONField(serialize = false) - public String getContainString(String key, 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); - - return getContainString(key, newJSONArray(value).toArray(), logic.getType()); - } - /**WHERE key contains childs - * @param key - * @param childs null ? "" : (empty ? no child : contains childs) - * @param type |, &, ! - * @return LOGIC [ ( key LIKE '[" + childs[i] + "]' OR key LIKE '[" + childs[i] + ", %' - * OR key LIKE '%, " + childs[i] + ", %' OR key LIKE '%, " + childs[i] + "]' ) ] - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getContainString(String key, 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!"); - } - - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)); - if (isPostgreSQL()) { - condition += (getKey(key) + " @> " + getValue(newJSONArray(c))); //operator does not exist: jsonb @> character varying "[" + c + "]"); - } - else if (isOracle()) { - condition += ("json_textcontains(" + getKey(key) + ", '$', " + getValue(c.toString()) + ")"); - } - else { - boolean isNum = c instanceof Number; - String v = (isNum ? "" : "\"") + childs[i] + (isNum ? "" : "\""); - condition += ("json_contains(" + getKey(key) + ", " + getValue(v) + ")"); - } - } - } - if (condition.isEmpty()) { - condition = (getKey(key) + SQL.isNull(true) + OR + getLikeString(key, "[]")); // key = '[]' 无结果! - } else { - condition = (getKey(key) + SQL.isNull(false) + AND + "(" + condition + ")"); - } - } - if (condition.isEmpty()) { - return ""; - } - return getCondition(not, condition); - } - //<> contain >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - - //key@:{} Subquery <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - @Override - public String getSubqueryString(Subquery subquery) throws Exception { - String range = subquery.getRange(); - SQLConfig cfg = subquery.getConfig(); - - cfg.setPreparedValueList(new ArrayList<>()); - String sql = (range == null || range.isEmpty() ? "" : range) + "(" + cfg.getSQL(isPrepared()) + ") "; - - preparedValueList.addAll(cfg.getPreparedValueList()); - - return sql; - } - - //key@:{} Subquery >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - - /**拼接条件 - * @param not - * @param condition - * @return - */ - private static String getCondition(boolean not, String condition) { - return not ? NOT + "(" + condition + ")" : condition; - } - - - /**转为JSONArray - * @param tv - * @return - */ - @NotNull - public static JSONArray newJSONArray(Object obj) { - JSONArray array = new JSONArray(); - if (obj != null) { - if (obj instanceof Collection) { - array.addAll((Collection) obj); - } else { - array.add(obj); - } - } - return array; - } - - //WHERE >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - //SET <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - /**获取SET - * @return - * @throws Exception - */ - @JSONField(serialize = false) - public String getSetString() throws Exception { - return getSetString(getMethod(), getContent(), ! isTest()); - } - //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 - /**获取SET - * @param method -the method used - * @param content -the content map - * @return - * @throws Exception - *

use entrySet+getValue() to replace keySet+get() to enhance efficiency

- */ - @JSONField(serialize = false) - public String getSetString(RequestMethod method, Map content, boolean verifyName) throws Exception { - Set set = content == null ? null : content.keySet(); - String setString = ""; - - if (set != null && set.size() > 0) { - boolean isFirst = true; - int keyType;// 0 - =; 1 - +, 2 - - - Object value; - - String idKey = getIdKey(); - for (Entry entry : content.entrySet()) { - String key = entry.getKey(); - //避免筛选到全部 value = key == null ? null : content.get(key); - if (key == null || idKey.equals(key)) { - continue; - } - - if (key.endsWith("+")) { - keyType = 1; - } else if (key.endsWith("-")) { - keyType = 2; - } else { - keyType = 0; //注意重置类型,不然不该加减的字段会跟着加减 - } - value = entry.getValue(); - key = getRealKey(method, key, false, true, verifyName); - - setString += (isFirst ? "" : ", ") + (getKey(key) + " = " + (keyType == 1 ? getAddString(key, value) : (keyType == 2 - ? getRemoveString(key, value) : getValue(value)) ) ); - - isFirst = false; - } - } - - if (setString.isEmpty()) { - throw new IllegalArgumentException("PUT 请求必须在Table内设置要修改的 key:value !"); - } - return " SET " + setString; - } - - /**SET key = concat(key, 'value') - * @param key - * @param value - * @return concat(key, 'value') - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getAddString(String key, Object value) throws IllegalArgumentException { - if (value instanceof Number) { - return getKey(key) + " + " + value; - } - if (value instanceof String) { - return SQL.concat(getKey(key), (String) getValue(value)); - } - throw new IllegalArgumentException(key + "+ 对应的值 " + value + " 不是Number,String,Array中的任何一种!"); - } - /**SET key = replace(key, 'value', '') - * @param key - * @param value - * @return REPLACE (key, 'value', '') - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getRemoveString(String key, Object value) throws IllegalArgumentException { - if (value instanceof Number) { - return getKey(key) + " - " + value; - } - if (value instanceof String) { - return SQL.replace(getKey(key), (String) getValue(value), "''");// " replace(" + key + ", '" + value + "', '') "; - } - throw new IllegalArgumentException(key + "- 对应的值 " + value + " 不是Number,String,Array中的任何一种!"); - } - //SET >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - /** - * @return - * @throws Exception - */ - @JSONField(serialize = false) - @Override - public String getSQL(boolean prepared) throws Exception { - return getSQL(this.setPrepared(prepared)); - } - /** - * @param config - * @return - * @throws Exception - */ - public static String getSQL(AbstractSQLConfig config) throws Exception { - if (config == null) { - Log.i(TAG, "getSQL config == null >> return null;"); - return null; - } - - //TODO procedure 改为 List procedureList; behind : true; function: callFunction(); String key; ... - // for (...) { Call procedure1();\n SQL \n; Call procedure2(); ... } - // 貌似不需要,因为 ObjecParser 里就已经处理的顺序等,只是这里要解决下 Schema 问题。 - - String sch = config.getSQLSchema(); - if (StringUtil.isNotEmpty(config.getProcedure(), true)) { - String q = config.getQuote(); - return "CALL " + q + sch + q + "."+ config.getProcedure(); - } - - String tablePath = config.getTablePath(); - if (StringUtil.isNotEmpty(tablePath, true) == false) { - Log.i(TAG, "getSQL StringUtil.isNotEmpty(tablePath, true) == false >> return null;"); - return null; - } - - switch (config.getMethod()) { - 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() : ""); - case DELETE: - 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)) { - 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(); - } - - config.setPreparedValueList(new ArrayList()); - 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 " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString(); - } - } - - /**获取条件SQL字符串 - * @param page - * @param column - * @param table - * @param where - * @return - * @throws Exception - */ - private static String getConditionString(String column, String table, AbstractSQLConfig config) throws Exception { - String where = config.getWhereString(true); - - Subquery from = config.getFrom(); - if (from != null) { - 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(); - - //no need to optimize - // if (config.getPage() <= 0 || ID.equals(column.trim())) { - return condition; // config.isOracle() ? condition : condition + config.getLimitString(); - // } - // - // - // //order: id+ -> id >= idOfStartIndex; id- -> id <= idOfStartIndex <<<<<<<<<<<<<<<<<<< - // String order = StringUtil.getNoBlankString(config.getOrder()); - // List orderList = order.isEmpty() ? null : Arrays.asList(StringUtil.split(order)); - // - // int type = 0; - // if (BaseModel.isEmpty(orderList) || BaseModel.isContain(orderList, ID+"+")) { - // type = 1; - // } - // else if (BaseModel.isContain(orderList, ID+"-")) { - // type = 2; - // } - // - // if (type > 0) { - // return condition.replace("WHERE", - // "WHERE id " + (type == 1 ? ">=" : "<=") + " (SELECT id FROM " + table - // + where + " ORDER BY id " + (type == 1 ? "ASC" : "DESC") + " LIMIT " + config.getOffset() + ", 1) AND" - // ) - // + " LIMIT " + config.getCount(); //子查询起始id不一定准确,只能作为最小可能! ;// - // } - // //order: id+ -> id >= idOfStartIndex; id- -> id <= idOfStartIndex >>>>>>>>>>>>>>>>>> - // - // - // //结果错误!SELECT * FROM User AS t0 INNER JOIN - // (SELECT id FROM User ORDER BY date ASC LIMIT 20, 10) AS t1 ON t0.id = t1.id - // //common case, inner join - // condition += config.getLimitString(); - // return table + " AS t0 INNER JOIN (SELECT id FROM " + condition + ") AS t1 ON t0.id = t1.id"; - } - - - private boolean keyPrefix; - @Override - public boolean isKeyPrefix() { - return keyPrefix; - } - @Override - public AbstractSQLConfig setKeyPrefix(boolean keyPrefix) { - this.keyPrefix = keyPrefix; - return this; - } - - - - public String getJoinString() throws Exception { - String joinOns = ""; - - if (joinList != null) { - String quote = getQuote(); - 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) - continue; - } - String type = j.getJoinType(); - - //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(); - jc.setPrepared(isPrepared()); - - jt = StringUtil.isEmpty(jc.getAlias(), true) ? jc.getTable() : jc.getAlias(); - tt = j.getTargetTable(); - - //如果要强制小写,则可在子类重写这个方法再 toLowerCase - // if (DATABASE_POSTGRESQL.equals(getDatabase())) { - // jt = jt.toLowerCase(); - // tn = tn.toLowerCase(); - // } - - switch (type) { - //前面已跳过 case "@": // APP JOIN - // continue; - - case "*": // CROSS JOIN - onGetCrossJoinString(j); - case "<": // LEFT JOIN - 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; - jc.setMain(false).setKeyPrefix(true); - - pvl.addAll(jc.getPreparedValueList()); - changed = true; - 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 - sql = " INNER JOIN " + jc.getTablePath() - + " ON " + quote + jt + quote + "." + quote + j.getKey() + quote + " = " + quote + tt + quote + "." + quote + j.getTargetKey() + quote; - break; - default: - throw new UnsupportedOperationException( - "join:value 中 value 里的 " + jt + "/" + j.getPath() - + "错误!不支持 " + jt + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" - + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" - ); - } - - joinOns += " \n " + sql; - } - - - if (changed) { - pvl.addAll(preparedValueList); - preparedValueList = pvl; - } - - } - - return joinOns; - } - - protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { - throw new UnsupportedOperationException("已禁用 * CROSS JOIN !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); - } - - /**新建SQL配置 - * @param table - * @param request - * @param joinList - * @param isProcedure - * @param callback - * @return - * @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!"); - } - - boolean explain = request.getBooleanValue(KEY_EXPLAIN); - if (explain && Log.DEBUG == false) { //不在 config.setExplain 抛异常,一方面处理更早性能更好,另一方面为了内部调用可以绕过这个限制 - throw new UnsupportedOperationException("DEBUG 模式下不允许传 " + KEY_EXPLAIN + " !"); - } - - String database = request.getString(KEY_DATABASE); - if (StringUtil.isEmpty(database, false) == false && DATABASE_LIST.contains(database) == false) { - throw new UnsupportedDataTypeException("@database:value 中 value 错误,只能是 [" + StringUtil.getString(DATABASE_LIST.toArray()) + "] 中的一种!"); - } - - String schema = request.getString(KEY_SCHEMA); - String datasource = request.getString(KEY_DATASOURCE); - - SQLConfig config = callback.getSQLConfig(method, database, schema, table); - config.setAlias(alias); - - config.setDatabase(database); //不删,后面表对象还要用的,必须放在 parseJoin 前 - config.setSchema(schema); //不删,后面表对象还要用的 - config.setDatasource(datasource); //不删,后面表对象还要用的 - - if (isProcedure) { - return config; - } - - config = parseJoin(method, config, joinList, callback); //放后面会导致主表是空对象时 joinList 未解析 - - if (request.isEmpty()) { // User:{} 这种空内容在查询时也有效 - return config; //request.remove(key); 前都可以直接return,之后必须保证 put 回去 - } - - - String idKey = callback.getIdKey(database, schema, table); - String idInKey = idKey + "{}"; - String userIdKey = callback.getUserIdKey(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); - 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); - if ((d instanceof Number && ((Number) d).longValue() > 0) || (d instanceof String && StringUtil.isNotEmpty(d, true))) { - newIdIn.add(d); - } - } - if (newIdIn.isEmpty()) { - throw new NotExistException(TAG + ": newSQLConfig idIn instanceof List >> 去掉无效 id 后 newIdIn.isEmpty()"); - } - idIn = newIdIn; - - if (method == DELETE || method == PUT) { - config.setCount(newIdIn.size()); - } - } - - Object id = request.get(idKey); - boolean hasId = id != null; - if (method == POST && hasId == false) { - id = callback.newId(method, database, schema, table); // null 表示数据库自增 id - } - - if (id != null) { //null无效 - if (id instanceof Number) { - if (((Number) id).longValue() <= 0) { //一定没有值 - throw new NotExistException(TAG + ": newSQLConfig " + table + ".id <= 0"); - } - } - else if (id instanceof String) { - if (StringUtil.isEmpty(id, true)) { //一定没有值 - throw new NotExistException(TAG + ": newSQLConfig StringUtil.isEmpty(" + table + ".id, true)"); - } - } - else if (id instanceof Subquery) {} - else { - throw new IllegalArgumentException(idKey + ":value 中 value 的类型只能是 Long , String 或 Subquery !"); - } - - if (idIn instanceof List) { //共用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); - if (d != null && id.toString().equals(d.toString())) { - contains = true; - break; - } - } - if (contains == false) {//empty有效 BaseModel.isEmpty(idIn) == false) { - throw new NotExistException(TAG + ": newSQLConfig idIn != null && (((List) idIn).contains(id) == false"); - } - } - - if (method == DELETE || method == PUT) { - config.setCount(1); - } - } - - - 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 group = request.getString(KEY_GROUP); - String having = request.getString(KEY_HAVING); - String order = request.getString(KEY_ORDER); - String raw = request.getString(KEY_RAW); - String json = request.getString(KEY_JSON); - - try { - //强制作为条件且放在最前面优化性能 - request.remove(idKey); - request.remove(idInKey); - //关键词 - request.remove(KEY_ROLE); - request.remove(KEY_EXPLAIN); - request.remove(KEY_CACHE); - request.remove(KEY_DATABASE); - request.remove(KEY_SCHEMA); - request.remove(KEY_COMBINE); - request.remove(KEY_FROM); - request.remove(KEY_COLUMN); - request.remove(KEY_GROUP); - request.remove(KEY_HAVING); - request.remove(KEY_ORDER); - request.remove(KEY_RAW); - request.remove(KEY_JSON); - - String[] rawArr = StringUtil.split(raw); - config.setRaw(rawArr == null || rawArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(rawArr))); - - Map tableWhere = new LinkedHashMap();//保证顺序好优化 WHERE id > 1 AND name LIKE... - - //已经remove了id和id{},以及@key - Set set = request.keySet(); //前面已经判断request是否为空 - if (method == POST) { //POST操作 - if (idIn != null) { - throw new IllegalArgumentException("POST 请求中不允许传 " + idInKey + " !"); - } - - if (set != null && set.isEmpty() == false) { //不能直接return,要走完下面的流程 - String[] columns = set.toArray(new String[]{}); - - Collection valueCollection = request.values(); - Object[] values = valueCollection == null ? null : valueCollection.toArray(); - - if (values == null || values.length != columns.length) { - throw new Exception("服务器内部错误:\n" + TAG - + " newSQLConfig values == null || values.length != columns.length !"); - } - column = (id == null ? "" : idKey + ",") + 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数量为准 - - items = new ArrayList<>(size); - items.add(id); //idList.get(i)); //第0个就是id - - for (int j = 1; j < size; j++) { - items.add(values[j-1]); //从第1个开始,允许"null" - } - } - - valuess.add(items); - config.setValues(valuess); - } - } - else { //非POST操作 - final boolean isWhere = method != PUT;//除了POST,PUT,其它全是条件!!! - - //条件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - 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 !"); - } - 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 { - 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 + "] 其中任何一个!"); - } - } - - 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); - } - } - - } - - //条件>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - Map tableContent = new LinkedHashMap(); - Object value; - for (String key : set) { - value = request.get(key); - - if (value instanceof Map) {//只允许常规Object - throw new IllegalArgumentException("不允许 " + key + " 等任何key的value类型为 {JSONObject} !"); - } - - //解决AccessVerifier新增userId没有作为条件,而是作为内容,导致PUT,DELETE出错 - if (isWhere) { - tableWhere.put(key, value); - if (whereList == null || whereList.contains(key) == false) { - andList.add(key); - } - } - else if (whereList != null && whereList.contains(key)) { - tableWhere.put(key, value); - } - else { - tableContent.put(key, value);//一样 instanceof JSONArray ? JSON.toJSONString(value) : value); - } - } - - combineMap.put("&", andList); - combineMap.put("|", orList); - combineMap.put("!", notList); - config.setCombine(combineMap); - - config.setContent(tableContent); - } - - - List cs = new ArrayList<>(); - - List rawList = config.getRaw(); - 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()); - } - } - - boolean distinct = column == null || rawColumnSQL != null ? false : column.startsWith(PREFFIX_DISTINCT); - if (rawColumnSQL == null) { - String[] fks = StringUtil.split(distinct ? column.substring(PREFFIX_DISTINCT.length()) : column, ";"); // key0,key1;fun0(key0,...);fun1(key0,...);key3;fun2(key0,...) - if (fks != null) { - 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()); - } - } - - if (fk.contains("(")) { // fun0(key0,...) - cs.add(fk); - } - else { //key0,key1... - ks = StringUtil.split(fk); - if (ks != null && ks.length > 0) { - cs.addAll(Arrays.asList(ks)); - } - } - } - } - } - - 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.setId(id); - //在 tableWhere 第0个 config.setIdIn(idIn); - - config.setRole(RequestRole.get(role)); - 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 等要合并 - - } - finally {//后面还可能用到,要还原 - //id或id{}条件 - if (hasId) { - request.put(idKey, id); - } - request.put(idInKey, idIn); - //关键词 - request.put(KEY_DATABASE, database); - request.put(KEY_ROLE, role); - request.put(KEY_EXPLAIN, explain); - request.put(KEY_CACHE, cache); - request.put(KEY_SCHEMA, schema); - request.put(KEY_COMBINE, combine); - request.put(KEY_FROM, from); - request.put(KEY_COLUMN, column); - request.put(KEY_GROUP, group); - request.put(KEY_HAVING, having); - request.put(KEY_ORDER, order); - request.put(KEY_RAW, raw); - request.put(KEY_JSON, json); - } - - return config; - } - - - - /** - * @param method - * @param config - * @param joinList - * @param callback - * @return - * @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); - - //TODO 解析出 SQLConfig 再合并 column, order, group 等 - if (joinList == null || joinList.isEmpty() || RequestMethod.isQueryMethod(method) == false) { - return config; - } - - - String table; - String alias; - for (Join j : joinList) { - table = j.getTable(); - 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); - - if (j.isAppJoin() == false) { //除了 @ APP JOIN,其它都是 SQL JOIN,则副表要这样配置 - if (joinConfig.getDatabase() == null) { - joinConfig.setDatabase(config.getDatabase()); //解决主表 JOIN 副表,引号不一致 - } - else if (joinConfig.getDatabase().equals(config.getDatabase()) == false) { - throw new IllegalArgumentException("主表 " + config.getTable() + " 的 @database:" + config.getDatabase() + " 和它 SQL JOIN 的副表 " + table + " 的 @database:" + joinConfig.getDatabase() + " 不一致!"); - } - if (joinConfig.getSchema() == null) { - joinConfig.setSchema(config.getSchema()); //主表 JOIN 副表,默认 schema 一致 - } - - if (cacheConfig != null) { - cacheConfig.setDatabase(joinConfig.getDatabase()).setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 - } - - - if (isQuery) { - config.setKeyPrefix(true); - } - - joinConfig.setMain(false).setKeyPrefix(true); - - if (j.isLeftOrRightJoin()) { - 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); - } - } - - //解决 query: 1/2 查数量时报错 + private static final String TAG = "AbstractSQLConfig"; + + public static String DEFAULT_DATABASE = DATABASE_MYSQL; + public static String DEFAULT_SCHEMA = "sys"; + public static String PREFFIX_DISTINCT = "DISTINCT "; + + // * 和 / 不能同时出现,防止 /* */ 段注释! # 和 -- 不能出现,防止行注释! ; 不能出现,防止隔断SQL语句!空格不能出现,防止 CRUD,DROP,SHOW TABLES等语句! + private static final Pattern PATTERN_RANGE; + private static final Pattern PATTERN_FUNCTION; + + /** + * 表名映射,隐藏真实表名,对安全要求很高的表可以这么做 + */ + public static final Map 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 改成更好的正则,校验前面为单词,中间为操作符,后面为值 + + + TABLE_KEY_MAP = new HashMap(); + TABLE_KEY_MAP.put(Table.class.getSimpleName(), Table.TABLE_NAME); + TABLE_KEY_MAP.put(Column.class.getSimpleName(), Column.TABLE_NAME); + TABLE_KEY_MAP.put(PgClass.class.getSimpleName(), PgClass.TABLE_NAME); + TABLE_KEY_MAP.put(PgAttribute.class.getSimpleName(), PgAttribute.TABLE_NAME); + TABLE_KEY_MAP.put(SysTable.class.getSimpleName(), SysTable.TABLE_NAME); + TABLE_KEY_MAP.put(SysColumn.class.getSimpleName(), SysColumn.TABLE_NAME); + TABLE_KEY_MAP.put(ExtendedProperty.class.getSimpleName(), ExtendedProperty.TABLE_NAME); + + 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()); + + + DATABASE_LIST = new ArrayList<>(); + DATABASE_LIST.add(DATABASE_MYSQL); + DATABASE_LIST.add(DATABASE_POSTGRESQL); + DATABASE_LIST.add(DATABASE_SQLSERVER); + DATABASE_LIST.add(DATABASE_ORACLE); + DATABASE_LIST.add(DATABASE_DB2); + + + 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...) + + } + + + @Override + public boolean limitSQLCount() { + return Log.DEBUG == false || AbstractVerifier.SYSTEM_ACCESS_MAP.containsKey(getTable()) == false; + } + + @NotNull + @Override + public String getIdKey() { + return KEY_ID; + } + + @NotNull + @Override + public String getUserIdKey() { + return KEY_USER_ID; + } + + + private Object id; //Table的id + private RequestMethod method; //操作方法 + private boolean prepared = true; //预编译 + private boolean main = true; + /** + * TODO 被关联的表通过就忽略关联的表?(这个不行 User:{"sex@":"/Comment/toId"}) + */ + private RequestRole role; //发送请求的用户的角色 + private boolean distinct = false; + private String database; //表所在的数据库类型 + private String schema; //表所在的数据库名 + private String datasource; //数据源 + private String table; //表名 + private String alias; //表别名 + private String group; //分组方式的字符串数组,','分隔 + private String having; //聚合函数的字符串数组,','分隔 + private String order; //排序方式的字符串数组,','分隔 + private List raw; //需要保留原始 SQL 的字段,','分隔 + private List json; //需要转为 JSON 的字段,','分隔 + private Subquery from; //子查询临时表 + private List column; //表内字段名(或函数名,仅查询操作可用)的字符串数组,','分隔 + private List> values; //对应表内字段的值的字符串数组,','分隔 + private Map content; //Request内容,key:value形式,column = content.keySet(),values = content.values() + private Map where; //筛选条件,key:value形式 + private Map> combine; //条件组合,{ "&":[key], "|":[key], "!":[key] } + + + //array item <<<<<<<<<< + private int count; //Table数量 + private int page; //Table所在页码 + private int position; //Table在[]中的位置 + private int query; //JSONRequest.query + private int type; //ObjectParser.type + private int cache; + private boolean explain; + + private List joinList; //连表 配置列表 + //array item >>>>>>>>>> + private boolean test; //测试 + + private String procedure; + + public SQLConfig setProcedure(String procedure) { + this.procedure = procedure; + return this; + } + + public String getProcedure() { + return procedure; + } + + public AbstractSQLConfig(RequestMethod method) { + setMethod(method); + } + + public AbstractSQLConfig(RequestMethod method, String table) { + this(method); + setTable(table); + } + + public AbstractSQLConfig(RequestMethod method, int count, int page) { + this(method); + setCount(count); + setPage(page); + } + + @NotNull + @Override + public RequestMethod getMethod() { + if (method == null) { + method = GET; + } + return method; + } + + @Override + public AbstractSQLConfig setMethod(RequestMethod method) { + this.method = method; + return this; + } + + @Override + public boolean isPrepared() { + return prepared; + } + + @Override + public AbstractSQLConfig setPrepared(boolean prepared) { + this.prepared = prepared; + return this; + } + + @Override + public boolean isMain() { + return main; + } + + @Override + public AbstractSQLConfig setMain(boolean main) { + this.main = main; + return this; + } + + + @Override + public Object getId() { + return id; + } + + @Override + public AbstractSQLConfig setId(Object id) { + this.id = id; + return this; + } + + @Override + public RequestRole 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) { + this.role = role; + return this; + } + + @Override + public boolean isDistinct() { + return distinct; + } + + @Override + public SQLConfig setDistinct(boolean distinct) { + this.distinct = distinct; + return this; + } + + @Override + public String getDatabase() { + return database; + } + + @Override + public SQLConfig setDatabase(String database) { + this.database = database; + return this; + } + + /** + * @return db == null ? DEFAULT_DATABASE : db + */ + @NotNull + public String getSQLDatabase() { + String db = getDatabase(); + return db == null ? DEFAULT_DATABASE : db; // "" 表示已设置,不需要用全局默认的 StringUtil.isEmpty(db, false)) { + } + + @Override + public boolean isMySQL() { + return isMySQL(getSQLDatabase()); + } + + public static boolean isMySQL(String db) { + return DATABASE_MYSQL.equals(db); + } + + @Override + public boolean isPostgreSQL() { + return isPostgreSQL(getSQLDatabase()); + } + + public static boolean isPostgreSQL(String db) { + return DATABASE_POSTGRESQL.equals(db); + } + + @Override + public boolean isSQLServer() { + return isSQLServer(getSQLDatabase()); + } + + public static boolean isSQLServer(String db) { + return DATABASE_SQLSERVER.equals(db); + } + + @Override + public boolean isOracle() { + return isOracle(getSQLDatabase()); + } + + public static boolean isOracle(String db) { + return DATABASE_ORACLE.equals(db); + } + + @Override + public boolean isDb2() { + return isDb2(getSQLDatabase()); + } + + public static boolean isDb2(String db) { + return DATABASE_DB2.equals(db); + } + + @Override + public String getQuote() { + return isMySQL() ? "`" : "\""; + } + + @Override + public String getSchema() { + return schema; + } + + /** + * @param sqlTable + * @return + */ + @NotNull + public String getSQLSchema() { + String table = getTable(); + //强制,避免因为全局默认的 @schema 自动填充进来,导致这几个类的 schema 为 sys 等其它值 + if (Table.TAG.equals(table) || Column.TAG.equals(table)) { + return SCHEMA_INFORMATION; //MySQL, PostgreSQL, SQL Server 都有的 + } + if (PgClass.TAG.equals(table) || PgAttribute.TAG.equals(table)) { + return ""; //PostgreSQL 的 pg_class 和 pg_attribute 表好像不属于任何 Schema + } + if (SysTable.TAG.equals(table) || SysColumn.TAG.equals(table) || ExtendedProperty.TAG.equals(table)) { + return SCHEMA_SYS; //SQL Server 在 sys 中的属性比 information_schema 中的要全,能拿到注释 + } + + String sch = getSchema(); + return sch == null ? DEFAULT_SCHEMA : sch; + } + + @Override + public AbstractSQLConfig setSchema(String schema) { + if (schema != null) { + String quote = getQuote(); + String s = schema.startsWith(quote) && schema.endsWith(quote) ? schema.substring(1, schema.length() - 1) : schema; + if (StringUtil.isEmpty(s, true) == false && StringUtil.isName(s) == false) { + throw new IllegalArgumentException("@schema:value 中value必须是1个单词!"); + } + } + this.schema = schema; + return this; + } + + @Override + public String getDatasource() { + return datasource; + } + + @Override + public SQLConfig setDatasource(String datasource) { + this.datasource = datasource; + return this; + } + + /** + * 请求传进来的Table名 + * + * @return + * @see {@link #getSQLTable()} + */ + @Override + public String getTable() { + return table; + } + + /** + * 数据库里的真实Table名 + * 通过 {@link #TABLE_KEY_MAP} 映射 + * + * @return + */ + @JSONField(serialize = false) + @Override + public String getSQLTable() { + // String t = TABLE_KEY_MAP.containsKey(table) ? TABLE_KEY_MAP.get(table) : table; + //如果要强制小写,则可在子类重写这个方法再 toLowerCase return DATABASE_POSTGRESQL.equals(getDatabase()) ? t.toLowerCase() : t; + return TABLE_KEY_MAP.containsKey(table) ? TABLE_KEY_MAP.get(table) : table; + } + + @JSONField(serialize = false) + @Override + public String getTablePath() { + String q = getQuote(); + + String sch = getSQLSchema(); + String sqlTable = getSQLTable(); + + return (StringUtil.isEmpty(sch, true) ? "" : q + sch + q + ".") + q + sqlTable + q + (isKeyPrefix() ? " AS " + getAliasWithQuote() : ""); + } + + @Override + public AbstractSQLConfig setTable(String table) { //Table已经在Parser中校验,所以这里不用防SQL注入 + this.table = table; + return this; + } + + @Override + public String getAlias() { + return alias; + } + + @Override + public AbstractSQLConfig setAlias(String alias) { + this.alias = alias; + return this; + } + + public String getAliasWithQuote() { + String a = getAlias(); + if (StringUtil.isEmpty(a, true)) { + a = getTable(); + } + String q = getQuote(); + //getTable 不能小写,因为Verifier用大小写敏感的名称判断权限 + //如果要强制小写,则可在子类重写这个方法再 toLowerCase return q + (DATABASE_POSTGRESQL.equals(getDatabase()) ? a.toLowerCase() : a) + q; + return q + a + q; + } + + @Override + public String getGroup() { + return group; + } + + public AbstractSQLConfig setGroup(String... keys) { + return setGroup(StringUtil.getString(keys)); + } + + @Override + public AbstractSQLConfig setGroup(String group) { + this.group = group; + return this; + } + + @JSONField(serialize = false) + 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()); + } + + c = ((AbstractSQLConfig) cfg).getGroupString(false); + if (StringUtil.isEmpty(c, true) == false) { + joinGroup += (first ? "" : ", ") + c; + first = false; + } + + } + } + + + group = StringUtil.getTrimedString(group); + String[] keys = StringUtil.split(group); + if (keys == null || keys.length <= 0) { + return StringUtil.isEmpty(joinGroup, true) ? "" : (hasPrefix ? " GROUP BY " : "") + joinGroup; + } + + for (int i = 0; i < keys.length; i++) { + if (isPrepared()) { //不能通过 ? 来代替,因为SQLExecutor statement.setString后 GROUP BY 'userId' 有单引号,只能返回一条数据,必须去掉单引号才行! + if (StringUtil.isName(keys[i]) == false) { + throw new IllegalArgumentException("@group:value 中 value里面用 , 分割的每一项都必须是1个单词!并且不要有空格!"); + } + } + + keys[i] = getKey(keys[i]); + } + + return (hasPrefix ? " GROUP BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinGroup, ", "); + } + + @Override + public String getHaving() { + return having; + } + + public AbstractSQLConfig setHaving(String... conditions) { + return setHaving(StringUtil.getString(conditions)); + } + + @Override + public AbstractSQLConfig setHaving(String having) { + this.having = having; + return this; + } + + /** + * TODO @having 改为默认 | 或连接,且支持 @having: { "key1>": 1, "key{}": "length(key2)>0", "@combine": "key1,key2" } + * + * @return HAVING conditoin0 AND condition1 OR condition2 ... + */ + @JSONField(serialize = false) + 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()); + } + + c = ((AbstractSQLConfig) cfg).getHavingString(false); + if (StringUtil.isEmpty(c, true) == false) { + joinHaving += (first ? "" : ", ") + c; + first = false; + } + + } + } + + String[] keys = StringUtil.split(getHaving(), ";"); + if (keys == null || keys.length <= 0) { + return StringUtil.isEmpty(joinHaving, true) ? "" : (hasPrefix ? " HAVING " : "") + joinHaving; + } + + String quote = getQuote(); + String tableAlias = getAliasWithQuote(); + + List raw = getRaw(); + boolean containRaw = raw != null && raw.contains(KEY_HAVING); + + String expression; + String method; + //暂时不允许 String prefix; + String suffix; + + //fun0(arg0,arg1,...);fun1(arg0,arg1,...) + for (int i = 0; i < keys.length; i++) { + + //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()); + } + } + + if (expression.length() > 50) { + throw new UnsupportedOperationException("@having:value 的 value 中字符串 " + expression + " 不合法!" + + "不允许传超过 50 个字符的函数或表达式!请用 @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 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) { + throw new IllegalArgumentException("字符 " + method + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 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 + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); + } + + 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() ? tableAlias + "." : "") + origin; + } + } + + keys[i] = method + "(" + StringUtil.getString(ckeys) + ")" + suffix; + } + + //TODO 支持 OR, NOT 参考 @combine:"&key0,|key1,!key2" + return (hasPrefix ? " HAVING " : "") + StringUtil.concat(StringUtil.getString(keys, AND), joinHaving, AND); + } + + @Override + public String getOrder() { + return order; + } + + public AbstractSQLConfig setOrder(String... conditions) { + return setOrder(StringUtil.getString(conditions)); + } + + @Override + public AbstractSQLConfig setOrder(String order) { + this.order = order; + return this; + } + + @JSONField(serialize = false) + 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()); + } + + c = ((AbstractSQLConfig) cfg).getOrderString(false); + if (StringUtil.isEmpty(c, true) == false) { + joinOrder += (first ? "" : ", ") + c; + first = false; + } + + } + } + + + String order = StringUtil.getTrimedString(getOrder()); + // SELECT * FROM sys.Moment ORDER BY userId ASC, rand(); 前面的 userId ASC 和后面的 rand() 都有效 + // if ("rand()".equals(order)) { + // return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(order, joinOrder, ", "); + // } + + if (getCount() > 0 && (isOracle() || isSQLServer() || isDb2())) { // Oracle, SQL Server, DB2 的 OFFSET 必须加 ORDER BY + + // String[] ss = StringUtil.split(order); + if (StringUtil.isEmpty(order, true)) { //SQL Server 子查询内必须指定 OFFSET 才能用 ORDER BY + String idKey = getIdKey(); + if (StringUtil.isEmpty(idKey, true)) { + idKey = "id"; //ORDER BY NULL 不行,SQL Server 会报错,必须要有排序,才能使用 OFFSET FETCH,如果没有 idKey,请求中指定 @order 即可 + } + order = idKey; //让数据库调控默认升序还是降序 + "+"; + } + + //不用这么全面,毕竟没有语法问题还浪费性能,如果有其它问题,让前端传的 JSON 直接加上 @order 来解决 + // boolean contains = false; + // if (ss != null) { + // for (String s : ss) { + // if (s != null && s.startsWith(idKey)) { + // s = s.substring(idKey.length()); + // if ("+".equals(s) || "-".equals(s)) {// || " ASC ".equals(s) || " DESC ".equals(s)) { + // contains = true; + // break; + // } + // } + // } + // } + + // if (contains == false) { + // order = (ss == null || ss.length <= 0 ? "" : order + ",") + idKey + "+"; + // } + } + + + String[] keys = StringUtil.split(order); + if (keys == null || keys.length <= 0) { + return StringUtil.isEmpty(joinOrder, true) ? "" : (hasPrefix ? " ORDER BY " : "") + joinOrder; + } + + for (int i = 0; i < keys.length; i++) { + String item = keys[i]; + if ("rand()".equals(item)) { + continue; + } + + int index = item.endsWith("+") ? item.length() - 1 : -1; //StringUtil.split返回数组中,子项不会有null + String sort; + if (index < 0) { + index = item.endsWith("-") ? item.length() - 1 : -1; + sort = index <= 0 ? "" : " DESC "; + } else { + sort = " ASC "; + } + + String origin = index < 0 ? item : item.substring(0, index); + + if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! + //这里既不对origin trim,也不对 ASC/DESC ignoreCase,希望前端严格传没有任何空格的字符串过来,减少传输数据量,节约服务器性能 + if (StringUtil.isName(origin) == false) { + throw new IllegalArgumentException("预编译模式下 @order:value 中 " + item + " 不合法! value 里面用 , 分割的" + + "每一项必须是 随机函数 rand() 或 column+ / column- 且其中 column 必须是 1 个单词!并且不要有多余的空格!"); + } + } + + keys[i] = getKey(origin) + sort; + } + + return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinOrder, ", "); + } + + @Override + public List getRaw() { + return raw; + } + + @Override + public SQLConfig setRaw(List raw) { + this.raw = raw; + return this; + } + + /** + * 获取原始 SQL 片段 + * + * @param key + * @param value + * @return + * @throws Exception + */ + @Override + public String getRawSQL(String key, Object value) throws Exception { + List rawList = getRaw(); + boolean containRaw = rawList != null && rawList.contains(key); + if (containRaw && value instanceof String == false) { + throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" + + "对应的 " + key + ":value 中 value 类型只能为 String!"); + } + + 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)) { + return (String) value; + } + } + + return rawSQL; + } + + + @Override + public List getJson() { + return json; + } + + @Override + public AbstractSQLConfig setJson(List json) { + this.json = json; + return this; + } + + + @Override + public Subquery getFrom() { + return from; + } + + @Override + public AbstractSQLConfig setFrom(Subquery from) { + this.from = from; + return this; + } + + @Override + public List getColumn() { + return column; + } + + @Override + public AbstractSQLConfig setColumn(List column) { + this.column = column; + return this; + } + + @JSONField(serialize = false) + public String getColumnString() throws Exception { + return getColumnString(false); + } + + @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) { + String origin; + String alias; + int index; + + List raw = getRaw(); + boolean containRaw = raw != null && raw.contains(KEY_COLUMN); + + for (String c : column) { + if (containRaw) { + // 由于 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); + continue; + } + } + index = c.lastIndexOf(":"); //StringUtil.split返回数组中,子项不会有null + origin = index < 0 ? c : c.substring(0, index); + alias = index < 0 ? null : c.substring(index + 1); + + if (alias != null && StringUtil.isName(alias) == false) { + throw new IllegalArgumentException("HEAD请求: 字符 " + alias + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" + + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); + } + + if (StringUtil.isName(origin) == false) { + int start = origin.indexOf("("); + if (start < 0 || origin.lastIndexOf(")") <= start) { + 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个单词!并且不要有多余的空格!"); + } + } + } + } + + return SQL.count(column != null && column.size() == 1 && StringUtil.isName(column.get(0)) ? getKey(column.get(0)) : "*"); + case POST: + if (column == null || column.isEmpty()) { + throw new IllegalArgumentException("POST 请求必须在Table内设置要保存的 key:value !"); + } + + String s = ""; + boolean pfirst = true; + for (String c : column) { + if (isPrepared() && StringUtil.isName(c) == false) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! + throw new IllegalArgumentException("POST请求: 每一个 key:value 中的key都必须是1个单词!"); + } + s += ((pfirst ? "" : ",") + getKey(c)); + + pfirst = false; + } + + return "(" + s + ")"; + case GET: + case GETS: + boolean isQuery = RequestMethod.isQueryMethod(method); //TODO 这个有啥用?上面应是 getMethod 的值 GET 和 GETS 了。 + String joinColumn = ""; + if (isQuery && joinList != null) { + SQLConfig ecfg; + SQLConfig cfg; + String c; + boolean first = true; + for (Join j : joinList) { + 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; + first = false; + } + + inSQLJoin = true; + } + } + + String tableAlias = getAliasWithQuote(); + + // String c = StringUtil.getString(column); //id,name;json_length(contactIdList):contactCount;... + + String[] keys = column == null ? null : column.toArray(new String[]{}); //StringUtil.split(c, ";"); + if (keys == null || keys.length <= 0) { + + boolean noColumn = column != null && inSQLJoin; + String mc = isKeyPrefix() == false ? (noColumn ? "" : "*") : (noColumn ? "" : tableAlias + ".*"); + + return StringUtil.concat(mc, joinColumn, ", ", true); + } + + + 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]; + + if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 + if ("".equals(RAW_MAP.get(expression)) || RAW_MAP.containsValue(expression)) { // newSQLConfig 提前处理好的 + continue; + } + + // 简单点, 后台配置就带上 AS + // int index = expression.lastIndexOf(":"); + // String alias = expression.substring(index+1); + // boolean hasAlias = StringUtil.isName(alias); + // String pre = index > 0 && hasAlias ? expression.substring(0, index) : expression; + // if (RAW_MAP.containsValue(pre) || "".equals(RAW_MAP.get(pre))) { // newSQLConfig 提前处理好的 + // expression = pre + (hasAlias ? " AS " + alias : ""); + // continue; + // } + } + + if (expression.length() > 50) { + throw new UnsupportedOperationException("@column:value 的 value 中字符串 " + expression + " 不合法!" + + "不允许传超过 50 个字符的函数或表达式!请用 @raw 简化传参!"); + } + + + 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,...) 这种格式!"); + } + + method = expression.substring(0, start); + boolean distinct = i <= 0 && method.startsWith(PREFFIX_DISTINCT); + String fun = distinct ? method.substring(PREFFIX_DISTINCT.length()) : method; + + 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 + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); + } + } + + } + + boolean isColumn = start < 0; + + String[] ckeys = StringUtil.split(isColumn ? expression : expression.substring(start + 1, end)); + String quote = getQuote(); + + // 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); + + distinct = j <= 0 && origin.startsWith(PREFFIX_DISTINCT); + if (distinct) { + origin = origin.substring(PREFFIX_DISTINCT.length()); + } + + 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 { + // 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 个空格!其它情况不允许空格!"); + } + } + } + + //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 (isName && isKeyPrefix()) { + ckeys[j] = tableAlias + "." + origin; + // if (isColumn) { + // ckeys[j] += " AS " + quote + (isMain() ? "" : tableAlias + ".") + (StringUtil.isEmpty(alias, true) ? origin : alias) + quote; + // } + if (isColumn && StringUtil.isEmpty(alias, true) == false) { + ckeys[j] += " AS " + quote + alias + quote; + } + } else { + ckeys[j] = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); + } + + if (distinct) { + ckeys[j] = PREFFIX_DISTINCT + ckeys[j]; + } + } + // } + + } + + 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); + + 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 + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); + } + + 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); + // } + } + + } + + 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!" + ); + } + } + + + @Override + public List> getValues() { + return values; + } + + @JSONField(serialize = false) + public String getValuesString() { + String s = ""; + if (values != null && values.size() > 0) { + Object[] items = new Object[values.size()]; + List vs; + for (int i = 0; i < values.size(); i++) { + vs = values.get(i); + if (vs == null) { + continue; + } + + items[i] = "("; + for (int j = 0; j < vs.size(); j++) { + items[i] += ((j <= 0 ? "" : ",") + getValue(vs.get(j))); + } + items[i] += ")"; + } + s = StringUtil.getString(items); + } + return s; + } + + @Override + public AbstractSQLConfig setValues(List> valuess) { + this.values = valuess; + return this; + } + + @Override + public Map getContent() { + return content; + } + + @Override + public AbstractSQLConfig setContent(Map content) { + this.content = content; + return this; + } + + @Override + public int getCount() { + return count; + } + + @Override + public AbstractSQLConfig setCount(int count) { + this.count = count; + return this; + } + + @Override + public int getPage() { + return page; + } + + @Override + public AbstractSQLConfig setPage(int page) { + this.page = page; + return this; + } + + @Override + public int getPosition() { + return position; + } + + @Override + public AbstractSQLConfig setPosition(int position) { + this.position = position; + return this; + } + + @Override + public int getQuery() { + return query; + } + + @Override + public AbstractSQLConfig setQuery(int query) { + this.query = query; + return this; + } + + @Override + public int getType() { + return type; + } + + @Override + public AbstractSQLConfig setType(int type) { + this.type = type; + return this; + } + + @Override + public int getCache() { + return cache; + } + + @Override + public AbstractSQLConfig setCache(int cache) { + this.cache = cache; + return this; + } + + public AbstractSQLConfig setCache(String cache) { + return setCache(getCache(cache)); + } + + public static int getCache(String cache) { + int cache2; + if (cache == null) { + cache2 = JSONRequest.CACHE_ALL; + } else { + // if (isSubquery) { + // throw new IllegalArgumentException("子查询内不支持传 " + JSONRequest.KEY_CACHE + "!"); + // } + + switch (cache) { + case "0": + case JSONRequest.CACHE_ALL_STRING: + cache2 = JSONRequest.CACHE_ALL; + break; + case "1": + case JSONRequest.CACHE_ROM_STRING: + cache2 = JSONRequest.CACHE_ROM; + break; + case "2": + case JSONRequest.CACHE_RAM_STRING: + cache2 = JSONRequest.CACHE_RAM; + break; + default: + throw new IllegalArgumentException(JSONRequest.KEY_CACHE + ":value 中 value 的值不合法!必须在 [0,1,2] 或 [ALL, ROM, RAM] 内 !"); + } + } + return cache2; + } + + @Override + public boolean isExplain() { + return explain; + } + + @Override + public AbstractSQLConfig setExplain(boolean explain) { + this.explain = explain; + return this; + } + + @Override + public List getJoinList() { + return joinList; + } + + @Override + public SQLConfig setJoinList(List joinList) { + this.joinList = joinList; + return this; + } + + @Override + public boolean hasJoin() { + return joinList != null && joinList.isEmpty() == false; + } + + + @Override + public boolean isTest() { + return test; + } + + @Override + public AbstractSQLConfig setTest(boolean test) { + this.test = test; + return this; + } + + /** + * 获取初始位置offset + * + * @return + */ + @JSONField(serialize = false) + public int getOffset() { + return getOffset(getPage(), getCount()); + } + + /** + * 获取初始位置offset + * + * @param page + * @param count + * @return + */ + public static int getOffset(int page, int count) { + return page * count; + } + + /** + * 获取限制数量 + * + * @return + */ + @JSONField(serialize = false) + public String getLimitString() { + if (count <= 0 || RequestMethod.isHeadMethod(getMethod(), true)) { + return ""; + } + return getLimitString(getPage(), getCount(), isOracle() || isSQLServer() || isDb2(), isOracle()); + } + + /** + * 获取限制数量 + * + * @param limit + * @return + */ + public static String getLimitString(int page, int count, boolean isTSQL, boolean isOracle) { + 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 " LIMIT " + count + (offset <= 0 ? "" : " OFFSET " + offset); // DELETE, UPDATE 不支持 OFFSET + } + + //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + @Override + public Map getWhere() { + return where; + } + + @Override + public AbstractSQLConfig setWhere(Map where) { + this.where = where; + return this; + } + + @NotNull + @Override + public Map> getCombine() { + List andList = combine == null ? null : combine.get("&"); + if (andList == null) { + andList = where == null ? new ArrayList() : new ArrayList(where.keySet()); + if (combine == null) { + combine = new HashMap<>(); + } + combine.put("&", andList); + } + return combine; + } + + @Override + public AbstractSQLConfig setCombine(Map> combine) { + this.combine = combine; + return this; + } + + /** + * noFunctionChar = false + * + * @param key + * @return + */ + @JSONField(serialize = false) + @Override + public Object getWhere(String key) { + return getWhere(key, false); + } + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 + + /** + * @param key - the key passed in + * @param exactMatch - whether it is exact match + * @return

use entrySet+getValue() to replace keySet+get() to enhance efficiency

+ */ + @JSONField(serialize = false) + @Override + public Object getWhere(String key, boolean exactMatch) { + if (exactMatch) { + return where == null ? null : where.get(key); + } + + 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(); + } + } + } + } + return null; + } + + @Override + public AbstractSQLConfig putWhere(String key, Object value, boolean prior) { + if (key != null) { + if (where == null) { + where = new LinkedHashMap(); + } + if (value == null) { + where.remove(key); + } else { + where.put(key, value); + } + + combine = getCombine(); + List andList = combine.get("&"); + if (value == null) { + if (andList != null) { + andList.remove(key); + } + } else if (andList == null || andList.contains(key) == false) { + int i = 0; + if (andList == null) { + andList = new ArrayList<>(); + } else if (prior && andList.isEmpty() == false) { + + String idKey = getIdKey(); + String idInKey = idKey + "{}"; + String userIdKey = getUserIdKey(); + String userIdInKey = userIdKey + "{}"; + + if (andList.contains(idKey)) { + i++; + } + if (andList.contains(idInKey)) { + i++; + } + if (andList.contains(userIdKey)) { + i++; + } + if (andList.contains(userIdInKey)) { + i++; + } + } + + if (prior) { + andList.add(i, key); //userId的优先级不能比id高 0, key); + } else { + andList.add(key); //AbstractSQLExecutor.onPutColumn里getSQL,要保证缓存的SQL和查询的SQL里 where 的 key:value 顺序一致 + } + } + combine.put("&", andList); + } + return this; + } + + /** + * 获取WHERE + * + * @return + * @throws Exception + */ + @JSONField(serialize = false) + @Override + public String getWhereString(boolean hasPrefix) throws Exception { + return getWhereString(hasPrefix, getMethod(), getWhere(), getCombine(), getJoinList(), !isTest()); + } + + /** + * 获取WHERE + * + * @param method + * @param where + * @return + * @throws Exception + */ + @JSONField(serialize = false) + public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, Map> combine, List joinList, boolean verifyName) throws Exception { + Set>> combineSet = combine == null ? null : combine.entrySet(); + if (combineSet == null || combineSet.isEmpty()) { + Log.w(TAG, "getWhereString combineSet == null || combineSet.isEmpty() >> return \"\";"); + return ""; + } + + List keyList; + + String whereString = ""; + + boolean isCombineFirst = true; + int logic; + + boolean isItemFirst; + String c; + String cs; + + for (Entry> ce : combineSet) { + keyList = ce == null ? null : ce.getValue(); + if (keyList == null || keyList.isEmpty()) { + continue; + } + + if ("|".equals(ce.getKey())) { + logic = Logic.TYPE_OR; + } else if ("!".equals(ce.getKey())) { + logic = Logic.TYPE_NOT; + } else { + logic = Logic.TYPE_AND; + } + + + isItemFirst = true; + cs = ""; + for (String key : keyList) { + c = getWhereItem(key, where.get(key), method, verifyName); + + if (StringUtil.isEmpty(c, true)) {//避免SQL条件连接错误 + continue; + } + + cs += (isItemFirst ? "" : (Logic.isAnd(logic) ? AND : OR)) + "(" + c + ")"; + + isItemFirst = false; + } + + if (StringUtil.isEmpty(cs, true)) {//避免SQL条件连接错误 + continue; + } + + whereString += (isCombineFirst ? "" : AND) + (Logic.isNot(logic) ? NOT : "") + " ( " + cs + " ) "; + isCombineFirst = false; + } + + + 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) + 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 s = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; + + if (s.isEmpty() && RequestMethod.isQueryMethod(method) == false) { + throw new UnsupportedOperationException("写操作请求必须带条件!!!"); + } + + return s; + } + + /** + * @param key + * @param value + * @param method + * @param verifyName + * @return + * @throws Exception + */ + 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;"); + return null; + } + if (key.endsWith("@")) {//引用 + // key = key.substring(0, key.lastIndexOf("@")); + throw new IllegalArgumentException(TAG + ".getWhereItem: 字符 " + key + " 不合法!"); + } + + // 原始 SQL 片段 + String rawSQL = getRawSQL(key, value); + + int keyType; + if (key.endsWith("$")) { + keyType = 1; + } else if (key.endsWith("~")) { + keyType = key.charAt(key.length() - 2) == '*' ? -2 : 2; //FIXME StringIndexOutOfBoundsException + } else if (key.endsWith("%")) { + keyType = 3; + } else if (key.endsWith("{}")) { + keyType = 4; + } else if (key.endsWith("}{")) { + keyType = 5; + } else if (key.endsWith("<>")) { + keyType = 6; + } else if (key.endsWith(">=")) { + keyType = 7; + } else if (key.endsWith("<=")) { + keyType = 8; + } else if (key.endsWith(">")) { + keyType = 9; + } else if (key.endsWith("<")) { + keyType = 10; + } else { // else绝对不能省,避免再次踩坑! keyType = 0; 写在for循环外面都没注意! + keyType = 0; + } + + key = getRealKey(method, key, false, true, verifyName); + + switch (keyType) { + case 1: + return getSearchString(key, value, rawSQL); + case -2: + case 2: + return getRegExpString(key, value, keyType < 0, rawSQL); + case 3: + return getBetweenString(key, value, rawSQL); + case 4: + return getRangeString(key, value, rawSQL); + case 5: + return getExistsString(key, value, rawSQL); + case 6: + return getContainString(key, value, rawSQL); + case 7: + return getCompareString(key, value, ">=", rawSQL); + case 8: + return getCompareString(key, value, "<=", rawSQL); + case 9: + return getCompareString(key, value, ">", rawSQL); + case 10: + return getCompareString(key, value, "<", rawSQL); + default: // TODO MySQL JSON类型的字段对比 key='[]' 会无结果! key LIKE '[1, 2, 3]' //TODO MySQL , 后面有空格! + return getEqualString(key, 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) { + throw new IllegalArgumentException(key + ":value 中value不合法!非PUT请求只支持 [Boolean, Number, String] 内的类型 !"); + } + + boolean not = key.endsWith("!"); // & | 没有任何意义,写法多了不好控制 + if (not) { + key = key.substring(0, key.length() - 1); + } + if (StringUtil.isName(key) == false) { + throw new IllegalArgumentException(key + ":value 中key不合法!不支持 ! 以外的逻辑符 !"); + } + + return getKey(key) + (not ? " != " : " = ") + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(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] 内的类型 !"); + } + if (StringUtil.isName(key) == false) { + throw new IllegalArgumentException(key + type + ":value 中key不合法!比较运算 [>, <, >=, <=] 不支持 [&, !, |] 中任何逻辑运算符 !"); + } + + return getKey(key) + " " + type + " " + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(value))); + } + + public String getKey(String key) { + if (isTest()) { + if (key.contains("'")) { // || key.contains("#") || key.contains("--")) { + throw new IllegalArgumentException("参数 " + key + " 不合法!key 中不允许有单引号 ' !"); + } + return getSQLValue(key).toString(); + } + + return getSQLKey(key); + } + + public String getSQLKey(String key) { + String q = getQuote(); + return (isKeyPrefix() ? getAliasWithQuote() + "." : "") + q + key + q; + } + + /** + * 使用prepareStatement预编译,值为 ? ,后续动态set进去 + */ + private List preparedValueList = new ArrayList<>(); + + private Object getValue(@NotNull Object value) { + if (isPrepared()) { + preparedValueList.add(value); + return "?"; + } + return getSQLValue(value); + } + + public Object getSQLValue(@NotNull Object value) { + // return (value instanceof Number || value instanceof Boolean) && DATABASE_POSTGRESQL.equals(getDatabase()) ? value : "'" + value + "'"; + return (value instanceof Number || value instanceof Boolean) ? value : "'" + value + "'"; //MySQL 隐式转换用不了索引 + } + + @Override + public List getPreparedValueList() { + return preparedValueList; + } + + @Override + public AbstractSQLConfig setPreparedValueList(List preparedValueList) { + this.preparedValueList = preparedValueList; + return this; + } + + //$ search <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + /** + * search key match value + * + * @param in + * @return {@link #getSearchString(String, Object[], int)} + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getSearchString(String key, 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, "getSearchString key = " + key); + + JSONArray arr = newJSONArray(value); + if (arr.isEmpty()) { + return ""; + } + return getSearchString(key, arr.toArray(), logic.getType()); + } + + /** + * search key match values + * + * @param in + * @return LOGIC [ key LIKE 'values[i]' ] + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getSearchString(String key, Object[] values, int type) throws IllegalArgumentException { + if (values == null || values.length <= 0) { + return ""; + } + + String condition = ""; + 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[]!"); + } + if (((String) v).isEmpty()) { // 允许查空格 StringUtil.isEmpty((String) v, true) + 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); + } + + return getCondition(Logic.isNot(type), condition); + } + + /** + * WHERE key LIKE 'value' + * + * @param key + * @param value + * @return key LIKE 'value' + */ + @JSONField(serialize = false) + public String getLikeString(String key, Object value) { + return getKey(key) + " LIKE " + getValue(value); + } + + //$ search >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + //~ regexp <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + /** + * search key match RegExp values + * + * @param key + * @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 { + 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, "getRegExpString key = " + key); + + JSONArray arr = newJSONArray(value); + if (arr.isEmpty()) { + return ""; + } + return getRegExpString(key, arr.toArray(), logic.getType(), ignoreCase); + } + + /** + * search key match RegExp values + * + * @param key + * @param values + * @param type + * @param ignoreCase + * @return LOGIC [ key REGEXP 'values[i]' ] + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getRegExpString(String key, Object[] values, int type, boolean ignoreCase) throws IllegalArgumentException { + if (values == null || values.length <= 0) { + return ""; + } + + String condition = ""; + for (int i = 0; i < values.length; i++) { + if (values[i] instanceof String == false) { + throw new IllegalArgumentException(key + "$:value 中value的类型只能为String或String[]!"); + } + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getRegExpString(key, (String) values[i], ignoreCase); + } + + return getCondition(Logic.isNot(type), condition); + } + + /** + * WHERE key REGEXP 'value' + * + * @param key + * @param value + * @param ignoreCase + * @return key REGEXP 'value' + */ + @JSONField(serialize = false) + public String getRegExpString(String key, String value, boolean ignoreCase) { + if (isPostgreSQL()) { + return getKey(key) + " ~" + (ignoreCase ? "* " : " ") + getValue(value); + } + if (isOracle()) { + return "regexp_like(" + getKey(key) + ", " + getValue(value) + (ignoreCase ? ", 'i'" : ", 'c'") + ")"; + } + return getKey(key) + " REGEXP " + (ignoreCase ? "" : "BINARY ") + getValue(value); + } + //~ regexp >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + //% between <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + /** + * WHERE key BETWEEN 'start' AND 'end' + * + * @param key + * @param value 'start,end' + * @return LOGIC [ key BETWEEN 'start' AND 'end' ] + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getBetweenString(String key, 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, "getBetweenString key = " + key); + + JSONArray arr = newJSONArray(value); + if (arr.isEmpty()) { + return ""; + } + return getBetweenString(key, arr.toArray(), logic.getType()); + } + + /** + * WHERE key BETWEEN 'start' AND 'end' + * + * @param key + * @param value 'start,end' TODO 在 '1,2' 和 ['1,2', '3,4'] 基础上新增支持 [1, 2] 和 [[1,2], [3,4]] ? + * @return LOGIC [ key BETWEEN 'start' AND 'end' ] + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getBetweenString(String key, Object[] values, int type) throws IllegalArgumentException { + if (values == null || values.length <= 0) { + return ""; + } + + String condition = ""; + String[] vs; + for (int i = 0; i < values.length; i++) { + if (values[i] instanceof String == false) { + 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 的规则 !"); + } + + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + "(" + getBetweenString(key, (Object) vs[0], (Object) vs[1]) + ")"; + } + + return getCondition(Logic.isNot(type), condition); + } + + /** + * WHERE key BETWEEN 'start' AND 'end' + * + * @param key + * @param value 'start,end' TODO 在 '1,2' 和 ['1,2', '3,4'] 基础上新增支持 [1, 2] 和 [[1,2], [3,4]] ? + * @return key BETWEEN 'start' AND 'end' + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getBetweenString(String key, 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 的规则 !"); + } + return getKey(key) + " BETWEEN " + getValue(start) + AND + getValue(end); + } + + + //% between >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + //{} range <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + + /** + * WHERE key > 'key0' AND key <= 'key1' AND ... + * + * @param key + * @param range "condition0,condition1..." + * @return key condition0 AND key condition1 AND ... + * @throws Exception + */ + @JSONField(serialize = false) + public String getRangeString(String key, Object range, String rawSQL) throws Exception { + Log.i(TAG, "getRangeString key = " + key); + if (range == null) {//依赖的对象都没有给出有效值,这个存在无意义。如果是客户端传的,那就能在客户端确定了。 + throw new NotExistException(TAG + "getRangeString(" + key + ", " + range + + ") range == null"); + } + + Logic logic = new Logic(key); + String k = logic.getKey(); + Log.i(TAG, "getRangeString k = " + k); + + if (range instanceof List) { + if (rawSQL != null) { + throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + "{} 不合法!" + + "Raw SQL 不支持 key{}:[] 这种键值对!"); + } + + if (logic.isOr() || logic.isNot()) { + List l = (List) range; + if (logic.isNot() && l.isEmpty()) { + return ""; // key!{}: [] 这个条件无效,加到 SQL 语句中 key IN() 会报错,getInString 里不好处理 + } + return getKey(k) + getInString(k, l.toArray(), logic.isNot()); + } + throw new IllegalArgumentException(key + "{}\":[] 中 {} 前面的逻辑运算符错误!只能用'|','!'中的一种 !"); + } else if (range instanceof String) {//非Number类型需要客户端拼接成 < 'value0', >= 'value1'这种 + String condition = ""; + String[] cs = rawSQL != null ? null : StringUtil.split((String) range, false); + + if (rawSQL != null) { + int index = rawSQL == null ? -1 : 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; + 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); + } 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 + " !不允许连续减号 -- !不允许空格!"); + } + + 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 则按原来位置拼接 + } + } + if (condition.isEmpty()) { + return ""; + } + + return getCondition(logic.isNot(), condition); + } 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() + + "!range 只能是 用','分隔条件的字符串 或者 可取选项JSONArray!"); + } + + /** + * WHERE key IN ('key0', 'key1', ... ) + * + * @param in + * @return IN ('key0', 'key1', ... ) + * @throws NotExistException + */ + @JSONField(serialize = false) + public String getInString(String key, 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])); + } + } + if (condition.isEmpty()) {//条件如果存在必须执行,不能忽略。条件为空会导致出错,又很难保证条件不为空(@:条件),所以还是这样好 + throw new NotExistException(TAG + ".getInString(" + key + ", [], " + not + + ") >> condition.isEmpty() >> IN()"); + } + return (not ? NOT : "") + " IN (" + condition + ")"; + } + //{} range >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + //}{ exists <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + /** + * WHERE EXISTS subquery + * 如果合并到 getRangeString,一方面支持不了 [1,2,2] 和 ">1" (转成 EXISTS(SELECT IN ) 需要static newSQLConfig,但它不能传入子类实例,除非不是 static),另一方面多了子查询临时表性能会比 IN 差 + * + * @param key + * @param value + * @return EXISTS ALL(SELECT ...) + * @throws NotExistException + */ + @JSONField(serialize = false) + public String getExistsString(String key, Object value, String rawSQL) throws Exception { + if (rawSQL != null) { + throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key}{ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); + } + if (value == null) { + return ""; + } + if (value instanceof Subquery == false) { + throw new IllegalArgumentException(key + "}{:subquery 类型为" + value.getClass().getSimpleName() + + "!subquery 只能是 子查询JSONObejct!"); + } + + Logic logic = new Logic(key); + key = logic.getKey(); + Log.i(TAG, "getExistsString key = " + key); + + return (logic.isNot() ? NOT : "") + " EXISTS " + getSubqueryString((Subquery) value); + } + //}{ exists >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + //<> contain <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + /** + * WHERE key contains value + * + * @param key + * @param value + * @return {@link #getContainString(String, Object[], int)} + * @throws NotExistException + */ + @JSONField(serialize = false) + public String getContainString(String key, 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); + + return getContainString(key, newJSONArray(value).toArray(), logic.getType()); + } + + /** + * WHERE key contains childs + * + * @param key + * @param childs null ? "" : (empty ? no child : contains childs) + * @param type |, &, ! + * @return LOGIC [ ( key LIKE '[" + childs[i] + "]' OR key LIKE '[" + childs[i] + ", %' + * OR key LIKE '%, " + childs[i] + ", %' OR key LIKE '%, " + childs[i] + "]' ) ] + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getContainString(String key, 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!"); + } + + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)); + if (isPostgreSQL()) { + condition += (getKey(key) + " @> " + getValue(newJSONArray(c))); //operator does not exist: jsonb @> character varying "[" + c + "]"); + } else if (isOracle()) { + condition += ("json_textcontains(" + getKey(key) + ", '$', " + getValue(c.toString()) + ")"); + } else { + boolean isNum = c instanceof Number; + String v = (isNum ? "" : "\"") + childs[i] + (isNum ? "" : "\""); + condition += ("json_contains(" + getKey(key) + ", " + getValue(v) + ")"); + } + } + } + if (condition.isEmpty()) { + condition = (getKey(key) + SQL.isNull(true) + OR + getLikeString(key, "[]")); // key = '[]' 无结果! + } else { + condition = (getKey(key) + SQL.isNull(false) + AND + "(" + condition + ")"); + } + } + if (condition.isEmpty()) { + return ""; + } + return getCondition(not, condition); + } + //<> contain >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + //key@:{} Subquery <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + @Override + public String getSubqueryString(Subquery subquery) throws Exception { + String range = subquery.getRange(); + SQLConfig cfg = subquery.getConfig(); + + cfg.setPreparedValueList(new ArrayList<>()); + String sql = (range == null || range.isEmpty() ? "" : range) + "(" + cfg.getSQL(isPrepared()) + ") "; + + preparedValueList.addAll(cfg.getPreparedValueList()); + + return sql; + } + + //key@:{} Subquery >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + /** + * 拼接条件 + * + * @param not + * @param condition + * @return + */ + private static String getCondition(boolean not, String condition) { + return not ? NOT + "(" + condition + ")" : condition; + } + + + /** + * 转为JSONArray + * + * @param tv + * @return + */ + @NotNull + public static JSONArray newJSONArray(Object obj) { + JSONArray array = new JSONArray(); + if (obj != null) { + if (obj instanceof Collection) { + array.addAll((Collection) obj); + } else { + array.add(obj); + } + } + return array; + } + + //WHERE >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + //SET <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + /** + * 获取SET + * + * @return + * @throws Exception + */ + @JSONField(serialize = false) + public String getSetString() throws Exception { + return getSetString(getMethod(), getContent(), !isTest()); + } + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 + + /** + * 获取SET + * + * @param method -the method used + * @param content -the content map + * @return + * @throws Exception

use entrySet+getValue() to replace keySet+get() to enhance efficiency

+ */ + @JSONField(serialize = false) + public String getSetString(RequestMethod method, Map content, boolean verifyName) throws Exception { + Set set = content == null ? null : content.keySet(); + String setString = ""; + + if (set != null && set.size() > 0) { + boolean isFirst = true; + int keyType;// 0 - =; 1 - +, 2 - - + Object value; + + String idKey = getIdKey(); + for (Entry entry : content.entrySet()) { + String key = entry.getKey(); + //避免筛选到全部 value = key == null ? null : content.get(key); + if (key == null || idKey.equals(key)) { + continue; + } + + if (key.endsWith("+")) { + keyType = 1; + } else if (key.endsWith("-")) { + keyType = 2; + } else { + keyType = 0; //注意重置类型,不然不该加减的字段会跟着加减 + } + value = entry.getValue(); + key = getRealKey(method, key, false, true, verifyName); + + setString += (isFirst ? "" : ", ") + (getKey(key) + " = " + (keyType == 1 ? getAddString(key, value) : (keyType == 2 + ? getRemoveString(key, value) : getValue(value)))); + + isFirst = false; + } + } + + if (setString.isEmpty()) { + throw new IllegalArgumentException("PUT 请求必须在Table内设置要修改的 key:value !"); + } + return " SET " + setString; + } + + /** + * SET key = concat(key, 'value') + * + * @param key + * @param value + * @return concat(key, ' value ') + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getAddString(String key, Object value) throws IllegalArgumentException { + if (value instanceof Number) { + return getKey(key) + " + " + value; + } + if (value instanceof String) { + return SQL.concat(getKey(key), (String) getValue(value)); + } + throw new IllegalArgumentException(key + "+ 对应的值 " + value + " 不是Number,String,Array中的任何一种!"); + } + + /** + * SET key = replace(key, 'value', '') + * + * @param key + * @param value + * @return REPLACE (key, 'value', '') + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getRemoveString(String key, Object value) throws IllegalArgumentException { + if (value instanceof Number) { + return getKey(key) + " - " + value; + } + if (value instanceof String) { + return SQL.replace(getKey(key), (String) getValue(value), "''");// " replace(" + key + ", '" + value + "', '') "; + } + throw new IllegalArgumentException(key + "- 对应的值 " + value + " 不是Number,String,Array中的任何一种!"); + } + //SET >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + /** + * @return + * @throws Exception + */ + @JSONField(serialize = false) + @Override + public String getSQL(boolean prepared) throws Exception { + return getSQL(this.setPrepared(prepared)); + } + + /** + * @param config + * @return + * @throws Exception + */ + public static String getSQL(AbstractSQLConfig config) throws Exception { + if (config == null) { + Log.i(TAG, "getSQL config == null >> return null;"); + return null; + } + + //TODO procedure 改为 List procedureList; behind : true; function: callFunction(); String key; ... + // for (...) { Call procedure1();\n SQL \n; Call procedure2(); ... } + // 貌似不需要,因为 ObjecParser 里就已经处理的顺序等,只是这里要解决下 Schema 问题。 + + String sch = config.getSQLSchema(); + if (StringUtil.isNotEmpty(config.getProcedure(), true)) { + String q = config.getQuote(); + return "CALL " + q + sch + q + "." + config.getProcedure(); + } + + String tablePath = config.getTablePath(); + if (StringUtil.isNotEmpty(tablePath, true) == false) { + Log.i(TAG, "getSQL StringUtil.isNotEmpty(tablePath, true) == false >> return null;"); + return null; + } + + switch (config.getMethod()) { + 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() : ""); + case DELETE: + 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)) { + 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(); + } + + config.setPreparedValueList(new ArrayList()); + 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 " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString(); + } + } + + /** + * 获取条件SQL字符串 + * + * @param page + * @param column + * @param table + * @param where + * @return + * @throws Exception + */ + private static String getConditionString(String column, String table, AbstractSQLConfig config) throws Exception { + String where = config.getWhereString(true); + + Subquery from = config.getFrom(); + if (from != null) { + 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(); + + //no need to optimize + // if (config.getPage() <= 0 || ID.equals(column.trim())) { + return condition; // config.isOracle() ? condition : condition + config.getLimitString(); + // } + // + // + // //order: id+ -> id >= idOfStartIndex; id- -> id <= idOfStartIndex <<<<<<<<<<<<<<<<<<< + // String order = StringUtil.getNoBlankString(config.getOrder()); + // List orderList = order.isEmpty() ? null : Arrays.asList(StringUtil.split(order)); + // + // int type = 0; + // if (BaseModel.isEmpty(orderList) || BaseModel.isContain(orderList, ID+"+")) { + // type = 1; + // } + // else if (BaseModel.isContain(orderList, ID+"-")) { + // type = 2; + // } + // + // if (type > 0) { + // return condition.replace("WHERE", + // "WHERE id " + (type == 1 ? ">=" : "<=") + " (SELECT id FROM " + table + // + where + " ORDER BY id " + (type == 1 ? "ASC" : "DESC") + " LIMIT " + config.getOffset() + ", 1) AND" + // ) + // + " LIMIT " + config.getCount(); //子查询起始id不一定准确,只能作为最小可能! ;// + // } + // //order: id+ -> id >= idOfStartIndex; id- -> id <= idOfStartIndex >>>>>>>>>>>>>>>>>> + // + // + // //结果错误!SELECT * FROM User AS t0 INNER JOIN + // (SELECT id FROM User ORDER BY date ASC LIMIT 20, 10) AS t1 ON t0.id = t1.id + // //common case, inner join + // condition += config.getLimitString(); + // return table + " AS t0 INNER JOIN (SELECT id FROM " + condition + ") AS t1 ON t0.id = t1.id"; + } + + + private boolean keyPrefix; + + @Override + public boolean isKeyPrefix() { + return keyPrefix; + } + + @Override + public AbstractSQLConfig setKeyPrefix(boolean keyPrefix) { + this.keyPrefix = keyPrefix; + return this; + } + + + public String getJoinString() throws Exception { + String joinOns = ""; + + if (joinList != null) { + String quote = getQuote(); + 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) + continue; + } + String type = j.getJoinType(); + + //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(); + jc.setPrepared(isPrepared()); + + jt = StringUtil.isEmpty(jc.getAlias(), true) ? jc.getTable() : jc.getAlias(); + tt = j.getTargetTable(); + + //如果要强制小写,则可在子类重写这个方法再 toLowerCase + // if (DATABASE_POSTGRESQL.equals(getDatabase())) { + // jt = jt.toLowerCase(); + // tn = tn.toLowerCase(); + // } + + switch (type) { + //前面已跳过 case "@": // APP JOIN + // continue; + + case "*": // CROSS JOIN + onGetCrossJoinString(j); + case "<": // LEFT JOIN + 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; + jc.setMain(false).setKeyPrefix(true); + + pvl.addAll(jc.getPreparedValueList()); + changed = true; + 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 + sql = " INNER JOIN " + jc.getTablePath() + + " ON " + quote + jt + quote + "." + quote + j.getKey() + quote + " = " + quote + tt + quote + "." + quote + j.getTargetKey() + quote; + break; + default: + throw new UnsupportedOperationException( + "join:value 中 value 里的 " + jt + "/" + j.getPath() + + "错误!不支持 " + jt + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" + + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" + ); + } + + joinOns += " \n " + sql; + } + + + if (changed) { + pvl.addAll(preparedValueList); + preparedValueList = pvl; + } + + } + + return joinOns; + } + + protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { + throw new UnsupportedOperationException("已禁用 * CROSS JOIN !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); + } + + /** + * 新建SQL配置 + * + * @param table + * @param request + * @param joinList + * @param isProcedure + * @param callback + * @return + * @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!"); + } + + boolean explain = request.getBooleanValue(KEY_EXPLAIN); + if (explain && Log.DEBUG == false) { //不在 config.setExplain 抛异常,一方面处理更早性能更好,另一方面为了内部调用可以绕过这个限制 + throw new UnsupportedOperationException("DEBUG 模式下不允许传 " + KEY_EXPLAIN + " !"); + } + + String database = request.getString(KEY_DATABASE); + if (StringUtil.isEmpty(database, false) == false && DATABASE_LIST.contains(database) == false) { + throw new UnsupportedDataTypeException("@database:value 中 value 错误,只能是 [" + StringUtil.getString(DATABASE_LIST.toArray()) + "] 中的一种!"); + } + + String schema = request.getString(KEY_SCHEMA); + String datasource = request.getString(KEY_DATASOURCE); + + SQLConfig config = callback.getSQLConfig(method, database, schema, table); + config.setAlias(alias); + + config.setDatabase(database); //不删,后面表对象还要用的,必须放在 parseJoin 前 + config.setSchema(schema); //不删,后面表对象还要用的 + config.setDatasource(datasource); //不删,后面表对象还要用的 + + if (isProcedure) { + return config; + } + + config = parseJoin(method, config, joinList, callback); //放后面会导致主表是空对象时 joinList 未解析 + + if (request.isEmpty()) { // User:{} 这种空内容在查询时也有效 + return config; //request.remove(key); 前都可以直接return,之后必须保证 put 回去 + } + + + String idKey = callback.getIdKey(database, schema, table); + String idInKey = idKey + "{}"; + String userIdKey = callback.getUserIdKey(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); + 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); + if ((d instanceof Number && ((Number) d).longValue() > 0) || (d instanceof String && StringUtil.isNotEmpty(d, true))) { + newIdIn.add(d); + } + } + if (newIdIn.isEmpty()) { + throw new NotExistException(TAG + ": newSQLConfig idIn instanceof List >> 去掉无效 id 后 newIdIn.isEmpty()"); + } + idIn = newIdIn; + + if (method == DELETE || method == PUT) { + config.setCount(newIdIn.size()); + } + } + + Object id = request.get(idKey); + boolean hasId = id != null; + if (method == POST && hasId == false) { + id = callback.newId(method, database, schema, table); // null 表示数据库自增 id + } + + if (id != null) { //null无效 + if (id instanceof Number) { + if (((Number) id).longValue() <= 0) { //一定没有值 + throw new NotExistException(TAG + ": newSQLConfig " + table + ".id <= 0"); + } + } else if (id instanceof String) { + if (StringUtil.isEmpty(id, true)) { //一定没有值 + throw new NotExistException(TAG + ": newSQLConfig StringUtil.isEmpty(" + table + ".id, true)"); + } + } else if (id instanceof Subquery) { + } else { + throw new IllegalArgumentException(idKey + ":value 中 value 的类型只能是 Long , String 或 Subquery !"); + } + + if (idIn instanceof List) { //共用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); + if (d != null && id.toString().equals(d.toString())) { + contains = true; + break; + } + } + if (contains == false) {//empty有效 BaseModel.isEmpty(idIn) == false) { + throw new NotExistException(TAG + ": newSQLConfig idIn != null && (((List) idIn).contains(id) == false"); + } + } + + if (method == DELETE || method == PUT) { + config.setCount(1); + } + } + + + 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 group = request.getString(KEY_GROUP); + String having = request.getString(KEY_HAVING); + String order = request.getString(KEY_ORDER); + String raw = request.getString(KEY_RAW); + String json = request.getString(KEY_JSON); + + try { + //强制作为条件且放在最前面优化性能 + request.remove(idKey); + request.remove(idInKey); + //关键词 + request.remove(KEY_ROLE); + request.remove(KEY_EXPLAIN); + request.remove(KEY_CACHE); + request.remove(KEY_DATABASE); + request.remove(KEY_SCHEMA); + request.remove(KEY_COMBINE); + request.remove(KEY_FROM); + request.remove(KEY_COLUMN); + request.remove(KEY_GROUP); + request.remove(KEY_HAVING); + request.remove(KEY_ORDER); + request.remove(KEY_RAW); + request.remove(KEY_JSON); + + String[] rawArr = StringUtil.split(raw); + config.setRaw(rawArr == null || rawArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(rawArr))); + + Map tableWhere = new LinkedHashMap();//保证顺序好优化 WHERE id > 1 AND name LIKE... + + //已经remove了id和id{},以及@key + Set set = request.keySet(); //前面已经判断request是否为空 + if (method == POST) { //POST操作 + if (idIn != null) { + throw new IllegalArgumentException("POST 请求中不允许传 " + idInKey + " !"); + } + + if (set != null && set.isEmpty() == false) { //不能直接return,要走完下面的流程 + String[] columns = set.toArray(new String[]{}); + + Collection valueCollection = request.values(); + Object[] values = valueCollection == null ? null : valueCollection.toArray(); + + if (values == null || values.length != columns.length) { + throw new Exception("服务器内部错误:\n" + TAG + + " newSQLConfig values == null || values.length != columns.length !"); + } + column = (id == null ? "" : idKey + ",") + 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数量为准 + + items = new ArrayList<>(size); + items.add(id); //idList.get(i)); //第0个就是id + + for (int j = 1; j < size; j++) { + items.add(values[j - 1]); //从第1个开始,允许"null" + } + } + + valuess.add(items); + config.setValues(valuess); + } + } else { //非POST操作 + final boolean isWhere = method != PUT;//除了POST,PUT,其它全是条件!!! + + //条件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + 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 !"); + } + 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 { + 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 + "] 其中任何一个!"); + } + } + + 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); + } + } + + } + + //条件>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + Map tableContent = new LinkedHashMap(); + Object value; + for (String key : set) { + value = request.get(key); + + if (value instanceof Map) {//只允许常规Object + throw new IllegalArgumentException("不允许 " + key + " 等任何key的value类型为 {JSONObject} !"); + } + + //解决AccessVerifier新增userId没有作为条件,而是作为内容,导致PUT,DELETE出错 + if (isWhere) { + tableWhere.put(key, value); + if (whereList == null || whereList.contains(key) == false) { + andList.add(key); + } + } else if (whereList != null && whereList.contains(key)) { + tableWhere.put(key, value); + } else { + tableContent.put(key, value);//一样 instanceof JSONArray ? JSON.toJSONString(value) : value); + } + } + + combineMap.put("&", andList); + combineMap.put("|", orList); + combineMap.put("!", notList); + config.setCombine(combineMap); + + config.setContent(tableContent); + } + + + List cs = new ArrayList<>(); + + List rawList = config.getRaw(); + 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()); + } + } + + boolean distinct = column == null || rawColumnSQL != null ? false : column.startsWith(PREFFIX_DISTINCT); + if (rawColumnSQL == null) { + String[] fks = StringUtil.split(distinct ? column.substring(PREFFIX_DISTINCT.length()) : column, ";"); // key0,key1;fun0(key0,...);fun1(key0,...);key3;fun2(key0,...) + if (fks != null) { + 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()); + } + } + + if (fk.contains("(")) { // fun0(key0,...) + cs.add(fk); + } else { //key0,key1... + ks = StringUtil.split(fk); + if (ks != null && ks.length > 0) { + cs.addAll(Arrays.asList(ks)); + } + } + } + } + } + + 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.setId(id); + //在 tableWhere 第0个 config.setIdIn(idIn); + + config.setRole(RequestRole.get(role)); + 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 等要合并 + + } finally {//后面还可能用到,要还原 + //id或id{}条件 + if (hasId) { + request.put(idKey, id); + } + request.put(idInKey, idIn); + //关键词 + request.put(KEY_DATABASE, database); + request.put(KEY_ROLE, role); + request.put(KEY_EXPLAIN, explain); + request.put(KEY_CACHE, cache); + request.put(KEY_SCHEMA, schema); + request.put(KEY_COMBINE, combine); + request.put(KEY_FROM, from); + request.put(KEY_COLUMN, column); + request.put(KEY_GROUP, group); + request.put(KEY_HAVING, having); + request.put(KEY_ORDER, order); + request.put(KEY_RAW, raw); + request.put(KEY_JSON, json); + } + + return config; + } + + + /** + * @param method + * @param config + * @param joinList + * @param callback + * @return + * @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); + + //TODO 解析出 SQLConfig 再合并 column, order, group 等 + if (joinList == null || joinList.isEmpty() || RequestMethod.isQueryMethod(method) == false) { + return config; + } + + + String table; + String alias; + for (Join j : joinList) { + table = j.getTable(); + 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); + + if (j.isAppJoin() == false) { //除了 @ APP JOIN,其它都是 SQL JOIN,则副表要这样配置 + if (joinConfig.getDatabase() == null) { + joinConfig.setDatabase(config.getDatabase()); //解决主表 JOIN 副表,引号不一致 + } else if (joinConfig.getDatabase().equals(config.getDatabase()) == false) { + throw new IllegalArgumentException("主表 " + config.getTable() + " 的 @database:" + config.getDatabase() + " 和它 SQL JOIN 的副表 " + table + " 的 @database:" + joinConfig.getDatabase() + " 不一致!"); + } + if (joinConfig.getSchema() == null) { + joinConfig.setSchema(config.getSchema()); //主表 JOIN 副表,默认 schema 一致 + } + + if (cacheConfig != null) { + cacheConfig.setDatabase(joinConfig.getDatabase()).setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 + } + + + if (isQuery) { + config.setKeyPrefix(true); + } + + joinConfig.setMain(false).setKeyPrefix(true); + + if (j.isLeftOrRightJoin()) { + 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); + } + } + + //解决 query: 1/2 查数量时报错 /* SELECT count(*) AS count FROM sys.Moment AS Moment LEFT JOIN ( SELECT count(*) AS count FROM sys.Comment ) AS Comment ON Comment.momentId = Moment.id LIMIT 1 OFFSET 0 */ - if (RequestMethod.isHeadMethod(method, true)) { - joinConfig.setMethod(GET); //子查询不能为 SELECT count(*) ,而应该是 SELECT momentId - joinConfig.setColumn(Arrays.asList(j.getKey())); //优化性能,不取非必要的字段 - - if (cacheConfig != null) { - cacheConfig.setMethod(GET); //子查询不能为 SELECT count(*) ,而应该是 SELECT momentId - cacheConfig.setColumn(Arrays.asList(j.getKey())); //优化性能,不取非必要的字段 - } - } - - j.setJoinConfig(joinConfig); - j.setCacheConfig(cacheConfig); - } - - config.setJoinList(joinList); - - return config; - } - - - - /**获取客户端实际需要的key - * verifyName = true - * @param method - * @param originKey - * @param isTableKey - * @param saveLogic 保留逻辑运算符 & | ! - * @return - */ - public static String getRealKey(RequestMethod method, String originKey - , boolean isTableKey, boolean saveLogic) throws Exception { - return getRealKey(method, originKey, isTableKey, saveLogic, true); - } - /**获取客户端实际需要的key - * @param method - * @param originKey - * @param isTableKey - * @param saveLogic 保留逻辑运算符 & | ! - * @param verifyName 验证key名是否符合代码变量/常量名 - * @return - */ - public static String getRealKey(RequestMethod method, String originKey - , boolean isTableKey, boolean saveLogic, boolean verifyName) throws Exception { - Log.i(TAG, "getRealKey saveLogic = " + saveLogic + "; originKey = " + originKey); - if (originKey == null || apijson.JSONObject.isArrayKey(originKey)) { - Log.w(TAG, "getRealKey originKey == null || apijson.JSONObject.isArrayKey(originKey) >> return originKey;"); - return originKey; - } - - String key = new String(originKey); - if (key.endsWith("$")) {//搜索 LIKE,查询时处理 - key = key.substring(0, key.length() - 1); - } - else if (key.endsWith("~")) {//匹配正则表达式 REGEXP,查询时处理 - key = key.substring(0, key.length() - 1); - if (key.endsWith("*")) {//忽略大小写 - key = key.substring(0, key.length() - 1); - } - } - else if (key.endsWith("%")) {//数字、文本、日期范围 BETWEEN AND - key = key.substring(0, key.length() - 1); - } - else if (key.endsWith("{}")) {//被包含 IN,或者说key对应值处于value的范围内。查询时处理 - key = key.substring(0, key.length() - 2); - } - else if (key.endsWith("}{")) {//被包含 EXISTS,或者说key对应值处于value的范围内。查询时处理 - key = key.substring(0, key.length() - 2); - } - else if (key.endsWith("<>")) {//包含 json_contains,或者说value处于key对应值的范围内。查询时处理 - key = key.substring(0, key.length() - 2); - } - else if (key.endsWith("()")) {//方法,查询完后处理,先用一个Map保存 - key = key.substring(0, key.length() - 2); - } - else if (key.endsWith("@")) {//引用,引用对象查询完后处理。fillTarget中暂时不用处理,因为非GET请求都是由给定的id确定,不需要引用 - key = key.substring(0, key.length() - 1); - } - else if (key.endsWith(">=")) {//比较。查询时处理 - key = key.substring(0, key.length() - 2); - } - else if (key.endsWith("<=")) {//比较。查询时处理 - key = key.substring(0, key.length() - 2); - } - else if (key.endsWith(">")) {//比较。查询时处理 - key = key.substring(0, key.length() - 1); - } - else if (key.endsWith("<")) {//比较。查询时处理 - key = key.substring(0, key.length() - 1); - } - else if (key.endsWith("+")) {//延长,PUT查询时处理 - if (method == PUT) {//不为PUT就抛异常 - key = key.substring(0, key.length() - 1); - } - } - else if (key.endsWith("-")) {//缩减,PUT查询时处理 - if (method == PUT) {//不为PUT就抛异常 - key = key.substring(0, key.length() - 1); - } - } - - String last = null;//不用Logic优化代码,否则 key 可能变为 key| 导致 key=value 变成 key|=value 而出错 - if (RequestMethod.isQueryMethod(method)) {//逻辑运算符仅供GET,HEAD方法使用 - last = key.isEmpty() ? "" : key.substring(key.length() - 1); - if ("&".equals(last) || "|".equals(last) || "!".equals(last)) { - key = key.substring(0, key.length() - 1); - } else { - last = null;//避免key + StringUtil.getString(last)错误延长 - } - } - - //"User:toUser":User转换"toUser":User, User为查询同名Table得到的JSONObject。交给客户端处理更好 - if (isTableKey) {//不允许在column key中使用Type:key形式 - key = Pair.parseEntry(key, true).getKey();//table以左边为准 - } else { - key = Pair.parseEntry(key).getValue();//column以右边为准 - } - - if (verifyName && StringUtil.isName(key.startsWith("@") ? key.substring(1) : key) == false) { - throw new IllegalArgumentException(method + "请求,字符 " + originKey + " 不合法!" - + " key:value 中的key只能关键词 '@key' 或 'key[逻辑符][条件符]' 或 PUT请求下的 'key+' / 'key-' !"); - } - - if (saveLogic && last != null) { - key = key + last; - } - Log.i(TAG, "getRealKey return key = " + key); - return key; - } - - - public static interface IdCallback { - /**为 post 请求新建 id, 只能是 Long 或 String - * @param method - * @param database - * @param schema - * @param table - * @return - */ - 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 - * @param schema - * @param table - * @return - */ - 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 - * @param table - * @return - */ - String getUserIdKey(String database, String schema, String datasource, String table); - } - - public static interface Callback extends IdCallback { - /**获取 SQLConfig 的实例 - * @param method - * @param database - * @param schema - * @param table - * @return - */ - SQLConfig getSQLConfig(RequestMethod method, String database, String schema, String table); - - /**combine 里的 key 在 request 中 value 为 null 或不存在,即 request 中缺少用来作为 combine 条件的 key: value - * @param combine - * @param key - * @param request - */ - public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception; - } - - public static abstract class SimpleCallback implements Callback { - - - @Override - public Object newId(RequestMethod method, String database, String schema, String table) { - 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; - } - - @Override - public String getUserIdKey(String database, String schema, String datasource, String table) { - return getUserIdKey(database, schema, table); - } - - @Override - public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception { - throw new IllegalArgumentException(name + ":{} 里的 @combine:value 中的value里 " + item + " 对应的条件 " + key + ":value 中 value 不能为 null!"); - } - - } + if (RequestMethod.isHeadMethod(method, true)) { + joinConfig.setMethod(GET); //子查询不能为 SELECT count(*) ,而应该是 SELECT momentId + joinConfig.setColumn(Arrays.asList(j.getKey())); //优化性能,不取非必要的字段 + + if (cacheConfig != null) { + cacheConfig.setMethod(GET); //子查询不能为 SELECT count(*) ,而应该是 SELECT momentId + cacheConfig.setColumn(Arrays.asList(j.getKey())); //优化性能,不取非必要的字段 + } + } + + j.setJoinConfig(joinConfig); + j.setCacheConfig(cacheConfig); + } + + config.setJoinList(joinList); + + return config; + } + + + /** + * 获取客户端实际需要的key + * verifyName = true + * + * @param method + * @param originKey + * @param isTableKey + * @param saveLogic 保留逻辑运算符 & | ! + * @return + */ + public static String getRealKey(RequestMethod method, String originKey + , boolean isTableKey, boolean saveLogic) throws Exception { + return getRealKey(method, originKey, isTableKey, saveLogic, true); + } + + /** + * 获取客户端实际需要的key + * + * @param method + * @param originKey + * @param isTableKey + * @param saveLogic 保留逻辑运算符 & | ! + * @param verifyName 验证key名是否符合代码变量/常量名 + * @return + */ + public static String getRealKey(RequestMethod method, String originKey + , boolean isTableKey, boolean saveLogic, boolean verifyName) throws Exception { + Log.i(TAG, "getRealKey saveLogic = " + saveLogic + "; originKey = " + originKey); + if (originKey == null || apijson.JSONObject.isArrayKey(originKey)) { + Log.w(TAG, "getRealKey originKey == null || apijson.JSONObject.isArrayKey(originKey) >> return originKey;"); + return originKey; + } + + String key = new String(originKey); + if (key.endsWith("$")) {//搜索 LIKE,查询时处理 + key = key.substring(0, key.length() - 1); + } else if (key.endsWith("~")) {//匹配正则表达式 REGEXP,查询时处理 + key = key.substring(0, key.length() - 1); + if (key.endsWith("*")) {//忽略大小写 + key = key.substring(0, key.length() - 1); + } + } else if (key.endsWith("%")) {//数字、文本、日期范围 BETWEEN AND + key = key.substring(0, key.length() - 1); + } else if (key.endsWith("{}")) {//被包含 IN,或者说key对应值处于value的范围内。查询时处理 + key = key.substring(0, key.length() - 2); + } else if (key.endsWith("}{")) {//被包含 EXISTS,或者说key对应值处于value的范围内。查询时处理 + key = key.substring(0, key.length() - 2); + } else if (key.endsWith("<>")) {//包含 json_contains,或者说value处于key对应值的范围内。查询时处理 + key = key.substring(0, key.length() - 2); + } else if (key.endsWith("()")) {//方法,查询完后处理,先用一个Map保存 + key = key.substring(0, key.length() - 2); + } else if (key.endsWith("@")) {//引用,引用对象查询完后处理。fillTarget中暂时不用处理,因为非GET请求都是由给定的id确定,不需要引用 + key = key.substring(0, key.length() - 1); + } else if (key.endsWith(">=")) {//比较。查询时处理 + key = key.substring(0, key.length() - 2); + } else if (key.endsWith("<=")) {//比较。查询时处理 + key = key.substring(0, key.length() - 2); + } else if (key.endsWith(">")) {//比较。查询时处理 + key = key.substring(0, key.length() - 1); + } else if (key.endsWith("<")) {//比较。查询时处理 + key = key.substring(0, key.length() - 1); + } else if (key.endsWith("+")) {//延长,PUT查询时处理 + if (method == PUT) {//不为PUT就抛异常 + key = key.substring(0, key.length() - 1); + } + } else if (key.endsWith("-")) {//缩减,PUT查询时处理 + if (method == PUT) {//不为PUT就抛异常 + key = key.substring(0, key.length() - 1); + } + } + + String last = null;//不用Logic优化代码,否则 key 可能变为 key| 导致 key=value 变成 key|=value 而出错 + if (RequestMethod.isQueryMethod(method)) {//逻辑运算符仅供GET,HEAD方法使用 + last = key.isEmpty() ? "" : key.substring(key.length() - 1); + if ("&".equals(last) || "|".equals(last) || "!".equals(last)) { + key = key.substring(0, key.length() - 1); + } else { + last = null;//避免key + StringUtil.getString(last)错误延长 + } + } + + //"User:toUser":User转换"toUser":User, User为查询同名Table得到的JSONObject。交给客户端处理更好 + if (isTableKey) {//不允许在column key中使用Type:key形式 + key = Pair.parseEntry(key, true).getKey();//table以左边为准 + } else { + key = Pair.parseEntry(key).getValue();//column以右边为准 + } + + if (verifyName && StringUtil.isName(key.startsWith("@") ? key.substring(1) : key) == false) { + throw new IllegalArgumentException(method + "请求,字符 " + originKey + " 不合法!" + + " key:value 中的key只能关键词 '@key' 或 'key[逻辑符][条件符]' 或 PUT请求下的 'key+' / 'key-' !"); + } + + if (saveLogic && last != null) { + key = key + last; + } + Log.i(TAG, "getRealKey return key = " + key); + return key; + } + + + public static interface IdCallback { + /** + * 为 post 请求新建 id, 只能是 Long 或 String + * + * @param method + * @param database + * @param schema + * @param table + * @return + */ + 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 + * @param schema + * @param table + * @return + */ + 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 + * @param table + * @return + */ + String getUserIdKey(String database, String schema, String datasource, String table); + } + + public static interface Callback extends IdCallback { + /** + * 获取 SQLConfig 的实例 + * + * @param method + * @param database + * @param schema + * @param table + * @return + */ + SQLConfig getSQLConfig(RequestMethod method, String database, String schema, String table); + + /** + * combine 里的 key 在 request 中 value 为 null 或不存在,即 request 中缺少用来作为 combine 条件的 key: value + * + * @param combine + * @param key + * @param request + */ + public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception; + } + + public static abstract class SimpleCallback implements Callback { + + + @Override + public Object newId(RequestMethod method, String database, String schema, String table) { + 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; + } + + @Override + public String getUserIdKey(String database, String schema, String datasource, String table) { + return getUserIdKey(database, schema, table); + } + + @Override + public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception { + throw new IllegalArgumentException(name + ":{} 里的 @combine:value 中的value里 " + item + " 对应的条件 " + key + ":value 中 value 不能为 null!"); + } + + } } From f58002f8d29241578344df6debd4953cebed5832 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 21 Jun 2021 15:38:46 +0800 Subject: [PATCH 149/944] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0c39db52c..eb3ea87f3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,7 +29,7 @@ - [kxlv2000](https://github.com/kxlv2000)(SUSTech) - [caohao-php](https://github.com/caohao-php)(腾讯工程师) - [Wscats](https://github.com/Wscats)(腾讯工程师) -- [jun0315](https://github.com/jun0315) +- [jun0315](https://github.com/jun0315)(腾讯工程师) #### 其中特别致谢:
From 2dfe8eda0aafd0dece15c179a05f4180a4242497 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 21 Jun 2021 15:51:02 +0800 Subject: [PATCH 150/944] =?UTF-8?q?=E6=AC=A2=E8=BF=8E=20AbstractSQLConfig?= =?UTF-8?q?=20=E5=B9=B6=E5=BA=94=E7=94=A8=E5=85=B3=E9=94=AE=E6=9B=B4?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/253 --- .../java/apijson/orm/AbstractSQLConfig.java | 6992 ++++++++--------- 1 file changed, 3443 insertions(+), 3549 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 755209951..7b14facfa 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -71,3559 +71,3453 @@ import apijson.orm.model.Table; import apijson.orm.model.TestRecord; -/** - * config sql for JSON Request - * +/**config sql for JSON Request * @author Lemon */ public abstract class AbstractSQLConfig implements SQLConfig { - private static final String TAG = "AbstractSQLConfig"; - - public static String DEFAULT_DATABASE = DATABASE_MYSQL; - public static String DEFAULT_SCHEMA = "sys"; - public static String PREFFIX_DISTINCT = "DISTINCT "; - - // * 和 / 不能同时出现,防止 /* */ 段注释! # 和 -- 不能出现,防止行注释! ; 不能出现,防止隔断SQL语句!空格不能出现,防止 CRUD,DROP,SHOW TABLES等语句! - private static final Pattern PATTERN_RANGE; - private static final Pattern PATTERN_FUNCTION; - - /** - * 表名映射,隐藏真实表名,对安全要求很高的表可以这么做 - */ - public static final Map 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 改成更好的正则,校验前面为单词,中间为操作符,后面为值 - - - TABLE_KEY_MAP = new HashMap(); - TABLE_KEY_MAP.put(Table.class.getSimpleName(), Table.TABLE_NAME); - TABLE_KEY_MAP.put(Column.class.getSimpleName(), Column.TABLE_NAME); - TABLE_KEY_MAP.put(PgClass.class.getSimpleName(), PgClass.TABLE_NAME); - TABLE_KEY_MAP.put(PgAttribute.class.getSimpleName(), PgAttribute.TABLE_NAME); - TABLE_KEY_MAP.put(SysTable.class.getSimpleName(), SysTable.TABLE_NAME); - TABLE_KEY_MAP.put(SysColumn.class.getSimpleName(), SysColumn.TABLE_NAME); - TABLE_KEY_MAP.put(ExtendedProperty.class.getSimpleName(), ExtendedProperty.TABLE_NAME); - - 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()); - - - DATABASE_LIST = new ArrayList<>(); - DATABASE_LIST.add(DATABASE_MYSQL); - DATABASE_LIST.add(DATABASE_POSTGRESQL); - DATABASE_LIST.add(DATABASE_SQLSERVER); - DATABASE_LIST.add(DATABASE_ORACLE); - DATABASE_LIST.add(DATABASE_DB2); - - - 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...) - - } - - - @Override - public boolean limitSQLCount() { - return Log.DEBUG == false || AbstractVerifier.SYSTEM_ACCESS_MAP.containsKey(getTable()) == false; - } - - @NotNull - @Override - public String getIdKey() { - return KEY_ID; - } - - @NotNull - @Override - public String getUserIdKey() { - return KEY_USER_ID; - } - - - private Object id; //Table的id - private RequestMethod method; //操作方法 - private boolean prepared = true; //预编译 - private boolean main = true; - /** - * TODO 被关联的表通过就忽略关联的表?(这个不行 User:{"sex@":"/Comment/toId"}) - */ - private RequestRole role; //发送请求的用户的角色 - private boolean distinct = false; - private String database; //表所在的数据库类型 - private String schema; //表所在的数据库名 - private String datasource; //数据源 - private String table; //表名 - private String alias; //表别名 - private String group; //分组方式的字符串数组,','分隔 - private String having; //聚合函数的字符串数组,','分隔 - private String order; //排序方式的字符串数组,','分隔 - private List raw; //需要保留原始 SQL 的字段,','分隔 - private List json; //需要转为 JSON 的字段,','分隔 - private Subquery from; //子查询临时表 - private List column; //表内字段名(或函数名,仅查询操作可用)的字符串数组,','分隔 - private List> values; //对应表内字段的值的字符串数组,','分隔 - private Map content; //Request内容,key:value形式,column = content.keySet(),values = content.values() - private Map where; //筛选条件,key:value形式 - private Map> combine; //条件组合,{ "&":[key], "|":[key], "!":[key] } - - - //array item <<<<<<<<<< - private int count; //Table数量 - private int page; //Table所在页码 - private int position; //Table在[]中的位置 - private int query; //JSONRequest.query - private int type; //ObjectParser.type - private int cache; - private boolean explain; - - private List joinList; //连表 配置列表 - //array item >>>>>>>>>> - private boolean test; //测试 - - private String procedure; - - public SQLConfig setProcedure(String procedure) { - this.procedure = procedure; - return this; - } - - public String getProcedure() { - return procedure; - } - - public AbstractSQLConfig(RequestMethod method) { - setMethod(method); - } - - public AbstractSQLConfig(RequestMethod method, String table) { - this(method); - setTable(table); - } - - public AbstractSQLConfig(RequestMethod method, int count, int page) { - this(method); - setCount(count); - setPage(page); - } - - @NotNull - @Override - public RequestMethod getMethod() { - if (method == null) { - method = GET; - } - return method; - } - - @Override - public AbstractSQLConfig setMethod(RequestMethod method) { - this.method = method; - return this; - } - - @Override - public boolean isPrepared() { - return prepared; - } - - @Override - public AbstractSQLConfig setPrepared(boolean prepared) { - this.prepared = prepared; - return this; - } - - @Override - public boolean isMain() { - return main; - } - - @Override - public AbstractSQLConfig setMain(boolean main) { - this.main = main; - return this; - } - - - @Override - public Object getId() { - return id; - } - - @Override - public AbstractSQLConfig setId(Object id) { - this.id = id; - return this; - } - - @Override - public RequestRole 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) { - this.role = role; - return this; - } - - @Override - public boolean isDistinct() { - return distinct; - } - - @Override - public SQLConfig setDistinct(boolean distinct) { - this.distinct = distinct; - return this; - } - - @Override - public String getDatabase() { - return database; - } - - @Override - public SQLConfig setDatabase(String database) { - this.database = database; - return this; - } - - /** - * @return db == null ? DEFAULT_DATABASE : db - */ - @NotNull - public String getSQLDatabase() { - String db = getDatabase(); - return db == null ? DEFAULT_DATABASE : db; // "" 表示已设置,不需要用全局默认的 StringUtil.isEmpty(db, false)) { - } - - @Override - public boolean isMySQL() { - return isMySQL(getSQLDatabase()); - } - - public static boolean isMySQL(String db) { - return DATABASE_MYSQL.equals(db); - } - - @Override - public boolean isPostgreSQL() { - return isPostgreSQL(getSQLDatabase()); - } - - public static boolean isPostgreSQL(String db) { - return DATABASE_POSTGRESQL.equals(db); - } - - @Override - public boolean isSQLServer() { - return isSQLServer(getSQLDatabase()); - } - - public static boolean isSQLServer(String db) { - return DATABASE_SQLSERVER.equals(db); - } - - @Override - public boolean isOracle() { - return isOracle(getSQLDatabase()); - } - - public static boolean isOracle(String db) { - return DATABASE_ORACLE.equals(db); - } - - @Override - public boolean isDb2() { - return isDb2(getSQLDatabase()); - } - - public static boolean isDb2(String db) { - return DATABASE_DB2.equals(db); - } - - @Override - public String getQuote() { - return isMySQL() ? "`" : "\""; - } - - @Override - public String getSchema() { - return schema; - } - - /** - * @param sqlTable - * @return - */ - @NotNull - public String getSQLSchema() { - String table = getTable(); - //强制,避免因为全局默认的 @schema 自动填充进来,导致这几个类的 schema 为 sys 等其它值 - if (Table.TAG.equals(table) || Column.TAG.equals(table)) { - return SCHEMA_INFORMATION; //MySQL, PostgreSQL, SQL Server 都有的 - } - if (PgClass.TAG.equals(table) || PgAttribute.TAG.equals(table)) { - return ""; //PostgreSQL 的 pg_class 和 pg_attribute 表好像不属于任何 Schema - } - if (SysTable.TAG.equals(table) || SysColumn.TAG.equals(table) || ExtendedProperty.TAG.equals(table)) { - return SCHEMA_SYS; //SQL Server 在 sys 中的属性比 information_schema 中的要全,能拿到注释 - } - - String sch = getSchema(); - return sch == null ? DEFAULT_SCHEMA : sch; - } - - @Override - public AbstractSQLConfig setSchema(String schema) { - if (schema != null) { - String quote = getQuote(); - String s = schema.startsWith(quote) && schema.endsWith(quote) ? schema.substring(1, schema.length() - 1) : schema; - if (StringUtil.isEmpty(s, true) == false && StringUtil.isName(s) == false) { - throw new IllegalArgumentException("@schema:value 中value必须是1个单词!"); - } - } - this.schema = schema; - return this; - } - - @Override - public String getDatasource() { - return datasource; - } - - @Override - public SQLConfig setDatasource(String datasource) { - this.datasource = datasource; - return this; - } - - /** - * 请求传进来的Table名 - * - * @return - * @see {@link #getSQLTable()} - */ - @Override - public String getTable() { - return table; - } - - /** - * 数据库里的真实Table名 - * 通过 {@link #TABLE_KEY_MAP} 映射 - * - * @return - */ - @JSONField(serialize = false) - @Override - public String getSQLTable() { - // String t = TABLE_KEY_MAP.containsKey(table) ? TABLE_KEY_MAP.get(table) : table; - //如果要强制小写,则可在子类重写这个方法再 toLowerCase return DATABASE_POSTGRESQL.equals(getDatabase()) ? t.toLowerCase() : t; - return TABLE_KEY_MAP.containsKey(table) ? TABLE_KEY_MAP.get(table) : table; - } - - @JSONField(serialize = false) - @Override - public String getTablePath() { - String q = getQuote(); - - String sch = getSQLSchema(); - String sqlTable = getSQLTable(); - - return (StringUtil.isEmpty(sch, true) ? "" : q + sch + q + ".") + q + sqlTable + q + (isKeyPrefix() ? " AS " + getAliasWithQuote() : ""); - } - - @Override - public AbstractSQLConfig setTable(String table) { //Table已经在Parser中校验,所以这里不用防SQL注入 - this.table = table; - return this; - } - - @Override - public String getAlias() { - return alias; - } - - @Override - public AbstractSQLConfig setAlias(String alias) { - this.alias = alias; - return this; - } - - public String getAliasWithQuote() { - String a = getAlias(); - if (StringUtil.isEmpty(a, true)) { - a = getTable(); - } - String q = getQuote(); - //getTable 不能小写,因为Verifier用大小写敏感的名称判断权限 - //如果要强制小写,则可在子类重写这个方法再 toLowerCase return q + (DATABASE_POSTGRESQL.equals(getDatabase()) ? a.toLowerCase() : a) + q; - return q + a + q; - } - - @Override - public String getGroup() { - return group; - } - - public AbstractSQLConfig setGroup(String... keys) { - return setGroup(StringUtil.getString(keys)); - } - - @Override - public AbstractSQLConfig setGroup(String group) { - this.group = group; - return this; - } - - @JSONField(serialize = false) - 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()); - } - - c = ((AbstractSQLConfig) cfg).getGroupString(false); - if (StringUtil.isEmpty(c, true) == false) { - joinGroup += (first ? "" : ", ") + c; - first = false; - } - - } - } - - - group = StringUtil.getTrimedString(group); - String[] keys = StringUtil.split(group); - if (keys == null || keys.length <= 0) { - return StringUtil.isEmpty(joinGroup, true) ? "" : (hasPrefix ? " GROUP BY " : "") + joinGroup; - } - - for (int i = 0; i < keys.length; i++) { - if (isPrepared()) { //不能通过 ? 来代替,因为SQLExecutor statement.setString后 GROUP BY 'userId' 有单引号,只能返回一条数据,必须去掉单引号才行! - if (StringUtil.isName(keys[i]) == false) { - throw new IllegalArgumentException("@group:value 中 value里面用 , 分割的每一项都必须是1个单词!并且不要有空格!"); - } - } - - keys[i] = getKey(keys[i]); - } - - return (hasPrefix ? " GROUP BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinGroup, ", "); - } - - @Override - public String getHaving() { - return having; - } - - public AbstractSQLConfig setHaving(String... conditions) { - return setHaving(StringUtil.getString(conditions)); - } - - @Override - public AbstractSQLConfig setHaving(String having) { - this.having = having; - return this; - } - - /** - * TODO @having 改为默认 | 或连接,且支持 @having: { "key1>": 1, "key{}": "length(key2)>0", "@combine": "key1,key2" } - * - * @return HAVING conditoin0 AND condition1 OR condition2 ... - */ - @JSONField(serialize = false) - 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()); - } - - c = ((AbstractSQLConfig) cfg).getHavingString(false); - if (StringUtil.isEmpty(c, true) == false) { - joinHaving += (first ? "" : ", ") + c; - first = false; - } - - } - } - - String[] keys = StringUtil.split(getHaving(), ";"); - if (keys == null || keys.length <= 0) { - return StringUtil.isEmpty(joinHaving, true) ? "" : (hasPrefix ? " HAVING " : "") + joinHaving; - } - - String quote = getQuote(); - String tableAlias = getAliasWithQuote(); - - List raw = getRaw(); - boolean containRaw = raw != null && raw.contains(KEY_HAVING); - - String expression; - String method; - //暂时不允许 String prefix; - String suffix; - - //fun0(arg0,arg1,...);fun1(arg0,arg1,...) - for (int i = 0; i < keys.length; i++) { - - //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()); - } - } - - if (expression.length() > 50) { - throw new UnsupportedOperationException("@having:value 的 value 中字符串 " + expression + " 不合法!" - + "不允许传超过 50 个字符的函数或表达式!请用 @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 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) { - throw new IllegalArgumentException("字符 " + method + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 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 + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); - } - - 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() ? tableAlias + "." : "") + origin; - } - } - - keys[i] = method + "(" + StringUtil.getString(ckeys) + ")" + suffix; - } - - //TODO 支持 OR, NOT 参考 @combine:"&key0,|key1,!key2" - return (hasPrefix ? " HAVING " : "") + StringUtil.concat(StringUtil.getString(keys, AND), joinHaving, AND); - } - - @Override - public String getOrder() { - return order; - } - - public AbstractSQLConfig setOrder(String... conditions) { - return setOrder(StringUtil.getString(conditions)); - } - - @Override - public AbstractSQLConfig setOrder(String order) { - this.order = order; - return this; - } - - @JSONField(serialize = false) - 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()); - } - - c = ((AbstractSQLConfig) cfg).getOrderString(false); - if (StringUtil.isEmpty(c, true) == false) { - joinOrder += (first ? "" : ", ") + c; - first = false; - } - - } - } - - - String order = StringUtil.getTrimedString(getOrder()); - // SELECT * FROM sys.Moment ORDER BY userId ASC, rand(); 前面的 userId ASC 和后面的 rand() 都有效 - // if ("rand()".equals(order)) { - // return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(order, joinOrder, ", "); - // } - - if (getCount() > 0 && (isOracle() || isSQLServer() || isDb2())) { // Oracle, SQL Server, DB2 的 OFFSET 必须加 ORDER BY - - // String[] ss = StringUtil.split(order); - if (StringUtil.isEmpty(order, true)) { //SQL Server 子查询内必须指定 OFFSET 才能用 ORDER BY - String idKey = getIdKey(); - if (StringUtil.isEmpty(idKey, true)) { - idKey = "id"; //ORDER BY NULL 不行,SQL Server 会报错,必须要有排序,才能使用 OFFSET FETCH,如果没有 idKey,请求中指定 @order 即可 - } - order = idKey; //让数据库调控默认升序还是降序 + "+"; - } - - //不用这么全面,毕竟没有语法问题还浪费性能,如果有其它问题,让前端传的 JSON 直接加上 @order 来解决 - // boolean contains = false; - // if (ss != null) { - // for (String s : ss) { - // if (s != null && s.startsWith(idKey)) { - // s = s.substring(idKey.length()); - // if ("+".equals(s) || "-".equals(s)) {// || " ASC ".equals(s) || " DESC ".equals(s)) { - // contains = true; - // break; - // } - // } - // } - // } - - // if (contains == false) { - // order = (ss == null || ss.length <= 0 ? "" : order + ",") + idKey + "+"; - // } - } - - - String[] keys = StringUtil.split(order); - if (keys == null || keys.length <= 0) { - return StringUtil.isEmpty(joinOrder, true) ? "" : (hasPrefix ? " ORDER BY " : "") + joinOrder; - } - - for (int i = 0; i < keys.length; i++) { - String item = keys[i]; - if ("rand()".equals(item)) { - continue; - } - - int index = item.endsWith("+") ? item.length() - 1 : -1; //StringUtil.split返回数组中,子项不会有null - String sort; - if (index < 0) { - index = item.endsWith("-") ? item.length() - 1 : -1; - sort = index <= 0 ? "" : " DESC "; - } else { - sort = " ASC "; - } - - String origin = index < 0 ? item : item.substring(0, index); - - if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! - //这里既不对origin trim,也不对 ASC/DESC ignoreCase,希望前端严格传没有任何空格的字符串过来,减少传输数据量,节约服务器性能 - if (StringUtil.isName(origin) == false) { - throw new IllegalArgumentException("预编译模式下 @order:value 中 " + item + " 不合法! value 里面用 , 分割的" - + "每一项必须是 随机函数 rand() 或 column+ / column- 且其中 column 必须是 1 个单词!并且不要有多余的空格!"); - } - } - - keys[i] = getKey(origin) + sort; - } - - return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinOrder, ", "); - } - - @Override - public List getRaw() { - return raw; - } - - @Override - public SQLConfig setRaw(List raw) { - this.raw = raw; - return this; - } - - /** - * 获取原始 SQL 片段 - * - * @param key - * @param value - * @return - * @throws Exception - */ - @Override - public String getRawSQL(String key, Object value) throws Exception { - List rawList = getRaw(); - boolean containRaw = rawList != null && rawList.contains(key); - if (containRaw && value instanceof String == false) { - throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" - + "对应的 " + key + ":value 中 value 类型只能为 String!"); - } - - 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)) { - return (String) value; - } - } - - return rawSQL; - } - - - @Override - public List getJson() { - return json; - } - - @Override - public AbstractSQLConfig setJson(List json) { - this.json = json; - return this; - } - - - @Override - public Subquery getFrom() { - return from; - } - - @Override - public AbstractSQLConfig setFrom(Subquery from) { - this.from = from; - return this; - } - - @Override - public List getColumn() { - return column; - } - - @Override - public AbstractSQLConfig setColumn(List column) { - this.column = column; - return this; - } - - @JSONField(serialize = false) - public String getColumnString() throws Exception { - return getColumnString(false); - } - - @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) { - String origin; - String alias; - int index; - - List raw = getRaw(); - boolean containRaw = raw != null && raw.contains(KEY_COLUMN); - - for (String c : column) { - if (containRaw) { - // 由于 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); - continue; - } - } - index = c.lastIndexOf(":"); //StringUtil.split返回数组中,子项不会有null - origin = index < 0 ? c : c.substring(0, index); - alias = index < 0 ? null : c.substring(index + 1); - - if (alias != null && StringUtil.isName(alias) == false) { - throw new IllegalArgumentException("HEAD请求: 字符 " + alias + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" - + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); - } - - if (StringUtil.isName(origin) == false) { - int start = origin.indexOf("("); - if (start < 0 || origin.lastIndexOf(")") <= start) { - 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个单词!并且不要有多余的空格!"); - } - } - } - } - - return SQL.count(column != null && column.size() == 1 && StringUtil.isName(column.get(0)) ? getKey(column.get(0)) : "*"); - case POST: - if (column == null || column.isEmpty()) { - throw new IllegalArgumentException("POST 请求必须在Table内设置要保存的 key:value !"); - } - - String s = ""; - boolean pfirst = true; - for (String c : column) { - if (isPrepared() && StringUtil.isName(c) == false) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! - throw new IllegalArgumentException("POST请求: 每一个 key:value 中的key都必须是1个单词!"); - } - s += ((pfirst ? "" : ",") + getKey(c)); - - pfirst = false; - } - - return "(" + s + ")"; - case GET: - case GETS: - boolean isQuery = RequestMethod.isQueryMethod(method); //TODO 这个有啥用?上面应是 getMethod 的值 GET 和 GETS 了。 - String joinColumn = ""; - if (isQuery && joinList != null) { - SQLConfig ecfg; - SQLConfig cfg; - String c; - boolean first = true; - for (Join j : joinList) { - 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; - first = false; - } - - inSQLJoin = true; - } - } - - String tableAlias = getAliasWithQuote(); - - // String c = StringUtil.getString(column); //id,name;json_length(contactIdList):contactCount;... - - String[] keys = column == null ? null : column.toArray(new String[]{}); //StringUtil.split(c, ";"); - if (keys == null || keys.length <= 0) { - - boolean noColumn = column != null && inSQLJoin; - String mc = isKeyPrefix() == false ? (noColumn ? "" : "*") : (noColumn ? "" : tableAlias + ".*"); - - return StringUtil.concat(mc, joinColumn, ", ", true); - } - - - 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]; - - if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 - if ("".equals(RAW_MAP.get(expression)) || RAW_MAP.containsValue(expression)) { // newSQLConfig 提前处理好的 - continue; - } - - // 简单点, 后台配置就带上 AS - // int index = expression.lastIndexOf(":"); - // String alias = expression.substring(index+1); - // boolean hasAlias = StringUtil.isName(alias); - // String pre = index > 0 && hasAlias ? expression.substring(0, index) : expression; - // if (RAW_MAP.containsValue(pre) || "".equals(RAW_MAP.get(pre))) { // newSQLConfig 提前处理好的 - // expression = pre + (hasAlias ? " AS " + alias : ""); - // continue; - // } - } - - if (expression.length() > 50) { - throw new UnsupportedOperationException("@column:value 的 value 中字符串 " + expression + " 不合法!" - + "不允许传超过 50 个字符的函数或表达式!请用 @raw 简化传参!"); - } - - - 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,...) 这种格式!"); - } - - method = expression.substring(0, start); - boolean distinct = i <= 0 && method.startsWith(PREFFIX_DISTINCT); - String fun = distinct ? method.substring(PREFFIX_DISTINCT.length()) : method; - - 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 + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); - } - } - - } - - boolean isColumn = start < 0; - - String[] ckeys = StringUtil.split(isColumn ? expression : expression.substring(start + 1, end)); - String quote = getQuote(); - - // 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); - - distinct = j <= 0 && origin.startsWith(PREFFIX_DISTINCT); - if (distinct) { - origin = origin.substring(PREFFIX_DISTINCT.length()); - } - - 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 { - // 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 个空格!其它情况不允许空格!"); - } - } - } - - //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 (isName && isKeyPrefix()) { - ckeys[j] = tableAlias + "." + origin; - // if (isColumn) { - // ckeys[j] += " AS " + quote + (isMain() ? "" : tableAlias + ".") + (StringUtil.isEmpty(alias, true) ? origin : alias) + quote; - // } - if (isColumn && StringUtil.isEmpty(alias, true) == false) { - ckeys[j] += " AS " + quote + alias + quote; - } - } else { - ckeys[j] = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); - } - - if (distinct) { - ckeys[j] = PREFFIX_DISTINCT + ckeys[j]; - } - } - // } - - } - - 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); - - 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 + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); - } - - 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); - // } - } - - } - - 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!" - ); - } - } - - - @Override - public List> getValues() { - return values; - } - - @JSONField(serialize = false) - public String getValuesString() { - String s = ""; - if (values != null && values.size() > 0) { - Object[] items = new Object[values.size()]; - List vs; - for (int i = 0; i < values.size(); i++) { - vs = values.get(i); - if (vs == null) { - continue; - } - - items[i] = "("; - for (int j = 0; j < vs.size(); j++) { - items[i] += ((j <= 0 ? "" : ",") + getValue(vs.get(j))); - } - items[i] += ")"; - } - s = StringUtil.getString(items); - } - return s; - } - - @Override - public AbstractSQLConfig setValues(List> valuess) { - this.values = valuess; - return this; - } - - @Override - public Map getContent() { - return content; - } - - @Override - public AbstractSQLConfig setContent(Map content) { - this.content = content; - return this; - } - - @Override - public int getCount() { - return count; - } - - @Override - public AbstractSQLConfig setCount(int count) { - this.count = count; - return this; - } - - @Override - public int getPage() { - return page; - } - - @Override - public AbstractSQLConfig setPage(int page) { - this.page = page; - return this; - } - - @Override - public int getPosition() { - return position; - } - - @Override - public AbstractSQLConfig setPosition(int position) { - this.position = position; - return this; - } - - @Override - public int getQuery() { - return query; - } - - @Override - public AbstractSQLConfig setQuery(int query) { - this.query = query; - return this; - } - - @Override - public int getType() { - return type; - } - - @Override - public AbstractSQLConfig setType(int type) { - this.type = type; - return this; - } - - @Override - public int getCache() { - return cache; - } - - @Override - public AbstractSQLConfig setCache(int cache) { - this.cache = cache; - return this; - } - - public AbstractSQLConfig setCache(String cache) { - return setCache(getCache(cache)); - } - - public static int getCache(String cache) { - int cache2; - if (cache == null) { - cache2 = JSONRequest.CACHE_ALL; - } else { - // if (isSubquery) { - // throw new IllegalArgumentException("子查询内不支持传 " + JSONRequest.KEY_CACHE + "!"); - // } - - switch (cache) { - case "0": - case JSONRequest.CACHE_ALL_STRING: - cache2 = JSONRequest.CACHE_ALL; - break; - case "1": - case JSONRequest.CACHE_ROM_STRING: - cache2 = JSONRequest.CACHE_ROM; - break; - case "2": - case JSONRequest.CACHE_RAM_STRING: - cache2 = JSONRequest.CACHE_RAM; - break; - default: - throw new IllegalArgumentException(JSONRequest.KEY_CACHE + ":value 中 value 的值不合法!必须在 [0,1,2] 或 [ALL, ROM, RAM] 内 !"); - } - } - return cache2; - } - - @Override - public boolean isExplain() { - return explain; - } - - @Override - public AbstractSQLConfig setExplain(boolean explain) { - this.explain = explain; - return this; - } - - @Override - public List getJoinList() { - return joinList; - } - - @Override - public SQLConfig setJoinList(List joinList) { - this.joinList = joinList; - return this; - } - - @Override - public boolean hasJoin() { - return joinList != null && joinList.isEmpty() == false; - } - - - @Override - public boolean isTest() { - return test; - } - - @Override - public AbstractSQLConfig setTest(boolean test) { - this.test = test; - return this; - } - - /** - * 获取初始位置offset - * - * @return - */ - @JSONField(serialize = false) - public int getOffset() { - return getOffset(getPage(), getCount()); - } - - /** - * 获取初始位置offset - * - * @param page - * @param count - * @return - */ - public static int getOffset(int page, int count) { - return page * count; - } - - /** - * 获取限制数量 - * - * @return - */ - @JSONField(serialize = false) - public String getLimitString() { - if (count <= 0 || RequestMethod.isHeadMethod(getMethod(), true)) { - return ""; - } - return getLimitString(getPage(), getCount(), isOracle() || isSQLServer() || isDb2(), isOracle()); - } - - /** - * 获取限制数量 - * - * @param limit - * @return - */ - public static String getLimitString(int page, int count, boolean isTSQL, boolean isOracle) { - 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 " LIMIT " + count + (offset <= 0 ? "" : " OFFSET " + offset); // DELETE, UPDATE 不支持 OFFSET - } - - //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - @Override - public Map getWhere() { - return where; - } - - @Override - public AbstractSQLConfig setWhere(Map where) { - this.where = where; - return this; - } - - @NotNull - @Override - public Map> getCombine() { - List andList = combine == null ? null : combine.get("&"); - if (andList == null) { - andList = where == null ? new ArrayList() : new ArrayList(where.keySet()); - if (combine == null) { - combine = new HashMap<>(); - } - combine.put("&", andList); - } - return combine; - } - - @Override - public AbstractSQLConfig setCombine(Map> combine) { - this.combine = combine; - return this; - } - - /** - * noFunctionChar = false - * - * @param key - * @return - */ - @JSONField(serialize = false) - @Override - public Object getWhere(String key) { - return getWhere(key, false); - } - //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 - - /** - * @param key - the key passed in - * @param exactMatch - whether it is exact match - * @return

use entrySet+getValue() to replace keySet+get() to enhance efficiency

- */ - @JSONField(serialize = false) - @Override - public Object getWhere(String key, boolean exactMatch) { - if (exactMatch) { - return where == null ? null : where.get(key); - } - - 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(); - } - } - } - } - return null; - } - - @Override - public AbstractSQLConfig putWhere(String key, Object value, boolean prior) { - if (key != null) { - if (where == null) { - where = new LinkedHashMap(); - } - if (value == null) { - where.remove(key); - } else { - where.put(key, value); - } - - combine = getCombine(); - List andList = combine.get("&"); - if (value == null) { - if (andList != null) { - andList.remove(key); - } - } else if (andList == null || andList.contains(key) == false) { - int i = 0; - if (andList == null) { - andList = new ArrayList<>(); - } else if (prior && andList.isEmpty() == false) { - - String idKey = getIdKey(); - String idInKey = idKey + "{}"; - String userIdKey = getUserIdKey(); - String userIdInKey = userIdKey + "{}"; - - if (andList.contains(idKey)) { - i++; - } - if (andList.contains(idInKey)) { - i++; - } - if (andList.contains(userIdKey)) { - i++; - } - if (andList.contains(userIdInKey)) { - i++; - } - } - - if (prior) { - andList.add(i, key); //userId的优先级不能比id高 0, key); - } else { - andList.add(key); //AbstractSQLExecutor.onPutColumn里getSQL,要保证缓存的SQL和查询的SQL里 where 的 key:value 顺序一致 - } - } - combine.put("&", andList); - } - return this; - } - - /** - * 获取WHERE - * - * @return - * @throws Exception - */ - @JSONField(serialize = false) - @Override - public String getWhereString(boolean hasPrefix) throws Exception { - return getWhereString(hasPrefix, getMethod(), getWhere(), getCombine(), getJoinList(), !isTest()); - } - - /** - * 获取WHERE - * - * @param method - * @param where - * @return - * @throws Exception - */ - @JSONField(serialize = false) - public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, Map> combine, List joinList, boolean verifyName) throws Exception { - Set>> combineSet = combine == null ? null : combine.entrySet(); - if (combineSet == null || combineSet.isEmpty()) { - Log.w(TAG, "getWhereString combineSet == null || combineSet.isEmpty() >> return \"\";"); - return ""; - } - - List keyList; - - String whereString = ""; - - boolean isCombineFirst = true; - int logic; - - boolean isItemFirst; - String c; - String cs; - - for (Entry> ce : combineSet) { - keyList = ce == null ? null : ce.getValue(); - if (keyList == null || keyList.isEmpty()) { - continue; - } - - if ("|".equals(ce.getKey())) { - logic = Logic.TYPE_OR; - } else if ("!".equals(ce.getKey())) { - logic = Logic.TYPE_NOT; - } else { - logic = Logic.TYPE_AND; - } - - - isItemFirst = true; - cs = ""; - for (String key : keyList) { - c = getWhereItem(key, where.get(key), method, verifyName); - - if (StringUtil.isEmpty(c, true)) {//避免SQL条件连接错误 - continue; - } - - cs += (isItemFirst ? "" : (Logic.isAnd(logic) ? AND : OR)) + "(" + c + ")"; - - isItemFirst = false; - } - - if (StringUtil.isEmpty(cs, true)) {//避免SQL条件连接错误 - continue; - } - - whereString += (isCombineFirst ? "" : AND) + (Logic.isNot(logic) ? NOT : "") + " ( " + cs + " ) "; - isCombineFirst = false; - } - - - 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) - 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 s = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; - - if (s.isEmpty() && RequestMethod.isQueryMethod(method) == false) { - throw new UnsupportedOperationException("写操作请求必须带条件!!!"); - } - - return s; - } - - /** - * @param key - * @param value - * @param method - * @param verifyName - * @return - * @throws Exception - */ - 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;"); - return null; - } - if (key.endsWith("@")) {//引用 - // key = key.substring(0, key.lastIndexOf("@")); - throw new IllegalArgumentException(TAG + ".getWhereItem: 字符 " + key + " 不合法!"); - } - - // 原始 SQL 片段 - String rawSQL = getRawSQL(key, value); - - int keyType; - if (key.endsWith("$")) { - keyType = 1; - } else if (key.endsWith("~")) { - keyType = key.charAt(key.length() - 2) == '*' ? -2 : 2; //FIXME StringIndexOutOfBoundsException - } else if (key.endsWith("%")) { - keyType = 3; - } else if (key.endsWith("{}")) { - keyType = 4; - } else if (key.endsWith("}{")) { - keyType = 5; - } else if (key.endsWith("<>")) { - keyType = 6; - } else if (key.endsWith(">=")) { - keyType = 7; - } else if (key.endsWith("<=")) { - keyType = 8; - } else if (key.endsWith(">")) { - keyType = 9; - } else if (key.endsWith("<")) { - keyType = 10; - } else { // else绝对不能省,避免再次踩坑! keyType = 0; 写在for循环外面都没注意! - keyType = 0; - } - - key = getRealKey(method, key, false, true, verifyName); - - switch (keyType) { - case 1: - return getSearchString(key, value, rawSQL); - case -2: - case 2: - return getRegExpString(key, value, keyType < 0, rawSQL); - case 3: - return getBetweenString(key, value, rawSQL); - case 4: - return getRangeString(key, value, rawSQL); - case 5: - return getExistsString(key, value, rawSQL); - case 6: - return getContainString(key, value, rawSQL); - case 7: - return getCompareString(key, value, ">=", rawSQL); - case 8: - return getCompareString(key, value, "<=", rawSQL); - case 9: - return getCompareString(key, value, ">", rawSQL); - case 10: - return getCompareString(key, value, "<", rawSQL); - default: // TODO MySQL JSON类型的字段对比 key='[]' 会无结果! key LIKE '[1, 2, 3]' //TODO MySQL , 后面有空格! - return getEqualString(key, 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) { - throw new IllegalArgumentException(key + ":value 中value不合法!非PUT请求只支持 [Boolean, Number, String] 内的类型 !"); - } - - boolean not = key.endsWith("!"); // & | 没有任何意义,写法多了不好控制 - if (not) { - key = key.substring(0, key.length() - 1); - } - if (StringUtil.isName(key) == false) { - throw new IllegalArgumentException(key + ":value 中key不合法!不支持 ! 以外的逻辑符 !"); - } - - return getKey(key) + (not ? " != " : " = ") + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(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] 内的类型 !"); - } - if (StringUtil.isName(key) == false) { - throw new IllegalArgumentException(key + type + ":value 中key不合法!比较运算 [>, <, >=, <=] 不支持 [&, !, |] 中任何逻辑运算符 !"); - } - - return getKey(key) + " " + type + " " + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(value))); - } - - public String getKey(String key) { - if (isTest()) { - if (key.contains("'")) { // || key.contains("#") || key.contains("--")) { - throw new IllegalArgumentException("参数 " + key + " 不合法!key 中不允许有单引号 ' !"); - } - return getSQLValue(key).toString(); - } - - return getSQLKey(key); - } - - public String getSQLKey(String key) { - String q = getQuote(); - return (isKeyPrefix() ? getAliasWithQuote() + "." : "") + q + key + q; - } - - /** - * 使用prepareStatement预编译,值为 ? ,后续动态set进去 - */ - private List preparedValueList = new ArrayList<>(); - - private Object getValue(@NotNull Object value) { - if (isPrepared()) { - preparedValueList.add(value); - return "?"; - } - return getSQLValue(value); - } - - public Object getSQLValue(@NotNull Object value) { - // return (value instanceof Number || value instanceof Boolean) && DATABASE_POSTGRESQL.equals(getDatabase()) ? value : "'" + value + "'"; - return (value instanceof Number || value instanceof Boolean) ? value : "'" + value + "'"; //MySQL 隐式转换用不了索引 - } - - @Override - public List getPreparedValueList() { - return preparedValueList; - } - - @Override - public AbstractSQLConfig setPreparedValueList(List preparedValueList) { - this.preparedValueList = preparedValueList; - return this; - } - - //$ search <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - /** - * search key match value - * - * @param in - * @return {@link #getSearchString(String, Object[], int)} - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getSearchString(String key, 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, "getSearchString key = " + key); - - JSONArray arr = newJSONArray(value); - if (arr.isEmpty()) { - return ""; - } - return getSearchString(key, arr.toArray(), logic.getType()); - } - - /** - * search key match values - * - * @param in - * @return LOGIC [ key LIKE 'values[i]' ] - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getSearchString(String key, Object[] values, int type) throws IllegalArgumentException { - if (values == null || values.length <= 0) { - return ""; - } - - String condition = ""; - 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[]!"); - } - if (((String) v).isEmpty()) { // 允许查空格 StringUtil.isEmpty((String) v, true) - 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); - } - - return getCondition(Logic.isNot(type), condition); - } - - /** - * WHERE key LIKE 'value' - * - * @param key - * @param value - * @return key LIKE 'value' - */ - @JSONField(serialize = false) - public String getLikeString(String key, Object value) { - return getKey(key) + " LIKE " + getValue(value); - } - - //$ search >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - //~ regexp <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - /** - * search key match RegExp values - * - * @param key - * @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 { - 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, "getRegExpString key = " + key); - - JSONArray arr = newJSONArray(value); - if (arr.isEmpty()) { - return ""; - } - return getRegExpString(key, arr.toArray(), logic.getType(), ignoreCase); - } - - /** - * search key match RegExp values - * - * @param key - * @param values - * @param type - * @param ignoreCase - * @return LOGIC [ key REGEXP 'values[i]' ] - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getRegExpString(String key, Object[] values, int type, boolean ignoreCase) throws IllegalArgumentException { - if (values == null || values.length <= 0) { - return ""; - } - - String condition = ""; - for (int i = 0; i < values.length; i++) { - if (values[i] instanceof String == false) { - throw new IllegalArgumentException(key + "$:value 中value的类型只能为String或String[]!"); - } - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getRegExpString(key, (String) values[i], ignoreCase); - } - - return getCondition(Logic.isNot(type), condition); - } - - /** - * WHERE key REGEXP 'value' - * - * @param key - * @param value - * @param ignoreCase - * @return key REGEXP 'value' - */ - @JSONField(serialize = false) - public String getRegExpString(String key, String value, boolean ignoreCase) { - if (isPostgreSQL()) { - return getKey(key) + " ~" + (ignoreCase ? "* " : " ") + getValue(value); - } - if (isOracle()) { - return "regexp_like(" + getKey(key) + ", " + getValue(value) + (ignoreCase ? ", 'i'" : ", 'c'") + ")"; - } - return getKey(key) + " REGEXP " + (ignoreCase ? "" : "BINARY ") + getValue(value); - } - //~ regexp >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - //% between <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - /** - * WHERE key BETWEEN 'start' AND 'end' - * - * @param key - * @param value 'start,end' - * @return LOGIC [ key BETWEEN 'start' AND 'end' ] - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getBetweenString(String key, 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, "getBetweenString key = " + key); - - JSONArray arr = newJSONArray(value); - if (arr.isEmpty()) { - return ""; - } - return getBetweenString(key, arr.toArray(), logic.getType()); - } - - /** - * WHERE key BETWEEN 'start' AND 'end' - * - * @param key - * @param value 'start,end' TODO 在 '1,2' 和 ['1,2', '3,4'] 基础上新增支持 [1, 2] 和 [[1,2], [3,4]] ? - * @return LOGIC [ key BETWEEN 'start' AND 'end' ] - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getBetweenString(String key, Object[] values, int type) throws IllegalArgumentException { - if (values == null || values.length <= 0) { - return ""; - } - - String condition = ""; - String[] vs; - for (int i = 0; i < values.length; i++) { - if (values[i] instanceof String == false) { - 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 的规则 !"); - } - - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + "(" + getBetweenString(key, (Object) vs[0], (Object) vs[1]) + ")"; - } - - return getCondition(Logic.isNot(type), condition); - } - - /** - * WHERE key BETWEEN 'start' AND 'end' - * - * @param key - * @param value 'start,end' TODO 在 '1,2' 和 ['1,2', '3,4'] 基础上新增支持 [1, 2] 和 [[1,2], [3,4]] ? - * @return key BETWEEN 'start' AND 'end' - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getBetweenString(String key, 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 的规则 !"); - } - return getKey(key) + " BETWEEN " + getValue(start) + AND + getValue(end); - } - - - //% between >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - //{} range <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - - /** - * WHERE key > 'key0' AND key <= 'key1' AND ... - * - * @param key - * @param range "condition0,condition1..." - * @return key condition0 AND key condition1 AND ... - * @throws Exception - */ - @JSONField(serialize = false) - public String getRangeString(String key, Object range, String rawSQL) throws Exception { - Log.i(TAG, "getRangeString key = " + key); - if (range == null) {//依赖的对象都没有给出有效值,这个存在无意义。如果是客户端传的,那就能在客户端确定了。 - throw new NotExistException(TAG + "getRangeString(" + key + ", " + range - + ") range == null"); - } - - Logic logic = new Logic(key); - String k = logic.getKey(); - Log.i(TAG, "getRangeString k = " + k); - - if (range instanceof List) { - if (rawSQL != null) { - throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + "{} 不合法!" - + "Raw SQL 不支持 key{}:[] 这种键值对!"); - } - - if (logic.isOr() || logic.isNot()) { - List l = (List) range; - if (logic.isNot() && l.isEmpty()) { - return ""; // key!{}: [] 这个条件无效,加到 SQL 语句中 key IN() 会报错,getInString 里不好处理 - } - return getKey(k) + getInString(k, l.toArray(), logic.isNot()); - } - throw new IllegalArgumentException(key + "{}\":[] 中 {} 前面的逻辑运算符错误!只能用'|','!'中的一种 !"); - } else if (range instanceof String) {//非Number类型需要客户端拼接成 < 'value0', >= 'value1'这种 - String condition = ""; - String[] cs = rawSQL != null ? null : StringUtil.split((String) range, false); - - if (rawSQL != null) { - int index = rawSQL == null ? -1 : 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; - 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); - } 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 + " !不允许连续减号 -- !不允许空格!"); - } - - 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 则按原来位置拼接 - } - } - if (condition.isEmpty()) { - return ""; - } - - return getCondition(logic.isNot(), condition); - } 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() - + "!range 只能是 用','分隔条件的字符串 或者 可取选项JSONArray!"); - } - - /** - * WHERE key IN ('key0', 'key1', ... ) - * - * @param in - * @return IN ('key0', 'key1', ... ) - * @throws NotExistException - */ - @JSONField(serialize = false) - public String getInString(String key, 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])); - } - } - if (condition.isEmpty()) {//条件如果存在必须执行,不能忽略。条件为空会导致出错,又很难保证条件不为空(@:条件),所以还是这样好 - throw new NotExistException(TAG + ".getInString(" + key + ", [], " + not - + ") >> condition.isEmpty() >> IN()"); - } - return (not ? NOT : "") + " IN (" + condition + ")"; - } - //{} range >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - //}{ exists <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - /** - * WHERE EXISTS subquery - * 如果合并到 getRangeString,一方面支持不了 [1,2,2] 和 ">1" (转成 EXISTS(SELECT IN ) 需要static newSQLConfig,但它不能传入子类实例,除非不是 static),另一方面多了子查询临时表性能会比 IN 差 - * - * @param key - * @param value - * @return EXISTS ALL(SELECT ...) - * @throws NotExistException - */ - @JSONField(serialize = false) - public String getExistsString(String key, Object value, String rawSQL) throws Exception { - if (rawSQL != null) { - throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key}{ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); - } - if (value == null) { - return ""; - } - if (value instanceof Subquery == false) { - throw new IllegalArgumentException(key + "}{:subquery 类型为" + value.getClass().getSimpleName() - + "!subquery 只能是 子查询JSONObejct!"); - } - - Logic logic = new Logic(key); - key = logic.getKey(); - Log.i(TAG, "getExistsString key = " + key); - - return (logic.isNot() ? NOT : "") + " EXISTS " + getSubqueryString((Subquery) value); - } - //}{ exists >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - //<> contain <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - /** - * WHERE key contains value - * - * @param key - * @param value - * @return {@link #getContainString(String, Object[], int)} - * @throws NotExistException - */ - @JSONField(serialize = false) - public String getContainString(String key, 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); - - return getContainString(key, newJSONArray(value).toArray(), logic.getType()); - } - - /** - * WHERE key contains childs - * - * @param key - * @param childs null ? "" : (empty ? no child : contains childs) - * @param type |, &, ! - * @return LOGIC [ ( key LIKE '[" + childs[i] + "]' OR key LIKE '[" + childs[i] + ", %' - * OR key LIKE '%, " + childs[i] + ", %' OR key LIKE '%, " + childs[i] + "]' ) ] - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getContainString(String key, 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!"); - } - - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)); - if (isPostgreSQL()) { - condition += (getKey(key) + " @> " + getValue(newJSONArray(c))); //operator does not exist: jsonb @> character varying "[" + c + "]"); - } else if (isOracle()) { - condition += ("json_textcontains(" + getKey(key) + ", '$', " + getValue(c.toString()) + ")"); - } else { - boolean isNum = c instanceof Number; - String v = (isNum ? "" : "\"") + childs[i] + (isNum ? "" : "\""); - condition += ("json_contains(" + getKey(key) + ", " + getValue(v) + ")"); - } - } - } - if (condition.isEmpty()) { - condition = (getKey(key) + SQL.isNull(true) + OR + getLikeString(key, "[]")); // key = '[]' 无结果! - } else { - condition = (getKey(key) + SQL.isNull(false) + AND + "(" + condition + ")"); - } - } - if (condition.isEmpty()) { - return ""; - } - return getCondition(not, condition); - } - //<> contain >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - //key@:{} Subquery <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - @Override - public String getSubqueryString(Subquery subquery) throws Exception { - String range = subquery.getRange(); - SQLConfig cfg = subquery.getConfig(); - - cfg.setPreparedValueList(new ArrayList<>()); - String sql = (range == null || range.isEmpty() ? "" : range) + "(" + cfg.getSQL(isPrepared()) + ") "; - - preparedValueList.addAll(cfg.getPreparedValueList()); - - return sql; - } - - //key@:{} Subquery >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - /** - * 拼接条件 - * - * @param not - * @param condition - * @return - */ - private static String getCondition(boolean not, String condition) { - return not ? NOT + "(" + condition + ")" : condition; - } - - - /** - * 转为JSONArray - * - * @param tv - * @return - */ - @NotNull - public static JSONArray newJSONArray(Object obj) { - JSONArray array = new JSONArray(); - if (obj != null) { - if (obj instanceof Collection) { - array.addAll((Collection) obj); - } else { - array.add(obj); - } - } - return array; - } - - //WHERE >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - //SET <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - /** - * 获取SET - * - * @return - * @throws Exception - */ - @JSONField(serialize = false) - public String getSetString() throws Exception { - return getSetString(getMethod(), getContent(), !isTest()); - } - //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 - - /** - * 获取SET - * - * @param method -the method used - * @param content -the content map - * @return - * @throws Exception

use entrySet+getValue() to replace keySet+get() to enhance efficiency

- */ - @JSONField(serialize = false) - public String getSetString(RequestMethod method, Map content, boolean verifyName) throws Exception { - Set set = content == null ? null : content.keySet(); - String setString = ""; - - if (set != null && set.size() > 0) { - boolean isFirst = true; - int keyType;// 0 - =; 1 - +, 2 - - - Object value; - - String idKey = getIdKey(); - for (Entry entry : content.entrySet()) { - String key = entry.getKey(); - //避免筛选到全部 value = key == null ? null : content.get(key); - if (key == null || idKey.equals(key)) { - continue; - } - - if (key.endsWith("+")) { - keyType = 1; - } else if (key.endsWith("-")) { - keyType = 2; - } else { - keyType = 0; //注意重置类型,不然不该加减的字段会跟着加减 - } - value = entry.getValue(); - key = getRealKey(method, key, false, true, verifyName); - - setString += (isFirst ? "" : ", ") + (getKey(key) + " = " + (keyType == 1 ? getAddString(key, value) : (keyType == 2 - ? getRemoveString(key, value) : getValue(value)))); - - isFirst = false; - } - } - - if (setString.isEmpty()) { - throw new IllegalArgumentException("PUT 请求必须在Table内设置要修改的 key:value !"); - } - return " SET " + setString; - } - - /** - * SET key = concat(key, 'value') - * - * @param key - * @param value - * @return concat(key, ' value ') - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getAddString(String key, Object value) throws IllegalArgumentException { - if (value instanceof Number) { - return getKey(key) + " + " + value; - } - if (value instanceof String) { - return SQL.concat(getKey(key), (String) getValue(value)); - } - throw new IllegalArgumentException(key + "+ 对应的值 " + value + " 不是Number,String,Array中的任何一种!"); - } - - /** - * SET key = replace(key, 'value', '') - * - * @param key - * @param value - * @return REPLACE (key, 'value', '') - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getRemoveString(String key, Object value) throws IllegalArgumentException { - if (value instanceof Number) { - return getKey(key) + " - " + value; - } - if (value instanceof String) { - return SQL.replace(getKey(key), (String) getValue(value), "''");// " replace(" + key + ", '" + value + "', '') "; - } - throw new IllegalArgumentException(key + "- 对应的值 " + value + " 不是Number,String,Array中的任何一种!"); - } - //SET >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - /** - * @return - * @throws Exception - */ - @JSONField(serialize = false) - @Override - public String getSQL(boolean prepared) throws Exception { - return getSQL(this.setPrepared(prepared)); - } - - /** - * @param config - * @return - * @throws Exception - */ - public static String getSQL(AbstractSQLConfig config) throws Exception { - if (config == null) { - Log.i(TAG, "getSQL config == null >> return null;"); - return null; - } - - //TODO procedure 改为 List procedureList; behind : true; function: callFunction(); String key; ... - // for (...) { Call procedure1();\n SQL \n; Call procedure2(); ... } - // 貌似不需要,因为 ObjecParser 里就已经处理的顺序等,只是这里要解决下 Schema 问题。 - - String sch = config.getSQLSchema(); - if (StringUtil.isNotEmpty(config.getProcedure(), true)) { - String q = config.getQuote(); - return "CALL " + q + sch + q + "." + config.getProcedure(); - } - - String tablePath = config.getTablePath(); - if (StringUtil.isNotEmpty(tablePath, true) == false) { - Log.i(TAG, "getSQL StringUtil.isNotEmpty(tablePath, true) == false >> return null;"); - return null; - } - - switch (config.getMethod()) { - 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() : ""); - case DELETE: - 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)) { - 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(); - } - - config.setPreparedValueList(new ArrayList()); - 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 " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString(); - } - } - - /** - * 获取条件SQL字符串 - * - * @param page - * @param column - * @param table - * @param where - * @return - * @throws Exception - */ - private static String getConditionString(String column, String table, AbstractSQLConfig config) throws Exception { - String where = config.getWhereString(true); - - Subquery from = config.getFrom(); - if (from != null) { - 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(); - - //no need to optimize - // if (config.getPage() <= 0 || ID.equals(column.trim())) { - return condition; // config.isOracle() ? condition : condition + config.getLimitString(); - // } - // - // - // //order: id+ -> id >= idOfStartIndex; id- -> id <= idOfStartIndex <<<<<<<<<<<<<<<<<<< - // String order = StringUtil.getNoBlankString(config.getOrder()); - // List orderList = order.isEmpty() ? null : Arrays.asList(StringUtil.split(order)); - // - // int type = 0; - // if (BaseModel.isEmpty(orderList) || BaseModel.isContain(orderList, ID+"+")) { - // type = 1; - // } - // else if (BaseModel.isContain(orderList, ID+"-")) { - // type = 2; - // } - // - // if (type > 0) { - // return condition.replace("WHERE", - // "WHERE id " + (type == 1 ? ">=" : "<=") + " (SELECT id FROM " + table - // + where + " ORDER BY id " + (type == 1 ? "ASC" : "DESC") + " LIMIT " + config.getOffset() + ", 1) AND" - // ) - // + " LIMIT " + config.getCount(); //子查询起始id不一定准确,只能作为最小可能! ;// - // } - // //order: id+ -> id >= idOfStartIndex; id- -> id <= idOfStartIndex >>>>>>>>>>>>>>>>>> - // - // - // //结果错误!SELECT * FROM User AS t0 INNER JOIN - // (SELECT id FROM User ORDER BY date ASC LIMIT 20, 10) AS t1 ON t0.id = t1.id - // //common case, inner join - // condition += config.getLimitString(); - // return table + " AS t0 INNER JOIN (SELECT id FROM " + condition + ") AS t1 ON t0.id = t1.id"; - } - - - private boolean keyPrefix; - - @Override - public boolean isKeyPrefix() { - return keyPrefix; - } - - @Override - public AbstractSQLConfig setKeyPrefix(boolean keyPrefix) { - this.keyPrefix = keyPrefix; - return this; - } - - - public String getJoinString() throws Exception { - String joinOns = ""; - - if (joinList != null) { - String quote = getQuote(); - 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) - continue; - } - String type = j.getJoinType(); - - //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(); - jc.setPrepared(isPrepared()); - - jt = StringUtil.isEmpty(jc.getAlias(), true) ? jc.getTable() : jc.getAlias(); - tt = j.getTargetTable(); - - //如果要强制小写,则可在子类重写这个方法再 toLowerCase - // if (DATABASE_POSTGRESQL.equals(getDatabase())) { - // jt = jt.toLowerCase(); - // tn = tn.toLowerCase(); - // } - - switch (type) { - //前面已跳过 case "@": // APP JOIN - // continue; - - case "*": // CROSS JOIN - onGetCrossJoinString(j); - case "<": // LEFT JOIN - 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; - jc.setMain(false).setKeyPrefix(true); - - pvl.addAll(jc.getPreparedValueList()); - changed = true; - 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 - sql = " INNER JOIN " + jc.getTablePath() - + " ON " + quote + jt + quote + "." + quote + j.getKey() + quote + " = " + quote + tt + quote + "." + quote + j.getTargetKey() + quote; - break; - default: - throw new UnsupportedOperationException( - "join:value 中 value 里的 " + jt + "/" + j.getPath() - + "错误!不支持 " + jt + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" - + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" - ); - } - - joinOns += " \n " + sql; - } - - - if (changed) { - pvl.addAll(preparedValueList); - preparedValueList = pvl; - } - - } - - return joinOns; - } - - protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { - throw new UnsupportedOperationException("已禁用 * CROSS JOIN !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); - } - - /** - * 新建SQL配置 - * - * @param table - * @param request - * @param joinList - * @param isProcedure - * @param callback - * @return - * @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!"); - } - - boolean explain = request.getBooleanValue(KEY_EXPLAIN); - if (explain && Log.DEBUG == false) { //不在 config.setExplain 抛异常,一方面处理更早性能更好,另一方面为了内部调用可以绕过这个限制 - throw new UnsupportedOperationException("DEBUG 模式下不允许传 " + KEY_EXPLAIN + " !"); - } - - String database = request.getString(KEY_DATABASE); - if (StringUtil.isEmpty(database, false) == false && DATABASE_LIST.contains(database) == false) { - throw new UnsupportedDataTypeException("@database:value 中 value 错误,只能是 [" + StringUtil.getString(DATABASE_LIST.toArray()) + "] 中的一种!"); - } - - String schema = request.getString(KEY_SCHEMA); - String datasource = request.getString(KEY_DATASOURCE); - - SQLConfig config = callback.getSQLConfig(method, database, schema, table); - config.setAlias(alias); - - config.setDatabase(database); //不删,后面表对象还要用的,必须放在 parseJoin 前 - config.setSchema(schema); //不删,后面表对象还要用的 - config.setDatasource(datasource); //不删,后面表对象还要用的 - - if (isProcedure) { - return config; - } - - config = parseJoin(method, config, joinList, callback); //放后面会导致主表是空对象时 joinList 未解析 - - if (request.isEmpty()) { // User:{} 这种空内容在查询时也有效 - return config; //request.remove(key); 前都可以直接return,之后必须保证 put 回去 - } - - - String idKey = callback.getIdKey(database, schema, table); - String idInKey = idKey + "{}"; - String userIdKey = callback.getUserIdKey(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); - 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); - if ((d instanceof Number && ((Number) d).longValue() > 0) || (d instanceof String && StringUtil.isNotEmpty(d, true))) { - newIdIn.add(d); - } - } - if (newIdIn.isEmpty()) { - throw new NotExistException(TAG + ": newSQLConfig idIn instanceof List >> 去掉无效 id 后 newIdIn.isEmpty()"); - } - idIn = newIdIn; - - if (method == DELETE || method == PUT) { - config.setCount(newIdIn.size()); - } - } - - Object id = request.get(idKey); - boolean hasId = id != null; - if (method == POST && hasId == false) { - id = callback.newId(method, database, schema, table); // null 表示数据库自增 id - } - - if (id != null) { //null无效 - if (id instanceof Number) { - if (((Number) id).longValue() <= 0) { //一定没有值 - throw new NotExistException(TAG + ": newSQLConfig " + table + ".id <= 0"); - } - } else if (id instanceof String) { - if (StringUtil.isEmpty(id, true)) { //一定没有值 - throw new NotExistException(TAG + ": newSQLConfig StringUtil.isEmpty(" + table + ".id, true)"); - } - } else if (id instanceof Subquery) { - } else { - throw new IllegalArgumentException(idKey + ":value 中 value 的类型只能是 Long , String 或 Subquery !"); - } - - if (idIn instanceof List) { //共用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); - if (d != null && id.toString().equals(d.toString())) { - contains = true; - break; - } - } - if (contains == false) {//empty有效 BaseModel.isEmpty(idIn) == false) { - throw new NotExistException(TAG + ": newSQLConfig idIn != null && (((List) idIn).contains(id) == false"); - } - } - - if (method == DELETE || method == PUT) { - config.setCount(1); - } - } - - - 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 group = request.getString(KEY_GROUP); - String having = request.getString(KEY_HAVING); - String order = request.getString(KEY_ORDER); - String raw = request.getString(KEY_RAW); - String json = request.getString(KEY_JSON); - - try { - //强制作为条件且放在最前面优化性能 - request.remove(idKey); - request.remove(idInKey); - //关键词 - request.remove(KEY_ROLE); - request.remove(KEY_EXPLAIN); - request.remove(KEY_CACHE); - request.remove(KEY_DATABASE); - request.remove(KEY_SCHEMA); - request.remove(KEY_COMBINE); - request.remove(KEY_FROM); - request.remove(KEY_COLUMN); - request.remove(KEY_GROUP); - request.remove(KEY_HAVING); - request.remove(KEY_ORDER); - request.remove(KEY_RAW); - request.remove(KEY_JSON); - - String[] rawArr = StringUtil.split(raw); - config.setRaw(rawArr == null || rawArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(rawArr))); - - Map tableWhere = new LinkedHashMap();//保证顺序好优化 WHERE id > 1 AND name LIKE... - - //已经remove了id和id{},以及@key - Set set = request.keySet(); //前面已经判断request是否为空 - if (method == POST) { //POST操作 - if (idIn != null) { - throw new IllegalArgumentException("POST 请求中不允许传 " + idInKey + " !"); - } - - if (set != null && set.isEmpty() == false) { //不能直接return,要走完下面的流程 - String[] columns = set.toArray(new String[]{}); - - Collection valueCollection = request.values(); - Object[] values = valueCollection == null ? null : valueCollection.toArray(); - - if (values == null || values.length != columns.length) { - throw new Exception("服务器内部错误:\n" + TAG - + " newSQLConfig values == null || values.length != columns.length !"); - } - column = (id == null ? "" : idKey + ",") + 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数量为准 - - items = new ArrayList<>(size); - items.add(id); //idList.get(i)); //第0个就是id - - for (int j = 1; j < size; j++) { - items.add(values[j - 1]); //从第1个开始,允许"null" - } - } - - valuess.add(items); - config.setValues(valuess); - } - } else { //非POST操作 - final boolean isWhere = method != PUT;//除了POST,PUT,其它全是条件!!! - - //条件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - 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 !"); - } - 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 { - 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 + "] 其中任何一个!"); - } - } - - 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); - } - } - - } - - //条件>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - Map tableContent = new LinkedHashMap(); - Object value; - for (String key : set) { - value = request.get(key); - - if (value instanceof Map) {//只允许常规Object - throw new IllegalArgumentException("不允许 " + key + " 等任何key的value类型为 {JSONObject} !"); - } - - //解决AccessVerifier新增userId没有作为条件,而是作为内容,导致PUT,DELETE出错 - if (isWhere) { - tableWhere.put(key, value); - if (whereList == null || whereList.contains(key) == false) { - andList.add(key); - } - } else if (whereList != null && whereList.contains(key)) { - tableWhere.put(key, value); - } else { - tableContent.put(key, value);//一样 instanceof JSONArray ? JSON.toJSONString(value) : value); - } - } - - combineMap.put("&", andList); - combineMap.put("|", orList); - combineMap.put("!", notList); - config.setCombine(combineMap); - - config.setContent(tableContent); - } - - - List cs = new ArrayList<>(); - - List rawList = config.getRaw(); - 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()); - } - } - - boolean distinct = column == null || rawColumnSQL != null ? false : column.startsWith(PREFFIX_DISTINCT); - if (rawColumnSQL == null) { - String[] fks = StringUtil.split(distinct ? column.substring(PREFFIX_DISTINCT.length()) : column, ";"); // key0,key1;fun0(key0,...);fun1(key0,...);key3;fun2(key0,...) - if (fks != null) { - 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()); - } - } - - if (fk.contains("(")) { // fun0(key0,...) - cs.add(fk); - } else { //key0,key1... - ks = StringUtil.split(fk); - if (ks != null && ks.length > 0) { - cs.addAll(Arrays.asList(ks)); - } - } - } - } - } - - 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.setId(id); - //在 tableWhere 第0个 config.setIdIn(idIn); - - config.setRole(RequestRole.get(role)); - 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 等要合并 - - } finally {//后面还可能用到,要还原 - //id或id{}条件 - if (hasId) { - request.put(idKey, id); - } - request.put(idInKey, idIn); - //关键词 - request.put(KEY_DATABASE, database); - request.put(KEY_ROLE, role); - request.put(KEY_EXPLAIN, explain); - request.put(KEY_CACHE, cache); - request.put(KEY_SCHEMA, schema); - request.put(KEY_COMBINE, combine); - request.put(KEY_FROM, from); - request.put(KEY_COLUMN, column); - request.put(KEY_GROUP, group); - request.put(KEY_HAVING, having); - request.put(KEY_ORDER, order); - request.put(KEY_RAW, raw); - request.put(KEY_JSON, json); - } - - return config; - } - - - /** - * @param method - * @param config - * @param joinList - * @param callback - * @return - * @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); - - //TODO 解析出 SQLConfig 再合并 column, order, group 等 - if (joinList == null || joinList.isEmpty() || RequestMethod.isQueryMethod(method) == false) { - return config; - } - - - String table; - String alias; - for (Join j : joinList) { - table = j.getTable(); - 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); - - if (j.isAppJoin() == false) { //除了 @ APP JOIN,其它都是 SQL JOIN,则副表要这样配置 - if (joinConfig.getDatabase() == null) { - joinConfig.setDatabase(config.getDatabase()); //解决主表 JOIN 副表,引号不一致 - } else if (joinConfig.getDatabase().equals(config.getDatabase()) == false) { - throw new IllegalArgumentException("主表 " + config.getTable() + " 的 @database:" + config.getDatabase() + " 和它 SQL JOIN 的副表 " + table + " 的 @database:" + joinConfig.getDatabase() + " 不一致!"); - } - if (joinConfig.getSchema() == null) { - joinConfig.setSchema(config.getSchema()); //主表 JOIN 副表,默认 schema 一致 - } - - if (cacheConfig != null) { - cacheConfig.setDatabase(joinConfig.getDatabase()).setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 - } - - - if (isQuery) { - config.setKeyPrefix(true); - } - - joinConfig.setMain(false).setKeyPrefix(true); - - if (j.isLeftOrRightJoin()) { - 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); - } - } - - //解决 query: 1/2 查数量时报错 + private static final String TAG = "AbstractSQLConfig"; + + public static String DEFAULT_DATABASE = DATABASE_MYSQL; + public static String DEFAULT_SCHEMA = "sys"; + public static String PREFFIX_DISTINCT = "DISTINCT "; + + // * 和 / 不能同时出现,防止 /* */ 段注释! # 和 -- 不能出现,防止行注释! ; 不能出现,防止隔断SQL语句!空格不能出现,防止 CRUD,DROP,SHOW TABLES等语句! + private static final Pattern PATTERN_RANGE; + private static final Pattern PATTERN_FUNCTION; + + /** + * 表名映射,隐藏真实表名,对安全要求很高的表可以这么做 + */ + public static final Map 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 改成更好的正则,校验前面为单词,中间为操作符,后面为值 + + + TABLE_KEY_MAP = new HashMap(); + TABLE_KEY_MAP.put(Table.class.getSimpleName(), Table.TABLE_NAME); + TABLE_KEY_MAP.put(Column.class.getSimpleName(), Column.TABLE_NAME); + TABLE_KEY_MAP.put(PgClass.class.getSimpleName(), PgClass.TABLE_NAME); + TABLE_KEY_MAP.put(PgAttribute.class.getSimpleName(), PgAttribute.TABLE_NAME); + TABLE_KEY_MAP.put(SysTable.class.getSimpleName(), SysTable.TABLE_NAME); + TABLE_KEY_MAP.put(SysColumn.class.getSimpleName(), SysColumn.TABLE_NAME); + TABLE_KEY_MAP.put(ExtendedProperty.class.getSimpleName(), ExtendedProperty.TABLE_NAME); + + 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()); + + + DATABASE_LIST = new ArrayList<>(); + DATABASE_LIST.add(DATABASE_MYSQL); + DATABASE_LIST.add(DATABASE_POSTGRESQL); + DATABASE_LIST.add(DATABASE_SQLSERVER); + DATABASE_LIST.add(DATABASE_ORACLE); + DATABASE_LIST.add(DATABASE_DB2); + + + 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...) + + } + + + @Override + public boolean limitSQLCount() { + return Log.DEBUG == false || AbstractVerifier.SYSTEM_ACCESS_MAP.containsKey(getTable()) == false; + } + + @NotNull + @Override + public String getIdKey() { + return KEY_ID; + } + @NotNull + @Override + public String getUserIdKey() { + return KEY_USER_ID; + } + + + private Object id; //Table的id + private RequestMethod method; //操作方法 + private boolean prepared = true; //预编译 + private boolean main = true; + /** + * TODO 被关联的表通过就忽略关联的表?(这个不行 User:{"sex@":"/Comment/toId"}) + */ + private RequestRole role; //发送请求的用户的角色 + private boolean distinct = false; + private String database; //表所在的数据库类型 + private String schema; //表所在的数据库名 + private String datasource; //数据源 + private String table; //表名 + private String alias; //表别名 + private String group; //分组方式的字符串数组,','分隔 + private String having; //聚合函数的字符串数组,','分隔 + private String order; //排序方式的字符串数组,','分隔 + private List raw; //需要保留原始 SQL 的字段,','分隔 + private List json; //需要转为 JSON 的字段,','分隔 + private Subquery from; //子查询临时表 + private List column; //表内字段名(或函数名,仅查询操作可用)的字符串数组,','分隔 + private List> values; //对应表内字段的值的字符串数组,','分隔 + private Map content; //Request内容,key:value形式,column = content.keySet(),values = content.values() + private Map where; //筛选条件,key:value形式 + private Map> combine; //条件组合,{ "&":[key], "|":[key], "!":[key] } + + + //array item <<<<<<<<<< + private int count; //Table数量 + private int page; //Table所在页码 + private int position; //Table在[]中的位置 + private int query; //JSONRequest.query + private int type; //ObjectParser.type + private int cache; + private boolean explain; + + private List joinList; //连表 配置列表 + //array item >>>>>>>>>> + private boolean test; //测试 + + private String procedure; + + public SQLConfig setProcedure(String procedure) { + this.procedure = procedure; + return this; + } + public String getProcedure() { + return procedure; + } + + public AbstractSQLConfig(RequestMethod method) { + setMethod(method); + } + public AbstractSQLConfig(RequestMethod method, String table) { + this(method); + setTable(table); + } + public AbstractSQLConfig(RequestMethod method, int count, int page) { + this(method); + setCount(count); + setPage(page); + } + + @NotNull + @Override + public RequestMethod getMethod() { + if (method == null) { + method = GET; + } + return method; + } + @Override + public AbstractSQLConfig setMethod(RequestMethod method) { + this.method = method; + return this; + } + @Override + public boolean isPrepared() { + return prepared; + } + @Override + public AbstractSQLConfig setPrepared(boolean prepared) { + this.prepared = prepared; + return this; + } + @Override + public boolean isMain() { + return main; + } + @Override + public AbstractSQLConfig setMain(boolean main) { + this.main = main; + return this; + } + + + @Override + public Object getId() { + return id; + } + @Override + public AbstractSQLConfig setId(Object id) { + this.id = id; + return this; + } + + @Override + public RequestRole 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) { + this.role = role; + return this; + } + + @Override + public boolean isDistinct() { + return distinct; + } + @Override + public SQLConfig setDistinct(boolean distinct) { + this.distinct = distinct; + return this; + } + + @Override + public String getDatabase() { + return database; + } + @Override + public SQLConfig setDatabase(String database) { + this.database = database; + return this; + } + /** + * @return db == null ? DEFAULT_DATABASE : db + */ + @NotNull + public String getSQLDatabase() { + String db = getDatabase(); + return db == null ? DEFAULT_DATABASE : db; // "" 表示已设置,不需要用全局默认的 StringUtil.isEmpty(db, false)) { + } + + @Override + public boolean isMySQL() { + return isMySQL(getSQLDatabase()); + } + public static boolean isMySQL(String db) { + return DATABASE_MYSQL.equals(db); + } + @Override + public boolean isPostgreSQL() { + return isPostgreSQL(getSQLDatabase()); + } + public static boolean isPostgreSQL(String db) { + return DATABASE_POSTGRESQL.equals(db); + } + @Override + public boolean isSQLServer() { + return isSQLServer(getSQLDatabase()); + } + public static boolean isSQLServer(String db) { + return DATABASE_SQLSERVER.equals(db); + } + @Override + public boolean isOracle() { + return isOracle(getSQLDatabase()); + } + public static boolean isOracle(String db) { + return DATABASE_ORACLE.equals(db); + } + @Override + public boolean isDb2() { + return isDb2(getSQLDatabase()); + } + public static boolean isDb2(String db) { + return DATABASE_DB2.equals(db); + } + + @Override + public String getQuote() { + return isMySQL() ? "`" : "\""; + } + + @Override + public String getSchema() { + return schema; + } + /** + * @param sqlTable + * @return + */ + @NotNull + public String getSQLSchema() { + String table = getTable(); + //强制,避免因为全局默认的 @schema 自动填充进来,导致这几个类的 schema 为 sys 等其它值 + if (Table.TAG.equals(table) || Column.TAG.equals(table)) { + return SCHEMA_INFORMATION; //MySQL, PostgreSQL, SQL Server 都有的 + } + if (PgClass.TAG.equals(table) || PgAttribute.TAG.equals(table)) { + return ""; //PostgreSQL 的 pg_class 和 pg_attribute 表好像不属于任何 Schema + } + if (SysTable.TAG.equals(table) || SysColumn.TAG.equals(table) || ExtendedProperty.TAG.equals(table)) { + return SCHEMA_SYS; //SQL Server 在 sys 中的属性比 information_schema 中的要全,能拿到注释 + } + + String sch = getSchema(); + return sch == null ? DEFAULT_SCHEMA : sch; + } + @Override + public AbstractSQLConfig setSchema(String schema) { + if (schema != null) { + String quote = getQuote(); + String s = schema.startsWith(quote) && schema.endsWith(quote) ? schema.substring(1, schema.length() - 1) : schema; + if (StringUtil.isEmpty(s, true) == false && StringUtil.isName(s) == false) { + throw new IllegalArgumentException("@schema:value 中value必须是1个单词!"); + } + } + this.schema = schema; + return this; + } + + @Override + public String getDatasource() { + return datasource; + } + @Override + public SQLConfig setDatasource(String datasource) { + this.datasource = datasource; + return this; + } + + /**请求传进来的Table名 + * @return + * @see {@link #getSQLTable()} + */ + @Override + public String getTable() { + return table; + } + /**数据库里的真实Table名 + * 通过 {@link #TABLE_KEY_MAP} 映射 + * @return + */ + @JSONField(serialize = false) + @Override + public String getSQLTable() { + // String t = TABLE_KEY_MAP.containsKey(table) ? TABLE_KEY_MAP.get(table) : table; + //如果要强制小写,则可在子类重写这个方法再 toLowerCase return DATABASE_POSTGRESQL.equals(getDatabase()) ? t.toLowerCase() : t; + return TABLE_KEY_MAP.containsKey(table) ? TABLE_KEY_MAP.get(table) : table; + } + @JSONField(serialize = false) + @Override + public String getTablePath() { + String q = getQuote(); + + String sch = getSQLSchema(); + String sqlTable = getSQLTable(); + + return (StringUtil.isEmpty(sch, true) ? "" : q + sch + q + ".") + q + sqlTable + q + ( isKeyPrefix() ? " AS " + getAliasWithQuote() : ""); + } + @Override + public AbstractSQLConfig setTable(String table) { //Table已经在Parser中校验,所以这里不用防SQL注入 + this.table = table; + return this; + } + + @Override + public String getAlias() { + return alias; + } + @Override + public AbstractSQLConfig setAlias(String alias) { + this.alias = alias; + return this; + } + public String getAliasWithQuote() { + String a = getAlias(); + if (StringUtil.isEmpty(a, true)) { + a = getTable(); + } + String q = getQuote(); + //getTable 不能小写,因为Verifier用大小写敏感的名称判断权限 + //如果要强制小写,则可在子类重写这个方法再 toLowerCase return q + (DATABASE_POSTGRESQL.equals(getDatabase()) ? a.toLowerCase() : a) + q; + return q + a + q; + } + + @Override + public String getGroup() { + return group; + } + public AbstractSQLConfig setGroup(String... keys) { + return setGroup(StringUtil.getString(keys)); + } + @Override + public AbstractSQLConfig setGroup(String group) { + this.group = group; + return this; + } + @JSONField(serialize = false) + 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()); + } + + c = ((AbstractSQLConfig) cfg).getGroupString(false); + if (StringUtil.isEmpty(c, true) == false) { + joinGroup += (first ? "" : ", ") + c; + first = false; + } + + } + } + + + group = StringUtil.getTrimedString(group); + String[] keys = StringUtil.split(group); + if (keys == null || keys.length <= 0) { + return StringUtil.isEmpty(joinGroup, true) ? "" : (hasPrefix ? " GROUP BY " : "") + joinGroup; + } + + for (int i = 0; i < keys.length; i++) { + if (isPrepared()) { //不能通过 ? 来代替,因为SQLExecutor statement.setString后 GROUP BY 'userId' 有单引号,只能返回一条数据,必须去掉单引号才行! + if (StringUtil.isName(keys[i]) == false) { + throw new IllegalArgumentException("@group:value 中 value里面用 , 分割的每一项都必须是1个单词!并且不要有空格!"); + } + } + + keys[i] = getKey(keys[i]); + } + + return (hasPrefix ? " GROUP BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinGroup, ", "); + } + + @Override + public String getHaving() { + return having; + } + public AbstractSQLConfig setHaving(String... conditions) { + return setHaving(StringUtil.getString(conditions)); + } + @Override + public AbstractSQLConfig setHaving(String having) { + this.having = having; + return this; + } + /**TODO @having 改为默认 | 或连接,且支持 @having: { "key1>": 1, "key{}": "length(key2)>0", "@combine": "key1,key2" } + * @return HAVING conditoin0 AND condition1 OR condition2 ... + */ + @JSONField(serialize = false) + 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()); + } + + c = ((AbstractSQLConfig) cfg).getHavingString(false); + if (StringUtil.isEmpty(c, true) == false) { + joinHaving += (first ? "" : ", ") + c; + first = false; + } + + } + } + + String[] keys = StringUtil.split(getHaving(), ";"); + if (keys == null || keys.length <= 0) { + return StringUtil.isEmpty(joinHaving, true) ? "" : (hasPrefix ? " HAVING " : "") + joinHaving; + } + + String quote = getQuote(); + String tableAlias = getAliasWithQuote(); + + List raw = getRaw(); + boolean containRaw = raw != null && raw.contains(KEY_HAVING); + + String expression; + String method; + //暂时不允许 String prefix; + String suffix; + + //fun0(arg0,arg1,...);fun1(arg0,arg1,...) + for (int i = 0; i < keys.length; i++) { + + //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()); + } + } + + if (expression.length() > 50) { + throw new UnsupportedOperationException("@having:value 的 value 中字符串 " + expression + " 不合法!" + + "不允许传超过 50 个字符的函数或表达式!请用 @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 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) { + throw new IllegalArgumentException("字符 " + method + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 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 + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); + } + + 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() ? tableAlias + "." : "") + origin; + } + } + + keys[i] = method + "(" + StringUtil.getString(ckeys) + ")" + suffix; + } + + //TODO 支持 OR, NOT 参考 @combine:"&key0,|key1,!key2" + return (hasPrefix ? " HAVING " : "") + StringUtil.concat(StringUtil.getString(keys, AND), joinHaving, AND); + } + + @Override + public String getOrder() { + return order; + } + public AbstractSQLConfig setOrder(String... conditions) { + return setOrder(StringUtil.getString(conditions)); + } + @Override + public AbstractSQLConfig setOrder(String order) { + this.order = order; + return this; + } + @JSONField(serialize = false) + 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()); + } + + c = ((AbstractSQLConfig) cfg).getOrderString(false); + if (StringUtil.isEmpty(c, true) == false) { + joinOrder += (first ? "" : ", ") + c; + first = false; + } + + } + } + + + String order = StringUtil.getTrimedString(getOrder()); + // SELECT * FROM sys.Moment ORDER BY userId ASC, rand(); 前面的 userId ASC 和后面的 rand() 都有效 + // if ("rand()".equals(order)) { + // return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(order, joinOrder, ", "); + // } + + if (getCount() > 0 && (isOracle() || isSQLServer() || isDb2())) { // Oracle, SQL Server, DB2 的 OFFSET 必须加 ORDER BY + + // String[] ss = StringUtil.split(order); + if (StringUtil.isEmpty(order, true)) { //SQL Server 子查询内必须指定 OFFSET 才能用 ORDER BY + String idKey = getIdKey(); + if (StringUtil.isEmpty(idKey, true)) { + idKey = "id"; //ORDER BY NULL 不行,SQL Server 会报错,必须要有排序,才能使用 OFFSET FETCH,如果没有 idKey,请求中指定 @order 即可 + } + order = idKey; //让数据库调控默认升序还是降序 + "+"; + } + + //不用这么全面,毕竟没有语法问题还浪费性能,如果有其它问题,让前端传的 JSON 直接加上 @order 来解决 + // boolean contains = false; + // if (ss != null) { + // for (String s : ss) { + // if (s != null && s.startsWith(idKey)) { + // s = s.substring(idKey.length()); + // if ("+".equals(s) || "-".equals(s)) {// || " ASC ".equals(s) || " DESC ".equals(s)) { + // contains = true; + // break; + // } + // } + // } + // } + + // if (contains == false) { + // order = (ss == null || ss.length <= 0 ? "" : order + ",") + idKey + "+"; + // } + } + + + String[] keys = StringUtil.split(order); + if (keys == null || keys.length <= 0) { + return StringUtil.isEmpty(joinOrder, true) ? "" : (hasPrefix ? " ORDER BY " : "") + joinOrder; + } + + for (int i = 0; i < keys.length; i++) { + String item = keys[i]; + if ("rand()".equals(item)) { + continue; + } + + int index = item.endsWith("+") ? item.length() - 1 : -1; //StringUtil.split返回数组中,子项不会有null + String sort; + if (index < 0) { + index = item.endsWith("-") ? item.length() - 1 : -1; + sort = index <= 0 ? "" : " DESC "; + } + else { + sort = " ASC "; + } + + String origin = index < 0 ? item : item.substring(0, index); + + if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! + //这里既不对origin trim,也不对 ASC/DESC ignoreCase,希望前端严格传没有任何空格的字符串过来,减少传输数据量,节约服务器性能 + if (StringUtil.isName(origin) == false) { + throw new IllegalArgumentException("预编译模式下 @order:value 中 " + item + " 不合法! value 里面用 , 分割的" + + "每一项必须是 随机函数 rand() 或 column+ / column- 且其中 column 必须是 1 个单词!并且不要有多余的空格!"); + } + } + + keys[i] = getKey(origin) + sort; + } + + return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinOrder, ", "); + } + + @Override + public List getRaw() { + return raw; + } + @Override + public SQLConfig setRaw(List raw) { + this.raw = raw; + return this; + } + + /**获取原始 SQL 片段 + * @param key + * @param value + * @return + * @throws Exception + */ + @Override + public String getRawSQL(String key, Object value) throws Exception { + List rawList = getRaw(); + boolean containRaw = rawList != null && rawList.contains(key); + if (containRaw && value instanceof String == false) { + throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" + + "对应的 " + key + ":value 中 value 类型只能为 String!"); + } + + 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)) { + return (String) value; + } + } + + return rawSQL; + } + + + @Override + public List getJson() { + return json; + } + @Override + public AbstractSQLConfig setJson(List json) { + this.json = json; + return this; + } + + + @Override + public Subquery getFrom() { + return from; + } + @Override + public AbstractSQLConfig setFrom(Subquery from) { + this.from = from; + return this; + } + + @Override + public List getColumn() { + return column; + } + @Override + public AbstractSQLConfig setColumn(List column) { + this.column = column; + return this; + } + @JSONField(serialize = false) + public String getColumnString() throws Exception { + return getColumnString(false); + } + @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; + + for (String c : column) { + if (containRaw) { + // 由于 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); + continue; + } + } + + index = c.lastIndexOf(":"); //StringUtil.split返回数组中,子项不会有null + origin = index < 0 ? c : c.substring(0, index); + alias = index < 0 ? null : c.substring(index + 1); + + if (alias != null && StringUtil.isName(alias) == false) { + throw new IllegalArgumentException("HEAD请求: 字符 " + alias + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" + + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); + } + + if (StringUtil.isName(origin) == false) { + int start = origin.indexOf("("); + if (start < 0 || origin.lastIndexOf(")") <= start) { + 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个单词!并且不要有多余的空格!"); + } + } + } + } + + return SQL.count(column != null && column.size() == 1 && StringUtil.isName(column.get(0)) ? getKey(column.get(0)) : "*"); + case POST: + if (column == null || column.isEmpty()) { + throw new IllegalArgumentException("POST 请求必须在Table内设置要保存的 key:value !"); + } + + String s = ""; + boolean pfirst = true; + for (String c : column) { + if (isPrepared() && StringUtil.isName(c) == false) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! + throw new IllegalArgumentException("POST请求: 每一个 key:value 中的key都必须是1个单词!"); + } + s += ((pfirst ? "" : ",") + getKey(c)); + + pfirst = false; + } + + return "(" + s + ")"; + case GET: + case GETS: + boolean isQuery = RequestMethod.isQueryMethod(method); //TODO 这个有啥用?上面应是 getMethod 的值 GET 和 GETS 了。 + String joinColumn = ""; + if (isQuery && joinList != null) { + SQLConfig ecfg; + SQLConfig cfg; + String c; + boolean first = true; + for (Join j : joinList) { + 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; + first = false; + } + + inSQLJoin = true; + } + } + + String tableAlias = getAliasWithQuote(); + + // String c = StringUtil.getString(column); //id,name;json_length(contactIdList):contactCount;... + + String[] keys = column == null ? null : column.toArray(new String[]{}); //StringUtil.split(c, ";"); + if (keys == null || keys.length <= 0) { + + boolean noColumn = column != null && inSQLJoin; + String mc = isKeyPrefix() == false ? (noColumn ? "" : "*") : (noColumn ? "" : tableAlias + ".*"); + + return StringUtil.concat(mc, joinColumn, ", ", true); + } + + + 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]; + + if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 + if ("".equals(RAW_MAP.get(expression)) || RAW_MAP.containsValue(expression)) { // newSQLConfig 提前处理好的 + continue; + } + + // 简单点, 后台配置就带上 AS + // int index = expression.lastIndexOf(":"); + // String alias = expression.substring(index+1); + // boolean hasAlias = StringUtil.isName(alias); + // String pre = index > 0 && hasAlias ? expression.substring(0, index) : expression; + // if (RAW_MAP.containsValue(pre) || "".equals(RAW_MAP.get(pre))) { // newSQLConfig 提前处理好的 + // expression = pre + (hasAlias ? " AS " + alias : ""); + // continue; + // } + } + + if (expression.length() > 50) { + throw new UnsupportedOperationException("@column:value 的 value 中字符串 " + expression + " 不合法!" + + "不允许传超过 50 个字符的函数或表达式!请用 @raw 简化传参!"); + } + + + 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,...) 这种格式!"); + } + + method = expression.substring(0, start); + boolean distinct = i <= 0 && method.startsWith(PREFFIX_DISTINCT); + String fun = distinct ? method.substring(PREFFIX_DISTINCT.length()) : method; + + 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 + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); + } + } + + } + + boolean isColumn = start < 0; + + String[] ckeys = StringUtil.split(isColumn ? expression : expression.substring(start + 1, end)); + String quote = getQuote(); + + // 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); + + distinct = j <= 0 && origin.startsWith(PREFFIX_DISTINCT); + if (distinct) { + origin = origin.substring(PREFFIX_DISTINCT.length()); + } + + 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 { + // 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 个空格!其它情况不允许空格!"); + } + } + } + + //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 (isName && isKeyPrefix()) { + ckeys[j] = tableAlias + "." + origin; + // if (isColumn) { + // ckeys[j] += " AS " + quote + (isMain() ? "" : tableAlias + ".") + (StringUtil.isEmpty(alias, true) ? origin : alias) + quote; + // } + if (isColumn && StringUtil.isEmpty(alias, true) == false) { + ckeys[j] += " AS " + quote + alias + quote; + } + } else { + ckeys[j] = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); + } + + if (distinct) { + ckeys[j] = PREFFIX_DISTINCT + ckeys[j]; + } + } + // } + + } + + 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); + + 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 + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); + } + + 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); + // } + } + + } + + 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!" + ); + } + } + + + @Override + public List> getValues() { + return values; + } + @JSONField(serialize = false) + public String getValuesString() { + String s = ""; + if (values != null && values.size() > 0) { + Object[] items = new Object[values.size()]; + List vs; + for (int i = 0; i < values.size(); i++) { + vs = values.get(i); + if (vs == null) { + continue; + } + + items[i] = "("; + for (int j = 0; j < vs.size(); j++) { + items[i] += ((j <= 0 ? "" : ",") + getValue(vs.get(j))); + } + items[i] += ")"; + } + s = StringUtil.getString(items); + } + return s; + } + @Override + public AbstractSQLConfig setValues(List> valuess) { + this.values = valuess; + return this; + } + + @Override + public Map getContent() { + return content; + } + @Override + public AbstractSQLConfig setContent(Map content) { + this.content = content; + return this; + } + + @Override + public int getCount() { + return count; + } + @Override + public AbstractSQLConfig setCount(int count) { + this.count = count; + return this; + } + @Override + public int getPage() { + return page; + } + @Override + public AbstractSQLConfig setPage(int page) { + this.page = page; + return this; + } + @Override + public int getPosition() { + return position; + } + @Override + public AbstractSQLConfig setPosition(int position) { + this.position = position; + return this; + } + + @Override + public int getQuery() { + return query; + } + @Override + public AbstractSQLConfig setQuery(int query) { + this.query = query; + return this; + } + @Override + public int getType() { + return type; + } + @Override + public AbstractSQLConfig setType(int type) { + this.type = type; + return this; + } + + @Override + public int getCache() { + return cache; + } + @Override + public AbstractSQLConfig setCache(int cache) { + this.cache = cache; + return this; + } + + public AbstractSQLConfig setCache(String cache) { + return setCache(getCache(cache)); + } + public static int getCache(String cache) { + int cache2; + if (cache == null) { + cache2 = JSONRequest.CACHE_ALL; + } + else { + // if (isSubquery) { + // throw new IllegalArgumentException("子查询内不支持传 " + JSONRequest.KEY_CACHE + "!"); + // } + + switch (cache) { + case "0": + case JSONRequest.CACHE_ALL_STRING: + cache2 = JSONRequest.CACHE_ALL; + break; + case "1": + case JSONRequest.CACHE_ROM_STRING: + cache2 = JSONRequest.CACHE_ROM; + break; + case "2": + case JSONRequest.CACHE_RAM_STRING: + cache2 = JSONRequest.CACHE_RAM; + break; + default: + throw new IllegalArgumentException(JSONRequest.KEY_CACHE + ":value 中 value 的值不合法!必须在 [0,1,2] 或 [ALL, ROM, RAM] 内 !"); + } + } + return cache2; + } + + @Override + public boolean isExplain() { + return explain; + } + @Override + public AbstractSQLConfig setExplain(boolean explain) { + this.explain = explain; + return this; + } + + @Override + public List getJoinList() { + return joinList; + } + @Override + public SQLConfig setJoinList(List joinList) { + this.joinList = joinList; + return this; + } + @Override + public boolean hasJoin() { + return joinList != null && joinList.isEmpty() == false; + } + + + @Override + public boolean isTest() { + return test; + } + @Override + public AbstractSQLConfig setTest(boolean test) { + this.test = test; + return this; + } + + /**获取初始位置offset + * @return + */ + @JSONField(serialize = false) + public int getOffset() { + return getOffset(getPage(), getCount()); + } + /**获取初始位置offset + * @param page + * @param count + * @return + */ + public static int getOffset(int page, int count) { + return page*count; + } + /**获取限制数量 + * @return + */ + @JSONField(serialize = false) + public String getLimitString() { + if (count <= 0 || RequestMethod.isHeadMethod(getMethod(), true)) { + return ""; + } + return getLimitString(getPage(), getCount(), isOracle() || isSQLServer() || isDb2(), isOracle()); + } + /**获取限制数量 + * @param limit + * @return + */ + public static String getLimitString(int page, int count, boolean isTSQL, boolean isOracle) { + 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 " LIMIT " + count + (offset <= 0 ? "" : " OFFSET " + offset); // DELETE, UPDATE 不支持 OFFSET + } + + //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + @Override + public Map getWhere() { + return where; + } + @Override + public AbstractSQLConfig setWhere(Map where) { + this.where = where; + return this; + } + @NotNull + @Override + public Map> getCombine() { + List andList = combine == null ? null : combine.get("&"); + if (andList == null) { + andList = where == null ? new ArrayList() : new ArrayList(where.keySet()); + if (combine == null) { + combine = new HashMap<>(); + } + combine.put("&", andList); + } + return combine; + } + @Override + public AbstractSQLConfig setCombine(Map> combine) { + this.combine = combine; + return this; + } + /** + * noFunctionChar = false + * @param key + * @return + */ + @JSONField(serialize = false) + @Override + public Object getWhere(String key) { + return getWhere(key, false); + } + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 + /** + * @param key - the key passed in + * @param exactMatch - whether it is exact match + * @return + *

use entrySet+getValue() to replace keySet+get() to enhance efficiency

+ */ + @JSONField(serialize = false) + @Override + public Object getWhere(String key, boolean exactMatch) { + if (exactMatch) { + return where == null ? null : where.get(key); + } + + 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(); + } + } + } + } + return null; + } + @Override + public AbstractSQLConfig putWhere(String key, Object value, boolean prior) { + if (key != null) { + if (where == null) { + where = new LinkedHashMap(); + } + if (value == null) { + where.remove(key); + } else { + where.put(key, value); + } + + combine = getCombine(); + List andList = combine.get("&"); + if (value == null) { + if (andList != null) { + andList.remove(key); + } + } + else if (andList == null || andList.contains(key) == false) { + int i = 0; + if (andList == null) { + andList = new ArrayList<>(); + } + else if (prior && andList.isEmpty() == false) { + + String idKey = getIdKey(); + String idInKey = idKey + "{}"; + String userIdKey = getUserIdKey(); + String userIdInKey = userIdKey + "{}"; + + if (andList.contains(idKey)) { + i ++; + } + if (andList.contains(idInKey)) { + i ++; + } + if (andList.contains(userIdKey)) { + i ++; + } + if (andList.contains(userIdInKey)) { + i ++; + } + } + + if (prior) { + andList.add(i, key); //userId的优先级不能比id高 0, key); + } else { + andList.add(key); //AbstractSQLExecutor.onPutColumn里getSQL,要保证缓存的SQL和查询的SQL里 where 的 key:value 顺序一致 + } + } + combine.put("&", andList); + } + return this; + } + + /**获取WHERE + * @return + * @throws Exception + */ + @JSONField(serialize = false) + @Override + public String getWhereString(boolean hasPrefix) throws Exception { + return getWhereString(hasPrefix, getMethod(), getWhere(), getCombine(), getJoinList(), ! isTest()); + } + /**获取WHERE + * @param method + * @param where + * @return + * @throws Exception + */ + @JSONField(serialize = false) + public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, Map> combine, List joinList, boolean verifyName) throws Exception { + Set>> combineSet = combine == null ? null : combine.entrySet(); + if (combineSet == null || combineSet.isEmpty()) { + Log.w(TAG, "getWhereString combineSet == null || combineSet.isEmpty() >> return \"\";"); + return ""; + } + + List keyList; + + String whereString = ""; + + boolean isCombineFirst = true; + int logic; + + boolean isItemFirst; + String c; + String cs; + + for (Entry> ce : combineSet) { + keyList = ce == null ? null : ce.getValue(); + if (keyList == null || keyList.isEmpty()) { + continue; + } + + if ("|".equals(ce.getKey())) { + logic = Logic.TYPE_OR; + } + else if ("!".equals(ce.getKey())) { + logic = Logic.TYPE_NOT; + } + else { + logic = Logic.TYPE_AND; + } + + + isItemFirst = true; + cs = ""; + for (String key : keyList) { + c = getWhereItem(key, where.get(key), method, verifyName); + + if (StringUtil.isEmpty(c, true)) {//避免SQL条件连接错误 + continue; + } + + cs += (isItemFirst ? "" : (Logic.isAnd(logic) ? AND : OR)) + "(" + c + ")"; + + isItemFirst = false; + } + + if (StringUtil.isEmpty(cs, true)) {//避免SQL条件连接错误 + continue; + } + + whereString += (isCombineFirst ? "" : AND) + (Logic.isNot(logic) ? NOT : "") + " ( " + cs + " ) "; + isCombineFirst = false; + } + + + 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) + 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 s = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; + + if (s.isEmpty() && RequestMethod.isQueryMethod(method) == false) { + throw new UnsupportedOperationException("写操作请求必须带条件!!!"); + } + + return s; + } + + /** + * @param key + * @param value + * @param method + * @param verifyName + * @return + * @throws Exception + */ + 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;"); + return null; + } + if (key.endsWith("@")) {//引用 + // key = key.substring(0, key.lastIndexOf("@")); + throw new IllegalArgumentException(TAG + ".getWhereItem: 字符 " + key + " 不合法!"); + } + + // 原始 SQL 片段 + String rawSQL = getRawSQL(key, value); + + int keyType; + if (key.endsWith("$")) { + keyType = 1; + } + else if (key.endsWith("~")) { + keyType = key.charAt(key.length() - 2) == '*' ? -2 : 2; //FIXME StringIndexOutOfBoundsException + } + else if (key.endsWith("%")) { + keyType = 3; + } + else if (key.endsWith("{}")) { + keyType = 4; + } + else if (key.endsWith("}{")) { + keyType = 5; + } + else if (key.endsWith("<>")) { + keyType = 6; + } + else if (key.endsWith(">=")) { + keyType = 7; + } + else if (key.endsWith("<=")) { + keyType = 8; + } + else if (key.endsWith(">")) { + keyType = 9; + } + else if (key.endsWith("<")) { + keyType = 10; + } else { // else绝对不能省,避免再次踩坑! keyType = 0; 写在for循环外面都没注意! + keyType = 0; + } + + key = getRealKey(method, key, false, true, verifyName); + + switch (keyType) { + case 1: + return getSearchString(key, value, rawSQL); + case -2: + case 2: + return getRegExpString(key, value, keyType < 0, rawSQL); + case 3: + return getBetweenString(key, value, rawSQL); + case 4: + return getRangeString(key, value, rawSQL); + case 5: + return getExistsString(key, value, rawSQL); + case 6: + return getContainString(key, value, rawSQL); + case 7: + return getCompareString(key, value, ">=", rawSQL); + case 8: + return getCompareString(key, value, "<=", rawSQL); + case 9: + return getCompareString(key, value, ">", rawSQL); + case 10: + return getCompareString(key, value, "<", rawSQL); + default: // TODO MySQL JSON类型的字段对比 key='[]' 会无结果! key LIKE '[1, 2, 3]' //TODO MySQL , 后面有空格! + return getEqualString(key, 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) { + throw new IllegalArgumentException(key + ":value 中value不合法!非PUT请求只支持 [Boolean, Number, String] 内的类型 !"); + } + + boolean not = key.endsWith("!"); // & | 没有任何意义,写法多了不好控制 + if (not) { + key = key.substring(0, key.length() - 1); + } + if (StringUtil.isName(key) == false) { + throw new IllegalArgumentException(key + ":value 中key不合法!不支持 ! 以外的逻辑符 !"); + } + + return getKey(key) + (not ? " != " : " = ") + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(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] 内的类型 !"); + } + if (StringUtil.isName(key) == false) { + throw new IllegalArgumentException(key + type + ":value 中key不合法!比较运算 [>, <, >=, <=] 不支持 [&, !, |] 中任何逻辑运算符 !"); + } + + return getKey(key) + " " + type + " " + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(value))); + } + + public String getKey(String key) { + if (isTest()) { + if (key.contains("'")) { // || key.contains("#") || key.contains("--")) { + throw new IllegalArgumentException("参数 " + key + " 不合法!key 中不允许有单引号 ' !"); + } + return getSQLValue(key).toString(); + } + + return getSQLKey(key); + } + public String getSQLKey(String key) { + String q = getQuote(); + return (isKeyPrefix() ? getAliasWithQuote() + "." : "") + q + key + q; + } + + /** + * 使用prepareStatement预编译,值为 ? ,后续动态set进去 + */ + private List preparedValueList = new ArrayList<>(); + private Object getValue(@NotNull Object value) { + if (isPrepared()) { + preparedValueList.add(value); + return "?"; + } + return getSQLValue(value); + } + public Object getSQLValue(@NotNull Object value) { + // return (value instanceof Number || value instanceof Boolean) && DATABASE_POSTGRESQL.equals(getDatabase()) ? value : "'" + value + "'"; + return (value instanceof Number || value instanceof Boolean) ? value : "'" + value + "'"; //MySQL 隐式转换用不了索引 + } + + @Override + public List getPreparedValueList() { + return preparedValueList; + } + @Override + public AbstractSQLConfig setPreparedValueList(List preparedValueList) { + this.preparedValueList = preparedValueList; + return this; + } + + //$ search <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + /**search key match value + * @param in + * @return {@link #getSearchString(String, Object[], int)} + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getSearchString(String key, 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, "getSearchString key = " + key); + + JSONArray arr = newJSONArray(value); + if (arr.isEmpty()) { + return ""; + } + return getSearchString(key, arr.toArray(), logic.getType()); + } + /**search key match values + * @param in + * @return LOGIC [ key LIKE 'values[i]' ] + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getSearchString(String key, Object[] values, int type) throws IllegalArgumentException { + if (values == null || values.length <= 0) { + return ""; + } + + String condition = ""; + 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[]!"); + } + if (((String) v).isEmpty()) { // 允许查空格 StringUtil.isEmpty((String) v, true) + 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); + } + + return getCondition(Logic.isNot(type), condition); + } + + /**WHERE key LIKE 'value' + * @param key + * @param value + * @return key LIKE 'value' + */ + @JSONField(serialize = false) + public String getLikeString(String key, Object value) { + return getKey(key) + " LIKE " + getValue(value); + } + + //$ search >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + + //~ regexp <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + /**search key match RegExp values + * @param key + * @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 { + 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, "getRegExpString key = " + key); + + JSONArray arr = newJSONArray(value); + if (arr.isEmpty()) { + return ""; + } + return getRegExpString(key, arr.toArray(), logic.getType(), ignoreCase); + } + /**search key match RegExp values + * @param key + * @param values + * @param type + * @param ignoreCase + * @return LOGIC [ key REGEXP 'values[i]' ] + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getRegExpString(String key, Object[] values, int type, boolean ignoreCase) throws IllegalArgumentException { + if (values == null || values.length <= 0) { + return ""; + } + + String condition = ""; + for (int i = 0; i < values.length; i++) { + if (values[i] instanceof String == false) { + throw new IllegalArgumentException(key + "$:value 中value的类型只能为String或String[]!"); + } + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getRegExpString(key, (String) values[i], ignoreCase); + } + + return getCondition(Logic.isNot(type), condition); + } + + /**WHERE key REGEXP 'value' + * @param key + * @param value + * @param ignoreCase + * @return key REGEXP 'value' + */ + @JSONField(serialize = false) + public String getRegExpString(String key, String value, boolean ignoreCase) { + if (isPostgreSQL()) { + return getKey(key) + " ~" + (ignoreCase ? "* " : " ") + getValue(value); + } + if (isOracle()) { + return "regexp_like(" + getKey(key) + ", " + getValue(value) + (ignoreCase ? ", 'i'" : ", 'c'") + ")"; + } + return getKey(key) + " REGEXP " + (ignoreCase ? "" : "BINARY ") + getValue(value); + } + //~ regexp >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + + //% between <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + /**WHERE key BETWEEN 'start' AND 'end' + * @param key + * @param value 'start,end' + * @return LOGIC [ key BETWEEN 'start' AND 'end' ] + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getBetweenString(String key, 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, "getBetweenString key = " + key); + + JSONArray arr = newJSONArray(value); + if (arr.isEmpty()) { + return ""; + } + return getBetweenString(key, arr.toArray(), logic.getType()); + } + + /**WHERE key BETWEEN 'start' AND 'end' + * @param key + * @param value 'start,end' TODO 在 '1,2' 和 ['1,2', '3,4'] 基础上新增支持 [1, 2] 和 [[1,2], [3,4]] ? + * @return LOGIC [ key BETWEEN 'start' AND 'end' ] + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getBetweenString(String key, Object[] values, int type) throws IllegalArgumentException { + if (values == null || values.length <= 0) { + return ""; + } + + String condition = ""; + String[] vs; + for (int i = 0; i < values.length; i++) { + if (values[i] instanceof String == false) { + 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 的规则 !"); + } + + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + "(" + getBetweenString(key, (Object) vs[0], (Object) vs[1]) + ")"; + } + + return getCondition(Logic.isNot(type), condition); + } + + /**WHERE key BETWEEN 'start' AND 'end' + * @param key + * @param value 'start,end' TODO 在 '1,2' 和 ['1,2', '3,4'] 基础上新增支持 [1, 2] 和 [[1,2], [3,4]] ? + * @return key BETWEEN 'start' AND 'end' + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getBetweenString(String key, 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 的规则 !"); + } + return getKey(key) + " BETWEEN " + getValue(start) + AND + getValue(end); + } + + + //% between >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + + //{} range <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + + /**WHERE key > 'key0' AND key <= 'key1' AND ... + * @param key + * @param range "condition0,condition1..." + * @return key condition0 AND key condition1 AND ... + * @throws Exception + */ + @JSONField(serialize = false) + public String getRangeString(String key, Object range, String rawSQL) throws Exception { + Log.i(TAG, "getRangeString key = " + key); + if (range == null) {//依赖的对象都没有给出有效值,这个存在无意义。如果是客户端传的,那就能在客户端确定了。 + throw new NotExistException(TAG + "getRangeString(" + key + ", " + range + + ") range == null"); + } + + Logic logic = new Logic(key); + String k = logic.getKey(); + Log.i(TAG, "getRangeString k = " + k); + + if (range instanceof List) { + if (rawSQL != null) { + throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + "{} 不合法!" + + "Raw SQL 不支持 key{}:[] 这种键值对!"); + } + + if (logic.isOr() || logic.isNot()) { + List l = (List) range; + if (logic.isNot() && l.isEmpty()) { + return ""; // key!{}: [] 这个条件无效,加到 SQL 语句中 key IN() 会报错,getInString 里不好处理 + } + return getKey(k) + getInString(k, l.toArray(), logic.isNot()); + } + throw new IllegalArgumentException(key + "{}\":[] 中 {} 前面的逻辑运算符错误!只能用'|','!'中的一种 !"); + } + else if (range instanceof String) {//非Number类型需要客户端拼接成 < 'value0', >= 'value1'这种 + String condition = ""; + String[] cs = rawSQL != null ? null : StringUtil.split((String) range, false); + + if (rawSQL != null) { + int index = rawSQL == null ? -1 : 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; + 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); + } + 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 + " !不允许连续减号 -- !不允许空格!"); + } + + 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 则按原来位置拼接 + } + } + if (condition.isEmpty()) { + return ""; + } + + return getCondition(logic.isNot(), condition); + } + 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() + + "!range 只能是 用','分隔条件的字符串 或者 可取选项JSONArray!"); + } + /**WHERE key IN ('key0', 'key1', ... ) + * @param in + * @return IN ('key0', 'key1', ... ) + * @throws NotExistException + */ + @JSONField(serialize = false) + public String getInString(String key, 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])); + } + } + if (condition.isEmpty()) {//条件如果存在必须执行,不能忽略。条件为空会导致出错,又很难保证条件不为空(@:条件),所以还是这样好 + throw new NotExistException(TAG + ".getInString(" + key + ", [], " + not + + ") >> condition.isEmpty() >> IN()"); + } + return (not ? NOT : "") + " IN (" + condition + ")"; + } + //{} range >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + //}{ exists <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + /**WHERE EXISTS subquery + * 如果合并到 getRangeString,一方面支持不了 [1,2,2] 和 ">1" (转成 EXISTS(SELECT IN ) 需要static newSQLConfig,但它不能传入子类实例,除非不是 static),另一方面多了子查询临时表性能会比 IN 差 + * @param key + * @param value + * @return EXISTS ALL(SELECT ...) + * @throws NotExistException + */ + @JSONField(serialize = false) + public String getExistsString(String key, Object value, String rawSQL) throws Exception { + if (rawSQL != null) { + throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key}{ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); + } + if (value == null) { + return ""; + } + if (value instanceof Subquery == false) { + throw new IllegalArgumentException(key + "}{:subquery 类型为" + value.getClass().getSimpleName() + + "!subquery 只能是 子查询JSONObejct!"); + } + + Logic logic = new Logic(key); + key = logic.getKey(); + Log.i(TAG, "getExistsString key = " + key); + + return (logic.isNot() ? NOT : "") + " EXISTS " + getSubqueryString((Subquery) value); + } + //}{ exists >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + //<> contain <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + /**WHERE key contains value + * @param key + * @param value + * @return {@link #getContainString(String, Object[], int)} + * @throws NotExistException + */ + @JSONField(serialize = false) + public String getContainString(String key, 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); + + return getContainString(key, newJSONArray(value).toArray(), logic.getType()); + } + /**WHERE key contains childs + * @param key + * @param childs null ? "" : (empty ? no child : contains childs) + * @param type |, &, ! + * @return LOGIC [ ( key LIKE '[" + childs[i] + "]' OR key LIKE '[" + childs[i] + ", %' + * OR key LIKE '%, " + childs[i] + ", %' OR key LIKE '%, " + childs[i] + "]' ) ] + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getContainString(String key, 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!"); + } + + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)); + if (isPostgreSQL()) { + condition += (getKey(key) + " @> " + getValue(newJSONArray(c))); //operator does not exist: jsonb @> character varying "[" + c + "]"); + } + else if (isOracle()) { + condition += ("json_textcontains(" + getKey(key) + ", '$', " + getValue(c.toString()) + ")"); + } + else { + boolean isNum = c instanceof Number; + String v = (isNum ? "" : "\"") + childs[i] + (isNum ? "" : "\""); + condition += ("json_contains(" + getKey(key) + ", " + getValue(v) + ")"); + } + } + } + if (condition.isEmpty()) { + condition = (getKey(key) + SQL.isNull(true) + OR + getLikeString(key, "[]")); // key = '[]' 无结果! + } else { + condition = (getKey(key) + SQL.isNull(false) + AND + "(" + condition + ")"); + } + } + if (condition.isEmpty()) { + return ""; + } + return getCondition(not, condition); + } + //<> contain >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + + //key@:{} Subquery <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + @Override + public String getSubqueryString(Subquery subquery) throws Exception { + String range = subquery.getRange(); + SQLConfig cfg = subquery.getConfig(); + + cfg.setPreparedValueList(new ArrayList<>()); + String sql = (range == null || range.isEmpty() ? "" : range) + "(" + cfg.getSQL(isPrepared()) + ") "; + + preparedValueList.addAll(cfg.getPreparedValueList()); + + return sql; + } + + //key@:{} Subquery >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + + /**拼接条件 + * @param not + * @param condition + * @return + */ + private static String getCondition(boolean not, String condition) { + return not ? NOT + "(" + condition + ")" : condition; + } + + + /**转为JSONArray + * @param tv + * @return + */ + @NotNull + public static JSONArray newJSONArray(Object obj) { + JSONArray array = new JSONArray(); + if (obj != null) { + if (obj instanceof Collection) { + array.addAll((Collection) obj); + } else { + array.add(obj); + } + } + return array; + } + + //WHERE >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + //SET <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + /**获取SET + * @return + * @throws Exception + */ + @JSONField(serialize = false) + public String getSetString() throws Exception { + return getSetString(getMethod(), getContent(), ! isTest()); + } + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 + /**获取SET + * @param method -the method used + * @param content -the content map + * @return + * @throws Exception + *

use entrySet+getValue() to replace keySet+get() to enhance efficiency

+ */ + @JSONField(serialize = false) + public String getSetString(RequestMethod method, Map content, boolean verifyName) throws Exception { + Set set = content == null ? null : content.keySet(); + String setString = ""; + + if (set != null && set.size() > 0) { + boolean isFirst = true; + int keyType;// 0 - =; 1 - +, 2 - - + Object value; + + String idKey = getIdKey(); + for (Entry entry : content.entrySet()) { + String key = entry.getKey(); + //避免筛选到全部 value = key == null ? null : content.get(key); + if (key == null || idKey.equals(key)) { + continue; + } + + if (key.endsWith("+")) { + keyType = 1; + } else if (key.endsWith("-")) { + keyType = 2; + } else { + keyType = 0; //注意重置类型,不然不该加减的字段会跟着加减 + } + value = entry.getValue(); + key = getRealKey(method, key, false, true, verifyName); + + setString += (isFirst ? "" : ", ") + (getKey(key) + " = " + (keyType == 1 ? getAddString(key, value) : (keyType == 2 + ? getRemoveString(key, value) : getValue(value)) ) ); + + isFirst = false; + } + } + + if (setString.isEmpty()) { + throw new IllegalArgumentException("PUT 请求必须在Table内设置要修改的 key:value !"); + } + return " SET " + setString; + } + + /**SET key = concat(key, 'value') + * @param key + * @param value + * @return concat(key, 'value') + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getAddString(String key, Object value) throws IllegalArgumentException { + if (value instanceof Number) { + return getKey(key) + " + " + value; + } + if (value instanceof String) { + return SQL.concat(getKey(key), (String) getValue(value)); + } + throw new IllegalArgumentException(key + "+ 对应的值 " + value + " 不是Number,String,Array中的任何一种!"); + } + /**SET key = replace(key, 'value', '') + * @param key + * @param value + * @return REPLACE (key, 'value', '') + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getRemoveString(String key, Object value) throws IllegalArgumentException { + if (value instanceof Number) { + return getKey(key) + " - " + value; + } + if (value instanceof String) { + return SQL.replace(getKey(key), (String) getValue(value), "''");// " replace(" + key + ", '" + value + "', '') "; + } + throw new IllegalArgumentException(key + "- 对应的值 " + value + " 不是Number,String,Array中的任何一种!"); + } + //SET >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + /** + * @return + * @throws Exception + */ + @JSONField(serialize = false) + @Override + public String getSQL(boolean prepared) throws Exception { + return getSQL(this.setPrepared(prepared)); + } + /** + * @param config + * @return + * @throws Exception + */ + public static String getSQL(AbstractSQLConfig config) throws Exception { + if (config == null) { + Log.i(TAG, "getSQL config == null >> return null;"); + return null; + } + + //TODO procedure 改为 List procedureList; behind : true; function: callFunction(); String key; ... + // for (...) { Call procedure1();\n SQL \n; Call procedure2(); ... } + // 貌似不需要,因为 ObjecParser 里就已经处理的顺序等,只是这里要解决下 Schema 问题。 + + String sch = config.getSQLSchema(); + if (StringUtil.isNotEmpty(config.getProcedure(), true)) { + String q = config.getQuote(); + return "CALL " + q + sch + q + "."+ config.getProcedure(); + } + + String tablePath = config.getTablePath(); + if (StringUtil.isNotEmpty(tablePath, true) == false) { + Log.i(TAG, "getSQL StringUtil.isNotEmpty(tablePath, true) == false >> return null;"); + return null; + } + + switch (config.getMethod()) { + 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() : ""); + case DELETE: + 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)) { + 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(); + } + + config.setPreparedValueList(new ArrayList()); + 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 " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString(); + } + } + + /**获取条件SQL字符串 + * @param page + * @param column + * @param table + * @param where + * @return + * @throws Exception + */ + private static String getConditionString(String column, String table, AbstractSQLConfig config) throws Exception { + String where = config.getWhereString(true); + + Subquery from = config.getFrom(); + if (from != null) { + 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(); + + //no need to optimize + // if (config.getPage() <= 0 || ID.equals(column.trim())) { + return condition; // config.isOracle() ? condition : condition + config.getLimitString(); + // } + // + // + // //order: id+ -> id >= idOfStartIndex; id- -> id <= idOfStartIndex <<<<<<<<<<<<<<<<<<< + // String order = StringUtil.getNoBlankString(config.getOrder()); + // List orderList = order.isEmpty() ? null : Arrays.asList(StringUtil.split(order)); + // + // int type = 0; + // if (BaseModel.isEmpty(orderList) || BaseModel.isContain(orderList, ID+"+")) { + // type = 1; + // } + // else if (BaseModel.isContain(orderList, ID+"-")) { + // type = 2; + // } + // + // if (type > 0) { + // return condition.replace("WHERE", + // "WHERE id " + (type == 1 ? ">=" : "<=") + " (SELECT id FROM " + table + // + where + " ORDER BY id " + (type == 1 ? "ASC" : "DESC") + " LIMIT " + config.getOffset() + ", 1) AND" + // ) + // + " LIMIT " + config.getCount(); //子查询起始id不一定准确,只能作为最小可能! ;// + // } + // //order: id+ -> id >= idOfStartIndex; id- -> id <= idOfStartIndex >>>>>>>>>>>>>>>>>> + // + // + // //结果错误!SELECT * FROM User AS t0 INNER JOIN + // (SELECT id FROM User ORDER BY date ASC LIMIT 20, 10) AS t1 ON t0.id = t1.id + // //common case, inner join + // condition += config.getLimitString(); + // return table + " AS t0 INNER JOIN (SELECT id FROM " + condition + ") AS t1 ON t0.id = t1.id"; + } + + + private boolean keyPrefix; + @Override + public boolean isKeyPrefix() { + return keyPrefix; + } + @Override + public AbstractSQLConfig setKeyPrefix(boolean keyPrefix) { + this.keyPrefix = keyPrefix; + return this; + } + + + + public String getJoinString() throws Exception { + String joinOns = ""; + + if (joinList != null) { + String quote = getQuote(); + 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) + continue; + } + String type = j.getJoinType(); + + //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(); + jc.setPrepared(isPrepared()); + + jt = StringUtil.isEmpty(jc.getAlias(), true) ? jc.getTable() : jc.getAlias(); + tt = j.getTargetTable(); + + //如果要强制小写,则可在子类重写这个方法再 toLowerCase + // if (DATABASE_POSTGRESQL.equals(getDatabase())) { + // jt = jt.toLowerCase(); + // tn = tn.toLowerCase(); + // } + + switch (type) { + //前面已跳过 case "@": // APP JOIN + // continue; + + case "*": // CROSS JOIN + onGetCrossJoinString(j); + case "<": // LEFT JOIN + 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; + jc.setMain(false).setKeyPrefix(true); + + pvl.addAll(jc.getPreparedValueList()); + changed = true; + 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 + sql = " INNER JOIN " + jc.getTablePath() + + " ON " + quote + jt + quote + "." + quote + j.getKey() + quote + " = " + quote + tt + quote + "." + quote + j.getTargetKey() + quote; + break; + default: + throw new UnsupportedOperationException( + "join:value 中 value 里的 " + jt + "/" + j.getPath() + + "错误!不支持 " + jt + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" + + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" + ); + } + + joinOns += " \n " + sql; + } + + + if (changed) { + pvl.addAll(preparedValueList); + preparedValueList = pvl; + } + + } + + return joinOns; + } + + protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { + throw new UnsupportedOperationException("已禁用 * CROSS JOIN !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); + } + + /**新建SQL配置 + * @param table + * @param request + * @param joinList + * @param isProcedure + * @param callback + * @return + * @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!"); + } + + boolean explain = request.getBooleanValue(KEY_EXPLAIN); + if (explain && Log.DEBUG == false) { //不在 config.setExplain 抛异常,一方面处理更早性能更好,另一方面为了内部调用可以绕过这个限制 + throw new UnsupportedOperationException("DEBUG 模式下不允许传 " + KEY_EXPLAIN + " !"); + } + + String database = request.getString(KEY_DATABASE); + if (StringUtil.isEmpty(database, false) == false && DATABASE_LIST.contains(database) == false) { + throw new UnsupportedDataTypeException("@database:value 中 value 错误,只能是 [" + StringUtil.getString(DATABASE_LIST.toArray()) + "] 中的一种!"); + } + + String schema = request.getString(KEY_SCHEMA); + String datasource = request.getString(KEY_DATASOURCE); + + SQLConfig config = callback.getSQLConfig(method, database, schema, table); + config.setAlias(alias); + + config.setDatabase(database); //不删,后面表对象还要用的,必须放在 parseJoin 前 + config.setSchema(schema); //不删,后面表对象还要用的 + config.setDatasource(datasource); //不删,后面表对象还要用的 + + if (isProcedure) { + return config; + } + + config = parseJoin(method, config, joinList, callback); //放后面会导致主表是空对象时 joinList 未解析 + + if (request.isEmpty()) { // User:{} 这种空内容在查询时也有效 + return config; //request.remove(key); 前都可以直接return,之后必须保证 put 回去 + } + + + String idKey = callback.getIdKey(database, schema, table); + String idInKey = idKey + "{}"; + String userIdKey = callback.getUserIdKey(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); + 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); + if ((d instanceof Number && ((Number) d).longValue() > 0) || (d instanceof String && StringUtil.isNotEmpty(d, true))) { + newIdIn.add(d); + } + } + if (newIdIn.isEmpty()) { + throw new NotExistException(TAG + ": newSQLConfig idIn instanceof List >> 去掉无效 id 后 newIdIn.isEmpty()"); + } + idIn = newIdIn; + + if (method == DELETE || method == PUT) { + config.setCount(newIdIn.size()); + } + } + + Object id = request.get(idKey); + boolean hasId = id != null; + if (method == POST && hasId == false) { + id = callback.newId(method, database, schema, table); // null 表示数据库自增 id + } + + if (id != null) { //null无效 + if (id instanceof Number) { + if (((Number) id).longValue() <= 0) { //一定没有值 + throw new NotExistException(TAG + ": newSQLConfig " + table + ".id <= 0"); + } + } + else if (id instanceof String) { + if (StringUtil.isEmpty(id, true)) { //一定没有值 + throw new NotExistException(TAG + ": newSQLConfig StringUtil.isEmpty(" + table + ".id, true)"); + } + } + else if (id instanceof Subquery) {} + else { + throw new IllegalArgumentException(idKey + ":value 中 value 的类型只能是 Long , String 或 Subquery !"); + } + + if (idIn instanceof List) { //共用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); + if (d != null && id.toString().equals(d.toString())) { + contains = true; + break; + } + } + if (contains == false) {//empty有效 BaseModel.isEmpty(idIn) == false) { + throw new NotExistException(TAG + ": newSQLConfig idIn != null && (((List) idIn).contains(id) == false"); + } + } + + if (method == DELETE || method == PUT) { + config.setCount(1); + } + } + + + 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 group = request.getString(KEY_GROUP); + String having = request.getString(KEY_HAVING); + String order = request.getString(KEY_ORDER); + String raw = request.getString(KEY_RAW); + String json = request.getString(KEY_JSON); + + try { + //强制作为条件且放在最前面优化性能 + request.remove(idKey); + request.remove(idInKey); + //关键词 + request.remove(KEY_ROLE); + request.remove(KEY_EXPLAIN); + request.remove(KEY_CACHE); + request.remove(KEY_DATABASE); + request.remove(KEY_SCHEMA); + request.remove(KEY_COMBINE); + request.remove(KEY_FROM); + request.remove(KEY_COLUMN); + request.remove(KEY_GROUP); + request.remove(KEY_HAVING); + request.remove(KEY_ORDER); + request.remove(KEY_RAW); + request.remove(KEY_JSON); + + String[] rawArr = StringUtil.split(raw); + config.setRaw(rawArr == null || rawArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(rawArr))); + + Map tableWhere = new LinkedHashMap();//保证顺序好优化 WHERE id > 1 AND name LIKE... + + //已经remove了id和id{},以及@key + Set set = request.keySet(); //前面已经判断request是否为空 + if (method == POST) { //POST操作 + if (idIn != null) { + throw new IllegalArgumentException("POST 请求中不允许传 " + idInKey + " !"); + } + + if (set != null && set.isEmpty() == false) { //不能直接return,要走完下面的流程 + String[] columns = set.toArray(new String[]{}); + + Collection valueCollection = request.values(); + Object[] values = valueCollection == null ? null : valueCollection.toArray(); + + if (values == null || values.length != columns.length) { + throw new Exception("服务器内部错误:\n" + TAG + + " newSQLConfig values == null || values.length != columns.length !"); + } + column = (id == null ? "" : idKey + ",") + 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数量为准 + + items = new ArrayList<>(size); + items.add(id); //idList.get(i)); //第0个就是id + + for (int j = 1; j < size; j++) { + items.add(values[j-1]); //从第1个开始,允许"null" + } + } + + valuess.add(items); + config.setValues(valuess); + } + } + else { //非POST操作 + final boolean isWhere = method != PUT;//除了POST,PUT,其它全是条件!!! + + //条件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + 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 !"); + } + 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 { + 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 + "] 其中任何一个!"); + } + } + + 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); + } + } + + } + + //条件>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + Map tableContent = new LinkedHashMap(); + Object value; + for (String key : set) { + value = request.get(key); + + if (value instanceof Map) {//只允许常规Object + throw new IllegalArgumentException("不允许 " + key + " 等任何key的value类型为 {JSONObject} !"); + } + + //解决AccessVerifier新增userId没有作为条件,而是作为内容,导致PUT,DELETE出错 + if (isWhere) { + tableWhere.put(key, value); + if (whereList == null || whereList.contains(key) == false) { + andList.add(key); + } + } + else if (whereList != null && whereList.contains(key)) { + tableWhere.put(key, value); + } + else { + tableContent.put(key, value);//一样 instanceof JSONArray ? JSON.toJSONString(value) : value); + } + } + + combineMap.put("&", andList); + combineMap.put("|", orList); + combineMap.put("!", notList); + config.setCombine(combineMap); + + config.setContent(tableContent); + } + + + List cs = new ArrayList<>(); + + List rawList = config.getRaw(); + 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()); + } + } + + boolean distinct = column == null || rawColumnSQL != null ? false : column.startsWith(PREFFIX_DISTINCT); + if (rawColumnSQL == null) { + String[] fks = StringUtil.split(distinct ? column.substring(PREFFIX_DISTINCT.length()) : column, ";"); // key0,key1;fun0(key0,...);fun1(key0,...);key3;fun2(key0,...) + if (fks != null) { + 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()); + } + } + + if (fk.contains("(")) { // fun0(key0,...) + cs.add(fk); + } + else { //key0,key1... + ks = StringUtil.split(fk); + if (ks != null && ks.length > 0) { + cs.addAll(Arrays.asList(ks)); + } + } + } + } + } + + 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.setId(id); + //在 tableWhere 第0个 config.setIdIn(idIn); + + config.setRole(RequestRole.get(role)); + 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 等要合并 + + } + finally {//后面还可能用到,要还原 + //id或id{}条件 + if (hasId) { + request.put(idKey, id); + } + request.put(idInKey, idIn); + //关键词 + request.put(KEY_DATABASE, database); + request.put(KEY_ROLE, role); + request.put(KEY_EXPLAIN, explain); + request.put(KEY_CACHE, cache); + request.put(KEY_SCHEMA, schema); + request.put(KEY_COMBINE, combine); + request.put(KEY_FROM, from); + request.put(KEY_COLUMN, column); + request.put(KEY_GROUP, group); + request.put(KEY_HAVING, having); + request.put(KEY_ORDER, order); + request.put(KEY_RAW, raw); + request.put(KEY_JSON, json); + } + + return config; + } + + + + /** + * @param method + * @param config + * @param joinList + * @param callback + * @return + * @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); + + //TODO 解析出 SQLConfig 再合并 column, order, group 等 + if (joinList == null || joinList.isEmpty() || RequestMethod.isQueryMethod(method) == false) { + return config; + } + + + String table; + String alias; + for (Join j : joinList) { + table = j.getTable(); + 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); + + if (j.isAppJoin() == false) { //除了 @ APP JOIN,其它都是 SQL JOIN,则副表要这样配置 + if (joinConfig.getDatabase() == null) { + joinConfig.setDatabase(config.getDatabase()); //解决主表 JOIN 副表,引号不一致 + } + else if (joinConfig.getDatabase().equals(config.getDatabase()) == false) { + throw new IllegalArgumentException("主表 " + config.getTable() + " 的 @database:" + config.getDatabase() + " 和它 SQL JOIN 的副表 " + table + " 的 @database:" + joinConfig.getDatabase() + " 不一致!"); + } + if (joinConfig.getSchema() == null) { + joinConfig.setSchema(config.getSchema()); //主表 JOIN 副表,默认 schema 一致 + } + + if (cacheConfig != null) { + cacheConfig.setDatabase(joinConfig.getDatabase()).setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 + } + + + if (isQuery) { + config.setKeyPrefix(true); + } + + joinConfig.setMain(false).setKeyPrefix(true); + + if (j.isLeftOrRightJoin()) { + 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); + } + } + + //解决 query: 1/2 查数量时报错 /* SELECT count(*) AS count FROM sys.Moment AS Moment LEFT JOIN ( SELECT count(*) AS count FROM sys.Comment ) AS Comment ON Comment.momentId = Moment.id LIMIT 1 OFFSET 0 */ - if (RequestMethod.isHeadMethod(method, true)) { - joinConfig.setMethod(GET); //子查询不能为 SELECT count(*) ,而应该是 SELECT momentId - joinConfig.setColumn(Arrays.asList(j.getKey())); //优化性能,不取非必要的字段 - - if (cacheConfig != null) { - cacheConfig.setMethod(GET); //子查询不能为 SELECT count(*) ,而应该是 SELECT momentId - cacheConfig.setColumn(Arrays.asList(j.getKey())); //优化性能,不取非必要的字段 - } - } - - j.setJoinConfig(joinConfig); - j.setCacheConfig(cacheConfig); - } - - config.setJoinList(joinList); - - return config; - } - - - /** - * 获取客户端实际需要的key - * verifyName = true - * - * @param method - * @param originKey - * @param isTableKey - * @param saveLogic 保留逻辑运算符 & | ! - * @return - */ - public static String getRealKey(RequestMethod method, String originKey - , boolean isTableKey, boolean saveLogic) throws Exception { - return getRealKey(method, originKey, isTableKey, saveLogic, true); - } - - /** - * 获取客户端实际需要的key - * - * @param method - * @param originKey - * @param isTableKey - * @param saveLogic 保留逻辑运算符 & | ! - * @param verifyName 验证key名是否符合代码变量/常量名 - * @return - */ - public static String getRealKey(RequestMethod method, String originKey - , boolean isTableKey, boolean saveLogic, boolean verifyName) throws Exception { - Log.i(TAG, "getRealKey saveLogic = " + saveLogic + "; originKey = " + originKey); - if (originKey == null || apijson.JSONObject.isArrayKey(originKey)) { - Log.w(TAG, "getRealKey originKey == null || apijson.JSONObject.isArrayKey(originKey) >> return originKey;"); - return originKey; - } - - String key = new String(originKey); - if (key.endsWith("$")) {//搜索 LIKE,查询时处理 - key = key.substring(0, key.length() - 1); - } else if (key.endsWith("~")) {//匹配正则表达式 REGEXP,查询时处理 - key = key.substring(0, key.length() - 1); - if (key.endsWith("*")) {//忽略大小写 - key = key.substring(0, key.length() - 1); - } - } else if (key.endsWith("%")) {//数字、文本、日期范围 BETWEEN AND - key = key.substring(0, key.length() - 1); - } else if (key.endsWith("{}")) {//被包含 IN,或者说key对应值处于value的范围内。查询时处理 - key = key.substring(0, key.length() - 2); - } else if (key.endsWith("}{")) {//被包含 EXISTS,或者说key对应值处于value的范围内。查询时处理 - key = key.substring(0, key.length() - 2); - } else if (key.endsWith("<>")) {//包含 json_contains,或者说value处于key对应值的范围内。查询时处理 - key = key.substring(0, key.length() - 2); - } else if (key.endsWith("()")) {//方法,查询完后处理,先用一个Map保存 - key = key.substring(0, key.length() - 2); - } else if (key.endsWith("@")) {//引用,引用对象查询完后处理。fillTarget中暂时不用处理,因为非GET请求都是由给定的id确定,不需要引用 - key = key.substring(0, key.length() - 1); - } else if (key.endsWith(">=")) {//比较。查询时处理 - key = key.substring(0, key.length() - 2); - } else if (key.endsWith("<=")) {//比较。查询时处理 - key = key.substring(0, key.length() - 2); - } else if (key.endsWith(">")) {//比较。查询时处理 - key = key.substring(0, key.length() - 1); - } else if (key.endsWith("<")) {//比较。查询时处理 - key = key.substring(0, key.length() - 1); - } else if (key.endsWith("+")) {//延长,PUT查询时处理 - if (method == PUT) {//不为PUT就抛异常 - key = key.substring(0, key.length() - 1); - } - } else if (key.endsWith("-")) {//缩减,PUT查询时处理 - if (method == PUT) {//不为PUT就抛异常 - key = key.substring(0, key.length() - 1); - } - } - - String last = null;//不用Logic优化代码,否则 key 可能变为 key| 导致 key=value 变成 key|=value 而出错 - if (RequestMethod.isQueryMethod(method)) {//逻辑运算符仅供GET,HEAD方法使用 - last = key.isEmpty() ? "" : key.substring(key.length() - 1); - if ("&".equals(last) || "|".equals(last) || "!".equals(last)) { - key = key.substring(0, key.length() - 1); - } else { - last = null;//避免key + StringUtil.getString(last)错误延长 - } - } - - //"User:toUser":User转换"toUser":User, User为查询同名Table得到的JSONObject。交给客户端处理更好 - if (isTableKey) {//不允许在column key中使用Type:key形式 - key = Pair.parseEntry(key, true).getKey();//table以左边为准 - } else { - key = Pair.parseEntry(key).getValue();//column以右边为准 - } - - if (verifyName && StringUtil.isName(key.startsWith("@") ? key.substring(1) : key) == false) { - throw new IllegalArgumentException(method + "请求,字符 " + originKey + " 不合法!" - + " key:value 中的key只能关键词 '@key' 或 'key[逻辑符][条件符]' 或 PUT请求下的 'key+' / 'key-' !"); - } - - if (saveLogic && last != null) { - key = key + last; - } - Log.i(TAG, "getRealKey return key = " + key); - return key; - } - - - public static interface IdCallback { - /** - * 为 post 请求新建 id, 只能是 Long 或 String - * - * @param method - * @param database - * @param schema - * @param table - * @return - */ - 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 - * @param schema - * @param table - * @return - */ - 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 - * @param table - * @return - */ - String getUserIdKey(String database, String schema, String datasource, String table); - } - - public static interface Callback extends IdCallback { - /** - * 获取 SQLConfig 的实例 - * - * @param method - * @param database - * @param schema - * @param table - * @return - */ - SQLConfig getSQLConfig(RequestMethod method, String database, String schema, String table); - - /** - * combine 里的 key 在 request 中 value 为 null 或不存在,即 request 中缺少用来作为 combine 条件的 key: value - * - * @param combine - * @param key - * @param request - */ - public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception; - } - - public static abstract class SimpleCallback implements Callback { - - - @Override - public Object newId(RequestMethod method, String database, String schema, String table) { - 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; - } - - @Override - public String getUserIdKey(String database, String schema, String datasource, String table) { - return getUserIdKey(database, schema, table); - } - - @Override - public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception { - throw new IllegalArgumentException(name + ":{} 里的 @combine:value 中的value里 " + item + " 对应的条件 " + key + ":value 中 value 不能为 null!"); - } - - } + if (RequestMethod.isHeadMethod(method, true)) { + joinConfig.setMethod(GET); //子查询不能为 SELECT count(*) ,而应该是 SELECT momentId + joinConfig.setColumn(Arrays.asList(j.getKey())); //优化性能,不取非必要的字段 + + if (cacheConfig != null) { + cacheConfig.setMethod(GET); //子查询不能为 SELECT count(*) ,而应该是 SELECT momentId + cacheConfig.setColumn(Arrays.asList(j.getKey())); //优化性能,不取非必要的字段 + } + } + + j.setJoinConfig(joinConfig); + j.setCacheConfig(cacheConfig); + } + + config.setJoinList(joinList); + + return config; + } + + + + /**获取客户端实际需要的key + * verifyName = true + * @param method + * @param originKey + * @param isTableKey + * @param saveLogic 保留逻辑运算符 & | ! + * @return + */ + public static String getRealKey(RequestMethod method, String originKey + , boolean isTableKey, boolean saveLogic) throws Exception { + return getRealKey(method, originKey, isTableKey, saveLogic, true); + } + /**获取客户端实际需要的key + * @param method + * @param originKey + * @param isTableKey + * @param saveLogic 保留逻辑运算符 & | ! + * @param verifyName 验证key名是否符合代码变量/常量名 + * @return + */ + public static String getRealKey(RequestMethod method, String originKey + , boolean isTableKey, boolean saveLogic, boolean verifyName) throws Exception { + Log.i(TAG, "getRealKey saveLogic = " + saveLogic + "; originKey = " + originKey); + if (originKey == null || apijson.JSONObject.isArrayKey(originKey)) { + Log.w(TAG, "getRealKey originKey == null || apijson.JSONObject.isArrayKey(originKey) >> return originKey;"); + return originKey; + } + + String key = new String(originKey); + if (key.endsWith("$")) {//搜索 LIKE,查询时处理 + key = key.substring(0, key.length() - 1); + } + else if (key.endsWith("~")) {//匹配正则表达式 REGEXP,查询时处理 + key = key.substring(0, key.length() - 1); + if (key.endsWith("*")) {//忽略大小写 + key = key.substring(0, key.length() - 1); + } + } + else if (key.endsWith("%")) {//数字、文本、日期范围 BETWEEN AND + key = key.substring(0, key.length() - 1); + } + else if (key.endsWith("{}")) {//被包含 IN,或者说key对应值处于value的范围内。查询时处理 + key = key.substring(0, key.length() - 2); + } + else if (key.endsWith("}{")) {//被包含 EXISTS,或者说key对应值处于value的范围内。查询时处理 + key = key.substring(0, key.length() - 2); + } + else if (key.endsWith("<>")) {//包含 json_contains,或者说value处于key对应值的范围内。查询时处理 + key = key.substring(0, key.length() - 2); + } + else if (key.endsWith("()")) {//方法,查询完后处理,先用一个Map保存 + key = key.substring(0, key.length() - 2); + } + else if (key.endsWith("@")) {//引用,引用对象查询完后处理。fillTarget中暂时不用处理,因为非GET请求都是由给定的id确定,不需要引用 + key = key.substring(0, key.length() - 1); + } + else if (key.endsWith(">=")) {//比较。查询时处理 + key = key.substring(0, key.length() - 2); + } + else if (key.endsWith("<=")) {//比较。查询时处理 + key = key.substring(0, key.length() - 2); + } + else if (key.endsWith(">")) {//比较。查询时处理 + key = key.substring(0, key.length() - 1); + } + else if (key.endsWith("<")) {//比较。查询时处理 + key = key.substring(0, key.length() - 1); + } + else if (key.endsWith("+")) {//延长,PUT查询时处理 + if (method == PUT) {//不为PUT就抛异常 + key = key.substring(0, key.length() - 1); + } + } + else if (key.endsWith("-")) {//缩减,PUT查询时处理 + if (method == PUT) {//不为PUT就抛异常 + key = key.substring(0, key.length() - 1); + } + } + + String last = null;//不用Logic优化代码,否则 key 可能变为 key| 导致 key=value 变成 key|=value 而出错 + if (RequestMethod.isQueryMethod(method)) {//逻辑运算符仅供GET,HEAD方法使用 + last = key.isEmpty() ? "" : key.substring(key.length() - 1); + if ("&".equals(last) || "|".equals(last) || "!".equals(last)) { + key = key.substring(0, key.length() - 1); + } else { + last = null;//避免key + StringUtil.getString(last)错误延长 + } + } + + //"User:toUser":User转换"toUser":User, User为查询同名Table得到的JSONObject。交给客户端处理更好 + if (isTableKey) {//不允许在column key中使用Type:key形式 + key = Pair.parseEntry(key, true).getKey();//table以左边为准 + } else { + key = Pair.parseEntry(key).getValue();//column以右边为准 + } + + if (verifyName && StringUtil.isName(key.startsWith("@") ? key.substring(1) : key) == false) { + throw new IllegalArgumentException(method + "请求,字符 " + originKey + " 不合法!" + + " key:value 中的key只能关键词 '@key' 或 'key[逻辑符][条件符]' 或 PUT请求下的 'key+' / 'key-' !"); + } + + if (saveLogic && last != null) { + key = key + last; + } + Log.i(TAG, "getRealKey return key = " + key); + return key; + } + + + public static interface IdCallback { + /**为 post 请求新建 id, 只能是 Long 或 String + * @param method + * @param database + * @param schema + * @param table + * @return + */ + 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 + * @param schema + * @param table + * @return + */ + 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 + * @param table + * @return + */ + String getUserIdKey(String database, String schema, String datasource, String table); + } + + public static interface Callback extends IdCallback { + /**获取 SQLConfig 的实例 + * @param method + * @param database + * @param schema + * @param table + * @return + */ + SQLConfig getSQLConfig(RequestMethod method, String database, String schema, String table); + + /**combine 里的 key 在 request 中 value 为 null 或不存在,即 request 中缺少用来作为 combine 条件的 key: value + * @param combine + * @param key + * @param request + */ + public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception; + } + + public static abstract class SimpleCallback implements Callback { + + + @Override + public Object newId(RequestMethod method, String database, String schema, String table) { + 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; + } + + @Override + public String getUserIdKey(String database, String schema, String datasource, String table) { + return getUserIdKey(database, schema, table); + } + + @Override + public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception { + throw new IllegalArgumentException(name + ":{} 里的 @combine:value 中的value里 " + item + " 对应的条件 " + key + ":value 中 value 不能为 null!"); + } + + } } From 817c2365bd38e28edbb4d096f692c06914b251bb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 21 Jun 2021 15:58:50 +0800 Subject: [PATCH 151/944] =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E9=83=A8?= =?UTF-8?q?=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/AbstractSQLConfig.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 7b14facfa..ce84abde7 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1035,21 +1035,21 @@ public String getColumnString(boolean inSQLJoin) throws Exception { if (isPrepared() && column != null) { List raw = getRaw(); - boolean containRaw = raw != null && raw.contains(KEY_COLUMN); + 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 更快 - 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; - } - } + for (String c : column) { + if (containRaw) { + // 由于 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); + continue; + } + } index = c.lastIndexOf(":"); //StringUtil.split返回数组中,子项不会有null origin = index < 0 ? c : c.substring(0, index); From 38454e2bff0b015ae6d7ff06eaf64428141976af Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 21 Jun 2021 16:08:16 +0800 Subject: [PATCH 152/944] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=B8=80=E4=BD=8D?= =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=EF=BC=8C=E6=84=9F=E8=B0=A2=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=20bug(=E5=88=86=E9=A1=B5=E6=9F=A5=E8=AF=A2=20query=3D?= =?UTF-8?q?2=20=E4=B8=8E=20@raw=20=E4=B8=AD=20SQL=20=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E4=B8=8D=E5=85=BC=E5=AE=B9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/253 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 49ec6ac0b..ac3661783 100644 --- a/README.md +++ b/README.md @@ -262,6 +262,7 @@ https://github.com/Tencent/APIJSON/issues/187 +
Date: Mon, 21 Jun 2021 16:11:28 +0800 Subject: [PATCH 153/944] =?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=20JieJo=EF=BC=8C=E6=84=9F=E8=B0=A2?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20query=3D2=20=E4=B8=8E=20@raw=20=E4=B8=AD?= =?UTF-8?q?=20SQL=20=E5=87=BD=E6=95=B0=E4=B8=8D=E5=85=BC=E5=AE=B9=E7=9A=84?= =?UTF-8?q?=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/253 --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eb3ea87f3..0fafca62b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,6 +30,7 @@ - [caohao-php](https://github.com/caohao-php)(腾讯工程师) - [Wscats](https://github.com/Wscats)(腾讯工程师) - [jun0315](https://github.com/jun0315)(腾讯工程师) +- [JieJo](https://github.com/JieJo) #### 其中特别致谢:
From cd83f506f2bc415a4c8c1292ef6dde37dd13a046 Mon Sep 17 00:00:00 2001 From: iceewei Date: Mon, 28 Jun 2021 14:56:01 +0800 Subject: [PATCH 154/944] =?UTF-8?q?=E8=8E=B7=E5=8F=96=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E7=9A=84key=E5=A2=9E=E5=8A=A0datasource=E7=BB=B4=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/build.sh | 0 .../src/main/java/apijson/orm/AbstractSQLExecutor.java | 5 +++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 APIJSONORM/build.sh diff --git a/APIJSONORM/build.sh b/APIJSONORM/build.sh new file mode 100644 index 000000000..e69de29bb diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 15b178be4..d0f70ed45 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -732,12 +732,13 @@ public PreparedStatement setArgument(@NotNull SQLConfig config, @NotNull Prepare @NotNull @Override public Connection getConnection(@NotNull SQLConfig config) throws Exception { - connection = connectionMap.get(config.getDatabase()); + String connectionKey = config.getDatasource() + "-" + config.getDatabase() + connection = connectionMap.get(connectionKey); if (connection == null || connection.isClosed()) { Log.i(TAG, "select connection " + (connection == null ? " = null" : ("isClosed = " + connection.isClosed()))) ; // PostgreSQL 不允许 cross-database connection = DriverManager.getConnection(config.getDBUri(), config.getDBAccount(), config.getDBPassword()); - connectionMap.put(config.getDatabase(), connection); + connectionMap.put(connectionKey, connection); } int ti = getTransactionIsolation(); From f4e5ea487eb630f90ef2861f92aba9a739096d9b Mon Sep 17 00:00:00 2001 From: iceewei Date: Tue, 29 Jun 2021 11:06:21 +0800 Subject: [PATCH 155/944] =?UTF-8?q?=E8=8E=B7=E5=8F=96=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0datasource=E5=8F=82=E6=95=B0=20&&=20=E5=9B=9E?= =?UTF-8?q?=E5=8C=85=E9=87=8C=E7=A7=BB=E9=99=A4@datasource?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 3 +++ APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index ce84abde7..59be413d6 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -70,6 +70,7 @@ import apijson.orm.model.SysTable; import apijson.orm.model.Table; import apijson.orm.model.TestRecord; +import org.omg.PortableServer.REQUEST_PROCESSING_POLICY_ID; /**config sql for JSON Request * @author Lemon @@ -2965,6 +2966,7 @@ else if (id instanceof Subquery) {} request.remove(KEY_ROLE); request.remove(KEY_EXPLAIN); request.remove(KEY_CACHE); + request.remove(KEY_DATASOURCE); request.remove(KEY_DATABASE); request.remove(KEY_SCHEMA); request.remove(KEY_COMBINE); @@ -3217,6 +3219,7 @@ else if (whereList != null && whereList.contains(key)) { request.put(KEY_ROLE, role); request.put(KEY_EXPLAIN, explain); 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); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index d0f70ed45..100fe7926 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -732,7 +732,7 @@ public PreparedStatement setArgument(@NotNull SQLConfig config, @NotNull Prepare @NotNull @Override public Connection getConnection(@NotNull SQLConfig config) throws Exception { - String connectionKey = config.getDatasource() + "-" + config.getDatabase() + String connectionKey = config.getDatasource() + "-" + config.getDatabase(); connection = connectionMap.get(connectionKey); if (connection == null || connection.isClosed()) { Log.i(TAG, "select connection " + (connection == null ? " = null" : ("isClosed = " + connection.isClosed()))) ; From 657bc89707c4afcb0558ac5b221079b1b2bdb22e Mon Sep 17 00:00:00 2001 From: iceewei Date: Tue, 29 Jun 2021 11:17:49 +0800 Subject: [PATCH 156/944] =?UTF-8?q?=E8=8E=B7=E5=8F=96=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0datasource=E5=8F=82=E6=95=B0=20&&=20=E5=9B=9E?= =?UTF-8?q?=E5=8C=85=E9=87=8C=E7=A7=BB=E9=99=A4@datasource?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 1 - 1 file changed, 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 59be413d6..1b63d9ba8 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -70,7 +70,6 @@ import apijson.orm.model.SysTable; import apijson.orm.model.Table; import apijson.orm.model.TestRecord; -import org.omg.PortableServer.REQUEST_PROCESSING_POLICY_ID; /**config sql for JSON Request * @author Lemon From 8dd76f3f5798f3131fee68decec6dd8cc5736f73 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 29 Jun 2021 15:01:42 +0800 Subject: [PATCH 157/944] =?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=20=E5=AD=A6=E4=B9=A0=E7=AC=94?= =?UTF-8?q?=E8=AE=B0=E5=92=8C=E6=BA=90=E7=A0=81=E8=A7=A3=E6=9E=90=EF=BC=8C?= =?UTF-8?q?=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/rainboy-learn/apijson-learn --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ac3661783..b782365bb 100644 --- a/README.md +++ b/README.md @@ -404,6 +404,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [FfApiJson](https://gitee.com/own_3_0/ff-api-json) 用 JSON 格式直接生成 SQL,借鉴 APIJSON 支持多数据源 +[apijson-learn](https://github.com/rainboy-learn/apijson-learn) APIJSON 学习笔记和源码解析 + [apijson-sample](https://gitee.com/greyzeng/apijson-sample) APIJSON 简单使用 Demo 及教程 [apijson-examples](https://gitee.com/drone/apijson-examples) APIJSON 的前端、业务后端、管理后端 Demo 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 158/944] =?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 159/944] =?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 160/944] =?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 161/944] 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 162/944] 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 163/944] =?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 164/944] =?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 165/944] =?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 166/944] =?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 167/944] 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 168/944] 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 169/944] 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 170/944] 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 171/944] 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 172/944] =?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 173/944] =?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 174/944] 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 175/944] 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 176/944] 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 177/944] =?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 178/944] =?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 179/944] =?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 180/944] =?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 181/944] =?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 182/944] =?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 183/944] 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 184/944] 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 185/944] 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 186/944] 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 187/944] 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 188/944] 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 189/944] 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 190/944] =?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 191/944] 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 192/944] =?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 193/944] =?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 194/944] =?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 195/944] 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 196/944] 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 197/944] 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 198/944] 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 199/944] =?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 200/944] =?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 201/944] =?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 202/944] =?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 203/944] =?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 204/944] =?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 205/944] 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 206/944] =?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 207/944] 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 208/944] 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 209/944] =?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 210/944] 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 211/944] 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 212/944] 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 213/944] =?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 214/944] =?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 215/944] =?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 216/944] 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 217/944] 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 218/944] 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 219/944] =?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 220/944] =?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 221/944] =?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 222/944] =?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 223/944] 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 224/944] 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 225/944] 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 226/944] =?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 227/944] =?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 228/944] =?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 229/944] =?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 230/944] =?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 231/944] =?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 232/944] 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 233/944] 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 234/944] 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 235/944] =?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 236/944] =?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 237/944] =?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 238/944] =?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 239/944] =?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 240/944] =?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 241/944] =?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 242/944] =?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 243/944] =?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 244/944] 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 245/944] 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 246/944] 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 247/944] 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 248/944] 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 249/944] 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 250/944] 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 251/944] 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 252/944] 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 253/944] 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 254/944] 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 255/944] 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 256/944] =?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 257/944] 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 258/944] =?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 259/944] =?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 260/944] =?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 261/944] =?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 262/944] =?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 263/944] =?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 264/944] =?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 265/944] =?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 266/944] =?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%20http://maven.apache.org/xs%20%E9%82%A3%E4%B9%88%E9%9C%80%E8%A6%81%E5%9C%A8%60DemoSQLConfig%60%EF%BC%8C40-61%E8%A1%8C%EF%BC%8C%E6%94%B9%E4%B8%BA%E8%87%AA%E5%B7%B1%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AF%B9%E5%BA%94%E7%9A%84%E9%93%BE%E6%8E%A5%20%20%60%60%60java+static%20%7B+DEFAULT_DATABASE%20=%20DATABASE_MYSQL;%20%20//%20TODO%20%E9%BB%98%E8%AE%A4%E6%95%B0%E6%8D%AE%E5%BA%93%E7%B1%BB%E5%9E%8B%EF%BC%8C%E6%94%B9%E6%88%90%E4%BD%A0%E8%87%AA%E5%B7%B1%E7%9A%84+DEFAULT_SCHEMA%20="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%20http://maven.apache.org/xs%20%20%E4%B8%BA%E4%BA%86%E6%96%B9%E4%BE%BF%E6%B5%8B%E8%AF%95%EF%BC%8C%E6%88%91%E8%BF%99%E9%87%8C%E4%BD%BF%E7%94%A8%E7%9A%84Chrome%E6%B5%8F%E8%A7%88%E5%99%A8%E7%9A%84Restlet%20Client%E6%8F%92%E4%BB%B6%EF%BC%8C%E5%A4%A7%E5%AE%B6%E5%8F%AF%E4%BB%A5%E6%A0%B9%E6%8D%AE%E8%87%AA%E5%B7%B1%E7%9A%84%E5%96%9C%E5%A5%BD%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%90%8C%E7%9A%84%E5%B7%A5%E5%85%B7%E6%B5%8B%E8%AF%95%E3%80%82%20-%E4%BD%BF%E7%94%A8%60%20http://localhost:8080/get%60%E6%B5%8B%E8%AF%95%E7%BB%93%E6%9E%9C%E3%80%82%EF%BC%88%E8%AF%B7%E6%B3%A8%E6%84%8F%20APIJSONApplication.java%20%E4%B8%AD%E4%BD%BF%E7%94%A8%20Tomcat%20%E9%BB%98%E8%AE%A4%E7%9A%84%208080%20%E7%AB%AF%E5%8F%A3%EF%BC%8C%E5%A6%82%E6%9E%9C%E4%B8%8D%E5%B0%8F%E5%BF%83%E5%BC%80%E7%9D%80PC%E7%AB%AF%E7%9A%84%E2%80%9C%E5%BE%AE%E4%BF%A1%E2%80%9D%EF%BC%8C8080%E7%AB%AF%E5%8F%A3%E4%BC%9A%E8%A2%AB%E5%8D%A0%E7%94%A8%EF%BC%8C%E5%BB%BA%E8%AE%AE%E6%94%B9%E6%88%90%208081,%209090%20%E7%AD%89%E5%85%B6%E5%AE%83%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E6%9C%AA%E5%8D%A0%E7%94%A8%E7%9A%84%E7%AB%AF%E5%8F%A3%E3%80%82%EF%BC%89+%E4%BD%BF%E7%94%A8%60%20http://localhost:8080/get%60%E6%B5%8B%E8%AF%95%E7%BB%93%E6%9E%9C%E3%80%82%EF%BC%88%E8%AF%B7%E6%B3%A8%E6%84%8F%20DemoApplication.java%20%E4%B8%AD%E4%BD%BF%E7%94%A8%20Tomcat%20%E9%BB%98%E8%AE%A4%E7%9A%84%208080%20%E7%AB%AF%E5%8F%A3%EF%BC%8C%E5%A6%82%E6%9E%9C%E4%B8%8D%E5%B0%8F%E5%BF%83%E5%BC%80%E7%9D%80PC%E7%AB%AF%E7%9A%84%E2%80%9C%E5%BE%AE%E4%BF%A1%E2%80%9D%EF%BC%8C8080%E7%AB%AF%E5%8F%A3%E4%BC%9A%E8%A2%AB%E5%8D%A0%E7%94%A8%EF%BC%8C%E5%BB%BA%E8%AE%AE%E6%94%B9%E6%88%90%208081,%209090%20%E7%AD%89%E5%85%B6%E5%AE%83%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E6%9C%AA%E5%8D%A0%E7%94%A8%E7%9A%84%E7%AB%AF%E5%8F%A3%E3%80%82%EF%BC%89%20%20%E9%9A%8F%E4%BE%BF%E6%89%BE%E4%B8%80%E4%B8%AA%E8%A1%A8%EF%BC%8C%E6%AF%94%E5%A6%82%60Moment%60%E8%A1%A8%EF%BC%8C%E6%88%91%E4%BB%AC%E5%8F%96%E5%85%B6%E4%B8%ADID%E4%B8%BA12%E7%9A%84%E4%B8%80%E6%9D%A1%E5%87%BA%E6%9D%A5%E7%9C%8B%E7%9C%8B%20From%205d99841ccd9003f4bb1c89c58360e0759a52196d%20Mon%20Sep%2017%2000:00:00%202001From:%20TommyLemon%20%3C1184482681@qq.com%3EDate:%20Thu,%2030%20Sep%202021%2016:10:07%20+0800Subject:%20[PATCH%20267/944]%20=?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?=%20=?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?=MIME-Version:%201.0Content-Type:%20text/plain;%20charset=UTF-8Content-Transfer-Encoding:%208bit---%20...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 268/944] =?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 269/944] =?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 270/944] =?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 271/944] =?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 272/944] =?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 273/944] =?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 274/944] =?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 275/944] =?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 276/944] 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 277/944] 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 278/944] =?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 279/944] 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 280/944] =?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 281/944] =?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 282/944] =?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 283/944] =?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 284/944] =?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 285/944] =?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 286/944] =?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 287/944] 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 288/944] =?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 289/944] =?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 290/944] =?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 291/944] =?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 292/944] =?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 293/944] =?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 294/944] 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 295/944] 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 296/944] 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 297/944] 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 298/944] 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 299/944] 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 300/944] =?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 301/944] =?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 302/944] 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 303/944] 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 304/944] =?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 305/944] =?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 306/944] =?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 307/944] =?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 308/944] =?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 319/944] 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 320/944] 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 321/944] 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 322/944] 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 323/944] =?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 324/944] 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 325/944] 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 326/944] =?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 327/944] =?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 328/944] =?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 329/944] 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 330/944] 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 331/944] =?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 332/944] =?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 333/944] =?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 334/944] =?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 335/944] =?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 336/944] 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 337/944] 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 338/944] =?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 339/944] =?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 340/944] =?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 341/944] =?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 342/944] =?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 343/944] =?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 344/944] =?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 345/944] =?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 346/944] =?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 347/944] =?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 348/944] =?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 349/944] =?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 350/944] 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 351/944] =?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 352/944] =?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 353/944] =?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 354/944] =?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 355/944] =?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 356/944] =?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 357/944] =?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 358/944] =?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 359/944] 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 360/944] =?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 361/944] =?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 362/944] =?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 363/944] =?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 364/944] =?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 365/944] =?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 366/944] =?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 367/944] =?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 368/944] =?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 369/944] =?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 370/944] =?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 371/944] 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 372/944] =?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 373/944] =?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 374/944] =?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 375/944] 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 376/944] 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 377/944] 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 378/944] 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 379/944] 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 380/944] 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 381/944] 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 382/944] 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 383/944] 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 384/944] 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 385/944] =?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 386/944] =?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 387/944] 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 388/944] 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 389/944] 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 390/944] 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 391/944] 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 392/944] 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 393/944] 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 394/944] 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 395/944] =?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 396/944] 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 397/944] =?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 398/944] 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 399/944] =?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 400/944] =?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 401/944] 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 402/944] =?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 403/944] =?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 404/944] =?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 405/944] =?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 406/944] =?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 407/944] =?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 408/944] =?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 409/944] 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 410/944] 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 411/944] =?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 412/944] =?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 413/944] 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 414/944] 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 415/944] 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 416/944] =?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 417/944] 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 418/944] 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 419/944] 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 420/944] 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 421/944] 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 422/944] 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 423/944] 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 424/944] 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 425/944] 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 426/944] 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 427/944] 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 428/944] 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 429/944] 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 430/944] =?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 431/944] =?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 //